文章目录
- 一、线性回归
- 1. 回归问题
- - 回归和分类
- 2. 预测未来
- - 机器学习实现预测的流程
- 3. 线性方程
- - 权值调整
- 4. 最简单的回归问题——线性回归问题
- - 利用线性回归进行预测的极速入门
- 二、线性回归的算法原理
- - 拟合
- 1. 线性回归算法的数学解析
- (1) 假设函数的数学表达式解析
- (2) 损失函数的数学表达式解析
- (3) 优化方法的数学表达
- (4) 范数
- 2. 线性回归算法的具体步骤
- 三、在python中使用线性回归算法
- 【完整代码】
- 四、线性回归算法的使用场景
一、线性回归
- 回归问题
- 线性方程
- 偏差度量
- 权值更新
1. 回归问题
两百年前,与达尔文同时代的统计学家高尔顿在研究父代与子代的身高关系时,发现一种“趋中效应”:如果父代身高高于平均值,则子代具有更高概率比他父亲要矮,简单来说就是身高回归平均值。“回归”一词也由此而来。在回归的世界里,万物的发展轨迹都不是一条单调向上走或向下走的直线,而是循着均值来回波动,一时会坠入低谷,但也会迎来春暖花开,而一时春风得意,也早晚会遇到坎坷挫折,峰回路转,否极泰来,从这个角度看,回归与其说是一个统计学问题,不如说更像是一个哲学问题。
回归问题的具体例子很多,**简单来说各个数据点都沿着一条主轴来回波动的问题都算是回归问题。**回归问题中有许多非常接地气的问题,譬如根据历史气象记录预测明天的温度、根据历史行情预测明天股票的走势、根据历史记录预测某篇文章的点击率等都是回归问题。
在上面描述什么是回归问题时,刻意反复使用“历史”和“预测”这两个词,原因正是记录历史值和预测未来值是回归问题的两个代表性特征。
在机器学习中,回归问题和分类问题都同属有监督学习,在数据形式上也都十分相似,那么怎么区分一个问题究竟是回归问题还是分类问题呢?
- 回归和分类
回归问题和分类问题最大的区别在于预测结果:
- 连续——回归
- 离散——分类
根据预测值类型的不同,预测结果可以分为两种,一种是连续的,另一种是离散的,结果是连续的就是预测问题。
这里的“连续”不是一个简单的形容词,而是有着严格的数学定义。最直接的例子就是时间,时间当然是连续的,连续型数值在编程时通常用int和float类型来表示,包括线性连续和非线性连续两种。
相比之下,离散型数值的最大特征是缺乏中间过渡值,所以总会出现“阶跃”的现象,譬如“是”和“否”,通常用bool类型来表示。
2. 预测未来
许多人对“预测”的第一印象也许是传说中的一个故事,有两位高人结伴出行,晚上歇于一处破庙,甲对乙说,“睡觉别靠墙,我刚掐指一算,寅时墙会倒。”乙不屑一顾地摆摆手,“我刚才也掐指一算,墙是倒向右边,我靠左睡可保无忧。”
回归问题是一类预测连续值的问题,而能满足这样要求的数学模型称作回归模型。
机器学习的回归模型预测未来的条件:需要有充足的历史数据。只要找到相关联的线索,就能够推理出最终的结果。预测难在待预测对象与什么相关是未知的,但其中的关联关系藏在历史数据之中,需要通过机器学习算法把它挖掘出来。
从数学角度来看,就是对输入数据点的拟合。
- 机器学习实现预测的流程
机器学习算法究竟有什么魔力,竟然能够预测未来?不妨就以前面两个高人的故事为例,用科学观点来研究墙体坍塌的问题。墙体坍塌可能由许多偶然因素导致,我们都不是土木专家,不妨凭感觉随手列出几条可能导致墙坍塌的因素:譬如可能与砌墙的材质有关,土坯墙总比水泥墙容易垮塌;可能与使用时间的长短有关;可能与承建商有关,喜欢偷工减料的工程队容易出“豆腐渣工程”;还有一些外部环境因素,譬如整天风吹雨淋的墙容易垮塌;最后就是墙体坍塌之前总会有一些早期迹象,譬如已经出现很多裂缝等。
上面所列因素有三种情况:与坍塌密切相关,与坍塌有点关系,以及与坍塌毫无瓜葛。如果人工完成预测任务,当然最重要的工作就是找出哪些是密切相关的,放在第一位;哪些是有点关系的,放在参考位置;哪些毫无瓜葛,统统删掉。可是我们又怎么知道哪些因素有哪些关系呢?这时我们就可以制作一张调查表,把砌墙用的什么材料、已经用了多久、出现了多少条裂缝等情况一一填进去,这就是前面所说的数据集中每一条样本数据的维度。就像商家很喜欢通过网上问卷来了解用户偏好一样,我们也利用调查表来了解墙体坍塌有什么“偏好”。
最后一栏是“坍塌概率”,这是我们最关心的,也是有监督学习所必需的。这些已知的坍塌概率以及相关的维度数据将为未知概率的预测提供重要帮助。
最后也是最关键的一步,是找出各个维度和坍塌之间的概率,而这个步骤将由模型自行完成。我们要做的只是将长长的历史数据输入回归模型,回归模型就会通过统计方法寻找墙体坍塌的关联关系,看看使用时间的长短和承建商的选择谁更重要,相关术语叫作训练模型,从数学的角度看,这个过程就是通过调节模型参数从而拟合数据。怎样调节参数来拟合数据是每一款机器学习模型都需要思考的重要问题,我们后面再说。
模型训练完毕后,再把当前要预测的墙体情况按数据维度依次填好,回归模型就能告诉我们当前墙体坍塌概率的预测结果了。流程如图3-3所示。
可以看出,回归模型就是预测的关键,我们通过给模型“喂”数据来训练它,最终让它具备了预测的能力。也许你对“模型”这个词感到陌生又好奇,不知道该在脑海里给它分配一个什么样的形象。而图3-3的“模型”是一个大大的四方盒子,塞进数据就能吐出预测结果,像是奇幻故事中巫师手中具有神奇魔力的水晶球。不用着急,“模型”这个词将贯穿我们对机器学习的整个巡礼,就像庆典游行里的花车正等着我们逐一观赏呢。接下来迎面走来的就是第一款模型——线性模型。
总结:
- 收集数据
- 找出数据各个维度与结果之间的概率
- 将历史数据输入回归模型——训练模型——调节模型参数从而拟合数据
- 模型训练完毕后,把当前要预测的情况按数据维度一次填好,回归模型进行预测
3. 线性方程
线性——“像直线那样”
直线方程通常写成:
y
=
k
x
+
b
y=kx+b
y=kx+b,k为斜率,b为截距。
这两个参数控制直线进行“旋转”和“平移”的动作。
- 调整斜率,可以“旋转”直线:
- 调整截距,实现直线的上下平移:
线性方程与直线方程存在差别:直线是二维平面图像,线性所在的空间是多维的
机器学习中,斜率k使用权值w表示,通过调整w和b的值就能控制直线在多维空间中进行旋转和平移。
这个通过调整权值来达到目的的过程叫作权值调整或者权值更新,对于线性模型而言,学习过程的主要工作就是权值调整,只要旋动旋钮,合理搭配旋转和平移这两套简单的动作,就能完成对输入数据的拟合工作,从而解决回归问题。
- 权值调整
在机器学习中,通过调整权值来完成学习,并最终进行预测的算法很多,这也是一种非常常见的学习手段。对于为什么调整权值能够进行预测,实际上也有多种解释,上面从几何角度给出了解释,此外还有代数角度的解释。
以三个输入维度A、B、C来预测P为例,我们的线性方程可以写为:
F
=
W
1
∗
A
+
W
2
∗
B
+
W
3
∗
C
(3-1)
F = W_1*A+W_2*B+W_3*C\tag{3-1}
F=W1∗A+W2∗B+W3∗C(3-1)
假设我们知道P的值其实就是与A的值有关,与B、C毫无关系,那么,怎样调整线性方程才可以根据输入准确预测出P的值呢?
我们知道,线性方程的计算结果F是三个维度的加权和,想要使F与P最接近,只需要让线性方程中B、C这两个加项对结果影响最小即可。这个好办,只要使这两项的权值最小,也就是W2和W3的值为0就可以了。
这就是从代数角度来解释为什么调整权值能够提高预测结果的准确性。这里实际上体现了一种假设,就是待预测的结果与输入的某个或某几个维度相关,而调整权值的目的就是使得与预测结果相关度高的权值越高,确保相关维度的值对最终加权和的贡献越大,反之权值越低,贡献越小。
4. 最简单的回归问题——线性回归问题
- 利用线性回归进行预测的极速入门
已知:小明前年3岁,去年4岁,今年5岁,请问小明明年几岁?
首先这无疑是个预测连续值的问题,明明白白是一个回归问题。回归关注的是几个变量之间的相互变化关系,如果这种关系是线性的,那么这就是一个线性回归问题,适合用线性模型解决。我们按照机器学习的习惯,把已知条件整理成数据集,这是一个三行两列的矩阵:
[[2017, 3],
[2018, 4],
[2019, 5]]
这是一个二维矩阵,如果画出图像,两个维度之间的线性关系就一目了然。这里以年份为X轴、年龄为Y轴将记录的数据画出来,得到3个呈线性排列的数据点(见图3-8a)。把这些点用线段连接起来,就能更清楚地看到这3个点排成了一条直线(见图3-8b)。
这条直线写成线性方程就是
y
=
x
−
2014
y = x-2014
y=x−2014,即所谓的“假设函数”。线性回归的预测就依赖于这条方程,现在是2019年,我们当然只能知道2019年之前的真实数据,但对于未来也就是小明在2019年之后的年龄,通过这条线性方程即可以预测得到。譬如把“2020”作为x输入,就能计算出对应的y值是“6”,也就得到了2020年小明将是6岁的预测结果。这个例子很简单,但已经完整地展示了线性回归“预测魔力”背后的原理,线性回归的预测魔力还经常被运用在经济和金融等场景,听起来更高端,不过就原理来说,也只是这个简单例子的延伸和拓展。
二、线性回归的算法原理
我们的任务就是设计一套运行机制,让手头上的这些构件组合成一架精密的学习机器,然后严丝合缝地运转起来,源源不断地“吃”进我们准备的数据,“吐”出对未来的预测结果。
- 拟合
对于一个线性回归问题,也就是说,这里的“神秘方程”就是一个线性方程,相应的数据集点也一定是根据线性排布的,那么,我们要做的就是不断调整线性方程的两个旋钮,作出一条能够一一通过这些点的直线,也就是拟合。这个能够拟合数据集点的线性方程,就是我们要找的“神秘方程”。
我们知道调整的目的是使得线性方程尽可能拟合数据集点,而调整的方法是通过旋动旋钮来调整权值,但仔细一想就会发现还缺失中间一环:怎样调整权值才能最终达到拟合数据的目标?
这里触及机器学习最核心的概念:在错误中学习。
这中间一环需要分两个步骤:首先知道偏离了多少,然后向减少偏差的方向调整权值。
这个不断修正的过程就是机器学习中的“学习”,过程有一点像“愤怒的小鸟”里的修正弹道,不仅线性回归是这样“学习”的,后面要介绍的很多机器学习算法,甚至包括现在正大热的深度学习也都是这样学习的。
“在错误中学习”也不只是简单一句话,具体来说需要经过以下两个步骤:
- 偏差度量:想要修正弹道,我们不仅要知道偏了,还要知道偏了多少,找到目标和实际的偏差距离。日常中我们会选择用尺子一类的工具来度量距离,而在机器学习中我们使用“损失函数”,数学家已经为我们准备好了“尺子”,多款数学工具都可用于度量偏差的距离。
- 权值调整:调整权值要解决两个细节问题,即权值是要增加还是减少、增加多少或者减少多少?
- 再一次感谢数学家,这两个问题都可以直接使用现成的数学工具进行解决,机器学习中将这些数学工具称为“优化方法”。
1. 线性回归算法的数学解析
(1) 假设函数的数学表达式解析
线性函数数学表达式:
y
^
=
w
T
x
i
+
b
(3-2)
\hat y = w^T x_i+b \tag{3-2}
y^=wTxi+b(3-2)
- y ^ \hat y y^:读作“y hat”,和 y y y一样,并没有什么特殊含义。但与损失函数相关。
- w T w^T wT:表示线性代数的转置。在机器学习中看到T,可以当做“相乘”来理解,把 w T x i w^Tx_i wTxi看成 w w w与 x i x_i xi的乘积。当然,这只是便于理解,这里的 w w w与 x i x_i xi不是标量(即不是数值),而分别代表着一个n维向量,如w的具体含义为 w = [ w 1 , ⋯ , w n ] w= [ w_1,\cdots,w_n] w=[w1,⋯,wn]。向量没有乘积一说,在线性代数中,这种操作被称为求两个向量的内积(Dot Product),内积所得到的结果是一个标量,也就是一个数值。
内积唯一的要求就是维度相同,运算过程十分简单,就是按位相乘然后再求和,都是最简单的四则运算。譬如两个向量
[
1
,
3
,
5
]
[1,3,5]
[1,3,5]和
[
2
,
4
,
6
]
[2,4,6]
[2,4,6],求内积运算过程为:
[
1
,
3
,
5
]
T
[
2
,
4
,
6
]
=
1
∗
2
+
3
∗
4
+
5
∗
6
=
44
[1,3,5]^T[2,4,6] = 1*2+3*4+5*6=44
[1,3,5]T[2,4,6]=1∗2+3∗4+5∗6=44
式(3-2)与直线公式唯一的不同就是线性方程是两个向量“相乘”。
线性回归模型是用线性方程进行预测。按前面的约定,我们把机器学习模型的假设函数用符号H来表示,线性回归的假设函数就是线性函数,可以写成如下形式:
H
(
x
)
=
w
T
x
i
+
b
(3-3)
H(x)=w^Tx_i+b\tag{3-3}
H(x)=wTxi+b(3-3)
给预测函数输入数据,也就是给式中的x赋值,预测函数经过计算后就能够返回一个结果,这就是预测值
y
^
\hat y
y^。
(2) 损失函数的数学表达式解析
现在我们将数据输入假设函数H就可以得到对应的预测值yˆ,当然,这个预测值还很不准确,与真实值存在偏差。要提高模型预测的准确性,首先就是要度量偏差,然后再减少偏差。机器学习中使用损失函数L作为度量偏差的工具。
偏差是预测值和真实值之间的比较差距,那么作为度量偏差的工具,损失函数应该至少包含两个内容:一个当然是预测值 y ^ \hat y y^,另一个则应该是真实值,在机器学习中通常用符号y来表示。既然真实值已经占用了字母y,为了区别二者,同时也为了表示二者存在密切关系,因此这才选择了 y ^ \hat y y^作为预测值的符号,这就是假设函数为什么非要给y加顶“帽子”的原因。
接下来的问题就是怎样用数学式来表示[插图]与y的偏差。最简单的做法当属直接相减,用二者的差值做偏差。不过前面我们一直强调,线性回归实际上是用直线进行拟合,现在出现偏差,也就是线性方程作出来的直线和实际的点存在距离,应该使用更有几何意义的“距离”来度量。因此,线性回归的损失函数选择使用L2范数来度量偏差,数学表达式如下:
L
(
x
)
=
∥
y
^
−
y
∥
2
2
(3-4)
L(x) = \|\hat y - y\|^2_2 \tag{3-4}
L(x)=∥y^−y∥22(3-4)
- ∥ ∥ \|\| ∥∥:范数正则化。 ∥ ∥ n \|\|_n ∥∥n表示 L n L_n Ln范数正则化。
- 线性代数的损失函数选择的是 L 2 L_2 L2范数,即欧几里得距离。
也就是说,线性回归算法计算预测偏差的方法其实非常直接,就是看预测值点与实际值点之间相差的直线距离。
而且还有更简单的,我们看到L2范数包含有根号,为了方便计算,损失函数直接在外面加了个平方,这就与根号抵消了。当然,这个操作会对计算结果进行同步放大,因此,加了平方之后,原本误差小的,相比之下仍然误差小;原本误差大的,相比之小差值就更大了,而不会产生原本误差小变成了误差大这样的错误影响。
(3) 优化方法的数学表达
机器学习算法使用损失函数的最终目的,是为了使用优化方法将偏差减到最小。
优化方法通常使用梯度下降等现成算法,具体实现颇为复杂,但要用数学符号把意思表达出来却十分简单。无非就是说清楚两个要素,一个是损失函数,另一个是最小化。损失函数前面已经有现成的了,只要套一个最小化符号就大功告成:
m
i
n
w
,
b
∥
y
^
−
y
∥
2
2
(3-5)
\underset{w,b}{min} \|\hat{y} - y\|^2_2 \tag{3-5}
w,bmin∥y^−y∥22(3-5)
- m i n w , b \underset{w,b}{min} w,bmin的意思是通过调节参数w和b来达到求最小值。
w的调节方法:
w
新
=
w
旧
−
学
习
率
∗
损
失
值
(3-6)
w_{新} = w_{旧} - 学习率*损失值 \tag{3-6}
w新=w旧−学习率∗损失值(3-6)
通过梯度下降等优化方法求得最小值时,
损失值通过损失函数对w求偏导计算求得,这个偏导也称为梯度,通过损失值来调节w,不断缩小损失值直到最小,这也正是梯度下降的得名来由。
学习率是一个由外部输入的参数,被称为“超参数”,可以形象地理解为w通过这一次错误学到多少,想要w多调整一点,就把学习率调高一点。不过学习率也不是越高越好,过高的学习率可能导致调整幅度过大,错过了最佳收敛点,也就导致无法求得真正的最小值。
(4) 范数
最常用的 L 1 , L 2 L_1,L_2 L1,L2范数:
-
L
1
L_1
L1范数:表示向量中每个元素绝对值的和。根据定义,L1范数的计算分两步,首先逐个求得元素的绝对值,然后相加求和即可。
∥ x ∥ 1 = ∑ i = 1 n ∣ x i ∣ (3.7) \|x\|_1=\sum^{n}_{i=1}|x_i|\tag{3.7} ∥x∥1=i=1∑n∣xi∣(3.7) -
L
2
L_2
L2范数:表示向量中每个元素的平方和的平方根。根据定义,L2范数的计算分三步,首先逐个求得元素的平方,然后相加求和,最后求和的平方根。
∥ x ∥ 2 = ∑ i = 1 n x i 2 (3-8) \|x\|_2=\quad{\sum^{n}_{i=1}x_i^2}\tag{3-8} ∥x∥2=i=1∑nxi2(3-8)
2. 线性回归算法的具体步骤
线性回归算法可用于解决预测问题,输入的是多维的样本数据集点,每个数据集点包括信息维度和结果值部分。结果值是待预测对象的历史情况,如在小明年龄预测问题中,信息维度部分就是年份,结果值部分就是不同年份所对应的年龄,输出则是一个连续的数值。具体如表3-2所示。
- 第一步,为假设函数设定参数w,通过假设函数画出一条直线,即根据输入的点通过线性计算得到预测值。
- 第二步,将预测值带入损失函数,计算出一个损失值。
- 第三步,通过得到的损失值,利用梯度下降等凸优化方法,不断调整假设函数的参数w,使得损失值最小。这个不断调整参数w使得损失值最小化的过程就是线性回归的学习过程,通常称为训练模型。
三、在python中使用线性回归算法
常用的库:
- linear_model —— 线性模型算法族库,线性回归算法、Logistic回归算法等都是基于线性模型,因此都在该库内。
- neighbors —— 最近邻算法族库,KNN在该库中。
- naive_bayes —— 朴素贝叶斯模型算法族库。
- tree —— 决策树模型算法族库。
- svm —— 支持向量机模型算法族库。
- neural_network —— 神经网络模型算法族库。
Scikit-Learn对各类机器学习算法进行了良好封装,对于不同的模型算法,都只需要经过类似的简单三步就可以进行预测。这里我们就以线性回归算法来统一进行解释说明。
# 从Scikit-Learn库导入线性模型中的线性回归算法
from sklearn import linear_model
# 训练线性回归模型
model = linear_model.LinearRegression()
model.fit(x, y)
# 进行预测
model.predict(x_)
Scikit-Learn已经对算法细节进行了高度封装,因此整个调用过程非常简洁易懂,在导入线性模型算法后,只需要利用fit方法为模型传入训练数据,完成模型的训练工作,就可以直接使用模型的predict方法,通过传入待预测的数据进行结果预测。
要让机器学习模型真正发挥作用,另一个关键是数据。
上述这段代码无法正常运行,因为其中包含的“x”、“y”和“x_”,从编程的角度看,属于未定义变量,而从模型的角度看,正是需要外部向机器学习算法提供的数据。
这里简单生成一个数据集。
# 导入所需库
import matplotlib.pyplot as plt
import numpy as np
# 生成数据集
x = np.linspace(-3, 3, 30)
y = 2 * x + 1
# 数据集绘图
plt.scatter(x, y)
plt.show()
这里我们的目标是通过 y = 2 x + 1 y=2x+1 y=2x+1函数生成一个由30个元素组成的二维数据集。Numpy的linspace函数可以返回间隔均匀的数值,这里设置的数值区间在-3至3之间,个数为30,这就是自变量x序列,类型为ndarray。对应的,通过 2 x + 1 2x+1 2x+1,我们得到了因变量y。
显然,这些数据呈线性排列,如果我们并不知道数据集点之间满足
y
=
2
x
+
1
y=2x+1
y=2x+1的关系,就可以选择通过线性回归的方法学习得到。
不过,如果直接将上述x和y传入模型,代码会提示数据维度错误,这是因为Scikit-Learn中线性回归算法的fit方法需要传入的x和y是两组矩阵,每一行为同一样本的信息,具体格式为:
x:[[样本1], [样本2], [样本3], ..., [样本n]]
y:[[样本1标记值], [样本2标记值], [样本3标记值], ..., [样本n标记值]]
要把序列变成矩阵有多种处理方法,这里提供其中一种实现思路,即通过Python语言的List可以利用for的特性:
x = [[i] for i in x]
y = [[i] for i in y]
这样,x和y的格式正确,每个样本信息都单独作为矩阵的一行,再将x和y传入fit,数据信息就可以被正确识别,从而开始训练模型。
为了检验训练的结果,还需要提供一组测试用的x_。这里我们只测试两组:
x_ = [[1], [2]]
最后利用predict方法完成预测,返回的是一个类型同样为ndarray的序列,预测结果如下:
[[3.]
[5.]]
确实得到了与传入y=2x+1函数进行计算相同的正确结果。这时我们通过图像查看一下学习得到的线性函数与数据集点之间的关系,可以发现线性函数正确地串了一串“糖葫芦”,如图3-10所示。
plt.plot(x, model.predict(x))
我们还可以通过model.coef_和model.intercept_来查看这时的法向量w和截距b的具体赋值,分别为[[2.]]和[1.]。线性回归算法确实正确地学习到了目标函数y=2x+1的相关参数。
# 查看w、b
print("W: {} b: {}".format(model.coef_, model.intercept_))
上述例子即已经是在Python中使用线性回归模型的一次完整过程,模型的学习结果也让人相当满意。
不过,我们也多次提到,现实中总是要复杂一些。通过现实环境中收集得到的数据,总是存在着这样或那样的随机扰动。我们对上面的数据集生成代码稍加改动,以模拟这个过程:
x = x + np.random.rand(30)
这段代码利用Numpy库的函数,随机生成了0到1之间的扰动,这时的数据集图像就变得杂乱无章了(见图3-11)。
用同样的方法,我们可以通过回归分析算法学习得到线性函数(见图3-12)。
我们同样可以通过model.coef_和model.intercept_来查看这时的法向量w和截距b的具体赋值,分别为[[1.93014033]]和[0.01972149]。可以看出,加入随机扰动后,线性回归模型对法向量w的学习还是比较准确的,但对于截距项就出现了较大影响。
【完整代码】
# 导入所需库
import matplotlib.pyplot as plt
import numpy as np
# 生成数据集
x = np.linspace(-3, 3, 30)
y = 2 * x + 1
x = x + np.random.rand(30)
# 序列变成矩阵
x = [[i] for i in x]
y = [[i] for i in y]
# 测试数据x_
x_ = [[1], [2]]
# 从Scikit-Learn库导入线性模型中的线性回归算法
from sklearn import linear_model
# 训练线性回归模型
model = linear_model.LinearRegression()
model.fit(x, y)
# 进行预测
model.predict(x_)
print("Array({})".format(model.predict(x_)))
# 查看w、b
print("W: {} b: {}".format(model.coef_, model.intercept_))
# 数据集绘图
plt.scatter(x, y)
plt.plot(x, model.predict(x))
plt.show()