什么是回归问题?
什么是线性回归?
符号约定:
符号 | 含义 |
---|---|
m m m | 训练集中样本的数量 |
n n n | 特征的数量 |
x x x | 样本的特征变量/输入变量 |
y y y | 样本的目标变量/输出变量 |
( x , y ) (x,y) (x,y) | 训练集中的样本: ( 输 入 变 量 , 输 出 变 量 ) (输入变量,输出变量) (输入变量,输出变量) |
( x ( i ) , y ( i ) ) (x^{(i)},y^{(i)}) (x(i),y(i)) | 代表第 i i i 个观察样本的特征和输出 |
h h h | 代表学习算法的解决方案或函数,也称为假设(hypothesis) |
y ^ = h ( x ) {\hat y}=h(x) y^=h(x) | 代表预测值 |
例如:
x ( 2 ) = [ 162.2 31 8 118 ] , y ( 2 ) = 37000 , x 2 ( 2 ) = 31 , x 3 ( 2 ) = 8 x^{(2)}=\left[\begin{matrix}162.2\\31\\8\\118\end{matrix}\right],y^{(2)}=37000,x^{(2)}_2=31,x^{(2)}_3=8 x(2)=⎣⎢⎢⎡162.2318118⎦⎥⎥⎤,y(2)=37000,x2(2)=31,x3(2)=8
构建线性模型的核心问题是找到: h ( x ) = w 1 x 1 + w 2 x 2 + . . . + w n x n + w 0 ( b ) h(x)=w_1x_1+w_2x_2+...+w_nx_n+w_0(b) h(x)=w1x1+w2x2+...+wnxn+w0(b) 中的 w ( w 1 , w 2 , . . . , w n ) {\bf w}(w_1,w_2,...,w_n) w(w1,w2,...,wn) 和 w 0 ( b ) w_0(b) w0(b)
为了方便计算,可以设 x 0 = 1 x_0=1 x0=1,则方程变为: h ( x ) = w 0 x 0 + w 1 x 1 + w 2 x 2 + . . . + w n x n = w T x = ⟨ w , x ⟩ h(x)=w_0x_0+w_1x_1+w_2x_2+...+w_nx_n={\bf w}^T{\bf x}=\langle {\bf w},{\bf x}\rangle h(x)=w0x0+w1x1+w2x2+...+wnxn=wTx=⟨w,x⟩
w i w_i wi 的取值越大,线性模型越复杂
损失函数(Loss Function):
对学习结果的度量,度量单样本预测的错误程度,损失函数值越小,模型就越好
损失函数名称 | 损失函数定义 |
---|---|
0-1损失函数 | L o s s ( y ( i ) , h ( x ( i ) ) ) = { 1 , h ( x ( i ) ) ≠ y ( i ) 0 , h ( x ( i ) ) = y ( i ) Loss(y^{(i)},h(x^{(i)}))=\begin{cases} 1, & h(x^{(i)}) \neq y^{(i)} \\ 0, & h(x^{(i)}) = y^{(i)} \end{cases} Loss(y(i),h(x(i)))={ 1,0,h(x(i))=y(i)h(x(i))=y(i) |
平方损失函数 | L o s s ( y ( i ) , h ( x ( i ) ) ) = ( y ( i ) − h ( x ( i ) ) ) 2 Loss(y^{(i)},h(x^{(i)}))=(y^{(i)}-h(x^{(i)}))^2 Loss(y(i),h(x(i)))=(y(i)−h(x(i)))2 |
绝对损失函数 | L o s s ( y ( i ) , h ( x ( i ) ) ) = ∣ y ( i ) − h ( x ( i ) ) ∣ Loss(y^{(i)},h(x^{(i)}))=\mid y^{(i)}-h(x^{(i)})\mid Loss(y(i),h(x(i)))=∣y(i)−h(x(i))∣ |
对数损失函数/对数似然损失函数 | L o s s ( y ( i ) , P ( y ( i ) ∣ x i ) ) = − l o g P ( ( y ( i ) ∣ x ( i ) ) ) Loss(y^{(i)},P(y^{(i)}\mid x_i))=-logP((y^{(i)}\mid x^{(i)})) Loss(y(i),P(y(i)∣xi))=−logP((y(i)∣x(i))) |
代价函数: 度量对所有样本集预测值的平均误差,常用的代价函数包括均方误差、均方根误差、平均绝对误差等
代价函数常常采用均方误差 MSE \text{MSE} MSE: MSE = 1 m ∑ i = 1 m ( y ( i ) − y ( i ) ^ ) 2 \text{MSE}={1 \over m}\sum_{i=1}^m(y^{(i)}-\hat {y^{(i)}})^2 MSE=m1∑i=1m(y(i)−y(i)^)2,为方便计算,常定义为: J ( w ) = 1 2 m ∑ i = 1 m ( y ( i ) − y ( i ) ^ ) 2 J({\bf w})={1 \over 2m}\sum_{i=1}^m(y^{(i)}-\hat {y^{(i)}})^2 J(w)=2m1∑i=1m(y(i)−y(i)^)2
目标:要找到一组 w ( w 0 , w 1 , w 2 , . . . , w n ) {\bf w}(w_0,w_1,w_2,...,w_n) w(w0,w1,w2,...,wn),使得 MSE \text{MSE} MSE 具有最小值: m i n w ( 1 m ∑ i = 1 m ( y ( i ) − y ( i ) ^ ) 2 ) min_{w}({1 \over m}\sum_{i=1}^m(y^{(i)}-\hat {y^{(i)}})^2) minw(m1∑i=1m(y(i)−y(i)^)2),那么,该如何寻找这一组 w {\bf w} w 呢?
很显然,代价函数 J ( w ) = 1 2 m ∑ i = 1 m ( y ( i ) ^ − y ( i ) ) 2 J({\bf w})={1 \over 2m}\sum_{i=1}^m(\hat {y^{(i)}}-y^{(i)})^2 J(w)=2m1∑i=1m(y(i)^−y(i))2 是一个二次函数,即它是一个凸函数,具有最小值
以二维 w ( w 0 , w 1 ) {\bf w}(w_0,w_1) w(w0,w1) 为例,由不同的 ( w 0 , w 1 ) (w_0,w_1) (w0,w1) 可以得到不同的代价值,且代价函数可以找到最小值,如图所示:
如果想要找到最小值,我们只需对代价函数求偏导,并令偏导数为 0 0 0(最小值点),对凸函数来说是可以找到解析解的:
令 ∂ J ( w 0 , w 1 ) ∂ w 0 = 1 m ∑ i = 1 m ( w 0 + w 1 x ( i ) − y ( i ) ) = 0 令 \ \frac{\partial J(w_0,w_1)}{\partial w_0}={1 \over m}\sum_{i=1}^m(w_0+w_1x^{(i)}-y^{(i)})=0 令 ∂w0∂J(w0,w1)=m1i=1∑m(w0+w1x(i)−y(i))=0
令 ∂ J ( w 0 , w 1 ) ∂ w 1 = 1 m ∑ i = 1 m x ( i ) ( w 0 + w 1 x ( i ) − y ( i ) ) = 0 令 \ \frac{\partial J(w_0,w_1)}{\partial w_1}={1 \over m}\sum_{i=1}^mx^{(i)}(w_0+w_1x^{(i)}-y^{(i)})=0 令 ∂w1∂J(w0,w1)=m1i=1∑mx(i)(w0+w1x(i)−y(i))=0
解 得 : w 0 = 1 m ∑ i = 1 m ( y ( i ) − w 1 x ( i ) ) , w 1 = ∑ i = 1 m y ( i ) ( x ( i ) − x ‾ ) ∑ i = 1 n ( x ( i ) ) 2 − 1 m ( ∑ i = 1 m x ( i ) ) 2 解得:w_0={1 \over m}\sum_{i=1}^m(y^{(i)}-w_1x^{(i)}),w_1={\sum_{i=1}^my^{(i)}(x^{(i)}-\overline{x}) \over \sum_{i=1}^n(x^{(i)})^2-{1 \over m}(\sum_{i=1}^mx^{(i)})^2} 解得:w0=m1i=1∑m(y(i)−w1x(i)),w1=∑i=1n(x(i))2−m1(∑i=1mx(i))2∑i=1my(i)(x(i)−x)
梯度: 它是一个矢量,对一元函数来说,梯度就是函数的导数;对多元函数来说,多元函数 f ( x ) f(x) f(x) 在某一点处的梯度表示 f ( x ) f(x) f(x) 在该点处的最大方向导数,其方向为具有最大增长率的方向,那么梯度的反方向就是函数值下降率最大的方向
梯度下降的目标仍然是希望找到代价函数 J ( w 0 , w 1 , . . . , w n ) J(w_0,w_1,...,w_n) J(w0,w1,...,wn) 的最小值,比 LSM \text{LSM} LSM 更好的是,梯度下降法可以应用于更一般的函数,而不只是凸函数
利用梯度下降,我们可以在代价函数的某一点处开始,根据梯度的反方向,逐渐修改 ( w 0 , w 1 , . . . , w n ) (w_0,w_1,...,w_n) (w0,w1,...,wn),来使代价函数值逐渐减小
梯度下降的数学定义:
w = ( w 0 , w 1 , . . . , w n ) w=(w_0,w_1,...,w_n) w=(w0,w1,...,wn),里面第 i i i 个元素用 w i w_i wi 来表示,则:
w i = w i − α ∂ ∂ w i J ( w 0 , w 1 , . . . , w n ) , for i=0 to i=n ⏟ 不断重复,直到收敛 \underbrace{ {w_i}=w_i-\alpha\frac{\partial }{\partial w_i}J(w_0,w_1,...,w_n),\text{for i=0 to i=n}}_{\rm \text{不断重复,直到收敛}} 不断重复,直到收敛 wi=wi−α∂wi∂J(w0,w1,...,wn),for i=0 to i=n
上述公式表明:
梯度下降法具有三种模式,即批量梯度下降、随机梯度下降和小批量梯度下降
批量梯度下降在计算代价函数的梯度时,考虑了所有的训练样本,代价函数: J ( w ) = 1 2 m ∑ j = 1 m ( y ( j ) ^ − y ( j ) ) 2 J({\bf w})={1 \over 2m}\sum_{j=1}^m(\hat {y^{(j)}}-y^{(j)})^2 J(w)=2m1∑j=1m(y(j)^−y(j))2, m m m 为样本个数,因此 w i w_i wi 为:
w i = w i − α 1 m ∑ j = 1 m x i ( j ) ( y ( j ) ^ − y ( j ) ) = w i − α 1 m ∑ j = 1 m x i ( j ) ( ⟨ w , x ⟩ − y ( j ) ) {w_i}=w_i-\alpha{1 \over m}\sum_{j=1}^mx^{(j)}_i\left(\hat {y^{(j)}}-y^{(j)}\right)=w_i-\alpha{1 \over m}\sum_{j=1}^mx^{(j)}_i\left(\langle {\bf w},{\bf x}\rangle-y^{(j)}\right) wi=wi−αm1j=1∑mxi(j)(y(j)^−y(j))=wi−αm1j=1∑mxi(j)(⟨w,x⟩−y(j))
特点:
随机梯度下降每一次更新 ω \omega ω 时,随机选择一个训练样本来计算代价函数的梯度,设选择了第 j j j 个训练样本:
w i = w i − α 1 m x i ( j ) ( y ( j ) ^ − y ( j ) ) = w i − α x i ( j ) ( ⟨ w , x ⟩ − y ( j ) ) {w_i}=w_i-\alpha{1 \over m}x^{(j)}_i\left(\hat {y^{(j)}}-y^{(j)}\right)=w_i-\alpha x^{(j)}_i\left(\langle {\bf w},{\bf x}\rangle-y^{(j)}\right) wi=wi−αm1xi(j)(y(j)^−y(j))=wi−αxi(j)(⟨w,x⟩−y(j))
特点:
小批量梯度下降结合了(一)(二)各自的优点,既不使用所有样本,也不使用单个样本,而是随机选择小批量样本
每次计算代价函数时使用 b b b 个样本,其中 k k k 表示第 k ( k < m ) k(k
w i = w i − α 1 b ∑ j = 1 b x i ( k ) ( y ( k ) ^ − y ( k ) ) = w i − α 1 b ∑ j = 1 b x i ( k ) ( ⟨ w , x ⟩ − y ( k ) ) {w_i}=w_i-\alpha{1 \over b}\sum_{j=1}^bx_i^{(k)}\left(\hat {y^{(k)}}-y^{(k)}\right)=w_i-\alpha{1 \over b}\sum_{j=1}^bx_i^{(k)}\left(\langle {\bf w},{\bf x}\rangle-y^{(k)}\right) wi=wi−αb1j=1∑bxi(k)(y(k)^−y(k))=wi−αb1j=1∑bxi(k)(⟨w,x⟩−y(k))
其性能介于随机和批量之间,其中,选择的批量数不能太小,这样会导致每次计算量太小,不适合并行来最大利用计算资源,批量数也不能太大,这样会导致内存消耗增加,浪费计算
梯度下降存在的缺陷:
属性缩放:
(一)归一化(最大-最小规范化):
x ′ = x − x m i n x m a x − x m i n x'={x-x_{min} \over x_{max}-x_{min}} x′=xmax−xminx−xmin
将数据映射到 [ 0 , 1 ] [0,1] [0,1] 区间内,数据归一化的目的是使得各特征对目标变量的影响一致,会将特征数据进行伸缩变化,所以数据归一化是会改变特征数据分布的
(二)Z-Score 标准化:
x ′ = x − μ σ x'={x-\mu \over \sigma} x′=σx−μ
其 中 σ 2 = 1 m ∑ i = 1 m ( x ( i ) ) 2 , μ = 1 m ∑ i = 1 m x ( i ) 其中 \ \sigma^2={1 \over m}\sum_{i=1}^m(x^{(i)})^2,\mu={1 \over m}\sum_{i=1}^mx^{(i)} 其中 σ2=m1i=1∑m(x(i))2,μ=m1i=1∑mx(i)
处理后的数据均值为 0 0 0,方差为 1 1 1,数据标准化为了不同特征之间具备可比性,经过标准化变换之后的特征数据分布没有发生改变
当特征不足或者现有特征与样本标签的相关性不强时,模型容易出现欠拟合,通过挖掘组合特征等新的特征,往往能够取得更好的效果
简单模型的学习能力较差,通过增加模型的复杂度可以使模型拥有更强的拟合能力,如:在线性模型中添加高次项,使得线性模型变为曲线,又比如在神经网络模型中增加网络层数或神经元个数等,但模型过于复杂容易出现过拟合
而正则化就是是用来防止过拟合的,在经验风险上加上与模型复杂度有关的正则化项或惩罚项,但当模型出现欠拟合现象时,则需要有针对性地减小正则化系数
线性回归模型的两种正则化项:
J ( w ) = 1 2 m ∑ i = 1 m ( ⟨ w , x ⟩ − y ( i ) ) 2 + 1 2 λ ∑ j = 1 n w j 2 , ( λ > 0 ) J({\bf w})={1 \over 2m}\sum_{i=1}^m\left(\langle{\bf w},{\bf x}\rangle-y^{(i)}\right)^2+{1 \over 2}\lambda\sum_{j=1}^nw_j^2,(\lambda >0) J(w)=2m1i=1∑m(⟨w,x⟩−y(i))2+21λj=1∑nwj2,(λ>0)
w i w_i wi 的取值越大,线性模型越复杂,其中 ∑ j = 1 n w j 2 \sum_{j=1}^nw_j^2 ∑j=1nwj2 正是度量了线性模型的复杂度,为使代价函数 J ( w ) J({\bf w}) J(w) 更小,应使 λ ∑ j = 1 n w j 2 \lambda\sum_{j=1}^nw_j^2 λ∑j=1nwj2 尽量小,使 w ( w 0 , w 1 , w 2 , . . . , w n ) {\bf w}(w_0,w_1,w_2,...,w_n) w(w0,w1,w2,...,wn) 中的元素尽量小(接近零)
∂ ∂ w i J ( w ) = 1 m ∑ j = 1 m x i ( j ) ( ⟨ w , x ⟩ − y ( j ) ) + λ w i , ( λ > 0 ) \frac{\partial }{\partial w_i}J({\bf w})={1 \over m}\sum_{j=1}^mx^{(j)}_i\left(\langle {\bf w},{\bf x}\rangle-y^{(j)}\right)+\lambda w_i,(\lambda>0) ∂wi∂J(w)=m1j=1∑mxi(j)(⟨w,x⟩−y(j))+λwi,(λ>0)
J ( w ) = 1 2 m ∑ i = 1 m ( ⟨ w , x ⟩ − y ( i ) ) 2 + λ ∑ j = 1 n ∣ w j ∣ , ( λ > 0 ) J({\bf w})={1 \over 2m}\sum_{i=1}^m\left(\langle {\bf w},{\bf x}\rangle-y^{(i)}\right)^2+\lambda\sum_{j=1}^n\mid w_j\mid,(\lambda >0) J(w)=2m1i=1∑m(⟨w,x⟩−y(i))2+λj=1∑n∣wj∣,(λ>0)
在套索回归中,通过 ∑ j = 1 n ∣ w j ∣ \sum_{j=1}^n\mid w_j\mid ∑j=1n∣wj∣ L 1 L_1 L1 范数度量了线性模型的复杂度,为使代价函数 J ( w ) J({\bf w}) J(w) 更小,应使 λ ∑ j = 1 n ∣ w j ∣ \lambda\sum_{j=1}^n\mid w_j\mid λ∑j=1n∣wj∣ 尽量小,使 w ( w 0 , w 1 , w 2 , . . . , w n ) {\bf w}(w_0,w_1,w_2,...,w_n) w(w0,w1,w2,...,wn) 中的元素尽量小(接近零)
注: Lasso \text{Lasso} Lasso 回归的代价函数 J ( w ) J({\bf w}) J(w) 不是连续可导的(带绝对值),无法直接应用梯度下降
导入所需的库:
%matplotlib inline
import random # 用于随机梯度下降、随机初始化权重
import torch
首先,构造一个带有噪声的线性模型(人造数据集),我们使用线性模型参数 w = [ 2 − 3.4 ] T {\bf w}=\left[\begin{matrix}2&-3.4\end{matrix}\right]^T w=[2−3.4]T, b ( w 0 ) = 4.2 b(w_0)=4.2 b(w0)=4.2 和噪声项 ϵ \epsilon ϵ 生成数据集及其标签: y = x w + b + ϵ {\bf y}={\bf xw}+b+ϵ y=xw+b+ϵ
def synthetic_data(w, b, num_examples):
"""生成 y = xw + b + 噪声"""
X = torch.normal(0, 1, (num_examples, len(w))) # 生成服从 μ=0,σ=1 正态分布的随机值
y = torch.matmul(X, w) + b
y += torch.normal(0, 0.01, y.shape)
return X, y.reshape((-1, 1))
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)
print('features:', features[0:10], '\n','labels:', labels[0:10]) # 查看生成的结果
分析调用 features, labels = synthetic_data(true_w, true_b, 1000)
:
传入参数: w = [ 2 − 3.4 ] , b ( w 0 ) = 4.2 {\bf w}=\left[\begin{matrix}2&-3.4\end{matrix}\right],b(w_0)=4.2 w=[2−3.4],b(w0)=4.2, n u m _ e x a m p l e s = 1000 ( 样 本 数 ) {\tt num\_examples=1000}(样本数) num_examples=1000(样本数)
X = torch.normal(0, 1, (num_examples, len(w)))
生成了服从 μ = 0 , σ = 1 \mu=0,\sigma=1 μ=0,σ=1 正态分布的随机值张量,作为样本的输入:
X = [ x 1 ( 1 ) x 2 ( 1 ) x 1 ( 2 ) x 2 ( 2 ) ⋮ ⋮ x 1 ( 1000 ) x 2 ( 1000 ) ] 1000 × 2 , n u m _ e x a m p l e s = 1000 , l e n ( w ) = 2 {\bf X} =\left[\begin{matrix}x_1^{(1)}&x_2^{(1)}\\x_1^{(2)}&x_2^{(2)}\\\vdots&\vdots\\x_1^{(1000)}&x_2^{(1000)}\end{matrix}\right]_{1000×2},{\tt num\_examples=1000},{\tt len(w)=2} X=⎣⎢⎢⎢⎢⎡x1(1)x1(2)⋮x1(1000)x2(1)x2(2)⋮x2(1000)⎦⎥⎥⎥⎥⎤1000×2,num_examples=1000,len(w)=2
y = torch.matmul(X, w) + b
形成与 X X X 具有线性关系的因变量 y {\bf y} y,作为样本的输出,即:
y = ⟨ X , w ⟩ + b = [ x 1 ( 1 ) x 2 ( 1 ) x 1 ( 2 ) x 2 ( 2 ) ⋮ ⋮ x 1 ( 1000 ) x 2 ( 1000 ) ] [ 2 − 3.4 ] + b = [ 2 x 1 ( 1 ) − 3.4 x 2 ( 1 ) + b 2 x 1 ( 2 ) − 3.4 x 2 ( 2 ) + b ⋮ 2 x 1 ( 1000 ) − 3.4 x 2 ( 1000 ) + b ] 1000 × 1 = [ y ( 1 ) y ( 2 ) ⋮ y ( 1000 ) ] 1000 × 1 {\bf y} =\langle{\bf X},{\bf w}\rangle+b =\left[\begin{matrix}x_1^{(1)}&x_2^{(1)}\\x_1^{(2)}&x_2^{(2)}\\\vdots&\vdots\\x_1^{(1000)}&x_2^{(1000)}\end{matrix}\right]\left[\begin{matrix}2\\-3.4\end{matrix}\right]+b =\left[\begin{matrix}2x_1^{(1)}-3.4x_2^{(1)}+b\\2x_1^{(2)}-3.4x_2^{(2)}+b\\\vdots\\2x_1^{(1000)}-3.4x_2^{(1000)}+b\end{matrix}\right]_{1000×1}=\left[\begin{matrix}y^{(1)}\\y^{(2)}\\\vdots\\y^{(1000)}\end{matrix}\right]_{1000×1} y=⟨X,w⟩+b=⎣⎢⎢⎢⎢⎡x1(1)x1(2)⋮x1(1000)x2(1)x2(2)⋮x2(1000)⎦⎥⎥⎥⎥⎤[2−3.4]+b=⎣⎢⎢⎢⎢⎡2x1(1)−3.4x2(1)+b2x1(2)−3.4x2(2)+b⋮2x1(1000)−3.4x2(1000)+b⎦⎥⎥⎥⎥⎤1000×1=⎣⎢⎢⎢⎡y(1)y(2)⋮y(1000)⎦⎥⎥⎥⎤1000×1
注意:返回的是 s h a p e = ( 1000 ) \tt shape=(1000) shape=(1000) 的张量,需要进行 r e s h a p e ( − 1 , 1 ) \tt reshape(-1, 1) reshape(−1,1)
y += torch.normal(0, 0.01, y.shape)
分别为 y {\bf y} y 中的每一项加入了 μ = 0 , σ = 0.01 \mu=0,\sigma=0.01 μ=0,σ=0.01 的高斯白噪声
查看前十个数据集,每个样本包含一个二维 f e a t u r e s \tt features features: x ( x 1 , x 2 ) {\bf x(x_1,x_2)} x(x1,x2) 和一个输出值 l a b e l s \tt labels labels:
# 绘制散点图,进一步观察数据集,散点大小为 20,边界为黑色边界
plt.scatter(features[:, 0], labels, label = 'x1', s=20, edgecolor="black")
plt.scatter(features[:, 1], labels, label = 'x2', s=20, edgecolor="black")
"""
注意,有些版本需要从计算图上分离出来,并将tensor转换为numpy数组,才能绘图
plt.scatter(features[:, 0].detach().numpy(), labels.detach().numpy(), label = 'x1')
plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), label = 'x2')
"""
plt.legend()
def data_iter(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples))
random.shuffle(indices)
for i in range(0, num_examples, batch_size):
batch_indices = torch.tensor(indices[i:min(i + batch_size, num_examples)])
print('indices[{:},{:}]'.format(i, i + batch_size))
yield features[batch_indices], labels[batch_indices]
batch_size = 10 # 定义批量数大小
for X, y in data_iter(batch_size, features, labels):
print(X, '\n', y)
batch_size = 10
定义批量数大小为 10 10 10,则可以以 10 10 10 个样本为一组,分成 100 100 100 组,再随机抽样
分析调用 data_iter(batch_size, features, labels)
:
传入参数 b a t c h _ s i z e = 10 \tt batch\_size=10 batch_size=10,即每次小批量梯度下降的批量数为 10 10 10, f e a t u r e s : ( 1000 , 2 ) \tt features:(1000,2) features:(1000,2), l a b e l s : ( 1000 , 1 ) \tt labels:(1000,1) labels:(1000,1)
num_examples = len(features)
统计样本数,样本数为 1000 1000 1000indices = list(range(num_examples))
生成从 0 0 0 到 999 999 999 的顺序整数列表random.shuffle(indices)
将数组 indices
顺序随机打乱for i in range(0, num_examples, batch_size):
从 0 0 0 到 999 999 999 每隔 10 10 10 步取一个 i i ibatch_indices = torch.tensor(indices[i:min(i + batch_size, num_examples)])
中:indices[i:i + batch_size]
表示每次从乱序数组中取 10 10 10 个作为一个小批量;min(i + batch_size, num_examples)
是为了防止取数时超出乱序数组的右边界(这里似乎没必要);torch.tensor
将每次得到的 10 10 10 个随机数作为张量,如: t e n s o r ( [ 96 , 731 , 147 , 654 , 2 , 341 , 455 , 214 , 203 , 81 ] ) \tt tensor([96, 731, 147, 654, 2, 341, 455, 214, 203, 81]) tensor([96,731,147,654,2,341,455,214,203,81])features[batch_indices], labels[batch_indices]
,用张量在 features
和 labels
中可以分别取到 10 10 10 个样本yield
是 Python 的迭代器,是一种函数的返回值,每当函数运行到 yield
关键字时,都会返回一组数据,保留函数运行节点并退出函数运行,在下次调用时,将从上一保留的节点继续运行函数,可以多次调用该函数,得到不同的返回值利用 for
循环,会多次调用 data_iter()
函数,根据迭代器依次返回 100 100 100 组小批量样本,每个小批量为 10 10 10,这里仅展示一组
def linreg(X, w, b):
"""线性回归模型"""
return torch.matmul(X, w) + b
# 给定模型的超参数(初始值)
w = torch.normal(0, 0.01, size=(2, 1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
损失函数:
def squared_loss(y_hat, y):
"""使用均方误差损失函数"""
return (y_hat - y.reshape(y_hat.shape))**2 / 2
参数: y _ h a t \tt y\_hat y_hat、 y \tt y y 分别代表预测值 y ^ \hat y y^ 和真实值 y y y
y.reshape(y_hat.shape)
:虽然 y ^ \hat y y^ 和 y y y 可能都只是一个元素,但为了统一起见,将 y y y 统一形状优化算法:
def sgd(params, lr):
"""小批量随机梯度下降"""
with torch.no_grad():
for param in params:
param -= lr * param.grad
param.grad.zero_()
参数: p a r a m s \tt params params 代表一个包含 w {\bf w} w 和 b b b 的列表(即需要更新的参数), l r \tt lr lr 代表学习率
for param in params:
这里的运算将不放入计算图param -= lr * param.grad
即代表 w i = w i − ∂ ∂ w i J ( w 1 , w 2 , b ) w_i = w_i-\frac{\partial }{\partial w_i}J(w_1,w_2,b) wi=wi−∂wi∂J(w1,w2,b)param.grad.zero_()
梯度清零,这样下一次计算梯度的时候就不会和上一次计算相关了lr = 0.03 # 学习率为 0.03
num_epochs = 3 # 训练次数为 3
net = linreg # net 引用 linreg
loss = squared_loss # loss 引用 squared_loss
for epoch in range(num_epochs): # 训练轮次
for X, y in data_iter(batch_size, features, labels): # 当所有样本走完一次后,作为一次训练完成
l = loss(net(X, w, b), y) # 计算小批量的损失
(l.sum() / batch_size).backward() # 计算梯度
sgd([w, b], lr) # 梯度下降,更新参数
with torch.no_grad():
train_l = loss(net(features, w, b), labels) # 每个 epoch 打印一次损失值
print('epoch {:}, loss {:f}'.format(epoch + 1, float(train_l.mean())))
net(X, w, b)
返回值为:
r e t u r n = [ x 1 ( 1 ) x 2 ( 1 ) x 1 ( 2 ) x 2 ( 2 ) ⋮ ⋮ x 1 ( 10 ) x 2 ( 10 ) ] [ w 1 w 2 ] + b = [ w 1 x 1 ( 1 ) + w 2 x 2 ( 1 ) + b w 1 x 1 ( 2 ) + w 2 x 2 ( 2 ) + b ⋮ w 1 x 1 ( 10 ) + w 2 x 2 ( 10 ) + b ] = [ y ^ ( 1 ) y ^ ( 2 ) ⋮ y ^ ( 10 ) ] {\tt return} =\left[\begin{matrix}x_1^{(1)}&x_2^{(1)}\\x_1^{(2)}&x_2^{(2)}\\\vdots&\vdots\\x_1^{(10)}&x_2^{(10)}\end{matrix}\right] \left[\begin{matrix}w_1\\w_2\end{matrix}\right]+b =\left[\begin{matrix}w_1x_1^{(1)}+w_2x_2^{(1)}+b\\w_1x_1^{(2)}+w_2x_2^{(2)}+b\\\vdots\\w_1x_1^{(10)}+w_2x_2^{(10)}+b\end{matrix}\right] =\left[\begin{matrix}\hat y^{(1)}\\\hat y^{(2)}\\\vdots\\\hat y^{(10)}\end{matrix}\right] return=⎣⎢⎢⎢⎢⎡x1(1)x1(2)⋮x1(10)x2(1)x2(2)⋮x2(10)⎦⎥⎥⎥⎥⎤[w1w2]+b=⎣⎢⎢⎢⎢⎡w1x1(1)+w2x2(1)+bw1x1(2)+w2x2(2)+b⋮w1x1(10)+w2x2(10)+b⎦⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎡y^(1)y^(2)⋮y^(10)⎦⎥⎥⎥⎤
l = loss(net(X, w, b), y)
的返回值为:
r e t u r n = 1 2 ( [ y ^ ( 1 ) y ^ ( 2 ) ⋮ y ^ ( 10 ) ] − [ y ( 1 ) y ( 2 ) ⋮ y ( 10 ) ] ) 2 = [ l o s s ( 1 ) l o s s ( 2 ) ⋮ l o s s ( 10 ) ] {\tt return} ={1 \over 2}\left(\left[\begin{matrix}\hat y^{(1)}\\\hat y^{(2)}\\\vdots\\\hat y^{(10)}\end{matrix}\right]- \left[\begin{matrix}y^{(1)}\\y^{(2)}\\\vdots\\y^{(10)}\end{matrix}\right]\right)^2 =\left[\begin{matrix}loss^{(1)}\\loss^{(2)}\\\vdots\\loss^{(10)}\end{matrix}\right] return=21⎝⎜⎜⎜⎛⎣⎢⎢⎢⎡y^(1)y^(2)⋮y^(10)⎦⎥⎥⎥⎤−⎣⎢⎢⎢⎡y(1)y(2)⋮y(10)⎦⎥⎥⎥⎤⎠⎟⎟⎟⎞2=⎣⎢⎢⎢⎡loss(1)loss(2)⋮loss(10)⎦⎥⎥⎥⎤
由于 J ( w ) = 1 2 × 10 ∑ j = 1 10 ( y ( j ) ^ − y ( j ) ) 2 J({\bf w})={1 \over 2×10}\sum_{j=1}^{10}(\hat {y^{(j)}}-y^{(j)})^2 J(w)=2×101∑j=110(y(j)^−y(j))2,而这里是样本各自的损失,因此下一步需要先求和,除以 b a t c h _ s i z e \tt batch\_size batch_size,再求梯度
(l.sum() / batch_size).backward()
进行的操作是:
J ( w ) = ( [ l o s s ( 1 ) l o s s ( 2 ) ⋮ l o s s ( 10 ) ] . s u m ( ) ) 1 b a t c h _ s i z e = 1 2 × 10 ∑ j = 1 10 ( y ( j ) ^ − y ( j ) ) 2 J({\bf w})=\left(\left[\begin{matrix}loss^{(1)}\\loss^{(2)}\\\vdots\\loss^{(10)}\end{matrix}\right]{\tt .sum()}\right){1 \over {\tt batch\_size}}={1 \over 2×10}\sum_{j=1}^{10}(\hat {y^{(j)}}-y^{(j)})^2 J(w)=⎝⎜⎜⎜⎛⎣⎢⎢⎢⎡loss(1)loss(2)⋮loss(10)⎦⎥⎥⎥⎤.sum()⎠⎟⎟⎟⎞batch_size1=2×101j=1∑10(y(j)^−y(j))2
J ( w ) . b a c k w a r d ( ) ⟹ ∂ J ( w ) ∂ w i = [ ∂ J ( w ) ∂ w 1 ∂ J ( w ) ∂ w 2 ∂ J ( w ) ∂ b ] J({\bf w}){\tt .backward()}\implies\frac{\partial J({\bf w})}{\partial w_i} =\left[\begin{matrix}\frac{\partial J({\bf w})}{\partial w_1}\\\frac{\partial J({\bf w})}{\partial w_2}\\\frac{\partial J({\bf w})}{\partial b}\end{matrix}\right] J(w).backward()⟹∂wi∂J(w)=⎣⎢⎡∂w1∂J(w)∂w2∂J(w)∂b∂J(w)⎦⎥⎤
sgd([w, b], lr)
则实现了参数的更新
训练效果:
print('w的估计误差: {:}'.format(true_w - w.reshape(true_w.shape)))
print('b的估计误差: {:}'.format(true_b - b))
发现 w 1 w_1 w1、 w 2 w_2 w2 误差分别为 − 0.0002 -0.0002 −0.0002、 0.0002 0.0002 0.0002, b b b 的误差为 0.0002 0.0002 0.0002,误差较小
导入所需的库:
import torch.utils as data # 包含一些数据处理的模块
train_ids = data.TensorDataset(tuple) 将 数据data 和 标签label 张量进行打包,打包后通过
DataLoader()
函数获取数据
d a t a = [ x 1 ( 1 ) x 2 ( 1 ) ⋯ x n ( 1 ) x 1 ( 2 ) x 2 ( 2 ) ⋯ x n ( 2 ) ⋮ ⋮ ⋱ ⋮ x 1 ( i ) x 2 ( i ) ⋯ x n ( i ) ] i × n , l a b e l = [ y ( 1 ) y ( 2 ) ⋮ y ( i ) ] {\tt data}= \left[\begin{matrix}x_1^{(1)}&x_2^{(1)}&\cdots&x_n^{(1)}\\ x_1^{(2)}&x_2^{(2)}&\cdots&x_n^{(2)}\\ \vdots&\vdots&\ddots&\vdots\\ x_1^{(i)}&x_2^{(i)}&\cdots&x_n^{(i)}\end{matrix}\right]_{i×n}, {\tt label}= \left[\begin{matrix}y^{(1)}\\y^{(2)}\\\vdots\\y^{(i)}\end{matrix}\right] data=⎣⎢⎢⎢⎢⎡x1(1)x1(2)⋮x1(i)x2(1)x2(2)⋮x2(i)⋯⋯⋱⋯xn(1)xn(2)⋮xn(i)⎦⎥⎥⎥⎥⎤i×n,label=⎣⎢⎢⎢⎡y(1)y(2)⋮y(i)⎦⎥⎥⎥⎤
在维度上, d a t a {\tt data} data 的样本数 i i i 必须与 l a b e l {\tt label} label 的个数对应
data_iter = data.DataLoader(dataset=train_ids, batch_size, shuffle=False) 可以每次随机读取时取batch_size
个小批量数据
得到的data_iter
是一个对象,可以通过for...in...
来进行访问,且每个轮次访问的结果都是随机的
train_ids
从TensorDataset()
函数中返回的对象
batch_size
每次获取数据样本时的批量数
shuffle
决定是否打乱数据集,default=False
next(iter(data_iter)) 访问下一个批量
例如:
import torch.utils.data as data
import torch
train_data = torch.tensor([[1, 1, 1],
[2, 2, 2],
[3, 3, 3],
[4, 4, 4],
[5, 5, 5],
[6, 6, 6]])
train_label = torch.tensor([100, 200, 300, 400, 500, 600])
# 打包数据集
train_ids = data.TensorDataset(train_data, train_label)
# 返回的对象可通过切片来查看
print(train_ids[0:3])
print('=' * 100)
# 也可循环读取数据
for train_data, train_label in train_ids:
print(train_data, train_label)
print('=' * 100)
# DataLoader 获取数据
data_iter = data.DataLoader(dataset = train_ids, batch_size = 2, shuffle = True)
# 可以通过 for in 来遍历 data_iter,并且每次访问 data_iter 的结果都不一样
print('第一次访问:')
for x, y in data_iter:
print(x, y)
print('第二次访问:')
for x, y in data_iter:
print(x, y)
print('=' * 100)
# 通过调用 next(iter()) 可以获取下一个批量
for i in range(3):
print(next(iter(data_iter)))
导入所需的库:
import numpy as np
import torch
import torch.utils as data # 包含一些数据处理的模块
仍然以相同的方法生成一个人工数据集:
def synthetic_data(w, b, num_examples):
"""生成 y = xw + b + 噪声"""
X = torch.normal(0, 1, (num_examples, len(w))) # 生成服从 μ=0,σ=1 正态分布的随机值
y = torch.matmul(X, w) + b
y += torch.normal(0, 0.01, y.shape)
return X, y.reshape((-1, 1))
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)
通过 t o r c h . u t i l s . d a t a \tt torch.utils.data torch.utils.data 模块处理数据集
def load_array(data_arrays, batch_size, shuffle=True):
"""构造一个PyTorch数据迭代器"""
dataset = data.TensorDataset(*data_arrays) # *data_arrays 解包
return data.DataLoader(dataset, batch_size, shuffle = shuffle)
batch_size = 10
data_iter = load_array((features, labels), batch_size)
下面我们将使用 PyTorch 的神经网络 (Neural Network-NN) 库: t o r c h . n n \tt torch.nn torch.nn,我们以后再着重介绍这个库
# 导入相关库
import torch.nn as nn
# nn.Linear() 指的是线性层(全连接层),两个参数分别是输入节点个数和输出节点个数
# 2个节点表示有两个权重 w 和一个偏差 b,1个节点表示输出一个值
# nn.Sequential() 是一个容器,可以将一系列操作进行打包,方便复用
net = nn.Sequential(nn.Linear(2, 1))
# 初始化模型参数,weight 表示权重,bias 表示偏差
# net[0] 表示容器中的第一个操作
net[0].weight.data.normal_(0, 0.01) # 使用正态分布 normal_() 原地替换
net[0].bias.data.fill_(0) # 用全零原地替换 fill_(0)
定义代价函数,使用均方误差代价函数,定义优化算法:
loss = nn.MSELoss() # 引用均方误差代价函数
# 训练器:随机梯度下降,每次训练样本使用小批量,学习率为 0.03
# net.parameters() 获取容器中的所有参数:w1、w2、b
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
开始训练模型:
num_epochs = 3 # 训练次数为 3
for epoch in range(num_epochs):
for X, y in data_iter: # 获取小批量数据
l = loss(net(X), y) # 计算小批量的损失值
trainer.zero_grad() # 梯度清零
l.backward() # 损失函数反向传播求导
trainer.step() # 更新所有参数:w1、w2、b
l = loss(net(features), labels) # 一次训练结束,计算损失值
print('epoch {}, loss {:f}'.format(epoch + 1, l))
w = net[0].weight.data
print('w的估计误差:', true_w - w.reshape(true_w.shape))
b = net[0].bias.data
print('b的估计误差:', true_b - b)
参考资料:
[1]Search 动手学深度学习课程
[2]机器学习-第二章:回归.pdf,黄海广