李宏毅机器学习——线性回归
引言
这篇文章是李宏毅机器学习视频的笔记。这是我见过最简单的机器学习入门视频,没有之一。
本文主要介绍线性回归。
回归分析
它的输出是一个标量(数值),叫做回归(Regression)
机器学习的三步走
以宝可梦为例。
找到一个函数,输入一个宝可梦(神奇宝贝)的属性(特征),输出它进化后的战斗力(Combat Power,CP值)。
xcpx_{cp}xcp是进化前的CP值,xsx_sxs是它所属的种类,xhpx_{hp}xhp表示它的血量,xwx_wxw表示它的重量,xhx_hxh表示它的高度。
设计模型
设计模型,也就是确定一组函数。
假设进化后的CP值与进化前的CP有很大关系,我们可以将这个函数组写成:y=b+wxcpy = b + w x_{cp}y=b+wxcp的形式。
其中b和w称为(模型的)参数。
对于不同的b和w,就能得到不同的函数。
上面这个模型称为线性模型: y=b+∑wixiy = b + \sum w_ix_iy=b+∑wixi
其中xix_ixi可换成不同的属性,比如xcp,xhp,xw,xhx_{cp},x_{hp},x_w,x_hxcp,xhp,xw,xh。
要知道该放入哪些属性,需要一些领域知识。比如进化前的CP肯定和进化后的CP值有关。
上面多次说到的属性在机器学习中叫做特征(feature),wiw_iwi称之为权重,bbb称之为偏移量(bias)。
x是一个特征(feature)向量
判断函数的好坏
得到了一组函数后,我们需要判断函数的好坏,从而从中选择最好的函数来进行预测进化后的CP值。
那么如何判断呢?我们需要收集训练的资料,这个训练的资料必须包含输入数据(宝可梦的特征)和输出数据(这里是标量,CP值)之间的关系。
也就是同时包含原CP值和进化后CP值的数据。
如上图,我们有一只小乌龟,并且知道它进化后的数据。
其中x1x^1x1来表示第一份资料(数据),还记得上面用下标xcpx_{cp}xcp来表示它的特征吗,
结合起来,xcp1x^1_{cp}xcp1表示第一个神奇宝贝的CP值。y^1\hat{y}^1y^1表示它相应的观测到的输出值(进化后的CP值)。
假设我们抓10只宝可梦,同时知道CP值和进化后的CP值。
下图是原CP值(X轴)与进化后的CP值图示:
画图代码如下:
import matplotlib.pyplot as pltdef draw():cp = [338., 333., 328., 207., 226., 25., 179., 70., 208., 606.]y = [640., 633., 619., 393., 428., 27., 193., 66., 226., 1591.]plt.annotate("$(x_{cp}^n,\hat{y}^n)$", xy = (cp[4], y[4]), xytext = (250, 150), arrowprops = dict(facecolor = 'black', shrink = 0.1))plt.xlabel("Original CP")plt.ylabel("CP after evoluation")plt.plot(cp,y,'bo')plt.show()if __name__ == '__main__':draw()
有了这些数据以后,我们就可以衡量一个函数的好坏程度。
要衡量一个函数的好坏,我们需要另一个函数——损失函数。
它的输入是函数,输出是该函数有多差。损失函数可以定义如下:
L(f)=∑n=110(y^n−f(xcpn))2L(f) = \sum_{n=1}^{10}(\hat{y}^n - f(x_{cp}^n))^2 L(f)=n=1∑10(y^n−f(xcpn))2
我们有10只神奇宝贝,我们都知道它们进化后的CP值。把进化前的CP值输入到我们的函数中,看该函数输出的进化后的CP值与实际进化后的CP值相差有多大(相差越大说明越不准确,所以说损失函数的输出时函数有多差)。
f(xcpn)f(x_{cp}^n)f(xcpn)是我们函数预测出来的结果,y^n\hat{y}^ny^n是真正的结果,n为第n个数据
因为fff是由w,bw,bw,b决定的,因此上面的损失函数可以转换为
L(w,b)=∑n=110(y^n−(b+w⋅xcpn))2L(w,b) = \sum_{n=1}^{10}(\hat{y}^n - (b + w \cdot x_{cp}^n))^2 L(w,b)=n=1∑10(y^n−(b+w⋅xcpn))2
下面画出这个损失函数的图形:
上图中的每个点都代表一个函数,颜色代表损失值,红色表示非常大,蓝色代表较小。
橙色X点表示损失值最小的函数的点。画图代码如下:
import matplotlib.pyplot as plt
import numpy as npdef calculate():# data = np.loadtxt("pokemon.csv", delimiter=",", skiprows=1) #跳过第一行# x_data = data[:,0]# y_data = data[:,1]x_data = [338, 333, 328, 207, 226, 25, 179, 60, 208, 606]y_data = [640, 633, 619, 393, 428, 27, 193, 66, 226, 1591]# y_data = b + w * x_data# 通过穷举法x = np.arange(-200,-100,1) # bias 100个数 -200到-100(不包括)的数组,步长为1,就是-200,-199,...,-101y = np.arange(-5,5,0.1) #weight 100个数Z = np.zeros((len(x),len(y))) #len(x)行 len(y)列的零矩阵minx,miny = 0,0minLoss = 100000000 # 初始设个较大的值for i in range(len(x)):for j in range(len(y)):b,w = x[i],y[j]Z[j][i] = 0for n in range(len(x_data)): #对len(x_data)个数据计算损失值Z[j][i] = Z[j][i] + (y_data[n] - b - w*x_data[n])**2 #(实际的y - 预测的y)^2Z[j][i] = Z[j][i]/len(x_data) #共有len(x_data)个,求个均值if Z[j][i] < minLoss:minLoss = Z[j][i]minx, miny = i, jplt.contourf(x,y,Z,50,alpha=0.5,cmap=plt.get_cmap('jet'))#画三维等高线,并对区域进行填充plt.plot([x[minx]],[y[miny]],'x',ms=12,markeredgewidth=3,color='orange') #标记处损失函数值最小的参数位置plt.xlim(-200,-100)plt.ylim(-5,5)plt.xlabel(r'$b$',fontsize=16)plt.ylabel(r'$w$',fontsize=16)plt.show()if __name__ == '__main__':calculate()
接着我们画出我们的预测函数:
画图代码:
import matplotlib.pyplot as plt
import numpy as np
def draw():cp = [338, 333, 328, 207, 226, 25, 179, 60, 208, 606]y = [640, 633, 619, 393, 428, 27, 193, 66, 226, 1591]#plt.annotate("$(x_{cp}^n,\hat{y}^n)$", xy = (cp[4], y[4]), xytext = (250, 150), arrowprops = dict(facecolor = 'black', shrink = 0.1))plt.xlabel("Original CP")plt.ylabel("CP after evoluation")plt.plot(cp,y,'bo')pre_y = -196 + 2.699 * np.array(cp)plt.plot(cp, pre_y, 'r-')plt.show()if __name__ == '__main__':draw()
可以看到,还是比较好的画出了这个趋势。
因为这只有两个参数,所以我们可以穷举出来,并且可以看到效率不高,那么如何通过更加一般的方法找到最好的函数呢。继续往下看。
找到最好的函数
定义好了损失函数后,接下来就要通过这个损失函数来找到最好的预测函数。
f∗=argminfL(f)f^* = arg\,\min_{f}L(f) f∗=argfminL(f)
我们要找一个函数f,使得损失函数的值最小。我们知道f由w,b决定,也就是找到w∗,b∗w^*,b^*w∗,b∗使得
w∗,b∗=argminw,bL(w,b)w^*,b^* = arg\,\min_{w,b}L(w,b) w∗,b∗=argw,bminL(w,b)
也就是要解
w∗,b∗=argminw,bL(w,b)∑n=110(y^n−(b+w⋅xcpn))2w^*,b^* = arg\,\min_{w,b}L(w,b) \sum_{n=1}^{10}(\hat{y}^n - (b + w \cdot x_{cp}^n))^2 w∗,b∗=argw,bminL(w,b)n=1∑10(y^n−(b+w⋅xcpn))2
如何解呢?通过**梯度下降(Gradient Descent)**算法。
梯度下降
考虑有一个参数www的损失函数L(w)L(w)L(w) : w∗=argminwL(w)w^* = arg\,\min_{w}L(w)w∗=argminwL(w),找到最小的损失函数的www。
因为只有一个参数www,我们把www与L(w)L(w)L(w)的关系画出来:
我们可以看图知道L(w)L(w)L(w)的最低点在哪。那梯度下降算法该怎么做呢?
梯度下降算法过程:
- 随机选一个初始值 w0w^0w0,如下图
- 计算dLdw∣w=w0\frac{dL}{dw} | _ w = w^0dwdL∣w=w0
即找曲线在w0w^0w0点处切线的斜率。若该点的斜率为负数,表示函数值在递减,因此需要向右(将参数www的值增加)找到更小的值;反之,若斜率为负数,需要减小www的值。
上述文字如果通过公式来描述如下所示:
w1←w0−ηdLdw∣w=w0w^1 \leftarrow w^0 - \eta\frac{dL}{dw} | _ w = w^0 w1←w0−ηdwdL∣w=w0
初始是w0w^0w0,把微分的结果乘上一个负数与常数η\etaη,再与w0w^0w0相加就得到更新后的w1w^1w1。
如上所说,微分的结果如果是负的(应该往右),那么乘以负号刚好为正,w0w^0w0增加,反之亦然。
其中η\etaη是learning rate,可以理解为移动的步长。
3. 再计算w1w^1w1处的微分dLdw∣w=w1\frac{dL}{dw} | _ w = w^1dwdL∣w=w1来决定往左移还是往右移,然后就得到了w2w^2w2 : w2←w1−ηdLdw∣w=w1w^2 \leftarrow w^1 - \eta\frac{dL}{dw} | _ w = w^1w2←w1−ηdwdL∣w=w1
不断的重复第3步,直到得到微分是0的wTw^TwT。
但是从上面的图就可以发现,很有可能停在极小值(局部最小值),而不是最小值点。梯度下降算法并不能保证能找到最小值点。
上面只分析了一个参数,那如果有两个参数呢?
w∗,b∗=argminw,bL(w,b)w^*,b^* = arg\,\min_{w,b}L(w,b)w∗,b∗=argminw,bL(w,b)
其实步骤也差不多:
- 随机选初始值 w0,b0w^0,b^0w0,b0
- 分别计算对w0w^0w0和b0b^0b0的偏微分:∂L∂w∣w=w0,b=b0,∂L∂b∣w=w0,b=b0\frac{\partial L}{\partial w} | _ w = w^0,b = b^0,\frac{\partial L}{\partial b} | _ w = w^0,b = b^0∂w∂L∣w=w0,b=b0,∂b∂L∣w=w0,b=b0
w1←w0−η∂L∂w∣w=w0,b=b0b1←b0−η∂L∂b∣w=w0,b=b0w^1 \leftarrow w^0 - \eta\frac{\partial L}{\partial w} | _ w = w^0,b = b^0 \\ b^1 \leftarrow b^0 - \eta\frac{\partial L}{\partial b} | _ w = w^0,b = b^0 w1←w0−η∂w∂L∣w=w0,b=b0b1←b0−η∂b∂L∣w=w0,b=b0
同理得到w2w^2w2,b2b^2b2
w2←w1−η∂L∂w∣w=w1,b=b1b2←b1−η∂L∂b∣w=w1,b=b1w^2 \leftarrow w^1 - \eta\frac{\partial L}{\partial w} | _ w = w^1,b = b^1 \\ b^2 \leftarrow b^1 - \eta\frac{\partial L}{\partial b} | _ w = w^1,b = b^1 w2←w1−η∂w∂L∣w=w1,b=b1b2←b1−η∂b∂L∣w=w1,b=b1
上面的公式写起来有点复杂,我们可以把www和bbb对LLL的偏微分写成一个矩阵,记为∇L\nabla L∇L:
∇L=[∂L∂w∂L∂b]\nabla L= \left[ \begin{matrix} \frac{\partial L}{\partial w}\\ \frac{\partial L}{\partial b} \end{matrix} \right] ∇L=[∂w∂L∂b∂L]
接下来通过图示来分析下梯度下降的过程:
颜色代表损失函数的数值,以左下角的红点为初始值,在该位置计算www和bbb对LLL的偏微分,然后再乘上−η-\eta−η,得到一个向量(−η∂L/∂b,−η∂L/∂w)(-\eta \partial L / \partial b,-\eta \partial L / \partial w)(−η∂L/∂b,−η∂L/∂w)。
该向量指向的方向就是等高线的法线方向(损失函数值减少的方向)。
再多次计算偏微分,更新参数www。上面只是介绍了两个参数的情况,如果有多个参数方法也是一样的。
现在有个问题,我们用θ\thetaθ表示参数的集合
θ∗=argminθL(θ)\theta^*=arg\,\min_{\theta}L(\theta) θ∗=argθminL(θ)
每次我们更新参数,都会得到一个新的θ\thetaθ使得L(θ)L(\theta)L(θ)的值更小?也就是下式一定成立?
L(θ0)>L(θ1)>L(θ2)>⋯L(\theta^0) > L(\theta^1) > L(\theta^2) > \cdotsL(θ0)>L(θ1)>L(θ2)>⋯
我们知道,走到了最小值点时,微分值为0,因此参数就不再更新。但是微分值是0的点,不一定是极值点。
存在鞍点(saddle point) 微分为0,但是不是极值。
不过实际情况是,很难算出微分值刚好为0的点,常见的做法是找到一个微分值足够小的点,就不再计算下去了。
但是又会存在另一个问题,有可能微分值很小的点,它离极小值点还很远,比如上图的plateau点。
在做线性回归时,不太需要担心上面的问题,如上图右上角所示,它的图形通常是一个碗的形式,一般都能找到局部或全局最小点。
L(w,b)=∑n=110(y^n−(b+w⋅xcpn))2L(w,b) = \sum_{n=1}^{10}(\hat{y}^n - (b + w \cdot x_{cp}^n))^2L(w,b)=∑n=110(y^n−(b+w⋅xcpn))2
接下来想一下如何计算对www和bbb对LLL的偏微分?(复合函数的求导法则)
∂L∂w=∑n=1102(y^n−(b+w⋅xcpn))(−xcpn)\frac{\partial L}{\partial w} = \sum_{n=1}^{10}2(\hat{y}^n - (b + w \cdot x_{cp}^n)) (-x_{cp}^n)∂w∂L=∑n=1102(y^n−(b+w⋅xcpn))(−xcpn)
∂L∂b=∑n=1102(y^n−(b+w⋅xcpn))(−1)\frac{\partial L}{\partial b} = \sum_{n=1}^{10}2(\hat{y}^n - (b + w \cdot x_{cp}^n)) (-1)∂b∂L=∑n=1102(y^n−(b+w⋅xcpn))(−1)
代码实现
def gradient_descent():x_data = [338, 333, 328, 207, 226, 25, 179, 60, 208, 606]y_data = [640, 633, 619, 393, 428, 27, 193, 66, 226, 1591]b = -120w = -4lr = 0.0000001 #learning rateiteration = 10000 #迭代次数for i in range(iteration):b_grad = 0.0w_grad = 0.0for n in range(len(x_data)):w_grad = w_grad - 2.0 * (y_data[n] - (b + w * x_data[n])) * x_data[n]b_grad = b_grad - 2.0 * (y_data[n] - (b + w * x_data[n])) * 1.0b = b - lr * b_gradw = w - lr * w_gradprint(b,w)
我们把得出的参数代入到画图代码,得出的图形如下:
同时求出损失值为:86.371
画图代码为:
import matplotlib.pyplot as plt
import numpy as np
def draw(b,w):cp = [338, 333, 328, 207, 226, 25, 179, 60, 208, 606]y = [640, 633, 619, 393, 428, 27, 193, 66, 226, 1591]#data = np.loadtxt("pokemon.csv", delimiter=",", skiprows=1) #跳过第一行#cp = data[:,0]#y = data[:,1]#plt.annotate("$(x_{cp}^n,\hat{y}^n)$", xy = (cp[4], y[4]), xytext = (250, 150), arrowprops = dict(facecolor = 'black', shrink = 0.1))plt.xlabel("Original CP")plt.ylabel("CP after evoluation")plt.plot(cp,y,'bo')pre_y = b + w * np.array(cp)plt.plot(cp, pre_y, 'r-')error = 0for i in range(len(cp)):error = error + (abs(y[i] - (b + w * cp[i])))error = error / 10print(error)plt.show()if __name__ == '__main__':draw(-120.362,2.474)
如何评判结果
以之前通过暴力方法得到的预测函数为例,error就是真实值与实际值的差距。
我们把每个数据的error加起来求平均得到:
cp = [338, 333, 328, 207, 226, 25, 179, 60, 208, 606]y = [640, 633, 619, 393, 428, 27, 193, 66, 226, 1591]error = 0for i in range(len(cp)):error = error + (abs(y[i] - (-120.362 + 2.474 * cp[i])))error = error / 10print(error)
输出为:86.371(没错确实是这么多,我把视频中的参数带进去结果比这个还差一点点)
但是我们真正在意的是这个预测函数对我们从来没见过的宝可梦的预测能力,因此我们需要再抓10只宝可梦。
并且计算新的10只宝可梦的预测值与实际值的差值:
import matplotlib.pyplot as plt
import numpy as np
def draw():cp = [338, 333, 328, 207, 226, 25, 179, 60, 208, 606]y = [640, 633, 619, 393, 428, 27, 193, 66, 226, 1591]data = np.loadtxt("pokemon.csv", delimiter=",", skiprows=1) #跳过第一行cp = data[:,0]y = data[:,1]#plt.annotate("$(x_{cp}^n,\hat{y}^n)$", xy = (cp[4], y[4]), xytext = (250, 150), arrowprops = dict(facecolor = 'black', shrink = 0.1))plt.xlabel("Original CP")plt.ylabel("CP after evoluation")plt.plot(cp,y,'bo')pre_y = -120.362 + 2.474 * np.array(cp)plt.plot(cp, pre_y, 'r-')error = 0for i in range(len(cp)):error = error + (abs(y[i] - (-196 + 2.699 * cp[i])))error = error / 10print(error)plt.show()if __name__ == '__main__':draw()
其中pokemon.csv(我随便取了连续的10只新的宝可梦数据):
cp,cp_new
384,694
366,669
353,659
338,640
242,457
129,243
10,15
25,47
24,47
161,305
得出的结果为:83.832,图像为:
算出来的结果比训练数据的结果还要好一点!!(这有点不合理啊)。
那么是否还能做得更好,可能CP值与进化后的CP值不是一个一次线性关系,而是二次关系:
y=b+w1xcp+w2(xcp)2y = b + w_1x_{cp} + w_2(x_{cp})^2y=b+w1xcp+w2(xcp)2
可以看到,二次曲线更加合适。继续尝试三次曲线,四次曲线:
结果模型更复杂了,在训练数据上确实效果更好,但是在测试数据上结果反而更差。这种现象称为过拟合。
过拟合:在训练数据集上表示很好,但是在测试数据集上不理想称为过拟合。如何避免呢?增大训练数据集。
要解决过拟合问题可以收集更多的数据。
当我们找到更多的数据后,可以发现,似乎还有什么因素影响了进化后的CP值,观察数据可知,是宝可梦的种类。比如蓝色点种类都属于Pidgey。
因此重新回到第一步,重新设计下模型:
也就是根据不同的种类,分别计算不同的参数值。
最后会得到四条直线(其中有两条几乎重叠了)。
正则化
上面说了在训练数据上得到损失最小的函数就是最好的函数。这个叙述可能没有那么精确,因为可能会导致过拟合。
因此,我们要换一个损失函数。
y=b+∑wixiy = b + \sum w_ix_iy=b+∑wixi
原来的损失函数为:
L=∑n(y^n−(b+∑wixi))2L = \sum_n(\hat{y}^n - (b + \sum w_ix_i))^2 L=n∑(y^n−(b+∑wixi))2
原来的损失函数只是预测损失有多大,现在新的损失函数还增加了一项:
L=∑n(y^n−(b+∑wixi))2+λ∑(wi)2L = \sum_n(\hat{y}^n - (b + \sum w_ix_i))^2 + \lambda \sum(w_i)^2 L=n∑(y^n−(b+∑wixi))2+λ∑(wi)2
这样找到的函数不仅可以是损失较小,同时还需要使得wiw_iwi也很小,也就意味着这个函数比较平滑。
因为我们相信大多数情况下,与抖动很大的函数比,平滑的函数更可能是正确的。
其中λ\lambdaλ可以用来控制平滑程度,值越大越平滑。
假设λ\lambdaλ与训练数据集以及测试集的损失值的关系如上图。由上图可知,取100时较好。
任何事情都是过犹不及,我们需要平滑的函数,但也不能太过平滑。
李宏毅机器学习——线性回归相关推荐
- 2018-3-19李宏毅机器学习视频学习笔记九----Classification: Probabilistic Generative Model
视频来源: 李宏毅机器学习(2017)_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili https://www.bilibili.com/video/av10590361/?p=10 步骤: (1 ...
- 李宏毅机器学习完整笔记发布,AI界「最热视频博主」中文课程笔记全开源
点上方"小詹学Python",选择"置顶公众号"第一时间获取干货分享!本文转自:Datawhale 提起李宏毅老师,熟悉机器学习的读者朋友一定不会陌生.很多人选 ...
- 【李宏毅机器学习】Logistic Regression 逻辑回归(p11) 学习笔记
李宏毅机器学习学习笔记汇总 课程链接 文章目录 Logistic Regression Step 1: Function Set Step 2: Goodness of a Function Step ...
- 【李宏毅机器学习】Classification_1 分类(p10) 学习笔记
李宏毅机器学习学习笔记汇总 课程链接 文章目录 Classification Probabilistic Gernerative Model 分类:概率遗传模型 ==**如何完成这个任务?**== = ...
- python线性回归预测pm2.5_线性回归预测PM2.5----台大李宏毅机器学习作业1(HW1)
一.作业说明 给定训练集train.csv,要求根据前9个小时的空气监测情况预测第10个小时的PM2.5含量. 训练集介绍: (1).CSV文件,包含台湾丰原地区240天的气象观测资料(取每个月前20 ...
- 李宏毅机器学习—读书笔记
李宏毅机器学习笔记(LeeML-Notes) leeml-notes 机器学习 人工智慧(Artificial Intelligence)是我们想要达成的目标,希望机器可以跟人一样的聪明.机器学习(M ...
- 【李宏毅机器学习CP1-3】(task1)机器学习简介分类|回归
文章目录 1.机器学习的过程 2.机器学习相关分类 3.选择合适的model,loss function 4.CP3 一.回归定义和应用例子 回归定义 应用举例 二.模型步骤 Step 1:模型假设 ...
- 李宏毅机器学习-HW1
文章目录 前言 一.分析目标 二.数据预处理 1.初步处理 2.特征提取 3.Normalize和切分训练集和验证集 三.训练 四.验证 五.预测 1.数据预处理 2.预测 3.写入文件 总结 前言 ...
- 李宏毅机器学习HW1
本博文主要是完成李宏毅机器学习HW1作业 作业连接:https://ntumlta2019.github.io/ml-web-hw1/ 作业规则 所有代码必须用python3.6编写 允许所有pyth ...
- 百度飞桨2021李宏毅机器学习特训营学习笔记之回归及作业PM2.5预测
百度飞桨2021李宏毅机器学习特训营学习笔记之回归及作业PM2.5预测 前言 回归 什么是回归(Regression)? 怎么做回归? 线性回归(Linear Regression) 训练集与验证集 ...
最新文章
- rest-framework之解析器
- phpcms调用栏目描述_phpcms标签整理_当前栏目调用
- 访问进程环境变量environ时的一个坑
- 指针:自定义函数sumDiff(),调用它来求两个数的和、差
- Android 之父裁员 30%:开发者如何避免“被离职”?
- CUDA学习(九十八)
- 怎样获取php页面get的值,PHP循环获取GET和POST值的代码
- 计算机一级重点知识,计算机一级MSOffice考试重点:数制的基本概念
- es 创建索引_es的基本原理和操作文档
- 基于event 实现的线程安全的优先队列(python实现)
- java tcp demo_详解Java使用TCP实现数据传输实例
- 两个前端项目利用iframe进行通信
- JSP户外旅游网站系统myeclipse开发mysql数据库web结构计算机java编程
- 英语学习(九)复合句
- 如何用用计算机名访问共享打印机,局域网怎么连接共享打印机共享
- 文本表达:解决BERT中的各向异性方法总结
- A micro Lie theory for state estimation in robotics002
- laravel详情页
- BJOI2019 勘破神机 新解
- 微信传文件又慢又限制大小?试试这3个免费在线传文件工具!