第4章 机器学习基础
在本章中,你将学习机器学习中的线性回归、逻辑回归以及最优化方法中的梯度下降法。
其中,线性回归是逻辑回归的基础,而逻辑回归又可以在神经网络中被用来处理2分类问题,因此逻辑回归是神经网络的组成部分。
本章介绍线性回归的原因是线性回归是所有算法的基础,同时也是为了帮助读者理解
第5章神经网络的线性可分与线性不可分的问题做铺垫。
本章介绍逻辑回归中的Sigmoid是为了帮助读者理解后续神经网络中的激活函数。
本章介绍梯度下降法也是为了后续帮助读者理解神经网络的优化方法随机梯度下降法做铺垫。
在图像识别技术的学习历程中,读者需要具备一些基础知识,因此有必要理解和掌握本章所介绍的内容。
如果读者对本章内容已经比较熟悉了,那么可以略过本章。
本章的要点具体如下。
·线性回归·逻辑回归 ·梯度下降法
4.1 线性回归模型 介绍线性回归(Linear Regression)模型之前,首先介绍两个概念:线性关系和回归问题。
1)线性关系:变量之间的关系是一次函数,也就是说当一个自变量x和因变量y的关系被画出来时呈现的是一条直线,当两个自变量x1、x2和因变量y的关系被画出来时呈现的是一个平面。
反之,如果一个自变量x和因变量y的关系为非线性关系,那么它们的关系被画出来时呈现的是一条曲线,而如果两个自变量x1、x2和因变量y的关系为非线性关系时,那么它们的关系被画出来时呈现的就是一个曲面。
我们再用数学表达式来解释一下,y=a*x1+b*x2+c的自变量和因变量就是线性关系,而y=x2、y=sin(x)的自变量和因变量就是非线性关系。
2)回归问题:
即预测一个连续问题的数值。这里列举一个例子以方便读者理解:小王站在银行的柜台前想知道他可以办理多少贷款,银行的工作人员会问小王几个问题,比如年龄多少(特征1),每个月收入多少(特征2)。
之后根据小王的回答,银行工作人员根据模型(线性回归)分析结果,回答可以给小王10万(回归即是对据图数值的预测)的贷款。
线性回归会根据历史上其他人的历史贷款数据(年龄和工资对应的两个特征分别记为x1和x2),找出最好的拟合面来进行预测(如果是一个特征则是线)。
这个例子中用年龄和收入预测出具体的贷款额度,而贷款额度是一个连续的数值,因此该问题即为回归问题。
扩展一下,如果上面例子中的小王想知道他是否可以办理贷款,那么这就变成了一个二元分类问题——能办理贷款或者不能办理贷款。
线性回归主要用于处理回归问题,少数情况用于处理分类问题。
4.1.1 一元线性回归
一元线性回归是用来描述自变量和因变量都只有一个的情况,且自变量和因变量之间呈线性关系的回归模型,一元线性回归可以表示为y=a*x+b,其中只有x一个自变量,y为因变量,a为斜率(有时也称为x的权重),b为截距。
下面举例说明。 比如,我们目前有如下这样一组数据:
x = np.array([1,2,4,6,8]) #铺设管子的长度
y = np.array([2,5,7,8,9]) #收费,单位为元 x代表的是铺设管子的长度,y对应的是相应的收费。
我们希望通过一元线性回归模型寻找到一条合适的直线,最大程度地“拟合”自变量x(管子长度)和因变量y(收费)之间的关系。
这样,当我们知道一个管子的长度,想知道最可能的收费是多少的时候,线性回归模型就可以通过这条“拟合”的直线告诉我们最可能的收费是多少。
学习一元线性模型的过程就是通过训练数据集得到最合适的a和b的过程,也就是说该一元线性模型的参数即为a和b。
当输入一个新的测试数据点的时候,我们可以通过训练好的模型(y=a*x+b)来进行预测。例如,对于一个测试数据x_test(不在np.array([1,2,4,6,8])中),由于模型参数a和b已知,因此可以计算线性方程a*x_test+b得到预测的结果y_predict。 了解了一元线性回归的概念之后,我们来看一下如何通过训练来得到这样一个模型。
首先,我们要定义好一个模型的评价方式,即如何判断这个模型的好与不好,好到什么程度,坏到什么程度。
我们比较容易想到的就是通过计算预测值y_predict与真实值y之间的差距。也就是说,y_predict与y的距离越小,则代表我们的模型效果越好。
那么我们又该如何衡量这个模型的y_predict的值与y真实值之间的差距呢?首先比较容易想到的是直接计算这两个值之间的差值。
具体来说就是,对于每一个点(x)都计算y-y_predict,然后将所有的值进行累加最后除以样本数,目的是为了减少样本的对于结果的影响,即。
但是,这里有个问题,我们预测出来的y_predict有可能大于真实值y也有可能小于真实值y,这会导致我们累加之后的误差被削弱(比如,结果被正负中和导致最终累加误差接近于0),这个显然与实际情况不符合,因此需要对这种方式做一定的修改。
基于上面的原因,我们很容易想到,对于每一个点(x)都计算|yi-y_predicti|,之后再进行累加。
从某种角度来说,这个衡量标准是可以的,但是由于考虑到后续的误差计算以及存在求导等问题(该式导数不连续),实际使用过程中很少使用这种方法。
进一步优化后的评估方法为:对于每一个点(x)都计算y-y_predict,然后对这个结果做一次平方,最后为了忽略样本数的影响我们取平均值。
该方法是比较常用的度量预测值与真实值之间差距的方法。 接下来就是找到一个合适的方法对预测值和真实值的差距进行优化,即希望尽可能地小。
由于y_predicti=axi+b,所以我们可以将公式改写为。对于这个公式来说,只有a和b是待学习的参数,其他的x和y都可以从训练集中得到。
我们可以通过最小二乘法(又称最小平方法)来寻找最优的参数a和b。最小二乘法是一种数学优化技术,它通过最小化误差的平方和的方法寻找最优的参数。
利用这个最小二乘法的推导(推导过程将不在本书中详述,有兴趣的同学可以自行上网查询,这里我们直接给出推导结果),我们可以得到求解a和b的公式: 其中,x和y分别代表数据集中x和y的平均值。
至此,我们完成了一元线性回归的理论介绍,得到了一元线性回归模型y=a*x+b中的参数a和b。
为了扩展读者的思路,本节中我们为读者推荐另一种误差衡量标准:R Squared,公式如下: 我们仔细分析下,分子部分其实就是我们的预测模型产生的误差;而分母部分其实就是y的均值与预测值之间的差距。 下面我们来详细讲解下R2,首先R2肯定是小于等于1的。
R2的值是越大越好,当我们的预测模型完全预测准确(预测值与真实值一致的时候)时,R2就会得到其最大值1。
当我们的模型和基准模型()相同的时候,R2为0,说明这个时候训练了很长时间的模型只是达到了基准模型的效果。
如果R2小于0,则说明我们训练出来的模型连基准模型都达不到。
1.一元线性回归算法的实现思路 上文中,我们介绍了一元线性回归的核心思想以及如何求得最小误差(通过最小二乘法),那么接下来为了方便读者的理解,我们使用Python来实现一元线性回归的算法。
x = np.array([1,2,4,6,8]) #铺设管子的长度
y = np.array([2,5,7,8,9]) #收费,单位为元 我们想通过最小二乘法减少误差,找到那条拟合直线。
对于a和b的求解,之前我们已经给出了结论,现在是通过Python的方式进行实现。
我们打开Pycharm,新建一个Python的项目,创建演示数据集,输入如下代码:
if __name__=='__main__':
x = np.array([1,2,4,6,8]) #铺设管子的长度
y = np.array([2,5,7,8,9]) #费用
x_mean = np.mean(x) #求出x向量的均值
y_mean = np.mean(y) #求出y向量的均值 通过上述代码,我们能得到x与y的均值,然后,我们来尝试计算下a和b,a相对更复杂一些。
a和b的计算代码如下:
denominator = 0.0 #分母
numerator = 0.0 #分子
for x_i, y_i in zip(x, y): #将x,y向量合并起来形成元组(1,2),(2,5)
numerator += (x_i - x_mean) * (y_i - y_mean) #按照a的公式得到分子
denominator += (x_i - x_mean) ** 2 #按照a的公式得到分母
a = numerator / denominator
#得到a
b = y_mean - a * x_mean #得到b 我们得到a和b之后就可以使用Matplotlib来绘制图形了,从而使读者能够更加直观地查看拟合直线,其中,scatter这个方法可用于绘制各个训练数据点。
使用plot方法绘制拟合直线的代码具体如下: y_predict = a * x + b #求得预测值y_predict
plt.scatter(x,y,color='b') #
画出所有训练集的数据
plt.plot(x,y_predict,color='r') #画出拟合直线,颜色为红色
plt.xlabel('管子的长度', fontproperties = 'simHei', fontsize = 15) #设置x轴的标题
plt.ylabel('收费', fontproperties='simHei', fontsize=15) #设置y轴的标题
plt.show() 完整的代码与效果图分别如下: import numpy as np
import matplotlib.pyplot as plt
if __name__=='__main__':
x = np.array([1,2,4,6,8])
y = np.array([2,5,7,8,9])
x_mean = np.mean(x)
y_mean = np.mean(y)
denominator = 0.0
numerator = 0.0
for x_i, y_i in zip(x, y):
numerator += (x_i - x_mean) * (y_i - y_mean) #按照a的公式得到分子
denominator += (x_i - x_mean) ** 2 #按照a的公式得到分母
a = numerator / denominator #得到a
b = y_mean - a * x_mean #得到b
y_predict = a * x + b
plt.scatter(x,y,color='b')
plt.plot(x,y_predict,color='r')
plt.xlabel('管子的长度', fontproperties = 'simHei', fontsize = 15)
plt.ylabel('收费', fontproperties='simHei', fontsize=15)
plt.show()
轮到你来: 尝试模仿上述代码,自己设置一些样本数据,练习一下一元线性回归的整体流程。
当输入一个新的测试数据的时候,我们就能通过y_predict=ax_test+b,得到预测值,代码片段如下,结果为8.74(保留小数点2位并且考虑四舍五入):
x_test = 7
y_predict_value = a * x_test + b
print(y_predict_value)
2.一元线性回归的算法封装 与第3章讲到的KNN算法一样,在一元线性回归介绍的最后,我们将对此算法使用面向对象的思想来进行封装。
首先,我们需要初始化变量,其中,a和b是我们的训练数据通过最小二乘法得到的结果,我们在命名的时候可以约定如下规则,凡是初始化变量仅仅作为类内部计算使用,而非用户外部输入的变量,一律使用变量名+下划线的形式,代码如下:
import numpy as np
class SimpleLinearRegressionSelf:
def __init__(self):
"""初始化Simple linear regression模型"""
self.a_ = None
self.b_ = None
然后,我们实现一下fit方法,本方法主要是用来训练模型,也就是得到a值和b值,代码如下: def fit(self,x_train,y_train):
assert x_train.ndim == 1, \
"一元线性回归模型仅处理向量,而不能处理矩阵"
x_mean = np.mean(x_train)
y_mean = np.mean(y_train)
denominator = 0.0
numerator = 0.0
for x_i, y_i in zip(x_train, y_train):
numerator += (x_i - x_mean) * (y_i - y_mean) #按照a的公式得到分子
denominator += (x_i - x_mean) ** 2 #按照a的公式得到分母
self.a_ = numerator / denominator #得到a
self.b_ = y_mean - self.a_ * x_mean #得到b
return self
其次,我们来看下如何编写predict函数的代码以用来做预测。我们希望predict函数接收的是向量集合,在命名上我们使用x_test_group,实现代码具体如下: def predict(self,x_test_group):
return np.array([self._predict(x_test) for x_test in x_test_group])
#对于输入向量集合中的每一个向量都进行一次预测,预测的具体实现被封装在_predict函数中。
def _predict(self,x_test):
return self.a_ * x_test + self.b_ #求取每一个输入的x_test以得到预测值的具体实现 下面我们增加一下衡量模型的得分函数,实现代码如下:
def mean_squared_error(self,y_true,y_predict):
return np.sum((y_true - y_predict) ** 2) / len(y_true)
def r_square(self,y_true,y_predict):
return 1 - (self.mean_squared_error(y_true,y_predict) / np.var(y_true))
在本节的最后,我们使用封装好的一元回归模型来测试一下,当我们输入一个向量7的时候结果会怎样。
具体实现如下:
import numpy as np
from book.lr.LinearRegressionSelf import SimpleLinearRegression
if __name__ == '__main__':
x = np.array([1, 2, 4, 6, 8])
y = np.array([2, 5, 7, 8, 9])
lr = SimpleLinearRegression() #封装模型的类名 lr.fit(x,y) #训练模型得到a和b
print(lr.predict([7])) #输出结果与之前一致,都是8.74
print(lr.r_square([8,9],lr.predict([6,8]))) #得到的得分是0.09443783462224864
轮到你来: 模仿上述实现的思路与代码,编写一个属于自己的一元线性回归类,然后测试一下效果如何。