机器学习(maching learing)总是给人带来一种神秘的面纱,一是这个名字让人难免联想起人工智能。二是因为它过于强大和奇妙的应用。如人脸识别、ai换脸、语音助手、无人驾驶等。这些神奇而令人叹为观止的技术无疑让机器学习更显得更加“高大上”。但就因为这种高大上导致很多软件开发的同学看待这门技术,也觉得其像宇宙深处一样神秘且多变。
我相信很多同学在学习这门技术时,经常会望而却步。一是这门技术确实不是那么容易的入门,二是这门技术的从业者的门槛相对较高,一有挑战,二有顾虑所以很多初学者在学习这门技术时都会变成从入门到放弃。
其实我们不妨简单一点,这是一个当下应用较多的技术,而且未来的应用也会越加广泛。不妨把他作为一本武功秘籍,学学也无妨。如果是从事机器学习、ai相关的工作那自然是好。再退一步来说,这些算法在自然界中有很多的实例。就算不从事相关的工作,生活中用到岂不美妙。正如上个世纪八九十年代,一个业余会修电器的工人常人被邻里称赞。而当下信息爆炸的时代,掌握机器学习将来的应用可能丝毫不亚于以前的"修电"。
而线性回归作为机器学习最简单、又最基础且强大的算法,在生活中还是能得到很多应用的。是否学习机器学习,不妨花半个小时学完这篇文章,再做考量。
这篇文章是以入门为主,简单的会列出线性回归的推导过程,但至少体现其思想,如对数学掌握不是很牢靠的同学,也丝毫不影响使用。
生活中很多事物都是存在线性关系的,比如丈夫的收入与妻子颜值、电视的尺寸和价格、up主的粉丝数与视频播放量等等。简单的来说就是就是成一种直线的趋势,如图1.1所示:
从图中不难发现,java程序员的薪资跟工作年限大概是呈一种线性的关系。简单来说,就是java程序员的工资随着工作年限的增加而增加,而这种增加并不像指数式的喷发那么夸张。那么我们就想,能不能寻找一条直线,最大程度的拟合样本特征与样本输出标记之间的关系呢。
就像是中学的数学应用题,用一条光滑的曲线均匀的穿过点的两边。得到如下图所示的一根直线,这就是整个线性回归的过程。但是这里面的值不再是中学数学中所说的自变量跟因变量。而是特征与输出标记,比如图中的程序员工作年限就是特征,而其的薪资则是输出标记。这里面的特征只有一个,而实际上可能有很多个。而这种只有一个特征的回归我们又称为简单线性回归。
图中的样本的个数,我们设为n个。而图中的直线的表达式我们也知道,是y=ax+b。而我们只要把这条曲线的斜率a跟截距b算出来。对于第i个样本,他的特征值值为x(i) ,他的标记值则为y(i)。
而当我们求出这条直线的截图和斜率时,我们可以把x(i)代入得到一个值,我们把其称为 y ^ \hat{y} y^(i) ,这个值就代表的是样本i的预测值。这个预测值与真实的值会存在差距,我们希望的预测会比较准。而这个比较准就是希望 y ^ \hat{y} y^(i) 与y(i)(样本实际值)的差距尽量小。也就是使 ( y ( i ) − y ^ ( i ) ) 2 (y^{(i)}-\hat{y}^{(i)})^{2} (y(i)−y^(i))2尽量小,那么考虑到所有的样本,则得出:
∑ i = 1 n ( y ( i ) − y ^ ( i ) ) 2 \sum_{i=1}^n (y^{(i)}-\hat{y}^{(i)})^{2} i=1∑n(y(i)−y^(i))2
所以我们应该找的一条直线使得上述式子的值最小。而同样不难得出 y ^ ( i ) = a x i + b \hat{y}^{(i)}=ax^{i}+b y^(i)=axi+b,则得到:
∑ i = 1 n ( y ( i ) − a x ( i ) − b ) 2 \sum_{i=1}^n (y^{(i)}-ax^{(i)}-b)^{2} i=1∑n(y(i)−ax(i)−b)2
而简单线性回归就是求出a、b使得上述的值尽可能的小。而所谓的线性回归建模的过程就是找到a、b的过程。而上述的式子又称为损失函数(loss function),可以简写为cost(a,b)。其实机器学习的建模就是优化损失函数,获得机器学习模型的过程。
其实推导出最优解就是求cost(a,b)的最小值,而求最小值也就是极值最直接的办法就是求导,即得到
∂ c o s ( a , b ) ∂ a = 0 {∂cos(a,b)\over∂a} =0 ∂a∂cos(a,b)=0
∂ c o s ( a , b ) ∂ b = 0 {∂cos(a,b)\over∂b} =0 ∂b∂cos(a,b)=0
先对b求偏导使其为0
∑ i = 1 n ( y ( i ) − a x ( i ) − b ) = 0 \sum_{i=1}^n (y^{(i)}-ax^{(i)}-b)=0 i=1∑n(y(i)−ax(i)−b)=0
易得出
∑ i = 1 n y ( i ) − a ∑ i = 1 n x ( i ) − ∑ i = 1 n b = 0 \sum_{i=1}^ny^{(i)} -a\sum_{i=1}^nx^{(i)}-\sum_{i=1}^nb=0 i=1∑ny(i)−ai=1∑nx(i)−i=1∑nb=0
n b = ∑ i = 1 n y ( i ) − a ∑ i = 1 n x ( i ) nb = \sum_{i=1}^ny^{(i)} -a\sum_{i=1}^nx^{(i)} nb=i=1∑ny(i)−ai=1∑nx(i)
求出 b = y ( 均 ) − a x ( 均 ) b=y(均)-ax(均) b=y(均)−ax(均)
这样b的值就求出来了,那么继续对a求偏导使其为0。
∑ i = 1 n ( y ( i ) − a x ( i ) − b ) x ( i ) = 0 \sum_{i=1}^n(y^{(i)}-ax^{(i)}-b)x^{(i)}=0 i=1∑n(y(i)−ax(i)−b)x(i)=0
再把b的值代入得:
∑ i = 1 n ( y ( i ) − a x ( i ) − y ( 均 ) + a x ( 均 ) ) x ( i ) = 0 \sum_{i=1}^n(y^{(i)}-ax^{(i)}-y(均)+ax(均))x^{(i)}=0 i=1∑n(y(i)−ax(i)−y(均)+ax(均))x(i)=0
不难得出
∑ i = 1 n ( x ( i ) y ( i ) − x ( i ) y ( 均 ) ) − ∑ i = 1 n ( a ( x ( i ) ) 2 − a x ( 均 ) x ( i ) ) = 0 \sum_{i=1}^n(x^{(i)}y^{(i)}-x^{(i)}y(均))-\sum_{i=1}^n(a(x^{(i)})^{2}-ax(均)x^{(i)})=0 i=1∑n(x(i)y(i)−x(i)y(均))−i=1∑n(a(x(i))2−ax(均)x(i))=0
这样就得到了a的表达式:
a = ∑ i = 1 n ( x ( i ) y ( i ) − x ( i ) y ( 均 ) ) ∑ i = 1 n ( ( x ( i ) ) 2 − x ( 均 ) x ( i ) ) a={\sum_{i=1}^n(x^{(i)}y^{(i)}-x^{(i)}y(均))\over\sum_{i=1}^n((x^{(i)})^{2}-x(均)x^{(i)})} a=∑i=1n((x(i))2−x(均)x(i))∑i=1n(x(i)y(i)−x(i)y(均))
再简化
∑ i = 1 n ( x ( i ) − x ( 均 ) ) ( y ( i ) − y ( 均 ) ) ∑ i = 1 n ( x ( i ) − x ( 均 ) ) 2 {\sum_{i=1}^n(x^{(i)}-x(均))(y^{(i)}-y(均))\over\sum_{i=1}^{n}(x^{(i)}-x(均))^{2}} ∑i=1n(x(i)−x(均))2∑i=1n(x(i)−x(均))(y(i)−y(均))
import numpy as np
class SimpleLinearRegression:
def __init__(self):
self.a_ = None
self.b_ = None
def fit(self, x_train, y_train):
"""根据训练集训练模型"""
x_mean = np.mean(x_train)
y_mean = np.mean(y_train)
num = 0.0
d = 0.0
for x,y in zip(x_train,y_train):
num += (x-x_mean) * (y -y_mean)
d += (x - x_mean) ** 2
self.a_ = num / d
self.b_ = y_mean - self.a_ * x_mean
return self
def predict(self,x_predict):
return np.array([self._predict(x) for x in x_predict])
def _predict(self, x_single):
return self.a_ * x_single + self.b_
def __repr__(self):
return "简单线性回归"
我们可以看到,如果b的值比较直接,而a的值则需要通过for循环遍历相加,这种效率在计算大数据集上的效率可想而知,所以有另一种向量化的运算,这里不一一推导,其实也只有fit函数的改动
import numpy as np
class SimpleLinearRegression:
def __init__(self):
self.a_ = None
self.b_ = None
def fit(self, x_train, y_train):
"""根据训练集训练模型"""
x_mean = np.mean(x_train)
y_mean = np.mean(y_train)
num = (x_train - x_mean).dot(y_train-y_mean)
d = (x_train-x_mean).dot(x_train-x_mean)
self.a_ = num / d
self.b_ = y_mean - self.a_ * x_mean
return self
def predict(self,x_predict):
return np.array([self._predict(x) for x in x_predict])
def _predict(self, x_single):
return self.a_ * x_single + self.b_
def __repr__(self):
return "简单线性回归"
每一种机器学习模型都会有好有坏,我们来验证模型的好坏,就是拿测试数据对模型进行验证。所以衡量模型好坏的参数极其重要,而对于线性回归算法,则有以下几类衡量方法。
不难看出这两种算法再验证模型好坏的时候都能达到不错的效果,但是这个值是无限大的,就是控制不了其的大小,这样在同类型的时候或者同一种模型可以比较模型的好坏,这样如果不同的模型计算不同类型的好坏如何比较呢。所以在线性回归中使用最广的是另一种验证方式。
import numpy as np
from sklearn.metrics import r2_score
r2_score(y_true=np.array([]),y_pred=np.array([]))
多元线性回归,由于牵扯到多个维度,其函数表达式为:
y ^ ( i ) = θ 0 x 0 ( i ) + θ 1 x 1 ( i ) + θ 2 x 2 ( i ) + θ n x n ( i ) \hat{y}^{(i)}=\theta_{0}x^{(i)}_{0}+\theta_{1}x^{(i)}_{1}+\theta_{2}x^{(i)}_{2}+\theta_{n}x^{(i)}_{n} y^(i)=θ0x0(i)+θ1x1(i)+θ2x2(i)+θnxn(i)
同样,也可对其进行简写。
y ^ = x ⋅ θ T \hat{y}=x\cdot\theta^{T} y^=x⋅θT
θ = ( θ 1 , θ 2 , θ 3 , θ n ) \theta= (\theta_{1},\theta_{2},\theta_{3},\theta_{n} ) θ=(θ1,θ2,θ3,θn)
同样求
∑ i = 1 n ( y ( i ) − y ^ ( i ) ) 2 \sum_{i=1}^n (y^{(i)}-\hat{y}^{(i)})^{2} i=1∑n(y(i)−y^(i))2
则得出:
( y − x b ⋅ θ ) T ( y − x b ⋅ θ ) (y-x_b\cdot\theta)^{T}(y-x_b\cdot\theta) (y−xb⋅θ)T(y−xb⋅θ)
而我们的目标就是使上述式子尽可能小。
这个推导结果什么复杂,在这里就不细加推敲
θ = ( X b T X b ) − 1 X b T y \theta=(X^{T}_bX_b)^{-1}X^{T}_by θ=(XbTXb)−1XbTy