前言/基本介绍
- 学习机器学习的原理,有一点数学基础(微积分,线代,统计概率)是非常重要的。在此我不细究数学公式的推导。这块内容,需要大家自行线下学习。这样主要讲解概念和应用,首先我会讲一些基础性的东西,尽量用通俗易懂的解释,目前我发现一些关于机器学习的教程,上来就一大堆公式炫技,一下子就把广大数学基础不好的同学就排除在外了。我不喜欢这种方式。
- 当然一开始,我不会先讲tensorflow相关的代码和应用,还是需要把一些前置的概念和基础了解清楚。不然对初学者来说会比较懵。因为机器学习的知识是连贯的,线性递增的,如果搞不懂前面,后面也就没办法学了,会越来越有难度。
- 暂时我先不谈深度学习,先从最基础的学习算法开始,慢慢进入深度学习。
- 机器学习总结下来,如要分两种类别,监督学习和非监督式学习。
- 监督学习又分两种问题,回归问题和分类问题。那我们先从回归问题开始。
- 后面再开始讲非监督式学习和深度学习知识。
线性回归
选择从回归问题开始,是因为,这是机器学习中的基础中的基础,可以从这里以点带面。损失函数,梯度下降,学习率。都会从这里开始。所以我们先从最基础的单变量线性回归开始。
线性回归,有两种实现方式。这里只讲机器学习的线性回归,也许采用求斜率和截距的数学公式,更简单。但不是我们的目的。深度学习是一门系统性的学科,单纯靠公式不能解决全部问题。
预测房价问题
这个是一个经典的回归问题,比如我们有两组数据,一组是房子的大小,一组是房子的面积。那么要预测在某一个面积下房子的价格是多少,就是已知x求y的问题。我们将数据用图形表示如下:
X轴为房子的面积,Y轴为房子的价格。 PS:手画的有点丑,请不要介意。
1. 第一步确定计算价格的模型
设房屋的面积为x,售出价格为y。我们需要建立基于输入x来计算输出y的表达式,也就是模型(model)。顾名思义,线性回归假设输出与各个输入之间是线性关系,那么有以下公式:
y = x * w + b
在线性回归中w是斜率 , b是截距。但在机器学习中,我们喜欢定义为w是权重,b是偏置。(所以当我们看不同教材会又困惑,在此我说明一下) 我们的目标就是求出w和b。
这里我多讲一点,如果是多元线性回归,应该是怎么样的?比如房子的位置也是影响价格的一个因素。那么可能有多个x。也会有多个对应的w权重,定义如下:
y = x1 * w1 + x2*w2 + b。后面再讲吧,这里只是多提一句。以后再讲。
2. 第二步确定Loss函数
为什么要有损失函数这个概念,我们不用行不行?我们的目的是求出w和b , 但我们最开始是不知道w和b的值。 所以机器学习,往往最开始是随机初始化w和b,但这个肯定不是正确的结果。那我们应该如何优化w和b呢? 让它们的值逼近正确结果。 所以唯有找到一种比较的方法,去衡量参数的好坏,才能确定。Loss函数就是做这样的比较。我们需要衡量价格预测值与真实值之间的loss。所以它会比较Loss,这个Loss返回的结果越小,那么代表Loss也是最小的。也就证明我们的预估值是正确的。但怎么确定这个算法呢?
请看图
其实我们通过线性回归的目的是为了得到这条蓝色的线, 但又不能凭直觉去画,我们需要利用机器计算出来。 所以我们要计算每个参照点到蓝色线段的值(下图绿色线段表示误差)。
如何计算误差,这个时候需要引入我下面要讲到的均方差(MSE)。所以有下面公式。 均方误差可用来作为衡量预测结果的一个指标。
Y(X1)代表模型计算结果值,Yi代表实际值也称参考值。 简单来说就是预测与参考的差然后平方和后,再除以样本数,得到一个标准差的损失。 一般,我们会初始随机生成w和b, 这个w和b好不好,由Loss函数去判断,如果不好我们怎么优化? 下面我们就提出机器学习最重要的优化算法,梯度下降。
3. 梯度下降
上面的最小二乘法只是解决了我们初始w和b,好不好的问题,并做一个loss计算。 那么我们应该做,才能找到最优的w和b呢? 之前我们讲到Loss函数的作用是求出现实值与理想值的差距,差距找到了,那我们改如何缩短这个差距?我们将Loss函数化成一个图形展示,如下图所示。梯度下降算法,就是一个下山问题,沿着最陡的方向,一步一步逼近山底(极值)。
数学解释,借用下面这张图说明一下
J是关于Θ的一个函数,我们当前所处的位置为Θ0点,要从这个点走到J的最小值点,也就是山底。首先我们先确定前进的方向,也就是梯度的反向,然后走一段距离的步长,也就是α(学习率),走完这个段步长,就到达了Θ1这个点。
α在梯度下降算法中被称作为学习率或者步长,是一个超参数,意味着我们可以通过α来控制每一步走的距离,以保证不要步子跨的太大,太大,错过了最低点。同时也要保证不要走的太慢,太慢容易造成需要很长的训练时间。所以α的选择在梯度下降法中往往是很重要的。需要不断尝试。但后面我会提到对梯度下降的一些优化算法。来解决这样的问题。
那么大家可能会提出一个疑问,J(θ)是什么? J(θ)就是下降最快方向,J(θ)具体怎么得到呢? J(θ)也就是根据Loss函数分别对w,b两个参数求偏导。 具体数学公式如下:
关于公式怎么求或怎么推导过来的,我就不解释了,否则脱离我们的学习重心。这块大家可以私底下把链式法则相关得知识再看一看。
4. 反向传播
还要顺便讲一个概念就是反向传播。在深度学习领域会大量提到这个算法。 再我们做梯度的时候,我们w和b是要根据上一次偏导求出的值,反向传递回来。更新最新参数,再进行梯度更新。 否则会造成梯度消失的问题。 说直白一点就是保存w,b参数,下次梯度更新就从最新参数更新。后面看我的演示代码就清楚了。
使用原生JS进行线性回归
利用上面讲的原理,我们先来看看用原生javascript 怎么实现一个机器学习中线性回归的例子。
- 确定训练数据集和超参数 首先我们需要定义x和y的数据集 这个是一个符合正态分布的数据,由于js没有随机正态分布的方法。我们先定义一组写死的数据吧,为了演示。当然你可以用一个随机范围的数据。只是这样数据离散性非常大。不够直观。
const x= [13,18,23,36,42,48,58,72,85,94] //x轴 对应房子的平方
const y =[18,25,39,47,32,59,73,87,83,94] //y轴 对应房子的价格
const LEARNING_RATE = 0.0003; //学习率
let w = 0; //权重
let b = 0; //偏置
- 确定一个线性模型
const hypothesis = x => w * x + b;
- 定义损失函数,根据MSE得公式得到以下代码
const LossFuc = () => {
let sum = 0;
for (let i = 0; i < M; i++) {
//MSE
sum += Math.pow(hypothesis(x[i]) - y[i], 2);
}
return sum / (2 * M);
}
- 定义计算梯度得方法
const gradient=(arg,deriv)=>arg - LEARNING_RATE * (deriv / M)
5.定义训练方法
const training = () => {
let bSum = 0;
let wSum = 0;
//计算loss并求偏导
for (let i = 0; i < M; i++) {
//对w求偏导
wSum += (hypothesis(x[i]) - y[i]) * x[i];
//对b求偏导
bSum += hypothesis(x[i]) - y[i];
}
//计算梯度,更新参数,并反向传播数据
w = gradient(w,wSum);
b = gradient(b,bSum);
}
总结一下: 我们讲以上整个机器学习的过程,画了一个流程图,如下:
请大家牢记上面整个流程过程,往后会经常用到这套流程,可能会越来越复杂,但也是在此基础之上衍生。
代码已经传到codesandbox上面 演示地址
我们发现采用原生js编写机器学习代码。需要自己封装这些数学公式,简直就是刀耕火种。如果是往后涉及到深度学习,采用神经网络,这些CNN或RNN和GAN等算法,代码会非常复杂和难懂。 所以我们需要框架解决这样的问题。 对于前端人员来说,我们看下javascript中有没有机器学习框架。这里我就想到了TensorflowJS。 下面,我们就看一下,TensorflowJS能否很好的解决我们的问题。
采用TensorflowJs进行线性回归
现在让我们改造上面的代码,采用TensorflowJs进行线性回归,看是否有所提升。 首先,我先去研究研究官方的文档。首先,我就发现一个比较棘手的问题。 在TensorflowJs中所有的类型都属于tensor类型,我们如何将tensor类型转成正常的js原生类型。这个不像python可以转numpy类型去玩。这个时候,我发现tensor类型有一个dataSync方法可以帮助我们。
解决这个问题,那么我们安装我们写js原有的思路来吧。首先定义训练数据和超参数,随机初始化w和b
const trainX= [13,18,23,36,42,48,58,72,85,94]
const trainY =[18,25,39,47,32,59,73,87,83,94]
const LEARNING_RATE = 0.0003;
const w = tf.variable(tf.scalar(Math.random()));//使用tf.variable代表可训练的参数
const b = tf.variable(tf.scalar(Math.random()));
这里可能需要交代一些前置知识,因为机器学习有大家矩阵操作,涉及一些数据变换,什么是标量,张量,向量,矩阵,矩阵加减乘除,转置,广播等操作知识。这些知识如果大家不懂的话,后面可以单独开文讲一下。
定义线性模型计算
function predict(x) {
return tf.tidy(function() { //使用tidy会执行CG动作
return w.mul(x).add(b); //tensor类型默认提供一些计算方法。
});
}
我们发现tensorflowjs是走的函数式的编程风格。
定义损失函数MSE(均方差)计算
//损失函数,MSE
function loss(prediction, labels) {
const error = prediction
.sub(labels)
.square()
.mean();
return error;
}
最后确定训练主方法
function training() {
//sgd算法 随机梯度下降
const optimizer = tf.train.sgd(LEARNING_RATE);
//自动求导
optimizer.minimize(function() {
const predsYs = predict(tf.tensor1d(trainX));
stepLoss = loss(predsYs, tf.tensor1d(trainY));
return stepLoss;
});
}
从上面,损失函数和梯度下降的优化算法,我们就看出, 要比原生的实现简洁的多,不用自己循环累加,也不需要自己求导。TensorflowJS提供自动求导的方法和优化器。代码可读性要比自己纯js的方式要好一些了。所以还是提供了一定的便捷性的。
最终实现还是用react渲染。当然国际惯例,还是贴上演示代码。 codesandbox
使用神经网络解决线性回归问题
TensorflowJS真正强大的地方是采用神经网络进行深度学习。可能有些同学并不了解神经网络,没关系,我们先提前看一下,有个简单的概念。如果采用神经网络怎么解决线性回归问题。虽然有点杀鸡用牛刀的感觉。但可以看出神经网络,不仅能解决大问题,也能处理简单的问题。所以有很多深度学习的课程是直接从神经网络开始讲起。但我不喜欢这种方式,我更喜欢循序渐进的去讲。
废话不多说,我们来看一下,怎么用神经网络解决线性回归问题。代码会变的更简洁。所以我就不分段讲解了,干脆就一次性贴出来吧。以后开文章会慢慢的讲。
const model = tf.sequential(); //定义模型,采用神经网络的顺序模型
const nr_epochs = 10;
const x = [13, 18, 23, 36, 42, 48, 58, 72, 85, 94]
const y = [18, 25, 39, 47, 32, 59, 73, 87, 83, 94]
const xs = tf.tensor2d(x,[10,1]);
const ys = tf.tensor2d(y,[10,1]);
const LEARNING_RATE = 0.0001; //学习率
let w = 0
let b = 0
function initModel(cb) {
model.add(tf.layers.dense({ units: 1, inputShape: [1] })); //我们的问题非常简单,只需要单层神经网络即可
model.setWeights([tf.tensor2d([w], [1, 1]), tf.tensor1d([b])]); //标记后面要跟踪的参数
const optimizer = tf.train.sgd(LEARNING_RATE);//梯度下降优化
model.compile({ loss: 'meanSquaredError', optimizer: optimizer });
model.fit(xs, ys, { //开始训练
epochs: nr_epochs, callbacks: {
onEpochEnd: (epoch, logs) => { //处理每个epoch的回调
w = model.getWeights()[0].dataSync()[0];
b = model.getWeights()[1].dataSync()[0];
cb()
}
}
});
}
这个神经网络的训练是连续,所以我们不能通过按钮区控制它。 onEpochEnd 回调可以监测每个循环批次的过程。 神经网络线性回归例子codesandbox
留给大家思考的问题
- 如果我们的训练样本有很多的数据差,我们应该怎么做?
- 如果学习率设置的过大或过小,会怎么样,我们怎么优化学习率?
- 如果我们的样本非常多,应该怎么训练,怎么优化?
- 多项式回归应该怎么做?
- 逻辑回归怎么做?
关于TensorflowJS最后想说的
TensorflowJS更适合迁移学习, 它不太适合做大规模的模型训练。 比较成熟度和第三方支持都没有python更方便,语法方面,矩阵操作还是python更爽一些。 而且TensorflowJS偏冷门,学习资料较少,不像python,有大量的学习资料。但基于一个模型,做一些小的东西,我觉得是没问题的, 而且它也是可以运行在nodejs中的。所以总的来说,如果你非常熟悉JS,并且模型简单,可以考虑。 好,就到这里吧,谢谢大家看到最后。