有关数据集
链接:https://pan.baidu.com/s/1hLK_Safg5vslyszxi4k5_w
提取码:6666
线性回归
——《 Python机器学习算法:原理,实现与案例》读书笔记
线性回归模型
假设函数为:
h
w
,
b
(
x
)
=
∑
i
=
1
n
w
i
x
i
+
b
=
w
T
x
+
b
h_{w},_{b}(x)=\sum_{i=1}^{n} w_{i}x_{i}+b=w^{T}x+b
hw,b(x)=i=1∑nwixi+b=wTx+b
w
∈
R
n
,
b
∈
R
为
模
型
参
数
w \in R^{n},b \in R为模型参数
w∈Rn,b∈R为模型参数
通
常
将
b
纳
入
权
向
量
w
,
作
为
w
0
,
同
时
为
输
入
向
量
x
添
加
一
个
常
数
1
作
为
x
0
通常将b纳入权向量w,作为w_{0},同时为输入向量x添加一个常数1作为x_{0}
通常将b纳入权向量w,作为w0,同时为输入向量x添加一个常数1作为x0
w
=
(
b
,
w
1
,
w
2
,
.
.
.
,
w
n
)
T
w=(b,w_{1},w_{2},...,w_{n})^{T}
w=(b,w1,w2,...,wn)T
x
=
(
1
,
x
1
,
x
2
,
.
.
.
,
x
n
)
T
x=(1,x_{1},x_{2},...,x_{n})^{T}
x=(1,x1,x2,...,xn)T
通过训练确定模型参数w后,便可使用模型对输入进行预测
最小二乘法
损失函数:
线性回归模型通常使用均方误差(MSE)作为损失函数
假
设
训
练
集
D
有
m
个
样
本
,
M
S
E
定
义
为
:
假设训练集D有m个样本,MSE定义为:
假设训练集D有m个样本,MSE定义为:
J
(
w
)
=
1
2
m
∑
i
=
1
m
(
h
w
(
x
i
−
y
i
)
2
J(w)=\frac{1}{2m} \sum_{i=1}^{m}(h_{w}(x_{i}-y_{i})^{2}
J(w)=2m1i=1∑m(hw(xi−yi)2
=
1
2
m
∑
i
=
1
m
(
w
T
x
−
y
i
)
2
=\frac{1}{2m} \sum_{i=1}^{m}(w^{T}x-y_{i})^{2}
=2m1i=1∑m(wTx−yi)2
模型的目的:找到使损失函数最小的w
损失函数的最小值为其极值点,可对函数关于自变量求导,通过解方程求得
计
算
J
(
w
)
的
梯
度
:
计算J(w)的梯度:
计算J(w)的梯度:
▽
J
(
w
)
=
1
2
m
∑
i
=
1
m
∂
(
w
T
x
i
−
y
i
)
2
∂
w
\bigtriangledown J(w)=\frac{1}{2m}\sum_{i=1}^{m} \frac{\partial (w^{T}x_{i}-y_{i})^{2}}{\partial w}
▽J(w)=2m1i=1∑m∂w∂(wTxi−yi)2
=
1
2
m
∑
i
=
1
m
2
(
w
T
x
i
−
y
i
)
∂
(
w
T
x
i
−
y
i
)
∂
w
=\frac{1}{2m} \sum_{i=1}^{m}2(w^{T}x_{i}-y_{i})\frac{\partial (w^{T}x_{i}-y_{i})}{\partial w}
=2m1i=1∑m2(wTxi−yi)∂w∂(wTxi−yi)
=
1
m
∑
i
=
1
m
(
w
T
x
i
−
y
i
)
x
i
=\frac{1}{m} \sum_{i=1}^{m}(w^{T}x_{i}-y_{i})x_{i}
=m1i=1∑m(wTxi−yi)xi
令梯度为0,解得:
w
^
=
(
x
T
x
)
−
1
x
T
y
\hat{w} = (x^{T}x)^{-1}x^{T}y
w^=(xTx)−1xTy
w
^
即
为
使
得
损
失
函
数
最
小
的
w
\hat{w}即为使得损失函数最小的w
w^即为使得损失函数最小的w
以上求解最优w的方法称为普通最小二乘法(Ordinary Least Squares, OLS)
import numpy as np
class OLSLinearRegression:
def _ols(self, X, y):
"""最小二乘法估计w"""
tmp = np.linalg.inv(np.matmul(X.T, X))
tmp = np.matmul(tmp, X.T)
return np.matmul(tmp, y)
def _preprocess_data_X(self, X):
"""数据预处理"""
#扩展X,添加x0列并设置为1
m, n = X.shape
X_ = np.empty((m, n+1))
X_[:, 0] = 1
X_[:,1:] = X
return X_
def train(self, X_train, y_train):
"""训练模型"""
#预处理输入数据
X_train = self._preprocess_data_X(X_train)
#最小二乘估计参数
self.w = self._ols(X_train, y_train)
def predict(self, X):
"""预测"""
# 预处理
X = self._preprocess_data_X(X)
return np.matmul(X, self.w)
梯度下降
梯度下降的优化过程:
对于函数而言,梯度向量的反方向是函数值下降最快的方向
算法概述:
- 根据当前参数w计算损失函数梯度
- 沿着梯度反方向调整w
- 反复执行上述过程,直到梯度为0或者损失函数降低小于阈值,此时称算法收敛
我们计算出线性回归损失函数的梯度为:
▽
J
(
x
)
=
1
m
∑
i
=
1
m
(
w
T
x
i
−
y
i
)
x
i
\bigtriangledown J(x)=\frac{1}{m} \sum_{i=1}^{m}(w^{T}x_{i}-y_{i})x_{i}
▽J(x)=m1i=1∑m(wTxi−yi)xi
设 学 习 率 为 η , 梯 度 下 降 算 法 的 参 数 更 新 公 式 为 : 设学习率为 \eta ,梯度下降算法的参数更新公式为: 设学习率为η,梯度下降算法的参数更新公式为:
w : = w − η 1 m X T ( X w − y ) w:=w-\eta \frac{1}{m} X^{T}(Xw-y) w:=w−ηm1XT(Xw−y)
可以看出,执行梯度下降算法都是基于整个训练集计算梯度的,因此梯度下降也被称为批量梯度下降
当训练集非常大时,批量梯度下降算法会运行得极慢
随机梯度下降和小批量梯度下降可以看成是对批量梯度下降的近似
算法流程基本相同,只是每步使用少量的训练样本计算梯度
随机梯度下降
随机梯度下降—每一步只使用一个样本计算梯度
▽ J ( x ) = ( w T x i − y i ) x i \bigtriangledown J(x)=(w^{T}x_{i}-y_{i})x_{i} ▽J(x)=(wTxi−yi)xi
设 学 习 率 为 η , 随 机 梯 度 下 降 算 法 的 参 数 更 新 公 式 为 : 设学习率为 \eta ,随机梯度下降算法的参数更新公式为: 设学习率为η,随机梯度下降算法的参数更新公式为:
w : = w − η X T ( X w − y ) w:=w-\eta X^{T}(Xw-y) w:=w−ηXT(Xw−y)
每次只使用一个样本计算梯度,随机梯度下降运行数度很快,并且内存开销小。
对于损失函数的下降,随机梯度下降不像批量梯度下降那样缓慢降低,而是不断上下起伏,逐渐接近最小值。
另一个优势:当损失函数不规则时,更有可能跳过局部最小值,最终接近全局最小值
小批量梯度下降
小批量梯度下降是介于批量梯度下降慧荣随机梯度下降之间的折中方案,
每一步既不使用整个训练集又不使用单个样本,而使用一小批样本计算梯度
设
一
小
批
样
本
的
数
量
为
N
,
小
批
量
下
降
算
法
的
梯
度
计
算
公
式
为
:
设一小批样本的数量为N,小批量下降算法的梯度计算公式为:
设一小批样本的数量为N,小批量下降算法的梯度计算公式为:
▽
J
(
w
)
=
1
N
∑
i
=
k
k
+
N
(
w
T
x
i
−
y
i
)
x
i
\bigtriangledown J(w)=\frac{1}{N} \sum _{i=k}^{k+N}(w^{T}x_{i}-y_{i})x_{i}
▽J(w)=N1i=k∑k+N(wTxi−yi)xi
设
学
习
率
为
η
,
小
批
量
梯
度
下
降
算
法
的
参
数
更
新
公
式
为
:
设学习率为 \eta ,小批量梯度下降算法的参数更新公式为:
设学习率为η,小批量梯度下降算法的参数更新公式为:
w
:
=
w
−
η
1
N
∑
i
=
k
k
+
N
(
w
T
x
i
−
y
i
)
x
i
w:=w - \eta \frac{1}{N} \sum_{i=k}^{k+N} (w^{T}x_{i}-y_{i})x_{i}
w:=w−ηN1i=k∑k+N(wTxi−yi)xi
小批量梯度下降算法同时具备上述两种算法的优缺点,可视具体情况指定N值
import numpy as np
class GDLinearRegression:
def __init__(self, n_iter=200, eta=1e-3, tol=None):
#训练迭代次数
self.n_iter = n_iter
#学习率
self.eta = eta
#误差变化阈值
self.tol = tol
#模型参数
self.w = None
def _loss(self, y, y_pred):
"""计算损失"""
return np.sum((y_pred-y)**2) / y.size
def _grideient(self, X, y, y_pred):
"""计算梯度"""
return np.matmul(y_pred - y, X) / y.size
def _grideient_decent(self, w, X, y):
"""梯度下降算法"""
if self.tol is not None:
loss_old = np.inf
for step_i in range(self.n_iter):
#预测
y_pred = self._predict(X, w)
#计算损失
loss = self._loss(y, y_pred)
print('%4i Loss: %s' % (step_i, loss))
#指定阈值
if loss_old - loss < self.tol :
break
loss_old = loss
#计算梯度
grad = self._grideient(X, y, y_pred)
#更新参数w
w -= self.eta * grad
def _preprocess_data_X(self, X):
"""数据预处理"""
#扩展X,添加x0列并设置为1
m, n = X.shape
X_ = np.empty((m, n+1))
X_[:, 0] = 1
X_[:,1:] = X
return X_
def train(self, X_train, y_train):
"""训练"""
X_train = self._preprocess_data_X(X_train)
_, n = X_train.shape
self.w = np.random.random(n) * 0.05
#梯度下降训练
self._grideient_decent(self.w, X_train, y_train)
def _predict(self, X, w):
return np.matmul(X, w)
def predict(self, X):
"""预测"""
X = self._preprocess_data_X(X)
return self._predict(X, self.w)
实战
数据准备
# 数据准备
import sklearn
data = np.genfromtxt('winequality-red.csv', delimiter=';',skip_header=True)
X = data[:, :-1]
y = data[:, -1]
# 数据划分
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
最小二乘法
# OLSLinearRegression
## 训练
ols_lr = OLSLinearRegression()
ols_lr.train(X_train, y_train)
## 测试集
y_pred = ols_lr.predict(X_test)
mse = sklearn.metrics.mean_squared_error(y_test, y_pred)
# 训练集
y_train_pred = ols_lr.predict(X_train)
mse_train = sklearn.metrics.mean_squared_error(y_train, y_train_pred)
# 若测试集与训练集性能相差不大,可认为过拟合未发生
mse, mse_train
(0.43187133466403343, 0.4132690897797183)
梯度下降
#GDLinearRegression
## 学习率为0.01
gd_lr = GDLinearRegression(n_iter=3000, eta=0.01, tol=0.00001)
gd_lr.train(X_train, y_train)
##学习率为0.0001
gd_lr = GDLinearRegression(n_iter=3000, eta=0.0001, tol=0.00001)
gd_lr.train(X_train, y_train)
0 Loss: 19.024238551551097
1 Loss: 12900.737053180928
0 Loss: 14.027357729185224
1 Loss: 9.94782858060425
2 Loss: 8.256291151143442
3 Loss: 7.52322204476868
4 Loss: 7.175353500419736
5 Loss: 6.982785358071136
6 Loss: 6.853311650664905
7 Loss: 6.749943437128978
8 Loss: 6.65783460176615
9 Loss: 6.57101875396362
10 Loss: 6.487090584009939
11 Loss: 6.405073028009245
.
.
.
2991 Loss: 0.5160527899613486
2992 Loss: 0.5160393535075181
2993 Loss: 0.516025923591665
2994 Loss: 0.5160125002105204
2995 Loss: 0.515999083360818
2996 Loss: 0.5159856730392925
2997 Loss: 0.5159722692426804
2998 Loss: 0.5159588719677196
2999 Loss: 0.5159454812111505
梯度下降结果分析–结合最小二乘结果
- 学习率:
第一次学习率为0.01,调整后的学习率为0.0001 - 损失:
- 第1次,迭代几次,损失不降反升
- 第2次,损失随着迭代不断下降,但是迭代到了3000次,算法仍未收敛
# 学习率过大,迭代步长过大
# 算法收敛慢,本例的状况主要为X中各特征尺寸相差较大造成的
X.mean(axis=0)
array([ 8.31963727, 0.52782051, 0.27097561, 2.5388055 , 0.08746654,
15.87492183, 46.46779237, 0.99674668, 3.3111132 , 0.65814884,
10.42298311])
特征缩放
将X的各特征缩放到相同尺寸
两种常用方法:
归一化 & 标准化
归 一 化 : x i _ N o r m ( j ) = x i ( j ) − x m i n ( j ) x m a x ( j ) − x m i n ( j ) 归一化: x_{i\_Norm}^{(j)}=\frac{x_{i}^{(j)}-x_{min}^{(j)}}{x_{max}^{(j)}-x_{min}^{(j)}} 归一化:xi_Norm(j)=xmax(j)−xmin(j)xi(j)−xmin(j)
标 准 化 : x i _ S t d ( j ) = x i ( j ) − μ x ( j ) σ x ( j ) 标准化: x_{i\_Std}^{(j)}=\frac{x_{i}^{(j)}-\mu _{x}^{(j)}} {\sigma _{x}^{(j)}} 标准化:xi_Std(j)=σx(j)xi(j)−μx(j)
i : 第 i 个 样 本 , j : 第 j 个 特 征 i:第i个样本,j:第j个特征 i:第i个样本,j:第j个特征
归一化:使用极值将各特征值缩放到[0-1]区间
标准化:将各特征的均值设置为0,方差设置为1
对于大多数机器学习算法,标准化更为实用,因为标准化保持了异常值所蕴含的有用信息
# 归一化
sklearn.preprocessing.MinMaxScaler()
# 标准化
sklearn.preprocessing.StandardScaler()
StandardScaler()
# 标准化特征后的梯度下降
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
# 对训练集参数拟合,根据训练集的均值和标准差对训练集和测试集标准化
ss.fit(X_train)
X_train_std = ss.transform(X_train)
X_test_std = ss.transform(X_test)
# 梯度下降
##多次尝试调整学习率
gd_lr = GDLinearRegression(n_iter=3000, eta=0.005, tol=0.00001)
gd_lr.train(X_train_std, y_train)
# 用训练好的模型对测试集进行
y_pred = gd_lr.predict(X_test_std)
mse = sklearn.metrics.mean_squared_error(y_test, y_pred)
mae = sklearn.metrics.mean_absolute_error(y_test,y_pred )
mse, mae
可以看到,其实两种方法效果并不是很好,毕竟线性回归是非常简单的模型
至此,线性回归就告一段落了
(本文有些公式不是很严谨(但还是可以凑合推导出的),有意向获取更多相关内容,请查阅下书)
Python机器学习算法:原理,实现与案例 – 刘硕