机器学习之线性回归

在机器学习中,由于数据是多维度的,所以线性回归在此一般指多元线性回归。线性回归中的线性指的是,自变量和因变量之间呈线性关系,也就是自变量最高次数为1。回归指的是,自变量是连续分布的。否则就是就是分类问题。

对于求出一组数据中的回归直线,可采用最小二乘法

最小二乘法

标量表达

最小二乘法,最早我们在初中就接触过,只不过那个时候考虑的自变量只有一个。 最小二乘法的思想很简单,先假设一条直线,如
y = b x + a y=bx+a y=bx+a
很明显求出来的此条直线是不会经过所有数据点的,但我们要保证各数据点到此条直线的距离和是最短的。对此我们可以构造出一个函数

J = 1 2 ∑ i = 1 n ( y i − y ^ i ) 2 J = \frac{1}{2} \sum_{i=1}^{n}(y_i-\hat{y}_i)^2 J=21i=1n(yiy^i)2
其中,y帽代表预测值,很显然,此函数是关于a,b的函数.而我们的目的就是要求出此函数的最小值,学过高等数学的我们都知道,只需要对这a,b求偏导数,另其等于零,解出a,b即可。在初中我们就知道,解出来的a,b为:

a = y ˉ − b x ˉ b = ∑ i = 1 n x i y i − n x ˉ y ˉ ∑ i = 1 n x i 2 − n x ˉ 2 a = \bar{y}-b\bar{x} \\ b = \frac{\sum_{i=1}^{n}x_iy_i-n\bar{x}\bar{y}}{\sum_{i=1}^{n}x_i^2-n\bar{x}^2} a=yˉbxˉb=i=1nxi2nxˉ2i=1nxiyinxˉyˉ

其中, x ˉ \bar{x} xˉ y ˉ \bar{y} yˉ代表样本对应变量的平均值

我们可以通过Python来验证上面的想法,代码如下,通过jupyter编写:

import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np

#生成随机数据
x_train = 10*np.random.rand(20,)
y_train = x_train * 4 + 3+ np.random.randn(20,)
print(x_train)
print(y_train)
[4.20317897 9.45722496 3.3495886  0.87348741 8.28675402 9.94841941
 5.51671545 4.39436695 1.25734414 4.65941892 9.3974875  6.70832224
 3.33828963 2.3645433  5.14405277 6.60791116 2.8787364  8.71938945
 9.45601128 2.96794702]
[18.47497825 40.93485883 18.87926004  5.23501616 36.56810701 42.8693648
 24.67536109 21.64865443  8.72337565 22.39404305 38.21333044 29.37466431
 15.35262564 13.30489897 24.53073011 28.3152433  12.76249708 37.40211258
 41.68158987 14.40621423]
#画图 
plt.scatter(x_train,y_train)
plt.axis([0,10,0,45])
plt.show()


a = y ˉ − b x ˉ b = ∑ i = 1 n x i y i − n x ˉ y ˉ ∑ i = 1 n x i 2 − n x ˉ 2 a = \bar{y}-b\bar{x} \\ b = \frac{\sum_{i=1}^{n}x_iy_i-n\bar{x}\bar{y}}{\sum_{i=1}^{n}x_i^2-n\bar{x}^2} a=yˉbxˉb=i=1nxi2nxˉ2i=1nxiyinxˉyˉ

其中, x ˉ \bar{x} xˉ y ˉ \bar{y} yˉ代表样本对应变量的平均值


```python
#最小二乘法拟合曲线
b =  (np.dot(x_train,y_train) - len(x_train) * x_train.mean() * y_train.mean())/ \
     (np.dot(x_train,x_train) - len(x_train) * x_train.mean() * x_train.mean())
     
print(b)
3.9697234774479964
a = y_train.mean() - b * x_train.mean()
print(a)
3.047316524615642
#绘制拟合后的直线图和原训练集图
x_new = 10*np.random.rand(20,)
y_new = x_new * b + a
plt.scatter(x_train,y_train)
plt.plot(x_new,y_new)
plt.axis([0,10,0,45])
plt.show()

向量/矩阵表达

上面那个例子,说实话,上过中学的娃,都能知道是干啥,毕竟当时学过。但我们机器学习肯定没有这么简单的,光从数据的维度而言,就不会只有一维这么简单,那么此时,我们就要用向量和矩阵来表达数据和公式了。说实话,当初看到这些的时候,什么对矩阵求导、积分,一脸闷逼,好像大学本科高等数学和线性代数也没学过啊…感兴趣的,可以看看王松桂写的<<线性模型引论>>,此书中有提道对矩阵的相关高级操作。

矩阵求导

关于矩阵求导,这里给出我自己对于标量对矩阵/向量求导的推导理解过程,其他类型可以参考这篇文章:

结果就是,求导的结果和自变量同形,其各分量值为标量对对应的自变量分量求导。


现在回到线性回归这个问题,用向量表达就是(n表示n组数据):
J = 1 2 ∑ i = 1 n ( y i − y ^ i ) 2 = 1 2 ( X θ − y ) T ( X θ − y ) 其 中 θ = ( b a ) X = [ x 1 1 ⋮ ⋮ x n 1 ] y = ( y 1 ⋮ y n ) X , y 表 示 已 知 数 据 集 。 J = \frac{1}{2} \sum_{i=1}^{n}(y_i-\hat{y}_i)^2 = \frac{1}{2}(X\theta-y)^T(X\theta-y) \\ 其中\theta = \begin{pmatrix}b\\a\end{pmatrix} \\ X = \begin{bmatrix} x_1 & 1\\ \vdots & \vdots \\x_n & 1 \end{bmatrix} \\ y = \begin{pmatrix} y_1 \\ \vdots \\ y_n \end{pmatrix} \\ X,y表示已知数据集。 J=21i=1n(yiy^i)2=21(Xθy)T(Xθy)θ=(ba)X=x1xn11y=y1ynX,y
说明几点公式:
对 于 列 向 量 a , 有 ( a , a ) = a T a = ∣ ∣ a ∣ ∣ 2 对 于 n 维 列 向 量 x , y ; 有 ( x , y ) = x T y = ∑ i = 1 n x i y i 对于列向量a,有(a,a) = a^Ta = ||a||^2 \\ 对于n维列向量x,y;有(x,y) = x^Ty = \sum_{i=1}^{n}x_iy_i aa,a=aTa=a2nx,y;(x,y)=xTy=i=1nxiyi
上述式子进一步化简为:
J = 1 2 ( X θ − y ) T ( X θ − y ) = y T y − 2 y T X θ + b T X T X b J = \frac{1}{2}(X\theta-y)^T(X\theta-y) = y^Ty-2y^TX\theta+b^TX^TXb J=21(Xθy)T(Xθy)=yTy2yTXθ+bTXTXb
上述式子对θ求导后为(原理过程上图有讲):
∂ J ∂ θ = − 2 y T X + 2 θ T X T X 所 以 当 ∂ J ∂ θ = 0 , 即 θ = ( X T X ) − 1 X T y 时 , 函 数 J 取 最 小 值 \frac{\partial J}{\partial \theta} = -2y^TX+2\theta^TX^TX \\ 所以当\frac{\partial J}{\partial \theta} = 0,即\theta = (X^TX)^{-1}X^Ty时,函数J取最小值 θJ=2yTX+2θTXTXθJ=0θ=(XTX)1XTyJ

至于为什么函数J的的驻点就一定是极值点,并且是最小值点,因为它是一个凹函数(下凸函数),在此不做具体证明。

代码验证

import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np

#生成随机数据
x_train = 10*np.random.rand(20,)
y_train = x_train * 4 + 3+ np.random.randn(20,)
print(x_train)
print(y_train)
[4.20317897 9.45722496 3.3495886  0.87348741 8.28675402 9.94841941
 5.51671545 4.39436695 1.25734414 4.65941892 9.3974875  6.70832224
 3.33828963 2.3645433  5.14405277 6.60791116 2.8787364  8.71938945
 9.45601128 2.96794702]
[18.47497825 40.93485883 18.87926004  5.23501616 36.56810701 42.8693648
 24.67536109 21.64865443  8.72337565 22.39404305 38.21333044 29.37466431
 15.35262564 13.30489897 24.53073011 28.3152433  12.76249708 37.40211258
 41.68158987 14.40621423]
#最小二乘法拟合曲线
b =  (np.dot(x_train,y_train) - len(x_train) * x_train.mean() * y_train.mean())/ \
     (np.dot(x_train,x_train) - len(x_train) * x_train.mean() * x_train.mean())
     
print(b)
3.9697234774479964
a = y_train.mean() - b * x_train.mean()
print(a)
3.047316524615642
#通过向量形式表达  
#构造矩阵X
X_train_matrix = np.c_[np.reshape(x_train,20,1),np.ones((20,1))]
Y_train_matrix = np.reshape(y_train,20,1)
theta = np.linalg.inv(X_train_matrix.T.dot(X_train_matrix)).dot(X_train_matrix.T).dot(Y_train_matrix)
print(theta)
[3.96972348 3.04731652] 

明显,两种方法计算方法最后的计算结果,都是一样的。数据的保留精度不同

梯度下降法求解最优值

梯度下降法,本质上就是一步一步尝试着猜出最优解,使损失函数值(比如上面的函数J)尽可能的小。

就相当于,我们求一个函数的最小值点,常规方法,就是求导,得出极值点。但在这里,我们不这么玩,靠一步一步试,得到最接近最小值点的解。 如下图:

当然也不是乱试,原则就是:

  1. 初始化化所求量,如上面的θ
  2. 求梯度grad,就是求导,比如上面对θ求导
  3. θi+1 = θi - grad*learning_rete
  4. 等待grad < threshold,迭代停止,收敛,threshold是个超参数。
    leraning_rate是个超参数,太大容易使结果来回震荡,太小很耗时,需要走很长时间。不管太大或太小,都会迭代很多次。

梯度下降方法有很多,如批量(batch)梯度下降(bgd)、随机(stochastic)梯度下降(sgd)、最小批量(mini-batch)梯度下降,但不管哪种,只是上述步骤2求梯度的方法不同罢了。

比如,对于上式函数J,对θ求导为(换了一种形式):
∂ J ∂ θ = X T X θ − X T Y \frac{\partial J}{\partial \theta} = X^TX\theta-X^TY θJ=XTXθXTY

批量(batch)梯度下降(bgd)

批量梯度下降,就是将所有的测试集都用上。

实现代码

import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
#生成随机数据
x_train = 2*np.random.rand(20,)
y_train = x_train * 4 + 3+ np.random.randn(20,)
print(x_train)
print(y_train)
[1.34124453 1.46431689 1.07937238 1.80774599 0.85436174 0.41201233
 1.16252119 0.18062666 0.08666462 0.44226188 0.15469509 0.24445512
 1.05530068 1.49453413 1.07885761 1.19110527 1.66214405 0.76083629
 0.87179574 1.86032513]
[ 8.75164663  8.21063584  7.89309299 12.71092239  7.06533048  5.9379643
  6.23955397  2.91543904  2.13331839  5.98292998  3.30023902  2.66439422
  8.37856561  8.75034368  6.94711533  7.13997695 10.11981542  5.64088811
  7.89603528 11.27644022]
#最小二乘法拟合曲线
b =  (np.dot(x_train,y_train) - len(x_train) * x_train.mean() * y_train.mean())/ \
     (np.dot(x_train,x_train) - len(x_train) * x_train.mean() * x_train.mean())
     
print(b)
4.671356979008788
a = y_train.mean() - b * x_train.mean()
print(a)
2.512020438015292
#通过向量形式表达  
#构造矩阵X
X_train_matrix = np.c_[np.reshape(x_train,20,1),np.ones((20,1))]
Y_train_matrix = np.reshape(y_train,20,1)
theta = np.linalg.inv(X_train_matrix.T.dot(X_train_matrix)).dot(X_train_matrix.T).dot(Y_train_matrix)
print(theta)
[4.67135698 2.51202044]
#通过梯度下降法
#随机初始化theta
th =np.array([[0.61071354],[2.28425544]])
learning_rate = 0.1  
n_iter = 10000  #迭代次数
Y_train_matrix = Y_train_matrix.reshape(20,1)
##此种方式没有设置阈值,
for _ in range(n_iter):
    grad =  1/20 * X_train_matrix.T.dot(np.dot(X_train_matrix,th) -Y_train_matrix)
    th = th - grad *learning_rate
print(th)
[[4.67135698]
 [2.51202044]]

明显,上面三种方法求出来的结果,都是一样。

随机梯度下降(sgd)

随机梯度下降,就是从测试集中随机选择一组测试数据来求梯度。

实现部分代码

#随机梯度下降法
#初始化theta11
theta11 = np.random.randn(2,1)

##定义学习率函数,保证学习率随着迭代次数的增加,而变小
def learning_rate_fun(ti):
    return 5/(ti+10);
    
for epoch in range(500):
    for i in range(20):  ##样本数为20
        index = np.random.randint(20)   #随机抽取一组测试数据
        xi = X_train_matrix[index:index+1]
        yi = Y_train_matrix[index:index+1]
        grad =  xi.T.dot(np.dot(xi,theta11) -yi)
        theta11 = theta11 - grad*learning_rate_fun(epoch*20+i)
    
print(theta11)
[[4.64079423]
 [2.5593362 ]]

很明显,所求结果和前几种相比,还是较大差别的。

岭回归(Ridge regression)

上面介绍的是最小二乘法(Ordinary Least Squares),在此将介绍岭回归,并结合scikit-leraing完成相关代码编写(就不手动一行行写代码了,基本原理和前面都差不多)
岭回归的损失函数(loss function)如下:
J = m i n ∣ ∣ X w − y ∣ ∣ 2 + α ∣ ∣ w ∣ ∣ 2 J = min||Xw-y||^2+\alpha||w||^2 J=minXwy2+αw2
相比于最小二乘法,就是公式后多了一部分。这部分叫正则化惩罚项,是用来提高模型泛化能力的。也就是防止过拟合

那么,当使用惩罚项,会产生什么影响?

使用惩罚项,会提高模型的泛化能力,但是因为人为的改变了损失函数,所有在一定程度上牺牲了正确率,即对训练集已有数据的拟合效果。但是没关系,因为我们的模型目的是对未来新的数据进行预测。在惩罚项里面,会有个alpha,即惩罚项的权重,我们可以通过调整alpha超参数,根据需求来决定是更看重模型的正确率还是模型的泛化能力!

如何在机器学习里面防止过拟合呢?

模型参数W个数,越少越好,无招胜有招.模型参数W的值越小越好,这样如果X输入有误差,也不会太影响y预测结果.通过正则化惩罚项人为的修改已有的损失函数,比如使用L1、L2正则添加到loss func里面去

  • L1 = n个维度的w绝对值加和
  • L2 = n个维度的w平方和

让我们的SGD,在找最优解的过程中,考虑惩罚项的影响

实例代码

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
from sklearn import linear_model

#生成随机数据
x_train = 2*np.random.rand(20,1)
y_train = x_train * 4 + 3+ np.random.randn(20,1)
print(x_train)
print(y_train)

#构建模型
reg = linear_model.Ridge(alpha=0.5,max_iter = 10000,solver="sag")
reg.fit(x_train,y_train)
print(reg.coef_)
print(reg.intercept_)
[[3.52992626]]
[3.4325515]
#构建模型 方法二
model = linear_model.SGDRegressor( loss='squared_loss',  penalty='l2',max_iter=10000)
model.fit(x_train,y_train.ravel())
print(model.coef_)
print(model.intercept_)
[3.55602826]
[3.37321111]  

关于上面的模型初始化,具体可以传入那些参数,可以在使用时查看相关文档。比如

Lasso回归

Lasso回归最大的不同就是,Lasso使用的是L1正则化惩罚项。
损失函数如下:
min ⁡ w 1 2 n samples ∣ ∣ X w − y ∣ ∣ 2 2 + α ∣ ∣ w ∣ ∣ 1 \min_{w} { \frac{1}{2n_{\text{samples}}} ||X w - y||_2 ^ 2 + \alpha ||w||_1} wmin2nsamples1Xwy22+αw1

示例代碼

import numpy as np
from sklearn.linear_model import Lasso
from sklearn.linear_model import SGDRegressor


X = 2 * np.random.rand(100, 1)
y = 4 + 3 * X + np.random.randn(100, 1)

lasso_reg = Lasso(alpha=0.15, max_iter=10000)
lasso_reg.fit(X, y)
print(lasso_reg.predict(1.5))
print(lasso_reg.coef_)

sgd_reg = SGDRegressor(penalty='l1', n_iter=10000)
sgd_reg.fit(X, y.ravel())
print(sgd_reg.predict(1.5))
print(sgd_reg.coef_)

Elastic-Net 回归模型

Elastic-Net模型,损失函数如下:
min ⁡ w 1 2 n samples ∣ ∣ X w − y ∣ ∣ 2 2 + α ρ ∣ ∣ w ∣ ∣ 1 + α ( 1 − ρ ) 2 ∣ ∣ w ∣ ∣ 2 2 \min_{w} { \frac{1}{2n_{\text{samples}}} ||X w - y||_2 ^ 2 + \alpha \rho ||w||_1 + \frac{\alpha(1-\rho)}{2} ||w||_2 ^ 2} wmin2nsamples1Xwy22+αρw1+2α(1ρ)w22
此模型通过共同使用了L1和L2正则化。

样例代码

import numpy as np
from sklearn.linear_model import ElasticNet
from sklearn.linear_model import SGDRegressor

X = 2 * np.random.rand(100, 1)
y = 4 + 3 * X + np.random.randn(100, 1)

elastic_net = ElasticNet(alpha=0.0001, l1_ratio=0.15)
elastic_net.fit(X, y)
print(elastic_net.predict(1.5))

sgd_reg = SGDRegressor(penalty='elasticnet', n_iter=1000)
sgd_reg.fit(X, y.ravel())
print(sgd_reg.predict(1.5))

Polynomial regression

Polynomial regression是针对数据处理而言。问个问题。

在线性回归中,如果数据是非线性的变化,但是就想用线性的模型去拟合这个非线性的数据,怎么办?

  1. 非线性的数据去找非线性的算法生成的模型去拟合
  2. 可以把非线性的数据进行变化,变成类似线性的变化,然后使用线性的模型去拟合,PolynomialFeatures类其实就是这里说的第二种方式

比如,我们将下面式子:
y ^ ( w , x ) = w 0 + w 1 x 1 + w 2 x 2 + w 3 x 1 x 2 + w 4 x 1 2 + w 5 x 2 2 \hat{y}(w, x) = w_0 + w_1 x_1 + w_2 x_2 + w_3 x_1 x_2 + w_4 x_1^2 + w_5 x_2^2 y^(w,x)=w0+w1x1+w2x2+w3x1x2+w4x12+w5x22

做一下变换:
y ^ ( w , z ) = w 0 + w 1 z 1 + w 2 z 2 + w 3 z 3 + w 4 z 4 + w 5 z 5 \hat{y}(w, z) = w_0 + w_1 z_1 + w_2 z_2 + w_3 z_3 + w_4 z_4 + w_5 z_5 y^(w,z)=w0+w1z1+w2z2+w3z3+w4z4+w5z5

其中:
z = [ x 1 , x 2 , x 1 x 2 , x 1 2 , x 2 2 ] z = [x_1, x_2, x_1 x_2, x_1^2, x_2^2] z=[x1,x2,x1x2,x12,x22]
那么非线性变换的模型就转换成线性的了。

代码样例

import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression


m = 100
X = 6 * np.random.rand(m, 1) - 3
y = 0.5 * X ** 2 + X + 2 + np.random.randn(m, 1)

plt.plot(X, y, 'b.')

d = {1: 'g-', 2: 'r+', 10: 'y*'}
for i in d:
    poly_features = PolynomialFeatures(degree=i, include_bias=False)
    X_poly = poly_features.fit_transform(X)
    print(X[0])
    print(X_poly[0])
    print(X_poly[:, 0])

    lin_reg = LinearRegression(fit_intercept=True)
    lin_reg.fit(X_poly, y)
    print(lin_reg.intercept_, lin_reg.coef_)

    y_predict = lin_reg.predict(X_poly)
    plt.plot(X_poly[:, 0], y_predict, d[i])

plt.show()

相关问题

多项式回归:叫回归但并不是去做拟合的算法
PolynomialFeatures是来做预处理的,来转换我们的数据,把数据进行升维!

Q:升维有什么用?
A:升维就是增加更多的影响Y结果的因素,这样考虑的更全面,最终是要增加准确率!
还有时候,就像PolynomialFeatures去做升维,是为了让线性模型去拟合非线性的数据!

Q:PolynomialFeatures是怎么升维的?
A:可以传入degree超参数,如果等于2,那么就会在原有维度基础之上增加二阶的数据变化!
更高阶的以此类推

参考文章

你可能感兴趣的:(逻辑回归,机器学习,python,人工智能,深度学习)