第2节下:线性表示代码带写【带注释】

第2节下:线性表示代码带写【带注释】_第1张图片

第2节下:线性表示代码带写【带注释】_第2张图片

import torch
import matplotlib.pyplot as plt  # 画图用的
import random  # 随机


# 生成数据,w表示权重,是一维向量(张量),b表示偏置值,是一个标量
def create_data(w, b, data_num):
    # torch.normal是用于生成正态分布随机数的函数,它的作用是从指定的正态分布中生成张量,其中每个元素都是从该正态分布中采样的随机数。
    # 语法:torch.normal(均值mean, 标准差std, 形状size)
    x = torch.normal(0, 1, (data_num, len(w)))
    y = torch.matmul(x, w) + b  # 矩阵乘法

    noise = torch.normal(0, 0.01, y.shape)
    y += noise
    # 生成了一个和y形状相同的噪声张量noise,噪声来自于均值为0、标准差为0.01的正态分布,然后将噪声加到y上

    return x, y


num = 500

true_w = torch.tensor([8.1, 2, 2, 4])
true_b = torch.tensor(1.1)  # 进行张量计算必须把b也转化为张量

X, Y = create_data(true_w, true_b, num)


# 取数函数,用来控制每次只取一批大小为 batchsize 的数据,并不是直接把全部 data_num 个数据全部参与计算,避免一次性加载所有数据到内存中
def data_provider(data, label, batchsize):
    length = len(data)  # 获取数据总数
    indices = list(range(length))  # 生成一个内容为从0到length的列表,用作后续随机取数的索引(下标)
    # 一般情况下不按顺序取数据,要随机取!
    random.shuffle(indices)  # 打乱了indices数组,这样后面get_indicts取到的就是batchsize个打乱的数,再根据get_indicts取数就实现了随机取数

    for each in range(0, length, batchsize):  # for循环,从0到length取数(取的数即each),每次步长为batchsize
        get_indicts = indices[each: each + batchsize]  # 取出each到each+batchsize的索引,确保了步长依然为batchsize
        get_date = data[get_indicts]  # 根据索引从data中取出一批数据
        get_label = label[get_indicts]  # 根据索引从label中取出一批标签。
        # label是整个数据集的标签,是我们希望模型预测的内容,即目标值。例如在分类问题中,label是类别的标签(如用0或1分别表示类别A和B),在本例中即为y的值

        yield get_date, get_label  # 有存档点的return
        # 对于return语句来说,for循环执行到return就直接退出循环了,想在实现循环的同时每趟都返回数据,就使用yield


# 前向传播(预测),即定义模型,计算模型的预测值
def fun(x, w, b):
    pred_y = torch.matmul(x, w) + b
    return pred_y


# 计算MAE(均绝对误差)损失,即预测值与真实值之间的平均绝对值差
def maeLoss(pred_y, y):
    return torch.sum(abs(pred_y - y)) / len(y)


# 随机梯度下降,即SGD,用于更新模型参数 【更新参数是通过梯度下降法来实现的】
# 梯度是一个由偏导数组成的向量,表示多变量函数在每个方向上的变化速率
# 更新参数时,只需使用已计算的梯度(即para.grad)来更新参数,而不再需要重复计算梯度
# 实际训练时在sgd之前通过loss.backward()得到计算好的梯度,且梯度的结果是保存在参数内部的,sgd时直接调用即可
def sgd(paras, lr):
    with torch.no_grad():  # 属于这句代码的部分,不计算梯度(因为张量网上的全部计算都会积攒梯度,但不是所有梯度都是想要的)
        for para in paras:
            para -= para.grad * lr  # 更新参数/梯度下降的公式:参数=参数-梯度*学习率lr
            para.grad.zero_()  # 使用过的梯度(即para.grad)归0


# 开始训练
lr = 0.03  # 如果打印结果中loss下降速度太慢,可以加大学习率
w_0 = torch.normal(0, 0.01, true_w.shape, requires_grad=True)  # 这个w需要计算梯度
b_0 = torch.tensor(0.01, requires_grad=True)
# 先随便设置w和b的初始值,且权重初始值不为零,避免模型对所有输入做相同的预测

epochs = 50  # 训练轮数
batchsize = 16  # 一批数据有多少个

for epoch in range(epochs):
    data_loss = 0  # 每一轮初始loss归零
    for batch_x, batch_y in data_provider(X, Y, batchsize):
        pred_y = fun(batch_x, w_0, b_0)  # 调用前面的模型计算预测值
        loss = maeLoss(pred_y, batch_y)  # 调用前面的函数计算loss值
        loss.backward()  # 反向传播,封装了计算每个参数的梯度的过程,执行的结果就是得到了每个参数的梯度,并将这些梯度存储在每个参数的.grad属性中
        sgd([w_0, b_0], lr)  # 调用前面的函数进行sgd,更新参数w和b的值
        data_loss += loss  # 累计每一轮的loss总和
    print("epoch %03d: loss: %.6f" % (epoch, data_loss))
    # 后半部分括号里的内容会对应打印到前面的位置。%03d:格式化整数epoch,确保显示时是3位数; %.6f:格式化浮点数data_loss,显示6位小数

print("真实的函数值是:", true_w, true_b)
print("训练得到的函数值是:", w_0, b_0)

# 画图
# 没法画出来四个x对于y的函数,只有一对一地画图,所以需要切片取某一列x
# 第一个参数为横坐标:例如X[:, 0]表示对矩阵X切片,取全部行+第0列,即每行第一个值,即所有样本的第一个特征
# 第二个参数为纵坐标Y,第三个参数为散点图中每个点的大小,1表示非常小的点
idx = 0
plt.scatter(X[:, idx], Y, 1)  # 画散点图
plt.plot(X[:, idx].detach().numpy(), X[:, idx].detach().numpy() * w_0[idx].detach().numpy() + b_0.detach().numpy())
# 画线性图,依然是(横坐标,纵坐标),纵坐标即wx+b
# 但应注意数据在张量网上的时候不能画图,所以还需要取下来【.detach().numpy()】
plt.show()

你可能感兴趣的:(深度学习自学记录,深度学习,python)