linear代码解释
假设真实情如下:
外貌,性格,财富,内涵的权重依次为:8.1,2,2,4;偏置值为1.1,根据一个人的外貌,性格,财富,内涵可以计算出其恋爱次数
神经网络的目标便是根据所提供的数据最终找出近似的w,b
显然的是,该模型仅仅是一个线性回归模型.虽然简单,但我们的深度学习旅程也由此启程.
一.导入库
import torch import matplotlib.pyplot as plt #画图的 import random #生成随机数现来逐步解析:
import torch将PyTorch 库的所有功能导入到当前代码环境中,让我能调用torch命名空间下的所有类、函数和方法
import matplotlib.pyplot as plt #画图的是 Python绘制可视化图表的核心导入语句,matplotlib是 Python 最常用的绘图库,pyplot是其中封装了各类绘图接口的子模块,简写为plt是行业通用规范(能大幅简化代码)。
import random #生成随机数是 Python生成随机数 / 随机操作的核心导入语句,random是 Python 内置标准库(无需额外安装),封装了各类伪随机数生成函数,能满足从「简单随机数」到「随机抽样 / 打乱序列」的绝大多数需求。
二.产生数据
def create_data(w,b,data_num): #创建一个产生数据方法 x=torch.normal(0,1,(data_num,len(w))) #生成一个服从正态分布的张量 x,作为机器学习模型的输入特征。张量x的形状为:行数=样本数,列数=权重数 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=create_data(true_w,true_b,num)现逐步分析如下:
def create_data(w,b,data_num):定义数据生成函数:
根据给定的权重 true_w(张量:4*1)、偏置 true_b(张量:1*1),生成data_num(即样本数量)个样本,每个样本包含:
- 输入特征
x(随机生成); - 标签
y = w*x + b + 噪声(加入噪声模拟真实数据)。
在 Python 中,
len()函数作用于 PyTorch 张量时,只会返回张量「第一维度的长度」(等价于tensor.shape[0])。
x=torch.normal(0,1,(data_num,len(w)))生成一个服从正态分布的张量x,作为机器学习模型的输入特征。张量x的形状为:行数=样本数,列数=权重数
y=torch.matmul(x,w)+b #matmul表示矩阵相乘基于多维特征x和权重w,按照线性回归公式y = X·w + b计算标签y
noise=torch.normal(0,0.01,y.shape)#噪声要加到y上,因此noise张量的形状要与y.shape一致在机器学习生成线性回归数据时,噪声是模拟真实世界数据的关键:
- 真实数据不会严格遵循
y = X·w + b的理想线性关系,总会有测量误差、环境干扰等随机因素; - 加入小幅度噪声后,数据更贴近真实场景,训练出的模型也更具泛化能力(不会过拟合理想数据)。
y+=noise将噪声noise加到y上
return x,y返回张量x(data_num*len(w)) , y(data_num*1)
num=500 true_w=torch.tensor([8.1,2,2,4]) true_b=torch.tensor(1.1)确定样本数量为500,真实的张量true_w和true_b
X,Y=create_data(true_w,true_b,num)调用数据生成函数并传入实参true_w,true_b最终生成所需要的数据张量X(500*4)和Y(500*1)
三.数据可视化
plt.scatter(X[:,3],Y,1) #plt.scatter是画一个散点图。点大小为1,X取第3列 plt.show()现逐步分析如下:
plt.scatter(X[:,3],Y,1) #plt.scatter是画一个散点图。点大小为1,X取第3列plt.scatter
是matplotlib 的散点图绘制函数,核心作用是把「x 轴数据」和「y 轴数据」对应成坐标点画在图上.
X[:,3]
取所有行(所有样本),取第 4 列(索引从 0 开始),即取张量 X 中所有样本的第 4 列数据作为散点图的x 轴坐标
Y:
取Y所有的行和列
注意:所取的x轴数据形状必须和y轴数据形状一致
1:
点大小为1
plt.show()画图,结果如下:
四.批次数据提供程序
def data_provider(data,label,batchsize): #每次访问这个函数,就能提供一批数据 length=len(label) #获取样本总数 indices=list(range(length)) #给样本贴标签:0,1,2,3,...,length-1 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 #yield表示有存档点的return现逐步解析如下:
def data_provider(data,label,batchsize):#每次访问这个函数,就能提供一批数据这是机器学习中批量数据加载的核心骨架,作用是每次调用返回一批(batchsize个)数据和对应的标签,适配模型分批训练的需求.需要传入数据X,Y和批次样本数量batchiseze
即:每次计算
时不使用全部的500个样本的数据,而是从这500个样本中随机挑取一批(batchsize个)数据来计算
length=len(label)获取标签张量 / 数组的第一维度长度(即总样本数),并将其赋值给length
indices=list(range(length)) #indices 索引,是index的复数形式range(length)
生成一个整数序列:0,1,2,...,length-1(比如length=500,就是 0 到 499);
list(range(length))
把整数序列转为列表,得到列表[0,1,2,...,499],每个元素对应一个样本的位置;
indices=list(range(length))
最终得到「所有样本的列表」,后续可打乱这个列表实现随机取批次。
random.shuffle(indices) #shuffle 乱序不能按顺序取数据,要把序列打乱
for each in range(0,length,batchsize): get_indices=indices[each:each+batchsize] get_data=data[get_indices] get_label=label[get_indices]1.for each in range(0, length, batchsize):
range(0, 500, 64)生成批次起始位置:0, 64, 128, 192, 256, 320, 384, 448;each依次取这些值,代表「每一批数据的起始索引位置」
2.get_indices = indices[each:each+batchsize]
- 作用:从打乱的索引列表中,取出当前批次的索引,并赋值给get_indices;
- 示例:
- 当
each=0时,get_indices = indices[0:64]→ 取前 64 个随机索引; - 当
each=64时,get_indices = indices[64:128]→ 取下 64 个随机索引; - 最后一批
each=448时,get_indices = indices[448:512]→ 自动取到末尾(共 52 个索引,适配不足 batchsize 的情况)。
- 当
3.get_data = data[get_indices],get_label = label[get_indices]
- 作用:根据当前的批次索引get_indices,从总数据和标签中提取对应样本;
- 维度说明:
若
data是[500,4](500 个样本,4 维特征),则get_data是[64,4](64 个样本,4 维特征);- 若
label是[500,1](500 个标签),则get_label是[64,1](64 个标签)。
注意:data 是二维张量,但是直接用
data[get_indices]也完全合适,且是批量取样本的标准写法。对于形状为
[总样本数, 特征维度]的二维张量data(比如[500,4]):
get_indices是「样本索引列表」(比如[12, 45, 89]);data[get_indices]会按索引取出对应行(样本),返回形状为[批次大小, 特征维度]的二维张量(比如取 16 个索引,返回[16,4]);- 这是 PyTorch/NumPy 中「批量选取样本」的原生支持,完全合理且高效。
yield get_data,get_label #yield表示有存档点的return返回所需要的批次数据get_data,get_label
注意:因为每次取一批数据时,要求从上一批数据存档点处开始取,因此就要求使用yield返回而非return返回。
五.前向传播
batchsize=16 def qianxiang(x,w,b): pred_y=torch.matmul(x,w)+b return pred_ybatchsize=16每批次取16个样本
def qianxiang(x,w,b): pred_y=torch.matmul(x,w)+b return pred_y传入估计张量参数w_0,b_0,和数据X,计算预测值函数并返回张量
六.定义损失函数
def maeLoss(pred_y,y): return torch.sum(abs(y-pred_y))/len(y)传入标签Y和,计算样本loss的平均值
,
七.梯度下降算法更新参数
def sgd(paras,lr): with torch.no_grad(): for para in paras: para-=para.grad*lr #不能写成para=para-para.grad()*lr para.grad.zero_()梯度下降算法:传入估计参数paras和学习率lr
with torch.no_grad():
是 PyTorch 中临时关闭梯度计算的核心上下文管理器,会创建一个上下文环境,在这个环境内执行的所有操作:
- 不会构建计算图(PyTorch 记录梯度的核心);
- 不会计算张量的
grad梯度; - 不会消耗额外的内存 / 算力。
for para in paras:
para-=para.grad*lr
para.grad.zero_()
是 PyTorch 中参数更新 + 梯度清零的核心写法。
原地更新参数:
1.para.grad是反向传播计算出的梯度(张量,非方法,所以不加());
2.-=是「原地赋值」,直接修改para本身的数值,保留para的梯度追踪属性(requires_grad=True);
3. 结合torch.no_grad(),仅更新数值,不构建计算图。
梯度清零:
1.zero_()是「原地清零」方法,把para.grad重置为 0;
八.训练模型
lr=0.01 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))设置学习率lr,并随机生成估计参数w_0,b_0(注意,需要允许对其求梯度)
epochs=50 #训练轮数为50轮设置训练轮数epochs
for epoch in range(epochs):进行50轮循环训练
data_loss=0是机器学习训练中累加批次损失的初始化操作,核心作用是「从零开始累计每一批次的损失值,最终计算整轮训练的总损失 / 平均损失」。
在批量训练循环中,我们需要知道「整轮训练的总损失」(判断模型是否收敛),但模型是按批次计算损失的 ——data_loss=0就是用来承接这些批次损失的「容器」,初始化后逐批累加。
data_loss=0必须写在外层 epoch 循环内、内层 batch 循环外:
for batch_x,batch_y in data_provider(X,Y,batchsize):逐个取出data_provider生成的批次数据(特征 + 标签)
- 每次循环从数据提供器取一批数据;
batch_x接收批次特征(形状 16×4),batch_y接收批次标签(形状 16×1);- 循环会自动遍历完所有批次(500 个样本→32 批:前 31 批 16 个,最后 1 批 4 个)。
pred_y=fun(batch_x,w_0,b_0) loss=maeLoss(pred_y,batch_y)计算和
loss.backward()对loss的参量求导
sgd([w_0,b_0],lr)调用梯度下降算法更新参数w_0,b_0
data_loss+=loss累加批次损失
print('epoch %03d: loss: %.6f'%(epoch,data_loss))打印出每轮的批次损失
print('真实的参数值是',true_w,true_b) print('经过训练的参数值是',w_0,b_0)最终训练结果如下:
