【深度学习笔记】linear代码案例
·
源代码
import torch
import matplotlib.pyplot as plt #画图的
import random #随机数
def creat_data(w,b,data_num): #写一个产生数据的函数
x=torch.normal(0,1,(data_num,len(w)))
y=torch.matmul(x,w)+b #matmul表示矩阵相乘
noise=torch.normal(0,0.01,y.shape)#噪声要加到y上
y+=noise
return x,y
num=500
true_w=torch.tensor([8.1,2,2,4])
true_b=torch.tensor(1.1)
X,Y=creat_data(true_w,true_b,num)
# plt.scatter(X[:,0],Y,1)
# plt.show()
def data_provider(data,label,batchsize): #每次访问这个函数,就能提供一批数据
length=len(data)
indices=list(range(length))
#我不能按顺序取, 把数据打乱
random.shuffle(indices)
for each in range(0,length,batchsize):
get_indices=indices[each:each+batchsize]
get_data=data[get_indices]
get_label=label[get_indices]
yield get_data,get_label #有存档点的return
batchsize=16
# for batch_x,batch_y in data_provider(X,Y,batchsize):
# print(batch_x,batch_y)
# break
def fun(x,w,b):
pred_y=torch.matmul(x,w)+b
return pred_y
def maeloss(pre_y,y):
return torch.sum(abs(pre_y-y))/len(y)
def sgd(paras,lr): #随机梯度下降
with torch.no_grad():#属于这句代码的部分,不计算梯度
for para in paras:
para-=para.grad*lr #不能写成 para=para-para.grad*lr
para.grad.zero_() #使用过的梯度归0
lr=0.03
w_0=torch.normal(0,0.01,true_w.shape,requires_grad=True)
b_0=torch.tensor(0.01,requires_grad=True)
print(w_0,b_0)
epochs=50
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
print("epoch %03d:loss:%.6f"%(epoch,data_loss))
print("真实的函数值是",true_w,true_b)
print("深度训练得到的参数值是",w_0,b_0)
idx=1
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()
代码解读
这段代码实现了一个完整的线性回归训练流程。简单来说,它先“假装”知道一个真实的函数( y=8.1x1+2x2+2x3+4x4+1.1y=8.1x1+2x2+2x3+4x4+1.1 ),生成一堆带噪声的数据点;然后让计算机“假装不知道”这个函数,通过梯度下降算法去学习和逼近这个函数。
以下是代码的逐段解读:
1. 数据生成 (creat_data)
这部分代码负责生成用于训练的数据集。
- 输入:权重向量
w、偏置b、数据数量data_num。 - 过程:
x = torch.normal(0, 1, (data_num, len(w))):生成 500 行数据,每行有 4 个特征(因为w的长度是 4),数据服从均值为 0、标准差为 1 的正态分布。y = torch.matmul(x, w) + b:计算真实的标签值(矩阵乘法)。noise = torch.normal(0, 0.01, y.shape):生成微小的随机噪声,模拟现实世界数据的不准确性。y += noise:将噪声加到标签上。
- 输出:特征矩阵
X和标签向量Y。
2. 数据加载器 (data_provider)
在训练模型时,我们通常不一次性把所有数据(500条)喂给模型,而是分成一小批一小批(例如每批 16 条),这叫做小批量梯度下降。
- 功能:打乱数据顺序(防止模型学习到顺序偏差),然后按设定的
batchsize(16)依次返回数据。 yield:这是一个生成器,每次调用它会返回一个批次的数据,用完即停,节省内存。
3. 模型与损失函数
- 模型 (
fun):这就是我们要学习的函数 f(x)=wx+bf(x)=wx+b 。非常简单,就是矩阵乘法加偏置。 - 损失函数 (
maeloss):这里使用的是平均绝对误差。它计算预测值和真实值之间差的绝对值的平均数。损失越小,说明模型预测得越准。
4. 优化器 (sgd - 随机梯度下降)
这是模型“学习”的核心步骤。
- 原理:利用 PyTorch 的自动求导机制(
.grad),计算出损失函数对权重w和偏置b的梯度。 - 更新:沿着梯度的反方向更新参数(
para -= para.grad * lr),目的是让损失变小。 with torch.no_grad():告诉 PyTorch 这部分操作不需要记录计算图(因为这是人为修改参数,不是模型前向传播的一部分)。- 梯度清零 (
para.grad.zero_()):非常重要!每次计算完梯度后必须清零,否则梯度会累积,导致更新方向错误。
5. 训练主循环
- 初始化:
w_0和b_0被随机初始化(requires_grad=True表示需要计算梯度)。 - 迭代:进行 50 轮(
epochs)训练。- 对每一批数据,先计算预测值
pred_y。 - 计算这一批数据的损失
loss。 - 调用
loss.backward()自动计算梯度。 - 调用
sgd()更新参数。 - 累加损失用于打印。
- 对每一批数据,先计算预测值
6. 结果可视化
idx=1
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()
- 目的:画出学习结果。
idx=1:选择第 2 个特征(索引从 0 开始)进行可视化。scatter:画出所有数据点(横轴是第 2 个特征的值,纵轴是真实标签 Y)。plot:画出模型学到的针对该特征的线性关系。- 注意:这里画的是 y=w2×x2+by=w2×x2+b 。虽然严格来说,完整的预测是 4 个特征共同作用的结果,但为了在 2D 图上展示,我们只取出了该特征的权重和偏置来画线。
detach().numpy():这是 PyTorch 的语法,用于将张量从计算图中分离并转换为 NumPy- 数组,以便用 Matplotlib 画图。
7.总结
为了方便记忆,可以把整个过程想象成一个工厂质检流水线:
- 数据接口 (
data_provider):
是送料员。他从仓库(X,Y)里随机抓一把零件(batchsize),送到工作台。 - 模型 (
fun):
是工人。他拿着当前的工具(w_0,b_0)去加工零件,产出一个预测结果。 - 损失函数 (
maeloss):
是质检员。他对比工人的成品和标准样品,算出一个差错值(loss)。 - 自动求导 (
backward):
是复盘分析。质检员分析刚才的差错,推导出是哪个工具(w或b)出了问题,以及问题有多大(grad)。 - SGD (
sgd):
是修理工。他根据分析结果,把工具往正确的方向拧一下(-= grad * lr),并且把分析报告清空(zero_),准备下一轮质检。
代码中所有变量的类型详解
1. 数据生成阶段
| 变量名 | 类型 (Type) | 形状 (Shape) | 含义 |
|---|---|---|---|
true_w |
torch.Tensor |
(4,) |
真实权重。包含 4 个数值的张量。 |
true_b |
torch.Tensor |
() 或 (1,) |
真实偏置。通常是一个标量张量。 |
X |
torch.Tensor |
(500, 4) |
特征矩阵。500 行数据,每行 4 个特征。 |
Y |
torch.Tensor |
(500,) |
标签向量。500 个预测目标值。 |
2. 数据加载器 (data_provider) 内部
| 变量名 | 类型 (Type) | 形状 (Shape) | 含义 |
|---|---|---|---|
length |
int |
- | 数据总量,数值为 500。 |
indices |
list |
500 |
索引列表。内容是 [0, 1, ..., 499] 打乱后的结果。 |
get_indices |
list |
16 |
当前批次的索引。从 indices 中切片取出的 16 个索引。 |
get_data |
torch.Tensor |
(16, 4) |
取出的特征。根据索引从 X 中取出的 16 行特征数据。 |
get_label |
torch.Tensor |
(16,) |
取出的标签。根据索引从 Y 中取出的 16 个标签。 |
3. 模型与训练阶段
| 变量名 | 类型 (Type) | 形状 (Shape) | 含义 |
|---|---|---|---|
w_0 |
torch.Tensor |
(4,) |
待训练权重。初始随机值,带有梯度属性 (requires_grad=True)。 |
b_0 |
torch.Tensor |
() |
待训练偏置。初始随机值,带有梯度属性。 |
batch_x |
torch.Tensor |
(16, 4) |
喂给模型的输入。也就是 get_data。 |
batch_y |
torch.Tensor |
(16,) |
喂给模型的真实值。也就是 get_label。 |
pred_y |
torch.Tensor |
(16,) |
模型的预测输出。形状和 batch_y 一样。 |
loss |
torch.Tensor |
() |
损失标量。一个代表当前误差大小的数字(0维张量)。 |
indices = list(range(length)) 详细拆解
假设你的数据总共有 length = 500 条。
-
range(length)range(500)并不是一个列表,它是一个“范围对象”(rangeobject)。你可以把它理解成一个“生成数字的机器”,它知道如何产生0, 1, 2, ..., 499这些数字,但它目前还没有把它们全部存进内存。- 类型:
rangeobject。
-
list(...)- 这是一个转换函数。它把上面那个“机器”实际运行一遍,把产生的所有数字抓出来,放进一个列表(List)这种数据结构里。
- 结果:
[0, 1, 2, 3, ..., 498, 499]。 - 类型:
list。
-
为什么要这么做?
- 目的:为了打乱顺序。
- 代码里紧接着有一句
random.shuffle(indices)。Python 的列表(list)是支持“原地打乱”这个操作的,但是range对象或者 Tensor 是不支持直接打乱的。 - 逻辑:我们不直接打乱原始数据
X和Y(那样太耗时),而是打乱代表位置的“号码牌”indices。打乱后,indices可能变成了[2, 499, 100, 88, ...]。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)