小言_互联网的博客

【pytorch】深度学习 线性回归训练 参数w,b如何进行反向传播详解(附推导公式,证明及代码)

400人阅读  评论(0)

一、线性回归训练的代码与结果

(1)代码

import random

import numpy as np
import torch


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):
        j = torch.LongTensor(indices[i: min(i + batch_size, num_examples)])  # 最后一次可能不足一个batch
        yield features.index_select(0, j), labels.index_select(0, j)


def linreg(X, w, b):  # 本函数已保存在d2lzh包中方便以后使用
    return torch.mm(X, w) + b


def squared_loss(y_hat, y):  # 本函数已保存在pytorch_d2lzh包中方便以后使用
    return (y_hat - y.view(y_hat.size())) ** 2 / 2


def sgd(params, lr, batch_size):  # 本函数已保存在d2lzh_pytorch包中方便以后使用
    for param in params:
        param.data -= lr * param.grad / batch_size  # 注意这里更改param时用的param.data
        # print(param.grad.data)


if __name__ == '__main__':
    num_inputs = 2
    num_examples = 1000
    true_w = [2, -3.4]
    true_b = 4.2
    features = torch.randn(num_examples, num_inputs)
    labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
    labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()), dtype=torch.float)

    batch_size = 10
    w = torch.tensor(np.random.normal(0, 0.01, (num_inputs, 1)), dtype=torch.float)
    b = torch.zeros(1)
    w.requires_grad_(requires_grad=True)
    b.requires_grad_(requires_grad=True)
    lr = 0.01
    num_epochs = 5
    net = linreg
    loss = squared_loss

    for epoch in range(num_epochs):  # 训练模型一共需要num_epochs个迭代周期
        # 在每一个迭代周期中,会使用训练数据集中所有样本一次(假设样本数能够被批量大小整除)。X
        # 和y分别是小批量样本的特征和标签
        for X, y in data_iter(batch_size, features, labels):

            # with torch.no_grad():
                # # 测试w代码块
                # w1 = 0
                # for i in range(10):
                #     w1 += (X[i, 0] * w[0] + b - y[i]) * X[i, 0]
                # print('--------------')
                # print(w1)
                #
                # # 测试b代码块
                # b1 = 0
                # for i in range(10):
                #     b1 += ((X[i, 0] * w[0] + X[i, 1] * w[1]) + b - y[i])
                # print(b1)

            l = loss(net(X, w, b), y).sum()  # l是有关小批量X和y的损失
            l.backward()  # 小批量的损失对模型参数求梯度
            sgd([w, b], lr, batch_size)  # 使用小批量随机梯度下降迭代模型参数

            # 不要忘了梯度清零
            w.grad.data.zero_()
            b.grad.data.zero_()
        train_l = loss(net(features, w, b), labels)
        print('epoch %d, loss %f' % (epoch + 1, train_l.mean().item()))

(2)结果

epoch 1, loss 2.210815
epoch 2, loss 0.294341
epoch 3, loss 0.039363
epoch 4, loss 0.005334
epoch 5, loss 0.000763

二、w和b反向传播的计算过程

  • 在上述程序中,sgd([w, b], lr, batch_size)这一步中,代入sgd函数中有w.data -= Ir * w.grad / batch_sizeb.data -= Ir * b.grad / batch_size

下面详解如何计算w.grad和b.grad:

  • w是一个2X1的矩阵,设为 w 1 , w 2 w_{1},w_{2} 。b是一个标量。X在每一次循环中是一个10X2的矩阵,设为 X i j i = 1 , 2...10 ; j = 1 , 2 X_{ij},i=1,2...10;j=1,2 。y是一个1X10的矩阵,设为 y i , i = 1 , 2...10 y_{i},i=1,2...10

  • l = loss(net(X, w, b), y).sum()的计算公式:

  • l o s s loss
    l o s s = 1 2 ( y ^ y ) 2 = 1 2 [ ( X w + b ) y ] 2 loss = \frac{1}{2}*(\hat{y}-y)^{2} = \frac{1}{2}*[(Xw+b)-y]^{2}

  • 可得 l l
    l = i = 1 10 l o s s = 1 2 i = 1 10 [ ( X i w + b ) y i ] 2 l = \sum_{i=1}^{10}{loss} = \frac{1}{2}*\sum_{i=1}^{10}{[(X_{i}w+b)-y_{i}]^{2}}

  • 利用链式法则,计算 l o s s loss 关于 w 1 w_{1} 的偏微分:
    l o s s w 1 = l o s s y ^ y ^ w 1 = [ ( X w 1 + b ) y ] X \frac{\partial{loss}}{\partial{w_{1}}} = \frac{\partial{loss}}{\partial{\hat{y}}} \frac{\partial{\hat{y}}}{\partial{w_{1}}} = [(Xw_{1}+b)-y]X

  • 计算 l l 关于 w 1 w_{1} 的偏微分(其中 j = 1 j=1 ):
    l w 1 = l o s s w 1 = i = 1 10 [ ( X i j w 1 + b ) y i ] X i j \frac{\partial{l}}{\partial{w_{1}}} = \sum{\frac{\partial{loss}}{\partial{w_{1}}}} = \sum_{i=1}^{10}{[(X_{ij}w_{1}+b)-y_{i}]X_{ij}}

  • 关于 w 2 w_{2} 的偏微分计算同理。

  • 计算 l o s s loss 关于 b b 的偏微分:
    l o s s b = l o s s y ^ y ^ b = [ ( X w + b ) y ] \frac{\partial{loss}}{\partial{b}} = \frac{\partial{loss}}{\partial{\hat{y}}} \frac{\partial{\hat{y}}}{\partial{b}} = [(Xw+b)-y]

  • 计算 l l 关于 b b 的偏微分:
    l o s s b = l o s s b = i = 1 10 [ ( X i 1 w 1 + X i 2 w 2 + b ) y i ] \frac{\partial{loss}}{\partial{b}} = \sum{\frac{\partial{loss}}{\partial{b}}} = \sum_{i=1}^{10}{[(X_{i1}w_{1}+X_{i2}w_{2}+b)-y_{i}]}

三、对理论进行证明测试

  • 根据上面的理论,分别手动计算了 w 1 w_{1} b b 反向传播的值,与w1.grad,b.grad相互对照,看结果是否一致。

(1)相关代码块

with torch.no_grad():
    # 测试w代码块
    w1 = 0
    for i in range(10):
        w1 += (X[i, 0] * w[0] + b - y[i]) * X[i, 0]
    print('--------------')
    print(w1)

    # 测试b代码块
    b1 = 0
    for i in range(10):
        b1 += ((X[i, 0] * w[0] + X[i, 1] * w[1]) + b - y[i])
    print(b1)

(2)结果及分析

  • 结果解读:第一个和第二个tensor是 w 1 w_{1} b b 反向传播的值。第三个tensor是w1.grad和w2.grad。第四个tensor是b.grad的值。
  • 可以发现手动计算的 w 1 w_{1} b b 反向传播的值 7.8666 和 -60.6970 与w1.grad,b.grad的值7.8966,-60.6970一致( w 1 w_{1} 有一点差别可能是由于pytorch在计算 w 1 . g r a d w_{1}.grad 时加入了一些扰动),推导完全正确!可喜可乐。
--------------
tensor([7.8666])
tensor([-60.6970])
tensor([[ 7.8966],
        [51.1244]])
tensor([-60.6970])
--------------
tensor([-3.4083])
tensor([-12.8728])
tensor([[-3.3363],
        [ 8.8743]])
tensor([-12.8728])
--------------
tensor([-10.8057])
tensor([-40.0288])
tensor([[-10.7679],
        [ 48.8254]])
tensor([-40.0288])

(3)完整的测试代码

import random

import numpy as np
import torch


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):
        j = torch.LongTensor(indices[i: min(i + batch_size, num_examples)])  # 最后一次可能不足一个batch
        yield features.index_select(0, j), labels.index_select(0, j)


def linreg(X, w, b):  # 本函数已保存在d2lzh包中方便以后使用
    return torch.mm(X, w) + b


def squared_loss(y_hat, y):  # 本函数已保存在pytorch_d2lzh包中方便以后使用
    return (y_hat - y.view(y_hat.size())) ** 2 / 2


def sgd(params, lr, batch_size):  # 本函数已保存在d2lzh_pytorch包中方便以后使用
    for param in params:
        param.data -= lr * param.grad / batch_size  # 注意这里更改param时用的param.data
        print(param.grad.data)


if __name__ == '__main__':
    num_inputs = 2
    num_examples = 1000
    true_w = [2, -3.4]
    true_b = 4.2
    features = torch.randn(num_examples, num_inputs)
    labels = true_w[0] * features[:, 0] + true_w[1] * features[:, 1] + true_b
    labels += torch.tensor(np.random.normal(0, 0.01, size=labels.size()), dtype=torch.float)

    batch_size = 10
    w = torch.tensor(np.random.normal(0, 0.01, (num_inputs, 1)), dtype=torch.float)
    b = torch.zeros(1)
    w.requires_grad_(requires_grad=True)
    b.requires_grad_(requires_grad=True)
    lr = 0.01
    num_epochs = 5
    net = linreg
    loss = squared_loss

    for epoch in range(num_epochs):  # 训练模型一共需要num_epochs个迭代周期
        # 在每一个迭代周期中,会使用训练数据集中所有样本一次(假设样本数能够被批量大小整除)。X
        # 和y分别是小批量样本的特征和标签
        for X, y in data_iter(batch_size, features, labels):

            with torch.no_grad():
                # 测试w代码块
                w1 = 0
                for i in range(10):
                    w1 += (X[i, 0] * w[0] + b - y[i]) * X[i, 0]
                print('--------------')
                print(w1)

                # 测试b代码块
                b1 = 0
                for i in range(10):
                    b1 += ((X[i, 0] * w[0] + X[i, 1] * w[1]) + b - y[i])
                print(b1)

            l = loss(net(X, w, b), y).sum()  # l是有关小批量X和y的损失
            l.backward()  # 小批量的损失对模型参数求梯度
            sgd([w, b], lr, batch_size)  # 使用小批量随机梯度下降迭代模型参数

            # 不要忘了梯度清零
            w.grad.data.zero_()
            b.grad.data.zero_()
        train_l = loss(net(features, w, b), labels)
        print('epoch %d, loss %f' % (epoch + 1, train_l.mean().item()))

四、参考

CNN反向传播 - 文章对反向传播方向更新w和b的值讲解的很清晰


转载:https://blog.csdn.net/miracleoa/article/details/105616366
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场