在机器学习中,由于数据是多维度的,所以线性回归在此一般指多元线性回归。线性回归中的线性指的是,自变量和因变量之间呈线性关系,也就是自变量最高次数为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=1∑n(yi−y^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=1nxi2−nxˉ2∑i=1nxiyi−nxˉ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=1nxi2−nxˉ2∑i=1nxiyi−nxˉ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=1∑n(yi−y^i)2=21(Xθ−y)T(Xθ−y)其中θ=(ba)X=⎣⎢⎡x1⋮xn1⋮1⎦⎥⎤y=⎝⎜⎛y1⋮yn⎠⎟⎞X,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 对于列向量a,有(a,a)=aTa=∣∣a∣∣2对于n维列向量x,y;有(x,y)=xTy=i=1∑nxiyi
上述式子进一步化简为:
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)=yTy−2yTXθ+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)−1XTy时,函数J取最小值
至于为什么函数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)尽可能的小。
就相当于,我们求一个函数的最小值点,常规方法,就是求导,得出极值点。但在这里,我们不这么玩,靠一步一步试,得到最接近最小值点的解。 如下图:
当然也不是乱试,原则就是:
梯度下降方法有很多,如批量(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
批量梯度下降,就是将所有的测试集都用上。
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]]
明显,上面三种方法求出来的结果,都是一样。
随机梯度下降,就是从测试集中随机选择一组测试数据来求梯度。
#随机梯度下降法
#初始化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 ]]
很明显,所求结果和前几种相比,还是较大差别的。
上面介绍的是最小二乘法(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=min∣∣Xw−y∣∣2+α∣∣w∣∣2
相比于最小二乘法,就是公式后多了一部分。这部分叫正则化惩罚项,是用来提高模型泛化能力的。也就是防止过拟合
那么,当使用惩罚项,会产生什么影响?
使用惩罚项,会提高模型的泛化能力,但是因为人为的改变了损失函数,所有在一定程度上牺牲了正确率,即对训练集已有数据的拟合效果。但是没关系,因为我们的模型目的是对未来新的数据进行预测。在惩罚项里面,会有个alpha,即惩罚项的权重,我们可以通过调整alpha超参数,根据需求来决定是更看重模型的正确率还是模型的泛化能力!
如何在机器学习里面防止过拟合呢?
模型参数W个数,越少越好,无招胜有招.模型参数W的值越小越好,这样如果X输入有误差,也不会太影响y预测结果.通过正则化惩罚项人为的修改已有的损失函数,比如使用L1、L2正则添加到loss func里面去
让我们的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使用的是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} wmin2nsamples1∣∣Xw−y∣∣22+α∣∣w∣∣1
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模型,损失函数如下:
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} wmin2nsamples1∣∣Xw−y∣∣22+αρ∣∣w∣∣1+2α(1−ρ)∣∣w∣∣22
此模型通过共同使用了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是针对数据处理而言。问个问题。
在线性回归中,如果数据是非线性的变化,但是就想用线性的模型去拟合这个非线性的数据,怎么办?
比如,我们将下面式子:
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,那么就会在原有维度基础之上增加二阶的数据变化!
更高阶的以此类推
参考文章