线性回归原理
分类与回归的区别是预测的
y
y
y 值是否连续,线性回归属于监督学习中的回归算法,用来预测连续的
y
y
y 值;在第二课提到的
y
=
k
x
+
b
y=kx+b
y=kx+b,斜率
k
k
k 在机器学习中的术语叫做特征
x
x
x 的权重(weight),截距
b
b
b 在机器学习中的术语叫做偏置(bias),像这样只有一个输入特征的线性回归模型叫做一元线性回归模型,有多个输入特征的线性回归模型叫做多元线性回归模型:
f
(
x
1
,
x
2
,
.
.
,
x
n
)
=
w
1
x
1
+
w
2
x
2
+
.
.
.
+
w
n
x
n
+
b
f(x_{1},x_{2},..,x_{n})=w_{1}x_{1}+w_{2}x_{2}+...+w_{n}x_{n}+b
f(x1,x2,..,xn)=w1x1+w2x2+...+wnxn+b
权重
w
w
w 的大小在一定程度上反映了其对应特征
x
x
x 的重要性;
为了形式的统一,可以把上面的多元线性回归模型中的偏置项
b
b
b 改写成
w
0
w_{0}
w0,同时令
x
0
=
1
x_{0}=1
x0=1,多元线性回归模型即表示成如下的形式:
f
(
x
1
,
x
2
,
.
.
,
x
n
)
=
w
1
x
1
+
w
2
x
2
+
.
.
.
+
w
n
x
n
+
w
0
x
0
f(x_{1},x_{2},..,x_{n})=w_{1}x_{1}+w_{2}x_{2}+...+w_{n}x_{n}+w_{0}x_{0}
f(x1,x2,..,xn)=w1x1+w2x2+...+wnxn+w0x0
如果记
x
i
x_{i}
xi表示第
i
i
i个样本(一共
m
m
m个样本),
x
i
j
x_{i}^{j}
xij表示第
i
i
i个样本的第
j
j
j维特征(每个样本一共
n
n
n维特征),偏置需要的值
x
i
0
=
1
x_{i}^{0}=1
xi0=1,线性回归参数为:
w
=
[
w
0
,
w
1
,
w
2
,
.
.
.
,
w
n
]
T
w=[w_{0},w_{1},w_{2},...,w_{n}]^{T}
w=[w0,w1,w2,...,wn]T
注意,
w
0
w_{0}
w0属于偏置;
线性回归可以表述为以下形式:
- 标量展开式
f ( x i ) = w 0 x i 0 + w 1 x i 1 + w 2 x i 2 + . . . + w n x i n , i ∈ { 1 , 2 , . . . , m } f(x_{i})=w_{0}x_{i}^{0}+w_{1}x_{i}^{1}+w_{2}x_{i}^{2}+...+w_{n}x_{i}^{n},i\in \left \{ 1,2,...,m \right \} f(xi)=w0xi0+w1xi1+w2xi2+...+wnxin,i∈{1,2,...,m} - 标量求和式
f ( x i ) = ∑ j = 0 n w j x i j , i ∈ { 1 , 2 , . . . , m } f(x_{i})=\sum_{j=0}^{n}w_{j}x_{i}^{j},i\in \left \{ 1,2,...,m \right \} f(xi)=j=0∑nwjxij,i∈{1,2,...,m} - 向量内积式
f ( x i ) = w ⋅ x i = x i ⋅ w , i ∈ { 1 , 2 , . . . , m } f(x_{i})=w\cdot x_{i}=x_{i}\cdot w,i\in \left \{ 1,2,...,m \right \} f(xi)=w⋅xi=xi⋅w,i∈{1,2,...,m}
符号 ⋅ \cdot ⋅表示两个向量的内积运算。向量内积的运算法则是:两个向量对应维度的数值相乘再求和; - 矩阵乘法式
f ( x i ) = w T x i , i ∈ { 1 , 2 , . . . , m } f(x_{i})=w^{T}x_{i},i\in \left \{ 1,2,...,m \right \} f(xi)=wTxi,i∈{1,2,...,m}
其中 w w w和 x i x_{i} xi形状均为 ( n , 1 ) (n,1) (n,1)
损失函数
损失函数 L ( f ( x i ) , y i ) L(f(x_{i}),y_{i}) L(f(xi),yi)用于度量模型在单个样本上预测效果的好坏,常见的损失函数有:0-1损失函数、绝对损失函数、平方损失函数;
0-1损失:
L
(
f
(
x
i
)
,
y
i
)
=
1
,
f
(
x
i
)
≠
y
i
L(f(x_{i}),y_{i})=1,f(x_{i})\neq y_{i}
L(f(xi),yi)=1,f(xi)=yi
L
(
f
(
x
i
)
,
y
i
)
=
0
,
f
(
x
i
)
=
y
i
L(f(x_{i}),y_{i})=0,f(x_{i})= y_{i}
L(f(xi),yi)=0,f(xi)=yi
绝对损失:
L
(
f
(
x
i
)
,
y
i
)
=
∣
f
(
x
i
)
−
y
i
∣
L(f(x_{i}),y_{i})=|f(x_{i})-y_{i}|
L(f(xi),yi)=∣f(xi)−yi∣
平方损失:
L
(
f
(
x
i
)
,
y
i
)
=
(
f
(
x
i
)
−
y
i
)
2
L(f(x_{i}),y_{i})=(f(x_{i})-y_{i})^{2}
L(f(xi),yi)=(f(xi)−yi)2
经验风险
经验风险是模型在训练数据集上的平均损失,用来度量模型在整个训练数据集上预测效果的好坏,其数学表达式为:
R
=
1
m
∑
i
=
1
m
L
(
f
(
x
i
)
,
y
i
)
R=\frac{1}{m}\sum_{i=1}^{m}L(f(x_{i}),y_{i})
R=m1i=1∑mL(f(xi),yi)
如果使用平方损失计算经验风险,就成为了MSE(mean squared error,均方误差):
J
(
w
)
=
1
m
∑
i
=
1
m
(
f
(
x
i
)
−
y
i
)
2
J(w)=\frac{1}{m}\sum_{i=1}^{m}(f(x_{i})-y_{i})^{2}
J(w)=m1i=1∑m(f(xi)−yi)2
对于两向量
f
(
x
)
f(x)
f(x)和
y
y
y,形状均为
(
m
,
1
)
(m,1)
(m,1),则MSE可以用矩阵乘法表示:
J
(
w
)
=
1
m
[
f
(
x
)
−
y
]
T
[
f
(
x
)
−
y
]
J(w)=\frac{1}{m}[f(x)-y]^{T}[f(x)-y]
J(w)=m1[f(x)−y]T[f(x)−y]
用矩阵表达的好处是更易于使用机器处理;
梯度下降
梯度(gradient)其实是一个向量,一个函数对于其自变量分别求偏导数,这些偏导数所组成的向量就是函数的梯度。梯度下降是一种模型参数的更新方法:沿梯度的负方向进行参数更新;
经验风险函数大多是一个凸函数,它的梯度方向是经验风险增大的方向,如果要最小化经验风险就必须朝着负梯度方向更新参数:

假如模型的参数只有一个,那么经验风险
J
(
w
)
J(w)
J(w) 关于参数
w
w
w 的示意图如上,这是一个一元二次函数,也是一个凸函数;
- 这个函数的梯度可以看做只有一个元素的向量, 假如模型参数 w w w 的初始值为 5,则对应的经验风险为0.69,曲线上该点切线的斜率(导数)为正数,即梯度方向为模型参数 w w w 的正方向。从图中可以明显地观察到,沿 w w w 轴的正方向经验风险是在增大的;
- 假如模型参数 w w w 的初始值 2,则对应的经验风险是 0.48,曲线上该点切线的斜率为负数,即梯度方向为模型参数 w w w 的负方向。从图中也可以明显地观察到,沿 w w w 轴的负方向经验风险在增大;
所以,参数
w
w
w 在初始化之后要沿着梯度的负方向进行更新才能让经验风险越来越小:
w
=
w
−
α
∂
J
(
w
)
∂
w
w=w-\alpha \frac{\partial J(w)}{\partial w}
w=w−α∂w∂J(w)
α
\alpha
α 在机器学习中的术语叫做学习率,是一个人为设定的超参数,代表了参数
w
w
w 一次更新的幅度大小;
现在对之前的MSE做一点尺度上的修改,求平均的系数 1 m \frac{1}{m} m1变成 1 2 m \frac{1}{2m} 2m1,这样修改利于梯度表达的化简:
J
(
w
)
=
1
2
m
∑
i
=
1
m
(
f
(
x
i
)
−
y
i
)
2
J(w)=\frac{1}{2m}\sum_{i=1}^{m}(f(x_{i})-y_{i})^{2}
J(w)=2m1i=1∑m(f(xi)−yi)2
代入修改后的MSE,则线性回归的梯度下降计算为:
w
j
=
w
j
−
α
∂
J
(
w
)
∂
w
j
=
w
j
−
α
m
∑
i
=
1
m
[
(
f
(
x
i
)
−
y
i
)
x
i
j
]
w_{j}=w_{j}-\alpha \frac{\partial J(w)}{\partial w_{j}}=w_{j}-\frac{\alpha}{m}\sum_{i=1}^{m}[(f(x_{i})-y_{i})x_{i}^{j}]
wj=wj−α∂wj∂J(w)=wj−mαi=1∑m[(f(xi)−yi)xij]
将梯度项写成矩阵形式表达如下:
w
j
=
w
j
−
α
m
[
f
(
x
)
−
y
]
T
x
j
,
j
∈
{
0
,
1
,
2
,
.
.
.
,
n
}
w_{j}=w_{j}-\frac{\alpha}{m}[f(x)-y]^{T}x^{j},j\in \left \{ 0,1,2,...,n \right \}
wj=wj−mα[f(x)−y]Txj,j∈{0,1,2,...,n}
其中,
x
j
x^{j}
xj为样本集第
j
j
j维特征组成的向量,形状为
(
m
,
1
)
(m,1)
(m,1),
y
y
y形状为
(
m
,
1
)
(m,1)
(m,1);
x
x
x形状为
(
m
,
n
)
(m,n)
(m,n),调整
w
w
w的形状从
(
n
,
1
)
(n,1)
(n,1)到
(
1
,
n
)
(1,n)
(1,n),可进一步简化表达为:
w
=
w
−
α
m
[
f
(
x
)
−
y
]
T
x
w=w-\frac{\alpha}{m}[f(x)-y]^{T}x
w=w−mα[f(x)−y]Tx
实验:numpy实现线性回归
numpy相关方法回顾
基于以上原理,我将使用numpy实现线性回归以预测房价;在实现模型前,需要先了解一些numpy相关的方法:
- numpy.random.randn
生成服从标准正态分布的ndarray;
randn(d0, d1, ..., dn)
- numpy.ones
生成值全为1的ndarray;
np.ones(shape:"int or tuple", dtype=None)
- numpy.concatenate
拼接不同的ndarray,可以设置操作的轴方向;
concatenate((a1, a2, ...), axis=0)
- numpy.dot
对ndarray作乘法,如果a和b是矩阵(在numpy中,shape为 ( 6 , 1 ) (6,1) (6,1)也是矩阵),则作矩阵乘法,如果a和b是向量(numpy中,向量形状一般为 ( 6 , ) (6,) (6,)这样的形式),则两个向量作内积;
dot(a, b)
- numpy.savetxt
将ndarray保存到*.txt文件,X为一个ndarray,fname为保存的文件名;
np.savetxt(fname, X)
- numpy.loadtxt
从*.txt文件加载ndarray;
np.loadtxt(fname)
在numpy 中,形状为 ( m , 1 ) (m,1) (m,1)是矩阵, ( m , ) (m,) (m,)才是向量
加载波士顿房价数据集
在第二课中已经提到过该数据集:
from sklearn.datasets import load_boston
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
# 获取波士顿房价数据集
boston = load_boston()
# 获取数据集特征
X = boston.data
# 获取数据集标记
y = boston.target
X.shape # (506, 13)
y.shape # (506,)
# 特征X归一化到 [0,1] 范围内:提升模型收敛速度
X = MinMaxScaler().fit_transform(X)
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.2, random_state=2020)
需要注意的是特征归一化的使用:多维特征取值差异较大时会影响梯度下降的收敛效果,所以需要对每个特征进行归一化处理。如果不进行特征归一化处理,模型收敛会很慢甚至可能不收敛。
模型实现
根据原理实现模型:
import numpy as np
import matplotlib.pyplot as plt
class LinearRegression:
"""线性回归算法实现"""
def __init__(self,alpha=0.1,epoch=5000,fit_bias=True):
'''
alpha: 学习率,控制参数更新的幅度
epoch: 在整个训练集上训练迭代(参数更新)的次数
fit_bias: 是否训练偏置项参数
cost_record: 记录每一轮迭代的经验风险
'''
self.alpha = alpha
self.epoch = epoch
self.cost_record = []
self.fit_bias = fit_bias
# 预测函数
def predict(self,X_test):
'''
X_test: m x n 的 numpy 二维数组
'''
# 模型有偏置项参数时:为每个测试样本增加特征 x_0 = 1
if self.fit_bias:
x_0 = np.ones((X_test.shape[0],1))
X_test = np.concatenate((x_0,X_test),axis=1)
# 根据公式返回结果
return np.dot(X_test,self.w)
# 模型训练:使用梯度下降法更新参数
def fit(self,X_train,y_train):
'''
X_train: m x n 的 numpy 二维数组
y_train:有 m 个元素的 numpy 一维数组
'''
# 训练偏置项参数时:为每个训练样本增加特征 x_0 = 1
if self.fit_bias:
x_0 = np.ones((X_train.shape[0],1))
X_train = np.concatenate((x_0,X_train),axis=1)
# 训练样本数量
m = X_train.shape[0]
# 样本特征维数
n = X_train.shape[1]
# 初始模型参数
self.w = np.ones(n)
# 模型参数迭代
for i in range(self.epoch):
# 计算训练样本预测值
y_pred = np.dot(X_train,self.w)
# 计算训练集经验风险
cost = np.dot(y_pred-y_train,y_pred-y_train)/(2*m)
# 记录训练集经验风险
self.cost_record.append(cost)
# 参数更新
self.w -= self.alpha/m * np.dot(y_pred-y_train,X_train)
# 保存模型
self.save_model()
# 显示经验风险的收敛趋势图
def polt_cost(self):
plt.plot(np.arange(self.epoch),self.cost_record)
plt.xlabel("epoch")
plt.ylabel("cost")
plt.show()
# 保存模型参数
def save_model(self):
np.savetxt("model.txt",self.w)
# 加载模型参数
def load_model(self):
self.w = np.loadtxt('model.txt')
训练模型:
# 实例化一个对象
model = LinearRegression()
# 在训练集上训练
model.fit(X_train,y_train)
查看model.txt有以下参数:
2.377833240398708625e+01
-7.025529850988752045e+00
4.103394581694540300e+00
8.398391975124489539e-01
2.240962454086636857e+00
-7.467285220119970646e+00
2.176221710038299761e+01
1.103535484752093537e-01
-1.373536027934833470e+01
6.414765330227935713e+00
-6.110464483890787335e+00
-8.341996895234622400e+00
4.172278308851403494e+00
-1.871659543710807938e+01
显示经验风险的收敛趋势图:
model.polt_cost()
加载模型参数并预测:
# 实例化一个对象
model = LinearRegression()
# 加载训练好的模型参数
model.load_model()
# 在测试集上预测
y_pred = model.predict(X_test)
对比sklearn的实现
对比第二课使用sklearn封装的线性回归:
# 导入 sklearn 线性回归模型,为了区别本文,使用 as 重命名
from sklearn.linear_model import LinearRegression as _LinearRegression
import pandas as pd
# 实例化一个对象
our_model = LinearRegression()
sklearn_model = _LinearRegression()
# 在训练集上训练
our_model.fit(X_train,y_train)
sklearn_model.fit(X_train,y_train)
# 在测试集上预测
our_model_pred = our_model.predict(X_test)
sklearn_model_pred = sklearn_model.predict(X_test)
# 将数据转换为dataframe
"""
将测试集的标记值和两个模型的预测值转换为 dataframe 格式。由于传入的数据是列表,列表中的三个元素都是 numpy 中的一维数组,
所以可以看作是有 3 行数据的二维数组;为了方便展示和对比,后面进行了矩阵转置(3行变为3列),且只取前10行数据
"""
housing_price = pd.DataFrame([y_test,our_model_pred,sklearn_model_pred]).T.head(10)
# 设置列名
housing_price.columns=['true_value','our_model_pred','sklearn_model_pred']
housing_price
结果为:
观察可知,自定义实现的线性回归模型预测值和 sklearn 封装的线性回归模型预测值还是较为相近的;
两个模型特征权重对比,不包含偏置 w 0 w_{0} w0:
# 将两个模型的特征权重转换为 dataframe 格式
feature_weight = pd.DataFrame([our_model.w[1:],sklearn_model.coef_],columns=boston.feature_names).T
# 设置列名
feature_weight.columns = ['our_model','sklearn_mode']
# 按照我们的模型特征权重降序排列
# ascending: 是否按指定列的数组升序排列,默认为True,即升序排列
feature_weight = feature_weight.sort_values('our_model',ascending=False)
feature_weight

特征权重的大小反映了这个特征对于模型预测值的影响。权重为正时,该特征取值越大,房价越高,且权重越大,这种影响越显著。权重为负时,该特征取值的增大会抑制房价上涨
这篇博客介绍了线性回归的原理,包括一元和多元线性回归模型,以及梯度下降法。通过numpy实现了线性回归模型,并在波士顿房价数据集上进行训练,对比了自定义模型与sklearn库的线性回归模型预测结果,展示了经验风险的收敛趋势图。
4万+

被折叠的 条评论
为什么被折叠?



