前端基础机器学习:线性回归

本文最后更新于:a few seconds ago

前言

本章将使用TensorFlow.js,进行线性回归的学习。
使用parcel打包工具进行打包, 此工具免配置,适用于经验不同的开发者,比webpack方便。
并且会用两个例子,一个是简单的预测两数之间的关系,一个是预测孩子的身高与父母身高的关系,来加深印象。

前置条件

您需要有前端开发的基础知识。(js,html)
了解线性回归相关知识。
拥有高中或者高等数学知识。

推荐:Google机器学习速成教程

上号

准备环境

我们先在任意空文件夹中,打开我们的编辑器,初始化我们的编码环境

1
npm init -y

接下来我们来下载我们的TensorFlow.js包,和 parcel 打包工具。

1
npm install @tensorflow/tfjs 

全局安装parcel

1
npm install -g parcel-bundler

我们接下来会用tfjs-vis,来可视化我们的训练过程,我们把它也顺便安装上。

1
npm install @tensorflow/tfjs-vis

创建文件夹,进入文件夹创建index.html和index.js,目录结构类似这样:

1
2
3
4
5
6
|-node_modules
|-linearRegression
|--index.html
|--index.js
|-package.json
|-package-lock.json

我们等等要用到async+await语法,所以我们的在package.json声明一一些代码: last 1 Chrome version

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"name": "tensoflow.js",
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {
"@tensorflow-models/speech-commands": "^0.4.0",
"@tensorflow/tfjs": "^1.3.1",
"@tensorflow/tfjs-node": "^1.2.9",
"@tensorflow/tfjs-vis": "^1.2.0"
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"browserslist": [
"last 1 Chrome version"
]
}

用X来预测Y

创建HTML

我们首先把上面创建的HTML引入我们的js,让它来可视化我们的训练过程。

1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>线性回归</title>
</head>
<body>
<script src="./index.js"></script>
</body>
</html>

填充训练集和绘制散点图

引入我们需要的两个库

1
2
import * as tf from '@tensorflow/tfjs';
import * as tfvis from '@tensorflow/tfjs-vis';

填充我们的训练集

我们填充了xs,和ys,准备用xs来预测我们的ys。并且创建了一个匿名函数,让它在html加载完执行。
(数据也可自行填充)

1
2
3
4
5
6
7
import * as tf from '@tensorflow/tfjs';
import * as tfvis from '@tensorflow/tfjs-vis';

window.onload = () => {
const xl = [1, 2, 3, 4];
const yl = [2, 3, 5, 7];
};

绘制散点图

下面调用的tfvis的render方法里的scatterplot,来绘制散点图。scatterplot接收三个对象。我们只介绍部分,详情点击上面链接。
第一个对象可以接收一个name,类型是string,代表图表的名字
第二个对象接收一个values,类型是以{x,y}元组的数组(或嵌套数组)。

类型就是这样:[ { x: 1, y: 2 }, { x: 2, y: 3 }, { x: 3, y: 5 }, { x: 4, y: 7 } ]

第三个对象我们只用到了xAxisDomainyAxisDomain,用来调整我们图表的可视范围。

1
2
3
4
5
6
7
8
9
10
11
12
import * as tf from '@tensorflow/tfjs';
import * as tfvis from '@tensorflow/tfjs-vis';

window.onload = () => {
const xl = [1, 2, 3, 4];
const yl = [2, 3, 5, 7];
};
tfvis.render.scatterplot(
{ name: 'linear regression' },
{ values: xl.map((x, i) => ({ x, y: yl[i] })) },
{ xAxisDomain: [0, 5], yAxisDomain: [0, 8] }
);

接下来就可以运行一下我们的程序了,在命令行写入:

1
parcel .\linear-regression\index.html

然后打开页面,就能查看到我们的散点图了
散点图

创建一个神经网络模型

tf.sequential,创建一个连续的模型。
sequential模型的阔谱可以理解为堆栈,没有分支和跳过。我们在解决一些简单问题的时候,一般用此模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
import * as tf from '@tensorflow/tfjs';
import * as tfvis from '@tensorflow/tfjs-vis';

window.onload = () => {
const xl = [1, 2, 3, 4];
const yl = [2, 3, 5, 7];
};
tfvis.render.scatterplot(
{ name: 'linear regression' },
{ values: xl.map((x, i) => ({ x, y: yl[i] })) },
{ xAxisDomain: [0, 5], yAxisDomain: [0, 8] }
);
const model = tf.sequential();

为神经网络添加层

接下来就是为神经网络添加一个全连接层,它可以实现的操作是

output = activation(dot(input, kernel) + bias)

activation: 激活函数,我们现在还用不到,他能帮我们解决更多的问题
dot : 点乘
kernel: 权重矩阵,在我们这个例子里可以理解为简单的一个数值。
bias: 表示偏置矢量

简单理解就是: 它可以帮我们解决乘以一个权重,再加上一个参数的问题。代码如下:

1
2
const model = tf.sequential();
model.add(tf.layers.dense({ units: 1, inputShape: [1] }));

model.add: 表示添加一个层
tf.layers.dense(): 创建一个全连接层。 扩展tf.layers.dense()
units: 神经元的个数,一个神经元就能解决我们的问题了。
inputShape: 输入的值形状,[1]表示我们的输入是一维的数据。就是我们的x轴数据。

设置损失函数:均方误差与优化器: 随机梯度下降

神经网络在初始化的时候会瞎蒙一个权重值,这个权重值一般是错的,而这个损失函数,就是来告诉神经网络,这个权重值错得有多离谱。
而神经网络就会用优化器来降低我们的损失。降低损失

均方误差这个常用的损失函数,在我们这个线性回归问题的适用的,但不代表所有情况。

TensorFlow已经帮我们封装了公式的操作,我们只需要调用就行了,如下:

1
2
3
4
model.compile({ 
loss: tf.losses.meanSquaredError,
optimizer: tf.train.sgd(0.1)
});

loss:里面传了一个均方误差tf.losses.meanSquaredErrortf.losses里有很多的损失函数。
optimizer: 传了一个优化器,sgd就是随机梯度下降。并且设置了一个0.1的学习速率。这是一个超参数。 扩展:更多优化器.

优化学习速率,Google的这个交互教程能加快您对学习速率的理解。

训练模型并在网页上显示训练过程

我们训练的第一步是将训练的数据转换为 Tensor,这个张量是一个高维的物理量,这是TensorFlow世界的规定。
具体了解详情点击上面链接看示例。
第二步就能调用model.fit训练模型。
第一个参数是x的张量,第二个参数是y的张量。第三个参数接收一个对象,我们主要用到以下几个:
epochs: 迭代训练数据数组的次数,接收类型为Number。
batchSize : 每个梯度更新的样本数,接收类型为Number。
callback: 回调,我们要在此调用tfvis.show.fitCallbacks,来绘制我们的损失。
它返回的是一个Promise,所以我们要在前面加上await,函数前面也要记得加async。
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import * as tf from '@tensorflow/tfjs';
import * as tfvis from '@tensorflow/tfjs-vis';

window.onload = async () => {
const xl = [1, 2, 3, 4];
const yl = [2, 3, 5, 7];

tfvis.render.scatterplot(
{ name: '线性回归训练集' },
{ values: xl.map((x, i) => ({ x, y: yl[i] })) },
{ xAxisDomain: [0, 5], yAxisDomain: [0, 8] }
);

const model = tf.sequential();
model.add(tf.layers.dense({ units: 1, inputShape: [1] }));
model.compile({
loss: tf.losses.meanSquaredError,
optimizer: tf.train.sgd(0.1)
});

const xInputs = tf.tensor(xl);
const yInputs = tf.tensor(yl); // 将两个数据转换为张量
await model.fit(xInputs, yInputs, {
batchSize: 2, // 每次要去学的数据量多大: 随机梯度下降
epochs: 200, // 超参数
callbacks: tfvis.show.fitCallbacks(
{ name: '训练过程' },
['loss']
)
});
};

batchSize设置为2的时候,代表每次给模型学习两个点,这样模型产生的误差抖动还是较大的。
batchSize_2

我们将batchSize设置为4,让模型一次学4个点看看
batchSize_4

这次的曲线好多了,接下来我们就可以用这个训练好的模型来进行预测了。

预测模型

下面将用model.predict对输入的张量进行推理。
注意:输入的值要转换为张量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import * as tf from '@tensorflow/tfjs';
import * as tfvis from '@tensorflow/tfjs-vis';

window.onload = async () => {
const xl = [1, 2, 3, 4];
const yl = [2, 3, 5, 7];

tfvis.render.scatterplot(
{ name: '线性回归训练集' },
{ values: xl.map((x, i) => ({ x, y: yl[i] })) },
{ xAxisDomain: [0, 5], yAxisDomain: [0, 8] }
);

const model = tf.sequential();
model.add(tf.layers.dense({ units: 1, inputShape: [1] }));
model.compile({
loss: tf.losses.meanSquaredError,
optimizer: tf.train.sgd(0.1) // 学习率
});

const xInputs = tf.tensor(xl);
const yInputs = tf.tensor(yl);
await model.fit(xInputs, yInputs, {
batchSize: 4, // 每次要去学的数据量多大: 随机梯度下降
epochs: 200, // 超参数
callbacks: tfvis.show.fitCallbacks(
{ name: '训练过程' },
['loss']
)
});

const output = model.predict(tf.tensor([5]));
output.print()
alert(`当x为 5 的时候,推理的 y 为 ${output.dataSync()[0]}`);
};

到此我们就完成了线性回归的学习。

预测男女性身高

数据是伪造的,只要简单的填充一下数据,和改写一下区间就行,大体不变:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import * as tf from '@tensorflow/tfjs';
import * as tfvis from '@tensorflow/tfjs-vis';

window.onload = async () => {
const xl = [1.6, 1.7, 1.75, 2.3]; // 父母平均身高
const yl = [1.86, 1.87, 1.875, 1.93]; // 儿女身高



tfvis.render.scatterplot(
{ name: '根据父母身高预测孩子身高' },
{ values: xl.map((x, i) => ({ x, y: yl[i] })) },
{ xAxisDomain: [1, 3], yAxisDomain: [1, 3] }
);

const model = tf.sequential();
model.add(tf.layers.dense({ units: 1, inputShape: [1] }));
model.compile({ loss: tf.losses.meanSquaredError, optimizer: tf.train.sgd(0.1) });

const xInputs = tf.tensor(xl);
const yInputs = tf.tensor(yl);
await model.fit(xInputs, yInputs, {
batchSize: 4, // 每次要去学的数据量多大: 随机梯度下降
epochs: 200, // 超参数
callbacks: tfvis.show.fitCallbacks(
{ name: '训练过程' },
['loss']
)
});
const output = model.predict(tf.tensor([2]));
output.print()
alert(`当父母平均身高为 2 米的时候,儿子的身高 为 ${output.dataSync()[0]} 米`);
};

以上就是本次的线性回归,这是入门的基础内容,有错误请指正。

参考:
B乎
Google机器学习速成教程
TensorFlow.js官网API

结束语

怕上层楼,十日九风雨。
「祝英台近·晚春」
辛弃疾