PyTorch深度学习(二):线性回归和实现

PyTorch深度学习(二):线性回归和实现


一、线性回归概述

什么是回归问题

  • 有监督学习分为两类,即分类和回归问题,预测某一事物属于哪一类别,属于分类问题,如:猫狗分类;而当需要预测的内容是一个连续的值,属于回归问题,如:预测房价

什么是线性回归

  • 线性回归是回归问题的一种,当输出值与输入值之间满足线性关系时,即满足方程: 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),称为线性回归
  • 在二维平面中,它表现为一条直线,在多维空间中,它表现为一个超平面
  • 在构建这个超平面时,我们需要使预测值与真实值之间的误差最小化
    在这里插入图片描述

符号约定:

符号含义
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.2318118y(2)=37000x2(2)=31x3(2)=8

线性模型可以看作是一个单层神经网络(单层全连接层):
在这里插入图片描述


二、构建线性模型和优化算法(Optimal)

构建线性模型的核心问题是找到: 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=m1i=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)=2m1i=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(m1i=1m(y(i)y(i)^)2),那么,该如何寻找这一组 w {\bf w} w 呢?

1.最小二乘法(LSM)

很显然,代价函数 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)=2m1i=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  w0J(w0,w1)=m1i=1m(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  w1J(w0,w1)=m1i=1mx(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=1m(y(i)w1x(i))w1=i=1n(x(i))2m1(i=1mx(i))2i=1my(i)(x(i)x)

2.梯度下降

梯度: 它是一个矢量,对一元函数来说,梯度就是函数的导数;对多元函数来说,多元函数 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αwiJ(w0,w1,...,wn)for i=0 to i=n

  • w i {w_i} wi:表示对 w w w 中的第 i i i 个自变量进行更新,在一个循环内,所有对 w i w_i wi 的更新必须是同步的
    注: w i w_i wi 值不是立刻更新,而是当计算完所有 w i w_i wi 的更新值后再同时统一赋值,或者说计算和更新的过程都是同步的
  • α \alpha α:表示学习率,它反映了每次更新 w i w_i wi 值的跨度率
  • ∂ ∂ w i J ( w 0 , w 1 , . . . , w n ) \frac{\partial }{\partial w_i}J(w_0,w_1,...,w_n) wiJ(w0,w1,...,wn):表示代价函数对 w w w 中的 w i w_i wi 进行偏微分
  • α ∂ ∂ w i J ( w 0 , w 1 , . . . , w n ) \alpha\frac{\partial }{\partial w_i}J(w_0,w_1,...,w_n) αwiJ(w0,w1,...,wn):表示每次更新的步长
  • 因为梯度下降需要确定在代价函数中的某一个点处开始执行,因此需要给定一初始值,通常取 w 0 , w 1 , . . . , w n = 0 w_0,w_1,...,w_n=0 w0,w1,...,wn=0 开始

上述公式表明:

  • 如果代价函数对 w i w_i wi 的偏微分小于零,表示沿着 w i w_i wi 的正方向会使代价函数变小,因此我们可以选择增大 w i w_i wi 的值,具体增大 − α ∂ ∂ w i J ( w 0 , w 1 , . . . , w n ) -\alpha\frac{\partial }{\partial w_i}J(w_0,w_1,...,w_n) αwiJ(w0,w1,...,wn)
  • 如果代价函数对 ω i \omega_i ωi 的偏微分大于零,表示沿着 ω i \omega_i ωi 的正方向会使代价函数变大,因此我们可以选择减小 w i w_i wi 的值,具体减小 α ∂ ∂ w i J ( w 0 , w 1 , . . . , w n ) \alpha\frac{\partial }{\partial w_i}J(w_0,w_1,...,w_n) αwiJ(w0,w1,...,wn)
  • 如果代价函数对 w i w_i wi 的偏微分等于零,则修正项为零,则不需要修改 w i w_i wi 的值
  • 现在我们讨论学习率 α \alpha α:如果 α \alpha α 太高可能导致无法找到合适解(左图),如果 α \alpha α 太低可能导致学习速度过于缓慢(右图)
    在这里插入图片描述在这里插入图片描述

梯度下降法具有三种模式,即批量梯度下降、随机梯度下降和小批量梯度下降

2.1 批量梯度下降(BGD)

批量梯度下降在计算代价函数的梯度时,考虑了所有的训练样本,代价函数: 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)=2m1j=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=1mxi(j)(y(j)^y(j))=wiαm1j=1mxi(j)(w,xy(j))

特点:

  • 计算梯度的每一步都基于完整训练集
  • 训练集规模庞大时算法速度很慢
  • 特征数量多时,比标准方程快得多
  • 适合样本数规模适中,特征规模很大的数据集

2.2 随机梯度下降(SGD)

随机梯度下降每一次更新 ω \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,xy(j))

特点:

  • 计算速度更快
  • 成本函数的下降变得不规则,但总体是下降的
  • 最终会接近最小值(还会反弹)

批量梯度下降(左图),随机梯度下降(右图):
在这里插入图片描述 在这里插入图片描述

2.3 小批量随机梯度下降(MBGD)

小批量梯度下降结合了(一)(二)各自的优点,既不使用所有样本,也不使用单个样本,而是随机选择小批量样本

每次计算代价函数时使用 b b b 个样本,其中 k k k 表示第 k ( k < m ) k(k<m) k(k<m) 个样本:
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=1bxi(k)(y(k)^y(k))=wiαb1j=1bxi(k)(w,xy(k))

  • b = 1 b=1 b=1(随机梯度下降,SGD)
  • b = m b=m b=m(批量梯度下降,BGD)
  • b = batch_size b=\text{\tt batch\_size} b=batch_size,通常是 2 \tt 2 2 的指数倍,常见的有 32 , 64 , 128 \tt 32,64,128 32,64,128 等(小批量梯度下降,MBGD)

其性能介于随机和批量之间,其中,选择的批量数不能太小,这样会导致每次计算量太小,不适合并行来最大利用计算资源,批量数也不能太大,这样会导致内存消耗增加,浪费计算

梯度下降存在的缺陷:

  • 缺陷1:可能只找到局部最优解
    解决方法:多次实验,并随机设置初始点(超参数),线性回归的 MSE \text{MSE} MSE 代价函数只有一个最小值
  • 缺陷2:对属性尺度敏感,即在属性尺度大的维度上下降得更快,尺度小的维度上下降缓慢
    解决方法:属性缩放

属性缩放:

(一)归一化(最大-最小规范化):
x ′ = x − x m i n x m a x − x m i n x'={x-x_{min} \over x_{max}-x_{min}} x=xmaxxminxxmin

将数据映射到 [ 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=1m(x(i))2μ=m1i=1mx(i)

处理后的数据均值为 0 0 0,方差为 1 1 1,数据标准化为了不同特征之间具备可比性,经过标准化变换之后的特征数据分布没有发生改变

3.正则化

当特征不足或者现有特征与样本标签的相关性不强时,模型容易出现欠拟合,通过挖掘组合特征等新的特征,往往能够取得更好的效果

简单模型的学习能力较差,通过增加模型的复杂度可以使模型拥有更强的拟合能力,如:在线性模型中添加高次项,使得线性模型变为曲线,又比如在神经网络模型中增加网络层数或神经元个数等,但模型过于复杂容易出现过拟合

而正则化就是是用来防止过拟合的,在经验风险上加上与模型复杂度有关的正则化项或惩罚项,但当模型出现欠拟合现象时,则需要有针对性地减小正则化系数
在这里插入图片描述
线性回归模型的两种正则化项:

3.1 岭回归(Ridge Regression)—— L 2 L_2 L2范数正则化

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=1m(w,xy(i))2+21λj=1nwj2(λ>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) wiJ(w)=m1j=1mxi(j)(w,xy(j))+λwi(λ>0)

3.2 套索回归(Lasso Regression)—— L 1 L_1 L1范数正则化

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=1m(w,xy(i))2+λj=1nwj(λ>0)

在套索回归中,通过 ∑ j = 1 n ∣ w j ∣ \sum_{j=1}^n\mid w_j\mid j=1nwj 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=1nwj 尽量小,使 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

1.制作人工数据集

首先,构造一个带有噪声的线性模型(人造数据集),我们使用线性模型参数 w = [ 2 − 3.4 ] T {\bf w}=\left[\begin{matrix}2&-3.4\end{matrix}\right]^T w=[23.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=[23.4]b(w0)=4.2 n u m _ e x a m p l e s = 1000 ( 样 本 数 ) {\tt num\_examples=1000}(样本数) num_examples=1000

  1. 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×2num_examples=1000len(w)=2

  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)[23.4]+b=2x1(1)3.4x2(1)+b2x1(2)3.4x2(2)+b2x1(1000)3.4x2(1000)+b1000×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)

  3. 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()

在这里插入图片描述

2.实现小批量随机抽样

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)

  1. num_examples = len(features) 统计样本数,样本数为 1000 1000 1000
  2. indices = list(range(num_examples)) 生成从 0 0 0 999 999 999 的顺序整数列表
  3. random.shuffle(indices) 将数组 indices 顺序随机打乱
  4. for i in range(0, num_examples, batch_size): 0 0 0 999 999 999 每隔 10 10 10 步取一个 i i i
  5. batch_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])
  6. features[batch_indices], labels[batch_indices],用张量在 featureslabels 中可以分别取到 10 10 10 个样本
  7. yield 是 Python 的迭代器,是一种函数的返回值,每当函数运行到 yield 关键字时,都会返回一组数据,保留函数运行节点并退出函数运行,在下次调用时,将从上一保留的节点继续运行函数,可以多次调用该函数,得到不同的返回值

在这里插入图片描述

利用 for 循环,会多次调用 data_iter() 函数,根据迭代器依次返回 100 100 100 组小批量样本,每个小批量为 10 10 10,这里仅展示一组

3.初始化模型

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)

4.定义损失函数和优化算法

损失函数:

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

  1. y.reshape(y_hat.shape):虽然 y ^ \hat y y^ y y y 可能都只是一个元素,但为了统一起见,将 y y y 统一形状
  2. 这里不进行求和,即不考虑 1 m ∑ i = 1 m {1 \over m}\sum_{i=1}^m m1i=1m

优化算法:

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 代表学习率

  1. for param in params: 这里的运算将不放入计算图
  2. 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=wiwiJ(w1,w2,b)
  3. param.grad.zero_() 梯度清零,这样下一次计算梯度的时候就不会和上一次计算相关了

5.训练过程

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)+bw1x1(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=21y^(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×101j=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=110(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()wiJ(w)=w1J(w)w2J(w)bJ(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,误差较小


四、利用PyTorch函数简洁实现线性回归

1.PyTorch数据集处理模块

导入所需的库:

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×nlabel=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, num_workers) 可以每次随机读取时取 batch_size 个小批量数据

得到的 data_iter 是一个对象,可以通过 for...in... 来进行访问,且每个轮次访问的结果都是随机的

train_idsTensorDataset() 函数中返回的对象
batch_size 每次获取数据样本时的批量数
shuffle 决定是否打乱数据集,default=False
num_workers 读取数据的进程个数,当数据量较大时,可以使用多个进程来读取数据

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)))

在这里插入图片描述

2.实现流程

导入所需的库:

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,黄海广

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值