PyTorch 深度学习笔记 第一章 线性回归

133 阅读9分钟

第一章 线性回归

1.回归问题概述

设有变量 x1,x2x_1,x_2

要求一组参数 θ\theta 使得平面 h(x)=θ0+θ1x1+θ2x2h(x) = \theta_0 + \theta_1 x_1 + \theta_2 x_2 尽可能过所有点 (x1,x2,y)(x_1,x_2,y)

2.误差项定义

y(x)=h(x)+εy(x) = h(x) + \varepsilon

每一个点的误差 ε\varepsilon 都是相互独立的,并且服从相同的分布函数

这个分布函数是均值为 0 方差为 θ2\theta^2 的高斯分布

3.独立同分布的意义

4.似然函数的作用

使用上标 (i)(i) 表示这是第 i 个数据:

y(i)=θx(i)+ε(i)...(1)y^{(i)} = \theta x^{(i)} + \varepsilon^{(i)} ...(1)

由于误差服从高斯分布 p(ε(i))=12πσexp((ε(i))22σ2)...(2)p(\varepsilon^{(i)}) = \dfrac{1}{\sqrt{2 \pi}\sigma}\exp{(-\dfrac{(\varepsilon^{(i)})^2}{2\sigma^2})} ...(2)

为了求解参数 θ\theta 将 (1) 代入 (2) 得:

p(y(i)x(i);θ)=12πσexp((y(i)θx(i))22σ2)p(y^{(i)} \vert x^{(i)};\theta) = \dfrac{1}{\sqrt{2 \pi}\sigma}\exp{(-\dfrac{(y^{(i)}-\theta x^{(i)})^2}{2\sigma^2})}

y(i)y^{(i)}θx(i)\theta x^{(i)} 接近时,这个概率会变大

因此将每一组数据的 p(y(i)x(i);θ)p(y^{(i)} \vert x^{(i)};\theta) 相乘,得到的值的大小可以用于衡量所有数据的 y(i)y^{(i)}θx(i)\theta x^{(i)} 之间接近的程度,这就是似然函数:

L(θ)=i=1mp(y(i)x(i);θ)=i=1m12πσexp((y(i)θx(i))22σ2)L(\theta) = \prod_{i = 1}^m p(y^{(i)} \vert x^{(i)};\theta) = \prod_{i = 1}^m \dfrac{1}{\sqrt{2 \pi}\sigma}\exp{(-\dfrac{(y^{(i)}-\theta x^{(i)})^2}{2\sigma^2})}

似然函数表示了什么样的参数与数据组合后为真实值

但是乘法比较难算,所以对这个连乘取对数化为加法:

logL(θ)=logi=1m12πσexp((y(i)θx(i))22σ2)=mlog12πσ1σ212i=1m(y(i)θx(i))2\log{L(\theta)} = \log\prod_{i = 1}^m \dfrac{1}{\sqrt{2 \pi}\sigma}\exp{(-\dfrac{(y^{(i)}-\theta x^{(i)})^2}{2\sigma^2})} \\ = m \log{\dfrac{1}{\sqrt{2 \pi}\sigma}} - \dfrac{1}{\sigma^2}\dfrac{1}{2}\sum_{i=1}^m(y^{(i)}-\theta x^{(i)})^2

可见 mlog12πσm \log{\dfrac{1}{\sqrt{2 \pi}\sigma}} 是一个常数项,所以只需要研究第二项,设第二项为一个新的函数

J(θ)=12i=1m(y(i)θx(i))2J(\theta) = \dfrac{1}{2}\sum_{i=1}^m(y^{(i)}-\theta x^{(i)})^2

问题转化为求解参数 θ\theta 使得 J(θ)J(\theta) 最小

这个方法也叫做最小二乘法


这是一种推导思路

我还看到别的教程也有一种思路是,直接由总误差 = 12i=1m(y(i)θx(i))2\dfrac{1}{2}\sum_{i=1}^m(y^{(i)}-\theta x^{(i)})^2 得出要研究的方程就是 J(θ)=12i=1m(y(i)θx(i))2J(\theta) = \dfrac{1}{2}\sum_{i=1}^m(y^{(i)}-\theta x^{(i)})^2

然后之后梯度下降也就这么直接来求导就好了

也是没问题的……就是说一开始从高斯分布开始的话还能知道 J(θ)J(\theta) 在统计上的意义,就是根据概率大小衡量数据与真实值的接近程度

5.正规方程

使用矩阵表达各个变量:

J(θ)=12i=1m(y(i)θx(i))2=12(Xθy)T(Xθy)J(\theta) = \dfrac{1}{2}\sum_{i=1}^m(y^{(i)}-\theta x^{(i)})^2 = \dfrac{1}{2}(X\theta-y)^T(X\theta-y)

要使 J(θ)J(\theta) 最小,也就是求 J(θ)J(\theta) 的最小值点

J(θ)J(\theta) 求偏导:

θJ(θ)=θ(12(Xθy)T(Xθy))=θ(12(θTXTyT)(Xθy))=12θ(θTXTXθθTXTyyTXθ+yTy)=12(2XTXθXTy(yTX)T)=XTXθXTy\nabla_{\theta}J(\theta) = \nabla_{\theta}(\dfrac{1}{2}(X\theta-y)^T(X\theta-y)) \\ = \nabla_{\theta}(\dfrac{1}{2}(\theta^TX^T-y^T)(X\theta-y)) \\ = \dfrac{1}{2}\nabla_{\theta}(\theta^TX^TX\theta - \theta^TX^Ty - y^TX\theta + y^Ty) \\ = \dfrac{1}{2}(2X^TX\theta-X^Ty - (y^TX)^T) \\ = X^TX\theta-X^Ty

令偏导数等于 0,得 XTXθXTy=0X^TX\theta-X^Ty= 0θ=(XTX)1XTy\theta = (X^TX)^{-1}X^Ty

J(θ)J(\theta) 能直接求极值点固然好,但是极值点公式 θ=(XTX)1XTy\theta = (X^TX)^{-1}X^Ty(XTX)1(X^TX)^{-1} 不一定存在,就是说正规方程不一定有解

因此要一步步试探到极值点,用梯度下降的方法,根本上是分治的思路


对矩阵求导的计算可以直接谷歌 The Matrix Cookbook

6.梯度下降

沿着梯度的反方向走

梯度下降不一定得到全局最优解

最简单的例子:

教程来源:www.bilibili.com/video/BV1Y7…

代码:blog.csdn.net/bit452/cate…

数据 x y 用一个 y = w * x 拟合,使用梯度下降找 w

代码:

import matplotlib.pyplot as plt
 
# prepare the training set
x_data = [1.0, 2.0, 3.0]
y_data = [2.0, 4.0, 6.0]
 
# initial guess of weight 
w = 1.0
 
# define the model linear model y = w*x
def forward(x):
    return x*w
 
#define the cost function MSE 
def cost(xs, ys):
    cost = 0
    for x, y in zip(xs,ys):
        y_pred = forward(x)
        cost += (y_pred - y)**2
    return cost / len(xs)
 
# define the gradient function  gd
def gradient(xs,ys):
    grad = 0
    for x, y in zip(xs,ys):
        grad += 2*x*(x*w - y)
    return grad / len(xs)
 
epoch_list = []
cost_list = []
print('predict (before training)', 4, forward(4))
for epoch in range(100):
    cost_val = cost(x_data, y_data)
    grad_val = gradient(x_data, y_data)
    w-= 0.01 * grad_val  # 0.01 learning rate
    print('epoch:', epoch, 'w=', w, 'loss=', cost_val)
    epoch_list.append(epoch)
    cost_list.append(cost_val)
 
print('predict (after training)', 4, forward(4))
plt.plot(epoch_list,cost_list)
plt.ylabel('cost')
plt.xlabel('epoch')
plt.show() 

最简单,但是完整表示了梯度下降的过程,核心在:

    cost_val = cost(x_data, y_data) # 使用更新后的 w 计算预测值
    grad_val = gradient(x_data, y_data) # 计算梯度
    w-= 0.01 * grad_val  # 梯度下降

7.参数更新方法

新设一个损失函数:

J(θ)=12mi=1m(yiθxi)2J(\theta) = \dfrac{1}{2m}\sum_{i=1}^m(y^{i}-\theta x^{i})^2

1.批量梯度下降

J(θ)θj=1mi=1m(yiθxi)xji\dfrac{\partial{J(\theta)}}{\theta_j} = -\dfrac{1}{m}\sum_{i=1}^m(y^{i}-\theta x^{i})x_j^i

θj=θj+1mi=1m(yiθxi)xji\theta_j' = \theta_j + \dfrac{1}{m}\sum_{i=1}^m(y^{i}-\theta x^{i})x_j^i

容易得到最优解,但是由于每次考虑所有样本,速度很慢

2.随机梯度下降

θj=θj+(yiθxi)xji\theta_j' = \theta_j + (y^{i}-\theta x^{i})x_j^i

每次找一个样本,迭代速度快,但不一定每次都朝着收敛的方向

但是批量梯度下降可以用并行算 n 个梯度然后加起来,而随机梯度下降,每计算一个梯度都要依赖上一个梯度,就不能并行了

3.小批量梯度下降

θj=θjα110k=i9(ykθxk)xji\theta_j' = \theta_j -\alpha \dfrac{1}{10} \sum_{k=i}^9(y^{k}-\theta x^{k})x_j^i

批量梯度下降和随机梯度下降的折衷方法

每次更新选择一小部分数据来算,实用

一组数据称为一个 batch

或者把所有数据称为一个 Batch 的话,一组数据就叫一个 Mini-Batch


注意:每一次梯度下降是同步更新所有参数

以小批量梯度下降为例,正确的同步更新:

θ0=θ0α110k=i9(ykθxk)xji\theta_0' = \theta_0 -\alpha \dfrac{1}{10} \sum_{k=i}^9(y^{k}-\theta x^{k})x_j^i

θ1=θ1α110k=i9(ykθxk)xji\theta_1' = \theta_1 -\alpha \dfrac{1}{10} \sum_{k=i}^9(y^{k}-\theta x^{k})x_j^i

以此类推……

错误的异步更新:

θ0=θ0α110k=i9(ykθxk)xji\theta_0' = \theta_0 -\alpha \dfrac{1}{10} \sum_{k=i}^9(y^{k}-\theta x^{k})x_j^i

θ=(θ0,θ1,...)\theta' = (\theta_0',\theta_1,...)

θ1=θ1α110k=i9(ykθxk)xji\theta_1' = \theta_1 -\alpha \dfrac{1}{10} \sum_{k=i}^9(y^{k}-\theta' x^{k})x_j^i

以此类推……

其实这个“错误的异步更新”就是“随机梯度下降”

这里就是说确认在 batch 里批量梯度下降,不要和随机梯度下降搞混了

8.优化参数设置

batch 的选择可以优化

学习率:步长 可以优化

如果随着迭代次数的增加,损失函数发生波动或者反而变大,那么可能需要减小学习率

9.特征缩放

多个变量的度量不同,数字之间相差的大小也不同,如果可以将所有的特征变量缩放到大致相同范围,这样会减少梯度算法的迭代。

特征缩放不一定非要落到 [-1,1] 之间,只要数据足够接近就可以

X=XμσX' = \dfrac{X-\mu}{\sigma} 其中 μ\mu 为平均值 σ\sigma 是标准差

特征缩放了之后,参数也会跟着变化

不缩放:y=θ0+θ1x1+θ2x2+...+θnxny = \theta_0 + \theta_1 x_1 + \theta_2 x_2 + ... + \theta_n x_n

缩放:yμyσy=θ0+θ1x1μ1σ1+θ2x2μ2σ2+...+θnxnμnσn\dfrac{y-\mu_y}{\sigma_y} = \theta_0' + \theta_1' \dfrac{x_1-\mu_1}{\sigma_1} + \theta_2' \dfrac{x_2-\mu_2}{\sigma_2} + ... + \theta_n' \dfrac{x_n-\mu_n}{\sigma_n}

化为:y=[(θ0i=1nμiθiσi)σy+μy]+θ1σyσ1x1+θ2σyσ2x2+...+θnσyσnxny = [(\theta_0' - \sum_{i=1}^n \dfrac{\mu_i \theta_i'}{\sigma_i})\sigma_y+\mu_y] + \dfrac{\theta_1'\sigma_y}{\sigma_1}x_1 + \dfrac{\theta_2'\sigma_y}{\sigma_2}x_2 + ... + \dfrac{\theta_n'\sigma_y}{\sigma_n}x_n

所以

θ0=(θ0i=1nμiθiσi)σy+μy\theta_0 = (\theta_0' - \sum_{i=1}^n \dfrac{\mu_i \theta_i'}{\sigma_i})\sigma_y+\mu_y

θi=θiσyσi,i0\theta_i = \dfrac{\theta_i'\sigma_y}{\sigma_i}, i \ne 0

10.多项式回归

线性回归:y=θ0+θ1x1+θ2x2+...+θnxny = \theta_0 + \theta_1 x_1 + \theta_2 x_2 + ... + \theta_n x_n

多项式回归:y=θ0+θ1x1+θ2x12+...y = \theta_0 + \theta_1 x_1 + \theta_2 x_1^2 + ...

对于多项式回归,仍然照着线性回归的思路

对误差函数求导算梯度就行了

这个时候特征缩放就很重要了。假设 x1x_1 的范围为 (0,100),那么 x12x_1^2 的范围为 (0,10000),相差就很大了

但是使用特征缩放的话,对 x1,x12x_1,x_1^2 的缩放是不同的,最后能缩放到统一的范围

这个时候参数缩放的公式也推导也会跟线性回归时不同

11.梯度下降的问题

一是可能存在局部最优

一是可能存在鞍点,就是梯度 = 0 但不是极值

12.多层网络

图片来源:www.bilibili.com/video/BV1Y7…

简单的多层网络就是 y = w * x + b 的重复运用

W 权重 Weight

b 偏置 Bias

但是如果这么简单地堆叠,最终还是可以化简为一个线性回归式子

y=W2(W1x+b1)+b2=W2W1x+W2b1+b2=W3x+b3y = W_2(W_1 x + b_1)+ b_2 \\ = W_2W_1 x + W_2b_1 + b_2 \\ = W_3 x + b_3

所以对每层输出的结果都加一个非线性函数,就可以将各层区分开

13.反向传播

在输入 x 和 w 的时候,可以计算出 zx,zw\dfrac{\partial{z}}{\partial{x}},\dfrac{\partial{z}}{\partial{w}}

在返回 z 的时候,可以算出 Lz\dfrac{\partial{L}}{\partial{z}}

在计算梯度的时候,就可以根据链式法则算出 Lx=Lzzx,...\dfrac{\partial{L}}{\partial{x}} = \dfrac{\partial{L}}{\partial{z}}\dfrac{\partial{z}}{\partial{x}},...

如果是线性模型,z=wx+bz = wx + b,那么就有 zx=w,zw=x\dfrac{\partial{z}}{\partial{x}} = w,\dfrac{\partial{z}}{\partial{w}} = x

最后边一开始的梯度是怎么计算的呢,是因为把计算损失的过程也加入到了这个前向后向的一部分了

写模型的时候就是在构建这样一个计算图

例如 pytorch 的 tensor 类型包含两个成员,一个 data 一个 grad

14.pytorch 的使用

对 pytorch 的 tensor 变量进行原子运算的时候实际上是在构建计算图

调用 tensor 变量的 backward() 是计算一次梯度,计算的同时会清除这个变量的计算图,方便计算图在运行时变化

直接取标量使用 .item()

只进行数值计算而不构建计算图,可以对 tensor 变量的 .data 进行计算

最后参数 tensor 的梯度需要清零

有的时候可能需要梯度累加,所以并不能在 torch 里面写死自动清零

对之前的例子修改,使用 pytorch 重写得到:

import torch
x_data = [1.0, 2.0, 3.0]
y_data = [2.0, 4.0, 6.0]
 
w = torch.tensor([1.0]) # w的初值为1.0
w.requires_grad = True # 需要计算梯度
 
def forward(x):
    return x*w  # w是一个Tensor
 
 
def loss(x, y):
    y_pred = forward(x)
    return (y_pred - y)**2
 
print("predict (before training)", 4, forward(4).item())
 
for epoch in range(100):
    for x, y in zip(x_data, y_data):
        l =loss(x,y) # l是一个张量,tensor主要是在建立计算图 forward, compute the loss
        l.backward() #  backward,compute grad for Tensor whose requires_grad set to True
        print('\tgrad:', x, y, w.grad.item())
        w.data = w.data - 0.01 * w.grad.data   # 权重更新时,注意grad也是一个tensor
 
        w.grad.data.zero_() # after update, remember set the grad to zero
 
    print('progress:', epoch, l.item()) # 取出loss使用l.item,不要直接使用l(l是tensor会构建计算图)
 
print("predict (after training)", 4, forward(4).item())

再进一步,使用 pytorch 的模块来定义

模型继承 torch.nn.Module

损失函数是一个标量,直接用 .backward()

optimizer 自动取到本模型及其子类的 .parameters(),只需要对 optimizer 调用 .step() 就可以更新一次参数

import torch
# prepare dataset
# x,y是矩阵,3行1列 也就是说总共有3个数据,每个数据只有1个特征
x_data = torch.tensor([[1.0], [2.0], [3.0]])
y_data = torch.tensor([[2.0], [4.0], [6.0]])
 
#design model using class
"""
our model class should be inherit from nn.Module, which is base class for all neural network modules.
member methods __init__() and forward() have to be implemented
class nn.linear contain two member Tensors: weight and bias
class nn.Linear has implemented the magic method __call__(),which enable the instance of the class can
be called just like a function.Normally the forward() will be called 
"""
class LinearModel(torch.nn.Module):
    def __init__(self):
        super(LinearModel, self).__init__()
        # (1,1)是指输入x和输出y的特征维度,这里数据集中的x和y的特征都是1维的
        # 该线性层需要学习的参数是w和b  获取w/b的方式分别是~linear.weight/linear.bias
        self.linear = torch.nn.Linear(1, 1)
 
    def forward(self, x):
        y_pred = self.linear(x)
        return y_pred
 
model = LinearModel()
 
# construct loss and optimizer
# criterion = torch.nn.MSELoss(size_average = False)
criterion = torch.nn.MSELoss(reduction = 'sum')
optimizer = torch.optim.SGD(model.parameters(), lr = 0.01) # model.parameters()自动完成参数的初始化操作,这个地方我可能理解错了
 
# training cycle forward, backward, update
for epoch in range(100):
    y_pred = model(x_data) # forward:predict
    loss = criterion(y_pred, y_data) # forward: loss
    print(epoch, loss.item())
 
    optimizer.zero_grad() # the grad computer by .backward() will be accumulated. so before backward, remember set the grad to zero
    loss.backward() # backward: autograd,自动计算梯度
    optimizer.step() # update 参数,即更新w和b的值
 
print('w = ', model.linear.weight.item())
print('b = ', model.linear.bias.item())
 
x_test = torch.tensor([[4.0]])
y_test = model(x_test)
print('y_pred = ', y_test.data)

15.其他

15.1 PyCharm 使用

下载的话使用免费的 Community 版就好了

运行某 py 时可能会报错说没有导入某个包,这个时候去 File-Settings-Project:你的工程名-Python Interpreter,点击 + 号,就可以在弹出的界面选择你缺少的包并下载

有的时候下载完某个包之后 IDE 可能自动修改了你的 python 文件,给你多 import 一些包

比如我下载完 plotly 包之后,我的文件里多出一个

plotly.offline.init_notebook_mode()

然后他导致了一个 iplot can only run inside an IPython Notebook. 的错误,不知道是干嘛的,直接删了就好了

15.2 numpy 中 (n,1) 和 (n,) 的区别

stackoverflow.com/questions/2…

根据这个回答, 根本上的区别是存储方式的区别

个人感觉应该是 (n,1) 更好,因为别人的库都需要二维数组

15.3 pandas 中 df[column_name] 和 df[[column_name]] 的区别

假设 df 是一个 dataframe

df[column_name] 就是获取列名为 column_name 的这一列数据 大小为 (n,)

df[[column_name]] 也是获取列名为 column_name 的这一列数据 但大小为 (n,1)

df[] 外层的 [] 用于索引,造成这个区别的原因在于内层的东西

[] 内只传入一个 column_name 说明你只需要一列,所以 pandas 知道你只需要一列,所以返回 (n,)

但是如果传入一个列表,也就是传入一个 [a,b,c] 的话,pandas 就认为你需要多列,所以返回 (n,m) m 是你传入的列表中元素的个数

那么如果你传入一个 [column_name] 其实就是传入了一个列表,然后又因为列表中只有一个元素,所以 pandas 返回 (n,1)

15.4 pytorch 下载

在 pycharm 中下载的 pytorch 的包的名字叫做 torch,而不是 pytorch