#用于:数据处理、模型、训练、可视化。 
#对于y=x*w+b模型,对于已知的x和对应y,利用深度学习,去猜测w和b的值。
import torch  #张量计算、自动算梯度
import matplotlib.pyplot as plt  # 画图的

import random  // 生成随机数


----------数据处理-----------------


def create_data(w, b, data_num):  # 生成数据。对于y=x*w+b线性式子,生成x和对应的y的数据,用于训练和检测。
    x = torch.normal(0, 1, (data_num, len(w)))   #生成正态分布的随机数。参数分别为:均值、标准差、尺寸(是个组元 数据量x每组数据参数量)
    y = torch.matmul(x, w) + b  # matmul表示tensor矩阵相乘,进行x*w+b的运算,注意顺序。

    noise = torch.normal(0, 0.01, y.shape)  # 噪声要加到y上。同样用normal函数生成数据,尺寸是y的形状,注意以0为均值,方差非常小。
    y += noise

    return x, y #同时返回x和y

#设定真实的w和b,生成的数据量是500,生成一笔真实X,Y数据
num = 500  
true_w = torch.tensor([8.1, 2, 2, 4])  
true_b = torch.tensor(1.1)
X, Y = create_data(true_w, true_b, num) 

#创建并显示图形
plt.scatter(X[:, 3], Y, 1)  # 创建散点图的函数,用于展示两个变量之间的关系. x[:,3]:表示取全部行,3表示取第三列,取x第三列和y关系作图,1为点的大小
plt.show()


def data_provider(data, label, batchsize):  # 每次访问这个函数, 就能提供一批数据
    length = len(label)  //len获取label的数据长度,在这里对应总数据数量
    indices = list(range(length))   #获取一串0~num-1的的数字列表,作为数据下标
    random.shuffle(indices)  #我不能按顺序取,用random.shuffle把数据打乱

    for each in range(0, length, batchsize):  #batchsize为步长,每次取batchsize的数据数量
        get_indices = indices[each: each + batchsize] #在indices中,取下标为each~each+batchsize的数据,即取一组大小为batchsize的乱序数组下标
        get_data = data[get_indices] 取X的下标为get_indices列表为下标的数据
        get_label = label[get_indices] 取对应的Y数据

        yield get_data, get_label  # 有存档点的return 。yield 的核心作用是将一个普通函数转变为生成器函数。生成器是一种特殊的迭代器,它可以逐个地生成值,而不是一次性生成所有值并存储在内存中,这在处理大量数据时能显著节省内存。直到遇到 yield 语句,此时函数会暂停执行,并将 yield 后面的值返回。下次再对生成器进行迭代时,函数会从上次暂停的位置继续执行,直到再次遇到 yield 或函数结束。


batchsize = 16


# for batch_x, batch_y in data_provider(X, Y, batchsize):
#     print(batch_x, batch_y)
#     break

---------------模型-------------

def fun(x, w, b):  #构造模型,并由给定X生成对应预测的Y
    pred_y = torch.matmul(x, w) + b
    return pred_y


----------------训练-------------

def maeLoss(pre_y, y):  #计算损失loss的平均值
    return torch.sum(abs(pre_y - y)) / len(y)  


def sgd(paras, lr):  # 随机梯度下降,更新参数
    with torch.no_grad():  # 属于这句代码的部分,不计算梯度。如果不使用 torch.no_grad(),在更新参数的代码块中对参数的操作可能会被错误地记录为计算梯度的一部分,导致梯度计算错误。
        for para in paras:  #paras 就是 [w_0, b_0] 这个列表.第一次迭代时,para 代表 w_0.第二次迭代时,para 代表 b_0
            para -= para.grad * lr  # 不能写成   para = para - para.grad*lr , 进行梯度下降计算: 梯度para.grad 乘上 学习率lr
            para.grad.zero_()  # 使用过的梯度,归0

#初始的lr,预测的初始w_0、b_0
lr = 0.03  #lr过大,梯度下降太快就会:无法收敛、错过最优解、容易不稳定。 lr过小,就会:收敛太慢了、陷入局部最优解、容易受噪声影响
w_0 = torch.normal(0, 0.01, true_w.shape, requires_grad=True)  # 这个w需要计算梯度,需要标记requires_grad=true。 直接利用true-w的shape,
b_0 = torch.tensor(0.01, requires_grad=True)
print(w_0, b_0)

#训练50回
epochs = 50
for epoch in range(epochs):
    data_loss = 0  #初始loss
    for batch_x, batch_y in data_provider(X, Y, batchsize): #利用yield在函数provider中不断地取出一批数据,直到provider里的for执行结束
        pred_y = fun(batch_x, w_0, b_0) #利用取出的batch_x生成一笔预测对的y
        loss = maeLoss(pred_y, batch_y) #计算loss
        loss.backward() #实现反向传播和自动求导。从损失张量开始,沿着计算图反向传播,根据链式法则自动计算损失函数关于所有需要计算梯度的张量(即 requires_grad=True 的张量)的梯度。这些梯度会被存储在相应张量的 grad 属性中,后续可以被优化器用于参数更新。
        sgd([w_0, b_0], lr) #梯度进行下降
        data_loss += 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()) #detach()之后才能生成图。 由X的第三列数据和Y的关系生成折线图
plt.scatter(X[:, idx], Y, 1) #生散点图
plt.show()
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐