前言
tensorflowJS是一个基于javascript的机器学习库,由tensorflow官方团队移植到javascript并进行维护。可以在web和node环境下运行。为了方便数据可视化,将在html中进行编程。本文完成后会提供在线体验网页。
本文为博主的入门实操记录,对于理论性的内容不会提到太多,想更好的了解机器学习原理,强烈建议 先看台湾大学李宏毅教授关于机器学习的课程p2~p3, 里面会提到一些机器学习的基础概念并且讲的非常好!
(强推)李宏毅2021春机器学习课程
本文跟随tensorflowJS视频教程,使用美国金县房屋销售数据进行线性回归训练,对房价进行预测。提供本文样本数据下载,以及相关的参考资料连接。
跟随视频: 使用 TensorFlow.js 在 JavaScript 中进行机器学习
api文档: tensorflowJS API 数据可视化 tfjs-vis API
美国金县房屋销售数据: kc_house_data.csv
关于机器学习的理解仅为博主个人观点,可能存在偏差,欢迎交流指正。
1 项目初始化
1)创建文件夹命名为 csdn范例 。 2)进入文件夹,并创建 tensorflowJS.html 文件,基于最终要实现的功能进行简单的页面设计。
3)将数据集 kc_house_data.csv 放入文件夹中。
2 tensorflwJS 和 数据可视化库的引入
在 tensorflowJS.html 文件中的 head 标签中引入 tensorflowJS 以及数据可视化库 tfjs-vis
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@2.0.0/dist/tf.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-vis"></script>
3 加载csv文件,映射房屋面积与房价,并数据可视化
调用 tf 即可获取 tensorflowJS 的单例对象 tf.data.csv() 方法可通过传入url字符串加载csv文件。由于浏览器限制无法加载本地文件,只能通过超链接获取。所以先通过node环境的 http-serve 模块建立一个小型的服务器。
ps. node环境下在url基础上加上前缀 "file://" 即可
3.1 小型服务器实现
1)通过node的npm包管理下载 http-server 模块
npm i http-server -g
2)打开终端(或cmd,powershell)进入项目目录,输入命令行启动小型服务器
http-server -c-1
3)启动后可看到控制台提供的ip地址,选择其中一个通过浏览器打开,可以看到项目的目录,这时候就可以通过ip地址访问 html 和获取 csv 文件了。
!!!一定要通过ip地址访问html文件,否者在请求csv文件时会存在跨域问题!!!
3.2初始化变量定义
在html中创建 <script> 标签来执行javascript代码。先初始化一些变量,获取按钮dom,和用于log的div的dom
let
dataset = null,
pointDataset = null,
featureArray = null,
labelArray = null,
featureTensor = null,
labelTensor = null,
normalizedFeature = null,
normalizedLabel = null,
trainingFeatureTensor = null,
testingFeatureTensor = null,
trainingLabelTensor = null,
testingLabelTensor = null,
model = null,
traningInfo = document.getElementById("traningInfo"),
predictInfo = document.getElementById("predictInfo"),
loadBtn = document.getElementById("loadBtn"),
trainingBtn = document.getElementById("trainingBtn"),
predictBtn = document.getElementById("predictBtn"),
toggleVisorBtn = document.getElementById("toggleVisorBtn");
3.3 准备画散点图的函数
调用 tfvis 即可获取数据可视化库的单例对象 tfvis.render.scatterplot() 调用该函数画散点图
async function plot(pointsArray, featureName) {
tfvis.render.scatterplot({
name: `${featureName} vs House Price`
}, {
values: [pointsArray],
series: ['original']
}, {
xLabel: featureName,
yLabel: "price"
})
}
3.4 加载csv文件并完成数据可视化
为加载按钮编写响应函数 load() ,通过该函数: 1)加载 .csv 文件。 2)映射房屋面积与房价生成点集。 3)打乱点集顺序以防点集自身的一些规律影响模型的准确度。 4)画散点图。
tf.data.csv() 调用该函数加载 .csv 文件。返回 tf.data.CSVDataset 对象 tf.data.CSVDataset.toArray() 调用该函数,异步返回数据集的数组形式。返回 Array 对象 tf.util.shuffle() 调用该函数打乱一个数组对象顺序。返回 Array 对象。
async function load() {
loadBtn.setAttribute("disabled", "disabled");
trainingBtn.setAttribute("disabled", "disabled");
predictBtn.setAttribute("disabled", "disabled");
loadBtn.classList.add('disabled');
trainingBtn.classList.add('disabled');
predictBtn.classList.add('disabled');
traningInfo.innerHTML = `正在加载 kc_house_data.csv ... ...\n`;
dataset = tf.data.csv("http://192.168.158.1:8080/kc_house_data.csv");
traningInfo.innerHTML += `kc_house_data.csv 加载完成!\n`;
traningInfo.innerHTML += `可视化数据集中 ... ...\n`;
pointset = dataset.map(record => ({
x: record.sqft_living,
y: record.price,
}))
pointsArray = await pointsDataset.toArray();
if (pointsArray.length % 2 !== 0) {
pointsArray.pop();
}
tf.util.shuffle(pointsArray);
plot(pointsArray, "square feet");
traningInfo.innerHTML += `数据可视化完成!\n`;
loadBtn.removeAttribute("disabled");
loadBtn.classList.remove('disabled');
trainingBtn.removeAttribute("disabled");
trainingBtn.classList.remove('disabled');
}
loadBtn.onclick = load;
测试:正常加载,并且可以在网页右侧看到散点图
4 可视化面板展开隐藏功能实现
tfvis.visor().toggle() 调用以切换面板显示或隐藏
function toggleVisor() {
tfvis.visor().toggle();
}
toggleVisorBtn.onclick = toggleVisor;
5 训练模型
5.1 “最小-最大-归一化” 以及 “逆向最小-最大-归一化”的函数编写
最小最大归一化是将一组数据的特征保留的同时,将数的最小最大范围缩小到0~1。 例如 : [10, 20, 30] 中,将最小的 10 归一化后为下边界 0,最大数 30 归一化后为上边界 1,而 20 根据在数组中的特征,记为 0.5。 即归一化后数组为: [0, 0.5, 1] 。
记 x 为数组中的一个数,min 为数组中的最小数, max 为数组中的最大数。x’ 为归一化后的数,x’ 的计算公式为: x’ = (x - min ) / (max - min )
下面两个函数中 tensor 为张量对象,下面是一些会用到的该对象的简单计算函数 .min() 取张量中的最小值 .max() 取张量中的最大值 .sub() 减去一个数 .add() 加上一个数 .div() ?一个数 .mul() 乘以一个数
5.1.1 归一化函数
function normalize(tensor) {
const min = tensor.min();
const max = tensor.max();
return {
tensor: tensor.sub(min).div(max.sub(min)),
min,
max,
}
}
5.1.2 逆向归一化函数
function deNormalize(tensor, min, max) {
return tensor.mul(max.sub(min)).add(min);
}
5.2 训练模型
1)将点集分割为 因素张量 和 结果张量。
2)对张量进行 最小最大归一化,方便计算。
3)对 因素张量 和 结果张量 分别分割为 训练张量 和 测试张量,分别用来训练模型和检测模型。
4)定义模型 ----1、指定为线性模型。 ----2、创建一层全连接层神经网络,配置该神经网络中有一个神经元,使用偏差 => 更精确拟合方程,设置激活方程为线性方程。 ----3、配置loss方程为平方差 => loss方程是评价model好坏的标准,既当前模型的神经元计算的结果与真实值越接近,loss越低,模型越好,调整神经元可以改变loss。配置优化方案为梯度下降,步长为0.1 => 将不同配置的神经元所得到的loss画成一个函数图像的话,loss应为一条曲线,梯度下降是计算当前神经元下loss曲线的斜率,然后根据斜率调整神经元使得loss去往更低的点。步长是每次调整的大小。
5)通过 tfvis 获取每次迭代后数据可视化的回调函数 onEpochEnd 。
6)训练模型。 ----1、指定迭代次数为20次 => 既模型会进行20代优化,可以配置每代优化的次数,这里没有配置,具体可看该函数的api。 ----2、配置验证集为训练集的20% => 模型训练时可使用训练集中的数据对当前模型进行loss验证,该验证只是评估当前模型的好坏,对神经元的调整不产生影响。 ----3、每次迭代结束调用可视化回调函数 onEpochEnd 来显示数据。
tf.tensor2d() 返回一个二维张量对象 tf.split() 分割成指定数量的张量 tf.sequential() 创建一个顺序模型 tf.Sequential.add() 在神经层最顶端添加一层神经网络 tf.layers.dense() 定义一层全连接层神经网络 tf.LayersModel.compple() 编译模型,配置loss和优化器 tf.Sequential.fit() 按配置训练模型
async function run() {
loadBtn.setAttribute("disabled", "disabled");
loadBtn.classList.add('disabled');
trainingBtn.setAttribute("disabled", "disabled");
trainingBtn.classList.add('disabled');
traningInfo.innerHTML += `初始化 因素张量 和 结果张量 中 ... ...\n`;
featureTensor = tf.tensor2d(pointsArray.map(p => p.x), [pointsArray.length, 1]);
labelTensor = tf.tensor2d(pointsArray.map(p => p.y), [pointsArray.length, 1]);
normalizedFeature = normalize(featureTensor);
normalizedLabel = normalize(labelTensor);
traningInfo.innerHTML += `分割训练集和测试集 中 ... ...\n`;
[trainingFeatureTensor, testingFeatureTensor] = tf.split(normalizedFeature.tensor, 2);
[trainingLabelTensor, testingLabelTensor] = tf.split(normalizedFeature.tensor, 2);
traningInfo.innerHTML += `创建model 中 ... ...\n`;
model = tf.sequential();
model.add(tf.layers.dense({
units: 1,
useBias: true,
activation: "linear",
inputDim: 1,
}))
model.compile({
loss: "meanSquaredError",
optimizer: tf.train.sgd(0.1),
})
const {
onEpochEnd
} = tfvis.show.fitCallbacks({
name: "Training Performance",
}, ['loss']);
traningInfo.innerHTML += `训练model 中 ... ...\n`;
await model.fit(trainingFeatureTensor, trainingLabelTensor, {
epochs: 20,
validationSplit: 0.2,
callbacks: {
onEpochEnd,
}
})
traningInfo.innerHTML += `训练完成!!!\n`;
trainingBtn.setAttribute("disabled", "disabled");
trainingBtn.classList.add('disabled');
loadBtn.removeAttribute("disabled");
loadBtn.classList.remove('disabled');
predictBtn.removeAttribute("disabled");
predictBtn.classList.remove('disabled');
}
trainingBtn.onclick = run;
测试:功能正常,在可视化面板中可以看到训练中loss的变化曲线。
6 测试模型
6.1 添加按钮及样式
6.2 测试功能实现
未完待续。。。
|