代码里写得很详细,嘴对嘴级别的详细

'''
这段代码讲的监督学习里面的线性回归
故事背景
用一个人的身高、颜值、内涵、财富,来推断这个人谈过几次恋爱
'''

# 先导入需要的库
import random
import torch
import matplotlib.pyplot as plt

# 先创建w、b的初始值

#用身高、颜值、内涵、财富,来推断这个人谈过几次恋爱
# 所以数据的维度是4,身高、颜值、内涵、财富各一个维度
# 对应的w也是4个维度
true_w=torch.tensor([8.1,2,2,4])
# b只有一个维度,因为恋爱次数只有一个维度
true_b=torch.tensor(1.1)

# 设置样本数量
num=500

# 创建create_data函数,来生成训练集
def create_data(w,b,data_num):
    # x的维度是(data_num,len(w)),比如有500个,就是500个数据
    # 每一个数据是一个人,你可以看到这500个人的身高、颜值、内涵、财富各是多少
    #这些数据要服从正态分布,均值为0,方差为1
    x=torch.normal(mean=0,std=1,size=[data_num,len(w)])
    # 使用矩阵相乘matrix multiply,加上b求出y(这里的加上b用了广播的性质,因为维度不同)
    # x是(data_num,len(w)),x是500*4,w是[4]
    # w按照不同的情况,电脑会将[4]变成4*1或者1*4,所以这里是4*1,要满足矩阵乘法
    # y就是500*1,有500个人的数据,y是恋爱次数
    y=torch.matmul(x,w)+b

    # 要给样本加上噪音
    noise=torch.normal(mean=0,std=0.01,size=y.shape)
    y+=noise
    # x是500 * 4,y是500*1
    return x,y

# 调用create_data函数获得数据X,Y
X,Y=create_data(true_w, true_b, num)
'''
matplotlib只能看一个数据和另一个数据之间的关系
这个任务是用身高、颜值、内涵、财富,来推断这个人谈过几次恋爱
自变量是身高、颜值、内涵、财富,因变量是这个人谈过几次恋爱,自变量是4维的
因变量是1维的,我想用matplotlib画图看自变量和因变量之间的关系
但是matplot里面好像画不出来图,只能在4个变量中选一个出来
看这个变量和恋爱次数之间的关系,如果你硬要看,可以画四张图
看四个变量分别和因变量之间的关系,这里图方便,只选了一个自变量和因变量之间的关系
'''
# X是500 * 4,第一个维度是全选,500个人的都要,3是索引,只看第四个维度和因变量之间的关系
# x,y,s分别是自变量、因变量、点的尺寸
# plt.scatter(x=X[:,3],y=Y,s=1)
# plt.show()

batchsize=16

# 定义数据迭代器函数,一次可以获得一个batch的数据
# 就是torch.utils.data.DataLoader
# data是特征数据,就是自变量,label是标签数据,就是因变量,batchsize,批次大小
def data_provider(data,label,batchsize):
    # 获取标签的长度
    length=len(label)
    # 生成样本的索引列表
    indices=list(range(length))
    # 索引列表的顺序打乱,不能按照顺序取数据
    random.shuffle(indices)

    # 遍历数据
    # each的值是0,16,32,48....
    for each in range(0,length,batchsize):
        # 利用切片,获得数据
        # each=0时,切的是[0,16]
        # each = 16时,切的是[16, 32]。。。
        get_indices=indices[each:each+batchsize]
        # 使用打乱后的索引,只要有索引,就能获得数据
        get_data=data[get_indices]
        # 获得对应的标签
        get_label=label[get_indices]

        # 使用yield返回数据,调用一次函数就返回一次数据
        # 不用像return那样一次性返回完,占用内存
        yield get_data,get_label
        # yield的用法自己问gpt,eezz

# 康康数据迭代器能不能用
# for data,label in data_provider(X,Y,batchsize):
#     print(data,label)
#     # 就看一个batch,就16个
#     break

# 设置参数
# 学习率
lr=0.03
# 给定w和b的初始值
w_0=torch.normal(mean=0,std=0.1,size=true_w.shape,requires_grad=True)
b_0=torch.tensor(0.01,requires_grad=True)

# 设置训练轮数
epochs=50

# 定义前向传播函数
def fun(x,w,b):
    pred_y=torch.matmul(x,w)+b
    return pred_y

# 定义损失函数
def maeLoss(pred_y,y):
    # pred_y是预测出来的y是算出来的
    # y是真实的y,是标签值,是真实值
    # 求的是一个batch的平均损失
    return torch.sum(abs(y-pred_y))/len(y)

# 定义优化器,sgd,用的是梯度下降
# params是参数,lr是学习率
def sgd(paras,lr):
    with torch.no_grad():
        # 奶奶的,这句代码要重要写
        # 前面的代码用了require_grad=True
        # pytorch会死盯着w和b的每一个操作,记录过程,方便后续计算梯度
        # 假设你是小卖部的老板
        # w是商品的单价,有no_grad时,你算完当天的利润(loss.backward()算梯度),发现可乐买贵了
        # 直接把单价从3变成2.8
        # 改账单的时候,不用写账本记录【我为啥改成2.8的种种原因】
        # 有no_grad时你要写因为什么原因,在哪里什么时候,把价格改成2.8
        # 有no_grad时,你只是直接修改w和b的值,不修改梯度,省内存
        # 这句话是禁止在修改参数的过程中,生成无用的新梯度
        for para in paras:
            # 参数更新公式:参数 = 参数 - 学习率 * 参数梯度(梯度下降核心公式)
            # 不能写成 para = para - para.grad*lr(会创建新张量,丢失梯度信息)
            para -= para.grad * lr
            # 参数梯度清零:避免下一次迭代时梯度累积(每次批次训练都需要重新计算梯度)
            # 每次都要梯度清零,因为上一次的梯度会影响下次的梯度
            # pytorch中梯度张量活人会进行累加
            # 若不手动清零,下一轮新的梯度会和上一轮的旧梯度叠加在一起
            # 导致参数更新方向错误,模型无法正常训练
            para.grad.zero_()

# 开始训练
for epoch in range(epochs):
    data_loss=0
    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.backward()
        # 使用优化器,更新参数
        sgd([w_0,b_0],lr)
        # 计算损失
        data_loss+=loss.item()
    # 打印当前轮数和对应的总损失:%03d表示3位数字格式化,%.6f表示保留6位小数
    print("epoch %03d: loss: %.6f" % (epoch, data_loss))

# 训练结束后,打印真实的权重和偏置
print("真实的函数值是", true_w, true_b)
# 打印训练得到的权重和偏置(已接近真实值)
print("训练得到的参数值是", w_0, b_0)

# 选择第4个特征(索引3)进行可视化对比
idx = 3
# 绘制训练得到的线性模型:需要将张量转换为numpy数组(detach()分离计算图,避免梯度跟踪)
plt.plot(X[:, idx].detach().numpy(), X[:, idx].detach().numpy()*w_0[idx].detach().numpy()+b_0.detach().numpy())
# 绘制原始数据的散点图
plt.scatter(X[:, idx], Y, 1)
# 显示对比图像(散点为原始数据,直线为训练得到的模型)
plt.show()

更多推荐