手写一个简单的 线性回归模型

一、引入包

import torch
import matplotlib.pyplot as plt

import random

    二、创建数据函数

    def create_data(w, b, data_num):
        x = torch.normal(0, 1, (data_num, len(w)))  
        y = torch.matmul(x, w) + b
    
        noise = torch.normal(0, 0.01, y.shape)  # y 同型
        y += noise
    
        return x, y
    
    num = 500
    true_w = torch.tensor([0.1, 2, 2, 4])
    true_b = torch.tensor(1.1)
    
    X, Y = create_data(true_w, true_b, num)  # 创建数据 X为500x4 Y为500x1 的tensor类型数据
    
    # plt.scatter(X[:, 1], Y, 1)
    # plt.show()

    数据X使用torch生成服从N(0,1)的标准正态分布,这里规模用的是500x4。

    标签Y=X*w+b,x为500x4的矩阵,w为4x1的矩阵,所以y为500x1的矩阵,为了模仿真实世界的随机性,生成了一个与y同型的服从N(0,0.01)矩阵noise,然后加到y上。

    X,Y为实际数据

    三、打乱数据并按组取出函数

    # 每次访问提供 batchsize 个乱序数据
    def data_provider(data, label, batchsize):
        length = len(label)
        indices = list(range(length))
        random.shuffle(indices)     # 打乱数据
        for each in range(0, length, batchsize):
            get_indices = indices[each: each+batchsize]     # 取一组batchsize个乱序序号
            get_data = data[get_indices]
            get_label = label[get_indices]
    
            yield get_data, get_label
    
    batchsize = 16
    
    # for batch_x, batch_y in data_provider(X, Y, batchsize):
    #     print(batch_x, batch_y)
    #     break

    data就是X,label就是Y,batchsize=16

    获取Y的长度,然后生成一个[0,len(label))的数组indices,然后打乱这个数组,相当于打乱数据的序号。

    然后写一个for循环便利indices数组,步长为batchsize,每次返回一组16个数据和标签。

    四、一些其他函数

    def fun(x, w, b):
        perd_y = torch.matmul(x, w) + b
        return perd_y
    
    def maeLoss(pred_y, y):
        loss = torch.sum(abs(pred_y - y)) / len(y)
        return loss
    
    # 梯度下降, 反向传播不计算梯度
    def sgd(paras, lr):
        with torch.no_grad():   # 接下来不计算梯度
            for para in paras:
                para -= para.grad*lr
                para.grad.zero_()   # 把梯度归零

    fun函数会用于在训练时计算预测值

    maeLoss函数计算损失值,loss是一个标量

    sgd函数用于更新参数,lr会调节更新的幅度。并将梯度归零

    五、开始训练

    lr = 0.03
    w_0 = torch.normal(0, 0.01, true_w.shape, requires_grad=True)   # w_0需要被计算梯度
    b_0 = torch.tensor(0.01, requires_grad=True)
    epochs = 50  # 训练轮数
    
    for epoch in range(epochs):
        data_loss = 0
        for batch_x, batch_y in data_provider(X, Y, batchsize):
            pred = fun(batch_x, w_0, b_0)   # 前向传播
            loss = maeLoss(pred, batch_y)   # 计算损失
            loss.backward()                 # 反向传播计算梯度
            sgd([w_0, b_0], lr)             # 更新参数
    
            data_loss += loss
    
            print("epoch %03d: loss%.6f"%(epoch, data_loss))
    
    print("原来的函数值:", true_w, true_b)
    print((w_0, b_0))
    
    idx = 3
    plt.plot(X[:, idx].detach().numpy(), X[:, idx].detach().numpy()*w_0[idx].detach().numpy()+b_0.detach().numpy(), label="pred")
    plt.scatter(X[:, idx], Y, 1)
    plt.show()

    lr是学习率,决定了参数变化的大小,lr为超参数

    w_0是权重的初值,同样随机地设置为正态分布,注意一定要设置requires_grad=True(需要被计算梯度), 这样才会被识别为参数

    b_0为初始的偏置,同样requires_grad=True

    训练轮数为50轮,因为一轮训练可能不够找到最小的loss值

    然后写一个for循环迭代50轮,每一轮取16个数据,首先计算当前参数下的预测值pred,然后计算真实值和预测值的损失loss,然后计算loss的梯度并反向传播给w_0和b_0,最后用sgd更新参数

    下面是一些我个人产生过的疑问:

    1、为什么是计算loss的梯度?

    梯度是一个由偏导数组成的向量,它暗示了下降最快的方向,例如本例中:

    那么loss下降最快的方向就是w沿,b沿方向下降的情况。

    当然,这种普通的梯度下降法只能用于无约束条件下

    2、loss是一个标量,哪里来的梯度?

    loss 是标量,但它是参数 w_0 和 b_0 的函数,这并不冲突

    尽管计算结果是一个标量,但作为pytorch的一个类,在计算梯度时pytorch仍可以知道它的函数形式,可以正确地计算偏导数

    更多推荐