【动手学深度学习】基于Python动手实现线性神经网络

[复制链接]
发表于 2025-10-24 15:41:33 | 显示全部楼层 |阅读模式

1,走进深度学习

   在科幻巨制《钢铁侠》中,托尼·斯塔克身边那位无所不知、无所不能的智能助手贾维斯,不但显现了将来科技的无穷魅力,更是深度学习技能的一次梦幻预演。想象一下,一个可以大概及时分析数据、推测战斗战略、乃至明白并回应主人复杂指令的AI搭档,这不但仅是影戏的抱负,而是深度学习正徐徐迈向的现实。本日,就让我们一同揭开深度学习的秘密面纱,探索它怎样一步步将科幻空想照进现实。
  

  
2,设置分析

学习此教程,须要安装设置Anaconda和pytorch,最好创建一个专门的conda假造环境用来学习。学习之前须要举行如下预备:


  • 创建Conda假造环境
  • 安装开源学习库:d2l
  • 安装IDE:Jupyter Notebook
  • 安装深度学习框架:Pytorch
  • 参考书: 动手学深度学习(李沐)

3,线性神经网络

经典统计学习技能中的线性回归可以视为线性神经网络。本篇文章,我们将从深度学习的经典算法——线性神经网络开始,先容神经网络的底子知识。这些知识将为本书其他部门中更复杂的技能奠定底子。

4,线性回归从0开始实现

本身编写函数从0开始实现线性神经网络。
4.1,导入干系库

  1. # 在Jupyter Notebook中直接显示Matplotlib生成的图形
  2. %matplotlib inline
  3. import random
  4. import torch
  5. from d2l import torch as d2l
复制代码

4.2,天生数据

界说 synthetic_data 数据天生函数,天生模仿数据:
  1. # synthetic_data函数可以根据带有噪声的线性模型构造一个人造数据集
  2. def synthetic_data(w, b, num_examples):
  3.    
  4.     # 生成一个服从正态分布的随机数,均值0,标准差为1,(num_examples, len(w))是形状
  5.     X = torch.normal(0, 1, (num_examples, len(w)))
  6.     y = torch.matmul(X, w) + b   # 即y=Xw+b
  7.    
  8.     # 加入随机噪音,模拟真实数据
  9.     y += torch.normal(0, 0.01, y.shape)
  10.    
  11.     # 括号内第一个参数,-1 表示自动计算行的大小,以确保总的元素数量不变
  12.     # 括号内第二个参数1表示列
  13.     return X, y.reshape((-1, 1))
复制代码
用w和b,天生数据。须要注意的是,盘算y=Xw+b时:


  • w 是一维的,PyTorch 实行矩阵乘法时自动将其视为一个列向量举行矩阵乘法,w外形为2行1列
  • b是标量,末了pytorch内部自动扩展为y的外形,实行广播加法
  1. # 设置真实的w和 b
  2. true_w = torch.tensor([2, -3.4])  # 此处true_w 是一个包含两个元素的一维张量
  3. true_b = 4.2  # true_b 为一个标量
  4. """
  5. 在大多数情况下,当 X 是 (n_samples, n_features) 形状的特征矩阵时,w 会是 (n_features,) 形状的一维张量,代表每个特征的权重。
  6. 此时,PyTorch会自动将 w 转换为一个形状为 (n_features, 1) 的二维张量(列向量)进行矩阵乘法。
  7. """
  8. # 调用函数生成特征和标号 :返回的features 形状为 (1000, 2);labels形状为 (1000, 1)
  9. features, labels = synthetic_data(true_w, true_b, 1000)
  10. print(true_w.shape)  # w是一个包含两个元素的一维张量
  11. print(features.shape)
  12. print(labels.shape)
复制代码
打印内容如下:


features中的每一行都包罗一个二维数据样本, labels中的每一行都包罗一维标签值(一个标量)。我们输出第0个样本做查抄:
  1. print('features:', features[0],'\nlabel:', labels[0])
复制代码
输出结果如下:


绘制全部样本的第二个特性标签之间关系的图像
  1. # set_figsize()可加参数,用于设置 Matplotlib 绘图库生成的图形大小
  2. d2l.set_figsize()
  3. """
  4. 特征是通过从正态分布中抽样生成的,并且与权重 w 进行线性组合来产生标签 y。
  5. 因此,权重 w 的符号(正或负)将决定相应特征与标签之间的相关性是正相关还是负相关。
  6. true_w = torch.tensor([2, -3.4])
  7. * features[:, 1]表示选择所有行和索引为1的列(第二列)图像呈负相关;若选择第一列则为正相关
  8. * 绘制散点图,有的pytorch版本要求detach之后才能转到numpy,最后一个参数1表示散点的大小为1
  9. """
  10. d2l.plt.scatter(features[:, 1].detach().numpy(), labels.detach().numpy(), 1);
复制代码


4.3,读取数据集

界说 data_iter 函数用来读取小批量样本
  1. # 定义函数用来读取小批量样本(因为要用到小批量随机梯度下降做优化)
  2. # 函数的入参为:批量大小,特征,标签
  3. def data_iter(batch_size, features, labels):
  4.    
  5.     # 计算样本总数
  6.     num_examples = len(features)
  7.    
  8.     # 创建一个索引列表indices ,包含从0到num_examples-1的整数,这些索引用于访问特征和标签
  9.     # range(x)用于生成一个0到x(不包括x)的可迭代的整数序列对象
  10.     # 使用list()转换为列表
  11.     indices = list(range(num_examples))
  12.    
  13.     # random.shuffle打乱列表元素。表示这些样本是随机读取的,无特定的顺序,有助于提高模型的泛化能力
  14.     random.shuffle(indices)
  15.    
  16.     # 迭代(batch_size是步长)
  17.     for i in range(0, num_examples, batch_size):
  18.         # 切片。最后一个批量可能不是满的
  19.         batch_indices = torch.tensor(indices[i: min(i + batch_size, num_examples)])
  20.      
  21.                """
  22.                使用 yield 创建生成器。
  23.                每当函数执行到 yield 时,生成器函数使用yield关键字来暂停函数执行并产生一个值,然后在需要下一个值时再次恢复执行。
  24.                """
  25.         # 注意:indices是list,batch_indices转变成了tensor,比如tensor([1, 2, 3])
  26.         # 根据索引张量batch_indices来选择features中的某些行
  27.         yield features[batch_indices], labels[batch_indices]
复制代码

调用函数,输出一个小批量的数据做查抄
  1. # 注意batch_size和num_examples的区别,batch_size是小批量的大小
  2. batch_size = 10
  3. # 测试一下,打印输出一个小批量的内容
  4. for X, y in data_iter(batch_size, features, labels):
  5.     # 此处x是 10×2,y是10×1
  6.     print(X, '\n', y)
  7.     break
复制代码


4.4,初始化模子参数

开始用小批量随机梯度降落优化模子参数之前,须要有一些参数。
我们通过从均值为0、标准差为0.01的正态分布中采样随机数来初始化权重, 并将偏置初始化为0。
  1. # requires_grad=True 表示这个张量在自动微分过程中需要计算梯度以达到更新的目的
  2. w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
  3. # b是一个标量,初始化为0
  4. b = torch.zeros(1, requires_grad=True)
复制代码
  requires_grad=True体现须要盘算梯度:
  

  • 张量在自动微分过程中须要盘算梯度以到达更新的目标,由于在初始化参数之后,我们的使命是更新这些参数,直到这些参数充足拟合我们的数据。
  • 每次更新都须要盘算丧失函数关于模子参数的梯度。 有了这个梯度,我们就可以向减小丧失的方向更新每个参数。

4.5,界说模子、丧失函数、优化算法

界说线性回归模子
  1. def linreg(X, w, b):  
  2.     """线性回归模型"""
  3.     return torch.matmul(X, w) + b
复制代码
界说丧失函数为均方丧失(除以2是为了方便求导)
  1. def squared_loss(y_hat, y):  
  2.     """均方损失"""
  3.     # 尽量保证真实值y的形状和预测值y_hat的形状相同。
  4.     return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
复制代码
界说优化算法
  1. # 入参 param是一个list,里面包含w 和 b
  2. # 参数 lr(learning rate)表示学习率,除以batch_size是为了确保更新步骤不会因为批量大小的不同而产生过大的步长,从而保持学习过程的稳定性
  3. # 参数 batch_size为批量大小
  4. def sgd(params, lr, batch_size):  #@save
  5.     """小批量随机梯度下降"""
  6.     with torch.no_grad():    # 表示不需要计算梯度(更新的时候不做梯度计算)
  7.         for param in params:
  8.             # 下面式子是梯度下降法的固定更新参数公式
  9.             param -= lr * param.grad / batch_size
  10.             # 将梯度设为0,可以实现下一次计算梯度的时候与上次的梯度不相关
  11.             param.grad.zero_()
复制代码
  with torch.no_grad(): 体现此代码块内,禁用自动求导。 param.grad 中已经包罗了之前通过反向传播盘算出来的梯度信息。因此,在实行参数更新时,我们并不是不做梯度盘算,而是说梯度盘算是在之前的步调中完成的。
  由于在更新模子参数时,我们现实上已经完成了梯度盘算。具体来说,在深度学习的训练过程中,通常包罗以下几个步调:


  • 前向传播:在这个阶段,输入数据通过网络举行盘算,得到推测输出
  • 丧失盘算:将推测输出与真实标签对比,盘算出一个体现模子性能优劣的丧失值
  • 反向传播:基于丧失值,盘算丧失相对于每个参数的梯度。这是梯度盘算的步调
  • 参数更新:利用盘算出来的梯度和选定的优化算法(如SGD、Adam等),调解模子参数以减小丧失
   别的,对于 params 列表中的每一个 param(即模子的一个参数),实行以下步调:
  

  • param -= lr * param.grad / batch_size: 根据参数的梯度来调解参数值。param.grad 是当前参数的梯度,除以 batch_size 可以得到均匀梯度,乘以学习率 lr 后从当前参数值中减去,从而朝着淘汰丧失的方向移动
  • param.grad.zero_(): 在更新完参数后,我们须要将该参数的梯度设置为零。如果不如许做,在下一次反向传播时,新的梯度会累加到旧的梯度上

4.6,模子训练

指定训练参数
  1. # 学习率(lr为超参数)
  2. lr = 0.03
  3. # 整个数据扫描3遍(三个训练周期)
  4. num_epochs = 3
  5. # 模型选择前面定义好的线性模型linreg
  6. net = linreg
  7. # loss设为均方损失
  8. loss = squared_loss
复制代码
完备训练过程:
  1. for epoch in range(num_epochs):
  2.     for X, y in data_iter(batch_size, features, labels):
  3.         l = loss(net(X, w, b), y)  # X和y的小批量损失
  4.         # 反向传播计算梯度
  5.         l.sum().backward()
  6.         # 有了梯度之后再调用sgd更新参数(每次更新完参数都在sgd内部将梯度置为0,保证不影响下一个批量的计算效果)
  7.         sgd([w, b], lr, batch_size)  # 在sgd()函数中使用参数的梯度(.grad)更新参数
  8.         
  9.     # 评估每一个训练周期的效果(评估无需计算梯度)
  10.     """
  11.     with关键字在这里用于创建一个临时的、上下文相关的环境,在这个环境中,梯度计算被禁用。
  12.     """
  13.     with torch.no_grad():
  14.         # 此处传入的features和labels是整体数据维度
  15.         train_l = loss(net(features, w, b), labels)
  16.         # train_l.mean()代表求平均损失
  17.         print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
复制代码
反向传播代码表明:


  • PyTorch 的 backward() 函数要求输入是一个标量。而小批量丧失 l 是一个外形为(batch_size,1)的张量。因此累加求和后调用backward()自动求导盘算梯度;
  • 调用 .backward() 时,PyTorch会举行反向传播,盘算丧失相对于参数的梯度,并存储在参数的.grad 属性中
代码运行结果如下(运行结果体现,数据扫描到第三遍时均匀丧失已经很小了):


5,线性回归简便实现

利用深度学习框架来简便地实现线性回归模子
5.1,导入干系库

  1. import numpy as np
  2. import torch
  3. # 导入处理数据的模块
  4. from torch.utils import data
  5. from d2l import torch as d2l
复制代码

5.2,天生数据集

  1. # 定义真实的w和b
  2. true_w = torch.tensor([2, -3.4])
  3. true_b = 4.2
  4. # 使用上一节定义的函数生成数据
  5. features, labels = d2l.synthetic_data(true_w, true_b, 1000)
复制代码

5.3,读取数据集

界说 load_array 函数,共同pytorch框架读取数据
  1. def load_array(data_arrays, batch_size, is_train=True):
  2.     """构造一个PyTorch数据迭代器"""
  3.    
  4.     # 参数data_arrays是一个元组,包含特征(features)和标签(labels)
  5.         # *表示解包操作符,可以将列表或元组解为独立的参数
  6.         # 使用data.TensorDataset创建一个数据集对象
  7.     dataset = data.TensorDataset(*data_arrays)
  8.    
  9.     # 使用data.DataLoader创建一数据加载器,它提供了迭代数据集的功能,可自动处理批次大小、打乱数据(如果 shuffle=True)        及多进程数据加载等任务,is_train如果为True表示默认是训练数据,需要打乱
  10.     return data.DataLoader(dataset, batch_size, shuffle=is_train)
复制代码
  代码中的TensorDataset是PyTorch中的一个类,用于包装数据和标签,使其成为一个数据集
  调用函数,读取小批量
  1. batch_size = 10
  2. # 读取小批量
  3. data_iter = load_array((features, labels), batch_size)  
复制代码
读取并打印第一个小批量样本做查抄
  1. next(iter(data_iter))
复制代码


5.4,界说模子

利用pytorch,界说线性回归模子。线性回归是单层神经网络,利用的是神经网络中的Linear线性层(或全毗连层)
  1. # nn是神经网络的缩写(nerve network)
  2. from torch import nn
  3. """
  4. nn.Linear(2, 1)表示输入特征维度为2,输出特征维度为1的线性层
  5. nn.Sequential是一个容器,用于按顺序包装一系列的层
  6. 数据会按照在nn.Sequential中定义的顺序通过这些层
  7. """
  8. net = nn.Sequential(nn.Linear(2, 1))
复制代码

5.5,初始化模子参数

  1. """
  2. ①将weight初始化为服从正态分布的随机数,这个分布的均值为0,标准差为0.01。normal_是就地操作,意味着它会直接修改weight.data的值,而不是创建一个新的变量
  3. ②将第一个层的偏置(bias)初始化为0。fill_同样是一个就地操作,它会将bias.data中的所有元素设置为0。
  4. ③weight的形状由输入特征和输出特征的维度决定。
  5. """
  6. net[0].weight.data.normal_(0,0.01)
  7. net[0].bias.data.fill_(0)
复制代码


  • 此中,net[0]代表选择网络中的第一个层(现在只有一个全毗连层)

5.6,界说丧失函数和优化算法

   界说丧失函数
  

  • 盘算推测结果和现实结果绝对值的差距得到的Loss叫做MAE
  • 盘算现实结果和推测结果之差的平方得到的Loss叫做MSE
  1. # MSELoss:损失函数定义为均方误差
  2. loss = nn.MSELoss()
复制代码
  界说优化算法
  

  • 小批量随机梯度降落算法是一种优化神经网络的标准工具, PyTorch在optim模块中实现了该算法的很多变种
  • 实例化一个SGD实例时,我们要指定优化的参数 以及优化算法所需的学习率,这里设置为0.03
  • net.parameters() 方法会返回一个包罗模子中全部可训练的参数(即权重和偏置)的迭代器
  • 如果在模子中界说某些参数为不可训练(如,设置 requires_grad=False),则这些参数不会被 net.parameters() 返回,也不会被优化器更新
  • 优化器(如SGD)在训练循环中会根据盘算得到的梯度来更新网络的参数。每次迭代(或称为一个epoch)中,前向传播盘算丧失,反向传播盘算梯度
  1. # 创建了一个随机梯度下降(SGD)优化器实例 trainer,用于更新 net 中的参数
  2. # 这里的学习率(lr)被设置为0.03
  3. trainer = torch.optim.SGD(net.parameters(), lr=0.03)
复制代码

5.7,模子训练完备过程

  1. num_epochs = 3
  2. for epoch in range(num_epochs):
  3.     for X, y in data_iter:
  4.         
  5.         # 此处net已初始化参数w和b(5.5中),因此只需传入X即可
  6.         l = loss(net(X) ,y)
  7.         
  8.         # 将SGD优化器的梯度清零
  9.         trainer.zero_grad()
  10.         
  11.         l.backward()
  12.         
  13.         # 调用step()函数进行模型的更新
  14.         trainer.step()
  15.    
  16.     # 扫完一遍数据之后,在整体数据的维度(区别小批量数据维度)做一次评估
  17.     l = loss(net(features), labels)
  18.     print(f'epoch {epoch + 1}, loss {l:f}')
复制代码
反向传播代码表明:


  • 盘算模子参数的梯度时,无需累加。由于pytorch已经自动对小批量丧失 l 求和
  • 当调用 .backward() 时,PyTorch会举行反向传播,盘算丧失相对于网络参数的梯度,并存储参数的grad 属性中
  • 还须要调用trainer.step():实现对模子参数的更新
   代码运行结果如下:
  

比力天生数据集的真实参数和通过有限数据训练得到的模子参数
  1. w = net[0].weight.data
  2. print('w的估计误差:', true_w - w.reshape(true_w.shape))
  3. b = net[0].bias.data
  4. print('b的估计误差:', true_b - b)
复制代码
  代码运行结果如下:
  

可以看到:颠末训练后,参数的弊端已经很小了




免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

×
登录参与点评抽奖,加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表