月初TensorFlow开发者大会上,谷歌正式发布了TensorFlow的JS版本tensorflow.js,并演示了几个很有意思的demo,展现了浏览器环境下也能进行深度学习任务的能力。tensorflowjs利用WebGl加速,在浏览器环境下训练、部署机器学习模型。下面我尝试引入tensorflow.js并运行一个曲线拟合的例子。
1、文件形式引入
首先调用tf.sequential()构建模型,损失函数为均方差,优化器为sgd(梯度下降)。待拟合的点序列为(1,1),(2,3),(3,5),(4,7),训练模型,输入x=5。
打开浏览器,输出为:
Tensor
[[8.1529675],]
2、使用webpack
npm install @tensorflow/tfjs
首先利用npm安装tensorflow.js(也可用yarn),新建index.js文件,内容如下。
import * as tf from '@tensorflow/tfjs';
const model = tf.sequential();
model.add(tf.layers.dense({units: 1, inputShape: [1]}));
model.compile({loss: 'meanSquaredError', optimizer: 'sgd'});
const xs = tf.tensor2d([1, 2, 3, 4], [4, 1]);
const ys = tf.tensor2d([1, 3, 5, 7], [4, 1]);
model.fit(xs, ys).then(() => {
model.predict(tf.tensor2d([5], [1, 1])).print();
});
利用webpack可使用import语法引入tf.js。配置webpack.config.js文件如下。
const path = require('path');
module.exports={
//入口文件的配置项
entry:{
entry: './index.js'
},
//出口文件的配置项
output:{
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
}
}
运行webpack命令,将在目录下生成dist文件夹。cd进入该文件夹,用node运行bundle.js文件,输出结果。
3、曲线拟合
参考Fitting a Curve to Synthetic Data,这是TensorFlow官方关于曲线拟合的例子,其中使用Vega进行可视化展示。下面我将用Echarts替换Vega进行可视化展示并重写部分程序,代码结构如下所示。
其中index.js为入口文件,dist文件夹下为分发文件,webpack.config.js的内容如下。
const path = require('path');
module.exports={
mode: 'development',
//入口文件的配置项
entry:{
entry: './src/index.js'
},
//出口文件的配置项
output:{
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
//控制台报错信息
devtool: 'inline-source-map'
}
index.html内容如下。
Document
入口文件index.js内容如下。
import * as tf from '@tensorflow/tfjs';
var echarts = require('echarts');
const a = tf.variable(tf.scalar(Math.random()));
const b = tf.variable(tf.scalar(Math.random()));
const c = tf.variable(tf.scalar(Math.random()));
const d = tf.variable(tf.scalar(Math.random()));
function predict(x) {
return tf.tidy(() => {
return a.mul(x.pow(tf.scalar(3, 'int32')))
.add(b.mul(x.square()))
.add(c.mul(x))
.add(d);
});
}
function loss(prediction, labels) {
const error = prediction.sub(labels).square().mean();
return error;
}
const numIterations = 75;
const learningRate = 0.5;
const optimizer = tf.train.sgd(learningRate);
async function train(xs, ys, numIterations) {
for (let iter = 0; iter < numIterations; iter++) {
optimizer.minimize(() => {
const pred = predict(xs);
return loss(pred, ys);
});
await tf.nextFrame();
}
}
function generateData(numPoints, coeff, sigma = 0.04) {
return tf.tidy(() => {
const [a, b, c, d] = [
tf.scalar(coeff.a),
tf.scalar(coeff.b),
tf.scalar(coeff.c),
tf.scalar(coeff.d)
];
const xs = tf.randomUniform([numPoints], -1, 1);
const ys = a.mul(xs.pow(tf.scalar(3, 'int32')))
.add(b.mul(xs.square()))
.add(c.mul(xs))
.add(d)
.add(tf.randomNormal([numPoints], 0, sigma));
const ymin = ys.min();
const ymax = ys.max();
const yrange = ymax.sub(ymin);
const ysNormalized = ys.sub(ymin).div(yrange);
return {
xs,
ys: ysNormalized
};
})
}
async function plotData(xs, ys, preds) {
const xvals = await xs.data();
const yvals = await ys.data();
const predVals = await preds.data();
const valuesBefore = Array.from(xvals).map((x, i) => {
return [xvals[i], yvals[i]];
});
const valuesAfter= Array.from(xvals).map((x, i) => {
return [xvals[i], predVals[i]];
});
// 二维数组排序
valuesAfter.sort(function(x, y) {
return x[0] - y[0];
});
curveChart.setOption({
xAxis: {
min: -1,
max: 1
},
yAxis: {
min: 0,
max: 1
},
series: [{
symbolSize: 12,
data: valuesBefore,
type: 'scatter'
},{
data: valuesAfter,
encode: {
x: 0,
y: 1
},
type: 'line'
}]
});
}
async function learnCoefficients() {
const trueCoefficients = {a: -0.8, b: -0.2, c: 0.9, d: 0.5};
// 生成有误差的训练数据
const trainingData = generateData(100, trueCoefficients);
// 训练模型
await train(trainingData.xs, trainingData.ys, numIterations);
// 预测数据
const predictionsAfter = predict(trainingData.xs);
// 绘制散点图及拟合曲线
await plotData(trainingData.xs, trainingData.ys, predictionsAfter);
predictionsAfter.dispose();
}
const curveChart = echarts.init(document.getElementById('chart'));
learnCoefficients();
首先引入tensorflow.js及echarts,之后定义4个参数a、b、c、d,分别是待拟合曲线y=a*x^3+b*x^2+c*x+d的四个参数,初始设为随机值。
定义函数predict,传入x,返回拟合后的估计值y。函数loss为损失函数,这里定义loss为均方差。
定义优化器optimizer,其中学习率为0.5,学习率过小会导致训练速度慢,学习率过高会造成拟合参数在最优解附近“左右摇摆”。
定义async函数(Generator 函数的语法糖)train,train函数内根据迭代步数及学习率调用优化器并计算损失函数loss。
函数generateData随机生成[-1, 1]范围内的点,并根据传入的a、b、c、d加上一定的随机扰动生成数据点xs,ys,其中ys进行归一化处理。
函数plotData将随机生成的样本点映射为散点图,将根据训练后的参数拟合出的点映射为曲线。
函数renderCoefficients将a、b、c、d的值输出到document内。
函数learnCoefficients是index.js的main函数,函数内先设定预定义的a、b、c、d,再生成有误差的训练数据,利用训练数据训练a、b、c、d参数并参数输出到文档,之后利用训练好的参数拟合x数据,将结果绘制为散点图及曲线,最后通知GC清理。
此时拟合出的曲线图会有bug,如下所示。
原因分析:
传入echarts的点对是按生成顺序排序的,是无序数组,但绘制曲线时是按传入数组的顺序连接各点,因此在传入前需对二维数据进行排序。在curveChart.setOption前加入如下代码。
// 二维数组排序
valuesAfter.sort(function(x, y) {
return x[0] - y[0];
});
结果如下。
完整程序见我的github,具体步骤为:
step1 新建文件夹,cmd输入git clone [email protected]:orangecsy/tfjs-exercise.git,cd 1进入文件夹1;
step2 cmd输入webpack,打包;
step3 cd dist进入dist文件夹,cmd中输入http-server(需先npm install http-server)或使用webpack配置开发服务器;
step4 浏览器中输入http://127.0.0.1:8080/,即为结果。