从0开始机器学习--6.1神经网络概念+基础(深度学习框架介绍,什么是张量tensor?梯度?超参数?Epoch?批Batch?激励、损失函数?优化器?初始化?,含华为2024“智联杯”实战)

写在前面 

在之前的内容尤其是第四篇《机器学习模型》中,我们已经了解了八大基础的机器学习模型,并且通过这些相对基础的模型对机器学习和其任务有了一个较为直观的感受。随着技术的不断发展,神经网络模型也开始越来越活跃在大众的视线中。为了对具体的神经网络模型的原理、代码等有更好的理解,这篇博客主要分享关于神经网络的基础知识。由于我个人的实践经历,本文包括后续会主要以pytorch框架为基础展开

  • 1.python基础;
  • 2.ai模型概念+基础;
  • 3.数据预处理;
  • 4.机器学习模型--1.编码(嵌入);2.聚类;3.降维;4.回归(预测);5.分类;
  • 5.正则化技术;
  • 6.神经网络模型--1.概念+基础;2.几种常见的神经网络模型;
  • 7.对回归、分类模型的评价方式;
  • 8.简单强化学习概念;
  • 9.几种常见的启发式算法及应用场景;
  • 10.机器学习延申应用-数据分析相关内容--1.A/B Test;2.辛普森悖论;3.蒙特卡洛模拟;
  • 11.数据挖掘--关联规则挖掘
  • 12.数学建模--决策分析方法,评价模型
  • 13.主动学习(半监督学习)
  • 以及其他的与人工智能相关的学习经历,如数据挖掘、计算机视觉-OCR光学字符识别、大模型等。

合集链接https://blog.csdn.net/m0_73752612/category_12799244.html?fromshare=blogcolumn&sharetype=blogcolumn&sharerId=12799244&sharerefer=PC&sharesource=m0_73752612&sharefrom=from_link


目录

写在前面 

三种最常见的框架和对比(pytorch、tenserflow、keras)

1. PyTorch

pytorch实现线性回归任务代码

2. TensorFlow

tensorflow实现线性回归任务代码

3. Keras

keras实现线性回归任务代码

pytorch框架基础概念

梯度

什么是梯度

梯度在神经网络中的作用

梯度爆炸(Gradient Explosion) 

梯度消失(Gradient Vanishing)

梯度爆炸和梯度消失的直观对比

前馈网络(Feedforward Neural Network,FNN)

网络结构

前向传播(Feedforward Propagation)

反向传播(Backpropagation BP)

反向传播基本步骤:

反向传播核心原理:

前馈网络的特点

反馈

前向传播、反向传播和反馈的区别

前馈网络的应用

前馈网络的训练

多层前馈网络局限

数据类型torch.Tensor

torch.Tensor 的特点

基本数据格式

面经:为什么使用 PyTorch 的 torch.Tensor 而不是 NumPy?

初始化

Xavier 初始化(Glorot 初始化)

Glorot 条件

Kaiming 初始化

pytorch框架中的超参数

学习率(Learning Rate)

概念

学习率调度(Learning Rate Schedule)

Epoch

概念

训练中的 Epoch 作用

Epoch 与 Iteration 的区别

Epoch 数的选择

如何判断 epoch 数是否合适?

批(Batch) 批大小(Batch_size)

epoch、batch、step和数据量的意义和关系

Dropout概率p

Dropout工作原理

作用

model.train() 和 model.eval()

激励函数(激活函数)(Activation Function)

损失函数(Loss Function)

优化器(Optimizer)

搭建简易的pytorch框架的神经网络

回归任务

分类任务

快速搭建法

保存和提取

华为2024“智联杯”实战

train.py

predict.py

迁移学习(Transfer Learning)

总结


三种最常见的框架和对比(pytorch、tenserflow、keras)

三种最常见的深度学习框架——PyTorchTensorFlowKeras,在应用场景、易用性、性能等多个方面各具特点。下面我会从开源公司背景、使用难易程度、性能与灵活性、开发生态、著名使用场景及案例、适用的研究与生产环境、社区支持等角度做更详细的对比分析。

并且会给出对于同一任务--使用随机生成的样本数据进行线性回归预测--三种框架的实现代码。让大家先有一个直观的对区别的感受。至于代码具体是什么意思,在后面的内容中我会详细介绍。

1. PyTorch

开源公司

  • Meta(原Facebook)在2016年发布了PyTorch,旨在为研究和生产提供灵活、高效的深度学习工具。如今,PyTorch已经成为学术界的首选框架之一,甚至在工业界也得到了广泛应用。

使用难易程度

  • 动态计算图:PyTorch采用动态计算图(Define-by-Run)模式,意味着网络结构可以在每次前向传播(见下)时动态生成。与TensorFlow最初的静态图模式相比,PyTorch的调试和修改更加方便直观,特别适合快速原型开发和实验研究。
  • 易于学习:由于其面向对象的设计、直观的API以及与Python的紧密集成,PyTorch非常适合初学者和研究者使用。

性能与灵活性

  • PyTorch在研究和实验性场景中表现尤为突出。它提供了极大的灵活性,使得用户可以轻松定义复杂的神经网络结构,并在运行时调整。PyTorch的原生CUDA支持也使得GPU加速训练变得非常便捷。
  • 虽然在早期,PyTorch在大规模部署和分布式训练方面略显不足,但近年来它在生产环境中也得到了越来越多的应用,尤其是在与Caffe2的合并后,支持分布式部署

开发生态

  • PyTorch的生态系统不断扩展,包括torchvisiontorchaudiotorchtext等多个扩展库,适合处理计算机视觉、自然语言处理和语音识别等任务。
  • 在2018年推出的PyTorch 1.0版本中,加入了TorchScript功能,使得模型在研究与生产之间更容易转换。

著名使用场景及案例

  • Facebook AI Research(FAIR):很多Facebook内部的AI研究项目都使用了PyTorch,包括大规模的自然语言处理、图像处理等任务。举例来说,Facebook的图像识别模型,以及用于自动翻译的AI工具,都采用了PyTorch。
  • Hugging Face Transformers:这一广受欢迎的自然语言处理(NLP)库以PyTorch为基础。它提供了大量的预训练语言模型,如BERT、GPT等,广泛应用于文本生成、翻译、问答等任务。

适用场景

  • 学术研究:由于PyTorch的灵活性和直观性,它已成为学术研究中的首选框架,尤其适合需要反复调整模型结构和进行实验的研究者。
  • 快速原型开发:PyTorch的动态计算图使其非常适合快速迭代和开发实验性项目。

社区支持

  • PyTorch拥有一个非常活跃的社区,得到了大量的贡献者支持,学术界的很多顶尖论文也是基于PyTorch实现的。

pytorch实现线性回归任务代码

import torch
import torch.nn as nn
import torch.optim as optim

# 生成随机数据:y = 2x + 3
x_train = torch.randn(100, 1)
y_train = 2 * x_train + 3 + torch.randn(100, 1) * 0.1  # 添加一些噪声

# 定义简单的线性模型(pytorch框架)
class LinearRegressionModel(nn.Module):
    def __init__(self):
        super(LinearRegressionModel, self).__init__()
        self.linear = nn.Linear(1, 1)
        
    def forward(self, x):
        return self.linear(x)

# 初始化模型、损失函数、优化器
model = LinearRegressionModel()
criterion = nn.MSELoss()  # 均方误差损失
optimizer = optim.SGD(model.parameters(), lr=0.01)

# 训练模型
for epoch in range(100):
    model.train()
    optimizer.zero_grad()  # 梯度清零
    outputs = model(x_train)
    loss = criterion(outputs, y_train)  # 计算损失
    loss.backward()  # 反向传播
    optimizer.step()  # 更新权重

    if (epoch + 1) % 10 == 0:
        print(f'Epoch [{epoch+1}/100], Loss: {loss.item():.4f}')

# 查看训练后的模型参数
print(f'Learned parameters: {model.linear.weight.item():.4f}, {model.linear.bias.item():.4f}')

2. TensorFlow

开源公司

  • Google于2015年发布的TensorFlow,是目前最成熟的深度学习框架之一。它不仅仅支持深度学习,还能用于各种机器学习任务,Google内部和很多外部的AI系统都依赖TensorFlow。

使用难易程度

  • 静态计算图:TensorFlow最初使用静态计算图(Define-and-Run),用户需要先定义图结构,再执行训练。这种模式对初学者来说学习曲线较陡,代码调试也相对复杂。不过,Eager Execution的引入改善了这种局面,使得TensorFlow支持动态计算图,接近PyTorch的风格。
  • TensorFlow的API复杂度相对较高,需要深入理解其图计算和分布式计算模型才能发挥其全部潜力。

性能与灵活性

  • TensorFlow在分布式计算、大规模训练和跨平台部署上非常出色,支持从CPU、GPU到TPU(Tensor Processing Unit)的加速。尤其在Google的数据中心,TensorFlow搭配TPU能显著提升模型训练速度。
  • XLA编译器:TensorFlow支持通过XLA(Accelerated Linear Algebra)编译器优化模型计算,提升训练性能和模型部署效率。

开发生态

  • TensorFlow有一个非常庞大的生态系统,包含多个扩展库和工具,如TensorFlow Extended(TFX)用于生产级ML管道,TensorFlow Lite用于移动设备,TensorFlow.js用于Web端深度学习等。
  • TensorFlow的支持范围不仅限于深度学习,还能广泛应用于强化学习、图网络和传统机器学习算法。

著名使用场景及案例

  • Google Brain:几乎所有的Google AI项目都基于TensorFlow,包括Google Translate、YouTube推荐算法和Google Assistant的语音识别。
  • DeepMind:DeepMind的AlphaGo项目使用了TensorFlow进行训练,创造了AI历史上的一个里程碑。

适用场景

  • 大规模生产部署:TensorFlow因其对分布式计算的强大支持,广泛应用于工业级AI项目,尤其适合在企业环境中部署深度学习模型。
  • 跨平台模型部署:从服务器端到移动设备、Web端,TensorFlow有完整的工具链支持,适合需要将模型部署到不同设备的项目。

社区支持

  • TensorFlow拥有庞大的开发者和用户社区,定期推出新功能和优化,也有大量的官方文档和教程支持。

tensorflow实现线性回归任务代码

import tensorflow as tf

# 生成随机数据:y = 2x + 3
x_train = tf.random.normal((100, 1))
y_train = 2 * x_train + 3 + tf.random.normal((100, 1)) * 0.1

# 定义简单的线性模型(tensorflow实现)
model = tf.keras.Sequential([tf.keras.layers.Dense(1, input_shape=(1,))])
model.compile(optimizer='sgd', loss='mse')

# 训练模型
model.fit(x_train, y_train, epochs=100, verbose=0)

# 查看训练后的模型参数
weights, biases = model.layers[0].get_weights()
print(f'Learned parameters: {weights[0][0]:.4f}, {biases[0]:.4f}')

3. Keras

开源公司

  • François Chollet于2015年发布了Keras,最初是作为一个独立的高阶API,能够在多个后端(如TensorFlow、Theano、CNTK)上运行。2017年,它与TensorFlow深度集成,成为TensorFlow的官方高层API

使用难易程度

  • Keras的设计目标是简单易用。它的API高度封装,使用者只需编写少量代码就能快速搭建和训练神经网络。这使得Keras成为初学者和快速原型开发的首选。
  • 虽然其高层次封装提供了极大的便捷,但这也意味着灵活性较低,在需要精细控制模型时,Keras可能不够合适。

性能与灵活性

  • Keras虽然极大简化了深度学习开发的流程,但牺牲了一定的灵活性和性能。特别是在处理自定义层和操作时,Keras的抽象程度可能限制开发者的操作空间。
  • 当需要微调模型或者定义较复杂的神经网络结构时,使用底层的TensorFlow或PyTorch可能更合适。

开发生态

  • Keras现在是TensorFlow生态的一部分,所有的TensorFlow功能都可以通过Keras的高层API访问。
  • 通过与TensorFlow的深度集成,Keras同样支持分布式训练、模型压缩、迁移学习等高级功能。

著名使用场景及案例

  • 初学者教程与教学:Keras因其简洁的API,常用于机器学习和深度学习的教学中。很多大学课程和在线学习平台都会以Keras作为初学者的入门框架。
  • 快速原型开发:Keras是构建和测试小型深度学习模型的利器。在研究和生产环境中,Keras常用于快速验证模型的可行性。

适用场景

  • 新手入门:Keras因其简单易懂的API,非常适合作为深度学习入门工具。
  • 快速原型开发:对于需要快速迭代和测试想法的场景,Keras能够极大提高开发效率。

社区支持

  • Keras有着庞大的社区支持,由于其与TensorFlow的紧密结合,Keras用户可以借助TensorFlow社区的丰富资源。

keras实现线性回归任务代码

Keras作为TensorFlow的高级API实现方式会非常类似:

from keras.models import Sequential
from keras.layers import Dense
import numpy as np

# 生成随机数据:y = 2x + 3
x_train = np.random.randn(100, 1)
y_train = 2 * x_train + 3 + np.random.randn(100, 1) * 0.1

# 定义简单的线性模型
model = Sequential()
model.add(Dense(1, input_dim=1))
model.compile(optimizer='sgd', loss='mse')

# 训练模型
model.fit(x_train, y_train, epochs=100, verbose=0)

# 查看训练后的模型参数
weights, biases = model.layers[0].get_weights()
print(f'Learned parameters: {weights[0][0]:.4f}, {biases[0]:.4f}')

pytorch框架基础概念

梯度

图片来自网络zhuanlan.zhihu.com/p/396113407?utm_id=0 

什么是梯度

梯度是一个向量,表示函数在某一点处的偏导数(高数)。梯度告诉我们,在多维空间中,如何改变输入参数以最快地减少或增加输出值。换句话说,梯度指示了一个函数沿着输入空间的最大变化方向

在神经网络中,梯度是损失函数(模型输出与实际值之间的误差)关于网络参数(如权重和偏置)的导数。通过计算梯度,神经网络能够调整这些参数,使得损失函数的值逐步减小,这就是所谓的梯度下降(Gradient Descent)优化过程

梯度在神经网络中的作用

在神经网络中,训练的目标是找到一组最优的权重参数,使得模型预测的误差最小化。梯度在这个过程中起着至关重要的作用,它引导权重更新的方向。

  • 前向传播:输入通过神经网络层逐层传递,直到输出。(见下)
  • 计算损失:损失函数计算输出与真实标签之间的误差。
  • 反向传播:通过梯度计算损失函数相对于每个参数的偏导数,然后利用这些梯度更新权重。

这整个过程依赖于梯度下降算法(运筹学),使用梯度来更新网络的参数,通常按以下方式进行:

梯度爆炸(Gradient Explosion) 

梯度爆炸是指在训练深层神经网络时,梯度值在反向传播(见下)过程中指数级地增加,导致权重更新变得过大。最终,模型的参数可能会变得极其不稳定,训练过程无法正常进行。通常发生在网络层数较多时,特别是在递归神经网络(RNN)中更为常见。

产生原因

  • 在深层网络中,梯度会通过链式法则逐层传递。如果某些权重或激活函数导致梯度的值大于 1,在反向传播中,梯度经过多层传播时会被逐渐放大,导致梯度爆炸。

后果

  • 权重更新过大,模型无法收敛。
  • 损失函数(见下)的值会突然变得非常大,无法有效下降。

解决方法

  • 梯度裁剪(Gradient Clipping):将梯度值限制在某个合理范围内,防止它们爆炸性增大。
  • 使用正则化技术或适当的网络初始化策略。(在《5.正则化》中有具体介绍)
  • 使用合适的权重初始化策略,避免初始梯度过大。

梯度消失(Gradient Vanishing)

梯度消失是指在反向传播过程中,梯度值变得非常小,导致权重更新过慢甚至无法更新,网络难以学习到有效的参数。这种情况多见于激活函数是 sigmoid 或 tanh(见下)的深层神经网络

产生原因

  • 在深层网络中,如果激活函数的导数小于 1,反向传播时梯度会随着网络深度逐层缩小,导致较早的层几乎无法获得有效的梯度更新。尤其对于 sigmoid 和 tanh 激活函数,当输入值远离 0 时,其导数会变得非常小,导致梯度消失。

后果

  • 网络无法有效训练,特别是靠近输入层的权重几乎没有更新。
  • 训练时间变长,模型无法收敛到好的结果。

解决方法

  • 使用ReLU激活函数,ReLU 的梯度在正数范围内不会减小。
  • 使用改进的优化算法,如 Adam、RMSProp。
  • 权重初始化技巧,如 Xavier 初始化或 He 初始化。
  • 使用归一化技术,如批归一化(Batch Normalization)(见下),可以使梯度更加稳定。

梯度爆炸和梯度消失的直观对比

  • 梯度爆炸:梯度值过大,导致权重更新过快,损失值不稳定,可能无法收敛。
  • 梯度消失:梯度值过小,导致权重更新过慢,训练过程几乎停滞,无法有效学习。

前馈网络(Feedforward Neural Network,FNN)

前馈网络是最简单、最常见的神经网络类型之一。它是一个有向无环网络,数据和信号在网络中只从输入层单向传播到输出层,不会有反馈循环,也不会有数据回流到前面层。同层无连接、跨层无连接

网络结构

神经网络结构:输入-权重矩阵Wx+b阈值or偏置项-(神经元:隐藏层-激活函数加入非线性)-......-(输出-softmax-交叉熵损失),根据隐藏层的数量分为单隐层和多层神经网络。

  • 每一层中神经元的数量=上一层输出值的特征数*上一层的输出通道数

前馈网络主要由以下几部分组成:

  • 输入层(Input Layer):接收外部输入数据。
  • 隐藏层(Hidden Layers):介于输入层和输出层之间的层,用于提取和学习数据的特征。隐藏层可以有一层或多层。每个隐藏层中的节点与前一层的所有节点相连,经过权重和偏置调整,进行非线性变换
  • 输出层(Output Layer):最终产生预测结果或输出信号。

各层中的节点(或称为神经元)通过权重相连,输入数据经过一层层的计算、激活函数处理,最终输出结果。

前向传播(Feedforward Propagation)

在前馈网络中,数据从输入层进入,经过隐藏层,最终到达输出层。这一过程称为前向传播

  • 输入数据:将数据输入到输入层。
  • 加权求和:隐藏层中的每个节点对前一层的输入值乘以对应的权重,并加上偏置项,进行加权求和。
  • 激活函数:通过激活函数(如ReLU、Sigmoid)进行非线性变换,以增强模型表达复杂特征的能力。
  • 输出结果:在输出层,经过处理后的数据变为最终的预测结果。
output = activation_function(weighted_sum(input))

反向传播(Backpropagation BP)

反向传播是神经网络的核心训练算法之一,用来计算每个参数的梯度,并通过梯度下降算法调整网络的权重和偏置,从而最小化损失函数,提升模型的准确性。

神经网络需要反复迭代

反向传播基本步骤:
  1. 前向传播:输入数据通过网络层逐步传递,最终得到输出结果。在这个过程中,网络会记录各层的输出值。
  2. 计算损失:根据网络的输出结果和真实标签(ground truth),通过损失函数(如均方误差、交叉熵等)计算网络的误差(loss)。
  3. 反向传播误差:从输出层开始,误差逐层向后传播,根据链式法则计算每一层的参数(权重和偏置)的梯度。
  4. 参数更新:通过梯度下降或其他优化算法,利用计算得到的梯度更新网络参数,减小误差。
反向传播核心原理
  • 反向传播基于链式法则(Chain Rule)。每一层的梯度是通过上一层梯度乘以当前层的局部梯度得出的,误差从输出层逐层向输入层传播。
# 反向传播
loss.backward()  # 计算梯度
optimizer.step()  # 更新参数

前馈网络的特点

  • 反馈:前馈网络的节点连接没有回路,数据不会回流到前面层。
  • 全连接:每个节点与前一层的所有节点相连(常见的是全连接层,也叫密集层)。
  • 易于理解和实现:它是神经网络的基础构造,计算简单,适合用于各种任务,比如回归、分类等。
反馈
hidden_state = rnn_layer(input, hidden_state)
前向传播、反向传播和反馈的区别

前馈网络的应用

前馈神经网络主要用于处理结构化数据简单问题,但它在面对复杂的序列问题(如时间序列分析、语言模型)时表现不足,因为它无法建模数据间的时间依赖关系。在这种情况下,更复杂的网络结构(如循环神经网络、卷积神经网络)更加适合。

前馈网络的训练

前馈网络的训练过程包含以下步骤:

  • 前向传播:输入数据通过前馈网络传播,生成预测结果。
  • 计算损失:将预测结果与真实值进行比较,计算损失函数值(如均方误差或交叉熵)。
  • 反向传播:使用梯度下降算法和反向传播算法,更新网络中的权重和偏置。
  • 迭代更新:通过多轮训练(epoch),逐渐调整权重和偏置,使模型的损失降低,提升预测精度。

多层前馈网络局限

  • 神经网络由于强大的表示能力, 经常遭遇过拟合,表现为:训练误差持续降低, 但测试误差却可能上升
    • 早停:在训练过程中, 若训练误差降低, 但验证误差升高, 则停止训练 ,同时返回具有最小验证集误差的连接权和阈值。
    • 正则化:在误差目标函数中增加一项描述网络复杂程度的部分, 例如连接权值与阈值的平方和。常用交叉验证法。
  • 如何设置隐层神经元的个数仍然是个未决问题,实际应用中通常使用“试错法”调整,即“调参”

数据类型torch.Tensor

pytorch深度学习框架只接受tensor形式的数据(特点和原因见下)torch.Tensor 是 PyTorch 中的核心数据类型,类似于 NumPy 的 ndarray,但增加了许多深度学习所需的功能,比如 自动微分GPU 加速

torch.Tensor 的特点

1. 多维数组torch.Tensor 是一个多维数组,可以表示标量、向量、矩阵或更高维的张量。如:

  • 0 维:标量 (如 3)
  • 1 维:向量 (如 [1, 2, 3])
  • 2 维:矩阵 (如 [[1, 2], [3, 4]])
  • 3 维及以上:高维张量
import torch
scalar = torch.tensor(3)  # 0维标量
vector = torch.tensor([1, 2, 3])  # 1维向量
matrix = torch.tensor([[1, 2], [3, 4]])  # 2维矩阵

2. 支持自动微分:通过 requires_grad=Truetorch.Tensor 可以在计算中追踪每个操作,以便在反向传播时自动计算梯度。这对深度学习中的权重更新非常关键。 其实之前还有Variable变量,但从PyTorch0.4版本开始,Variable已被弃用,因为Tensor本身就可直接使用自动微分功能。现在所有张量默认都是可以追踪梯度的。只需设置 x=torch.tensor([1.0, 2.0, 3.0], requires_grad=True)。

x = torch.tensor(2.0, requires_grad=True)
y = x**2 + 3
y.backward()  # 计算y相对于x的梯度
print(x.grad)  # 输出:4.0

3. GPU 加速torch.Tensor 可以轻松地在 CPU 和 GPU 之间切换。你可以通过 .to(device) 方法将张量放在 GPU 上,从而加速大规模计算

tensor = torch.tensor([1, 2, 3])
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
tensor = tensor.to(device)  # 转移到GPU

4.  NumPy 的互操作性torch.Tensor 和 NumPy 的 ndarray 可以无缝互转。当张量位于 CPU 上时,转换不会引入额外的开销。 

import numpy as np
np_array = np.array([1, 2, 3])
torch_tensor = torch.from_numpy(np_array)  # NumPy 到 Tensor

new_np_array = torch_tensor.numpy()  # Tensor 到 NumPy

5. 高效的计算操作:PyTorch 提供了大量高效的矩阵和张量操作,包括基本的数学运算、矩阵乘法、转置、求逆等。PyTorch 的大部分操作支持自动广播机制,允许不同形状的张量进行运算。这些计算方式的代码与numpy几乎一摸一样(具体可见第一篇《python基础》,有具体介绍)。 

import torch
import numpy as np
np_data=np.arange(6).reshape((2,3))
torch_data=torch.from_numpy(np_data) #将numpy转化为tensor
tensor2array=torch_data.numpy()      #将tensor转化为numpy
print('np_data',np_data,'\ntorch_data',torch_data,'\ntensor2arr',tensor2array)
#abs sin mean......

data=[-1,-2,1,2]
tensor=torch.FloatTensor(data)    #将data转化为32bit浮点数的形式
print('\nabs','\nnumpy',np.abs(data),'\ntorch',torch.abs(tensor))

#矩阵运算
data=[[1,2],[3,4]]
tensor=torch.FloatTensor(data)
print('\nnumpy',np.matmul(data,data),'\ntorch',torch.mm(tensor,tensor))
#np.matmul(data,data)==data.dot(data)  
#tensor.dot(tensor)在这个例子中为1*1+2*2+3*3+4*4=30   .mm为矩阵乘法

#创建张量
x=torch.ones(2, 3)  # 生成一个2x3的全1矩阵
y=torch.zeros(2, 3)  # 生成一个2x3的全0矩阵
z=torch.rand(2, 3)  # 生成一个2x3的随机矩阵

#张量加法
result=x+y

#张量输出
print(result)
#输出:
# tensor([[1., 1., 1.],
#         [1., 1., 1.]])

6. tensor的数据格式:张量(Tensor)是一种多维数组,它是机器学习和深度学习中的基础数据结构。在不同的深度学习框架中,张量的存储格式可能有所不同,具体地,数据的排列顺序会影响计算效率以及内存使用。常见的张量数据格式有 NCHWNHWC,分别在不同的框架(如 PyTorch 和 TensorFlow)中使用。

基本数据格式

  • N:表示数据的批次大小(batch size),即一次计算的输入样本数量。
  • C:表示通道数(channels),通常与数据的维度(例如 RGB 图像的 3 个通道)或神经网络中的特征数量相关。
  • H:表示图像的高度(height),对于图像来说是垂直方向的尺寸。
  • W:表示图像的宽度(width),对于图像来说是水平方向的尺寸。

PyTorch 中,默认的图像数据格式是 NCHW;TensorFlow 中,默认的图像数据格式是 NHWC。NCHW 组织时,在计算设备中按照 W-H-C-N 的顺序来存储;按 NHWC 组织时,在计算设备中按照 C-W-H-N 的顺序来存储。

面经:为什么使用 PyTorch 的 torch.Tensor 而不是 NumPy?

  • 自动微分(Autograd):PyTorch 的 Tensor 支持自动求导,而 NumPy 的数组则没有这种功能。在深度学习中,反向传播(见上)依赖于对梯度(运筹学、高数中的基本概念,见上)的计算,而 PyTorch 的 Tensor 可以通过自动微分引擎 autograd 实现这一功能。
  • GPU 加速torch.Tensor 可以很方便地在 GPU 上进行计算,从而显著加快深度学习任务,而 NumPy 仅限于 CPU 计算。

初始化

在深度学习模型中,权重初始化是一个非常重要的步骤。适当的初始化可以帮助加速训练过程,避免梯度消失或爆炸问题。Xavier(也叫Glorot)初始化和Kaiming初始化是两种常见的权重初始化方法,它们有不同的适用条件和初始化策略,尤其是在权重分布的选择上。

随机初始化

  • 高斯初始化
  • 均匀分布初始化

Xavier 初始化(Glorot 初始化)

Glorot 条件

Glorot 条件是指在 Xavier 初始化 中,初始化时的权重矩阵的方差设置,使得信号和梯度的方差在前向传播和反向传播过程中保持一致。根据这个条件,

  • 前向传播时,每一层激活值的方差保持一致,从而避免在深度网络中出现信号过大或过小的情况。
  • 反向传播时,每一层对状态的梯度方差保持一致。

Xavier 初始化是一种设计用来防止深度网络在前向和反向传播时出现梯度消失或梯度爆炸的问题的初始化方法。它根据输入和输出的数量来初始化权重,目的是保持信号和梯度的方差在前向和反向传播过程中不发生大幅度变化。

Xavier 初始化条件:

  • 对于一个层的权重矩阵 W,假设输入的大小是 n_{\text{in}},输出的大小是 n_{\text{out}},则初始化时的权重应该满足以下条件:

    • 均匀分布:W \sim U\left(-\sqrt{\frac{6}{n_{\text{in}} + n_{\text{out}}}}, \ \sqrt{\frac{6}{n_{\text{in}} + n_{\text{out}}}}\right)

    • 正态分布:W \sim \mathcal{N}\left(0, \ \frac{2}{n_{\text{in}} + n_{\text{out}}}\right)

适用条件:

  • Xavier 初始化非常适用于Sigmoidtanh等激活函数。这些激活函数在输入较大时会导致梯度消失,而 Xavier 初始化有助于保持信号和梯度的方差稳定,避免消失或爆炸。

Kaiming 初始化

Kaiming 初始化(也叫 He 初始化)是一种优化的权重初始化方法,特别适用于ReLU(Rectified Linear Unit)激活函数。Kaiming 初始化考虑了ReLU函数的特点,即当输入小于0时输出为0,因此它需要对权重进行不同的初始化,以避免梯度消失。也需要满足Glorot条件。

Kaiming 初始化条件:

  • 对于一个层的权重矩阵 W,假设输入的大小是 n_{\text{in}},输出的大小是 n_{\text{out}},则初始化时的权重应该满足以下条件:

    • 均匀分布:W \sim U\left(-\sqrt{\frac{6}{n_{\text{in}}}}, \ \sqrt{\frac{6}{n_{\text{in}}}}\right)

    • 正态分布:W \sim \mathcal{N}\left(0, \ \frac{2}{n_{\text{in}}}\right)

适用条件:

  • Kaiming 初始化特别适用于ReLU及其变种激活函数(如 Leaky ReLU, ELU 等)。由于这些激活函数对于负值部分的抑制特性,Kaiming 初始化通过调整权重的方差,确保网络中的梯度能够更好地传播,从而避免梯度消失。

pytorch框架中的超参数

学习率(Learning Rate)

概念

学习率是神经网络训练过程中的一个超参数,用来控制模型在每一步优化中,更新参数的步幅大小(见下)学习率太大可能导致模型在最小化损失时跳过最优点(无法收敛),而学习率太小则会使训练过程缓慢,甚至陷入局部最优解学习率又叫步长

步长(Step Size)就是在每次梯度更新时,沿着梯度方向前进的距离,即学习率在每次更新时控制权重变化的幅度。步长越大,模型参数在每次迭代中变化的幅度越大。

学习率调度(Learning Rate Schedule)

指在训练过程中动态调整学习率的方法。常见的调度策略包括:

  • Step Decay:在设定的epoch数后,将学习率减少固定比例。
  • Exponential Decay:学习率按指数衰减。通常公式为:lr = initial_lr * exp(-k * epoch),其中 k 是衰减速率
  • Cyclical Learning Rate:在训练期间周期性地调整学习率。有库函数可用。
import torch
import torch.optim as optim
import matplotlib.pyplot as plt
import numpy as np
from torch.optim.lr_scheduler import CyclicLR

# 学习率调度器:Step Decay 和 Exponential Decay 通用的函数
def step_decay_schedule(initial_lr, drop, epochs_drop, epoch):
    return initial_lr * (drop ** (epoch // epochs_drop))

def exp_decay_schedule(initial_lr, k, epoch):
    return initial_lr * np.exp(-k * epoch)

# 初始化参数
initial_lr = 0.1
drop = 0.5  # Step Decay的衰减比例
epochs_drop = 10  # Step Decay每10个epoch减少一次
epochs = 50
k = 0.05  # Exponential Decay的衰减速率

# 创建子图
fig, axs = plt.subplots(1, 3, figsize=(18, 5))

# 1. Step Decay Learning Rate
lr_values = [step_decay_schedule(initial_lr, drop, epochs_drop, epoch) for epoch in range(epochs)]
axs[0].plot(lr_values)
axs[0].set_title("Step Decay Learning Rate")
axs[0].set_xlabel("Epoch")
axs[0].set_ylabel("Learning Rate")
axs[0].grid(True)

# 2. Exponential Decay Learning Rate
lr_values = [exp_decay_schedule(initial_lr, k, epoch) for epoch in range(epochs)]
axs[1].plot(lr_values)
axs[1].set_title("Exponential Decay Learning Rate")
axs[1].set_xlabel("Epoch")
axs[1].set_ylabel("Learning Rate")
axs[1].grid(True)

# 3. Cyclical Learning Rate (CLR)
model = torch.nn.Linear(1, 1)  # 一个简单模型
optimizer = optim.SGD(model.parameters(), lr=initial_lr)
scheduler = CyclicLR(optimizer, base_lr=0.01, max_lr=0.1, step_size_up=5, mode='triangular')

lr_values = []
for epoch in range(epochs):
    optimizer.step()  # 模拟训练步骤
    lr_values.append(optimizer.param_groups[0]['lr'])
    scheduler.step()

axs[2].plot(lr_values)
axs[2].set_title("Cyclical Learning Rate")
axs[2].set_xlabel("Epoch")
axs[2].set_ylabel("Learning Rate")
axs[2].grid(True)

# 调整子图布局
plt.tight_layout()
plt.show()

Epoch

概念

表示整个训练数据集完整地通过神经网络一次过程。使用 PyTorch 时,在代码中 epoch 通常在训练循环中作为for循环(在《1.python基础》中有具体介绍)的次数出现。

  1. 完整遍历一次数据集:一轮 epoch 就意味着神经网络将所有的训练样本(数据集)都输入一次,经过一次前向传播和反向传播。
  2. 多次epoch:通常训练过程中需要多次遍历数据集,因此模型会进行多轮 epoch 的训练。每一轮 epoch 都会根据上一次的梯度和损失进行权重的更新,帮助模型逐渐逼近最优解。

训练中的 Epoch 作用

  • 模型优化:在每个 epoch 中,模型通过反向传播(见上)更新参数,从而逐渐学习如何更好地拟合数据。
  • 损失的减少:随着 epoch 的增加,模型的损失函数通常会逐渐下降,表明模型越来越接近最佳解。

EpochIteration 的区别

  • Iteration(迭代):指一次前向传播和反向传播的过程,通常每一批(batch)(见下)数据完成一次前向传播和反向传播就算作一次迭代

因此,一个 epoch = 数据集大小 / 批大小 的迭代次数。例如,如果数据集有 1000 个样本,批大小为 100,那么每一轮 epoch 会有 10 次迭代。

Epoch 数的选择

  • 过小的 epoch 数:如果 epoch 数太少,模型可能没有足够的时间去学习和调整参数,这样训练过程就会中断,导致模型未能学到良好的特征(即欠拟合)。
  • 过大的 epoch 数:如果 epoch 数过多,模型可能会过度拟合训练集,表现为训练集上的性能很好,但在测试集上表现较差(即过拟合(在《4.4分类--决策树》中有具体介绍))。

理想的情况是选择合适的 epoch 数,让模型在训练集和测试集上都表现良好。

如何判断 epoch 数是否合适?

  • 观察损失曲线:通常训练过程中我们会绘制损失函数随 epoch 变化的曲线,观察其变化趋势。理想情况下,损失曲线应该随着 epoch 增加而逐渐下降,达到一个平稳点后停止下降。这时说明模型已经充分训练(即判断是否是过拟合的方式)
  • 使用早停(Early Stopping):为避免过拟合,通常可以设置早停机制,即在验证集上的性能不再提高时,提前停止训练。

批(Batch) 批大小(Batch_size)

批指的是在每次迭代中用于训练模型的样本子集。整个数据集通常很大,不能一次性送入模型训练,因此我们将其分成多个批次。

批数据训练是指每次只使用一个批次的数据进行训练,而不是整个数据集。批数据训练能够加快模型收敛速度,减少计算资源消耗。

批归一化(Batch Normalization)是指对每一批数据进行标准化,使得每层输出具有相似的分布,从而加速训练并减少梯度消失问题

以下这段代码可以直观的感受到批的作用。

import torch
import torch.utils.data as Data
BATCH_SIZE=4
x=torch.linspace(1,10,10)
y=torch.linspace(10,1,10)
torch_dataset=Data.TensorDataset(x,y) #数据集,使用datatensor训练,target算误差
loader=Data.DataLoader(               #使用dataloader来进行批训练
    dataset=torch_dataset,
    batch_size=BATCH_SIZE,
    shuffle=True   #是否需要再训练时随机打乱训练集
)
for epoch in range(3): #整体训练三次
    for step,(batch_x,batch_y) in enumerate(loader): #step是enumerate自带的计数功能的变量,(x,y)来自dataloader
        # training......
        print('Epoch:',epoch,'|Step',step,'|Batch_x',batch_x.numpy(),'|Batch_y',batch_y.numpy())
print()
Epoch: 0 |Step 0 |Batch_x [ 4.  5.  1. 10.] |Batch_y [ 7.  6. 10.  1.]
Epoch: 0 |Step 1 |Batch_x [9. 7. 8. 2.] |Batch_y [2. 4. 3. 9.]
Epoch: 0 |Step 2 |Batch_x [3. 6.] |Batch_y [8. 5.]
Epoch: 1 |Step 0 |Batch_x [ 5.  4. 10.  2.] |Batch_y [6. 7. 1. 9.]
Epoch: 1 |Step 1 |Batch_x [3. 7. 9. 8.] |Batch_y [8. 4. 2. 3.]
Epoch: 1 |Step 2 |Batch_x [1. 6.] |Batch_y [10.  5.]
Epoch: 2 |Step 0 |Batch_x [6. 8. 9. 3.] |Batch_y [5. 3. 2. 8.]
Epoch: 2 |Step 1 |Batch_x [7. 1. 5. 2.] |Batch_y [ 4. 10.  6.  9.]
Epoch: 2 |Step 2 |Batch_x [ 4. 10.] |Batch_y [7. 1.]
epoch、batch、step和数据量的意义和关系
  • Epoch:整个数据集经过网络一次训练。
  • Batch:每次训练时处理的部分样本。
  • Step:每个 batch 完成一次权重更新。
  • 数据量:决定了每个 epoch 包含的 batch 和 step 数量。

Dropout概率p

Dropout 是一种用于防止神经网络过拟合正则化技术,它通过在训练过程中机地丢弃一部分神经元,使得网络更具鲁棒性。Dropout 通过强制网络不依赖于某些特定的神经元或路径来达到更好的泛化性能。

Dropout工作原理

在每一次训练时,Dropout 会以一个预设的概率 p (通常为 0.5)随机地将神经网络中的一些神经元“关掉”或者忽略掉。这些被忽略的神经元的输出不会传递给下一层,也不会参与反向传播更新

具体过程

  1. 训练阶段
    • Dropout 在每一次前向传播时,会随机选择一部分神经元,以概率 p 保留,其他神经元以概率1−p 被丢弃(即其输出被设置为 0)。
    • 这种随机的丢弃使得网络在训练中不会依赖于某个特定的神经元,因为该神经元有可能在下一次更新时会被丢弃。
  2. 测试阶段
    • 在测试时,Dropout 不再随机丢弃神经元,而是使用全部神经元,并且将每个神经元的输出乘以保留概率 p,以保证输出的期望值与训练时一致。

作用

  • 防止过拟合:Dropout 通过在每次迭代中训练不同的“子网络”(由随机选择的神经元组成)来减轻神经网络的过拟合问题。这相当于训练了多个不同的网络,并且在测试时结合这些网络的效果来进行预测。
  • 增加网络的鲁棒性:由于每次都使用不同的神经元组合,网络不能过度依赖某些特定的神经元路径,从而提升了模型对新数据的泛化能力。

Dropout 的优点

  1. 减少过拟合:Dropout 通过随机丢弃部分神经元,使得网络不会依赖于某个特定神经元的输出,从而增加了网络的泛化能力。
  2. 简单且有效:相较于其他正则化技术,Dropout 实现简单且有效,并且能够显著提升深层网络的性能。

Dropout 的缺点

  1. 训练时间变长:由于每次训练时的网络都是不同的“子网络”,因此模型收敛所需的 epoch 可能会变多
  2. 小型网络效果较差:对于小型网络,Dropout 有时会导致丢弃过多的神经元,影响模型的表现。

超参数

  • Dropout 概率 p:决定了在每次训练时保留神经元的概率。常见的值为 0.5(即丢弃一半的神经元),但是在输入层一般会选择更大的保留率,例如 0.8。

实际应用场景

Dropout 被广泛应用于图像分类、自然语言处理等任务中。比如在中国的图像识别任务中,使用 Dropout 可以有效防止模型对特定背景或颜色的过度依赖。例如,训练一个用于识别不同城市街景的模型时,Dropout 可以防止模型仅仅依赖某些街道特征(如路牌颜色)进行识别,而是学习更具普适性的道路结构特征。还有一个著名应用场景是 AlexNet 。

model.train()model.eval()

在 PyTorch 中,model.train()model.eval() 是模型的两种模式设置方法,它们主要控制模型在训练过程中与推理(评估)过程中的行为差异。

激励函数(激活函数)(Activation Function)

激活函数赋予神经网络非线性能力,常见的有ReLU、Sigmoid、Tanh等。激励函数(AF)必须可微分,因为只有可微分,才能在反向传播时将误差传递回去。

默认首选,cnn(卷积神经网络)的卷积层中推荐relu,rnn(循环神经网络)推荐relu or tanh。softmax不是线图的激励函数,是用来做概率图的,分类问题每一个类别的概率。

多层网络实际就是利用激活函数的非线性,使得非线性叠加提升,因此,神经网络的深度比宽度更重要、更有效

同理,在图像卷积中,两层3*3的卷积核的效果优于1层5*5的卷积核的效果

  • 第一个3*3在一个5*5的范围中(不考虑padding)只能处理3*3的范围;
  • 第二个3*3处理这个小的3*3的范围,即只处理原始5*5的中心点。
  • 同样,在5*5的范围中(不考虑padding)一个5*5的卷积核也只能处理中心点。

以下代码可以直观的展示这些激励函数是什么。可以根据任务具体类型选择恰当的激活函数,如:

  • 最常用的是ReLU
  • sigmoid函数常用于概率
  • 阶跃函数常用于二分类问题
import torch
import torch.nn.functional as F #nn:神经网络模块
from torch.autograd import Variable
import matplotlib.pyplot as plt 
#制造一些伪数据
x=torch.linspace(-5,5,200) #x data tensor,将线段(-5,5)去200个点数据
x_np=x.data.numpy()        #因为plt作图不能直接接受tensor类型的数据

y_relu=F.relu(x).data.numpy()
y_sigmoid=F.sigmoid(x).data.numpy()
y_tanh=F.tanh(x).data.numpy()
y_softplus=F.softplus(x).data.numpy()

#四个激励函数的图
plt.figure(1,figsize=(8,6))
plt.subplot(221)       #用于创建一个 2x2 的网格子图,并激活其中的第一个子图,从左到右从上到下从1到4编号
plt.plot(x_np,y_relu,c='red',label='relu')
plt.ylim((-1,5))       #设置当前图表的 y 轴范围
plt.legend(loc='best') #将图例自动放置在最合适的位置
plt.subplot(222)
plt.plot(x_np,y_sigmoid,c='red',label='sigmoid')
plt.ylim((-0.2,1.2))
plt.legend(loc='best')
plt.subplot(223)
plt.plot(x_np,y_tanh,c='red',label='tanh')
plt.ylim((-0.2,1.2))
plt.legend(loc='best')
plt.subplot(224)
plt.plot(x_np,y_softplus,c='red',label='softplus')
plt.ylim((-0.2,6))
plt.legend(loc='best')
plt.show()

损失函数(Loss Function)

衡量模型输出与真实标签的差异,用于指导优化过程。对Softmax输出结果的好坏程度做一个“量化”。

常见的有均方误差(MSE)、交叉熵损失(对数的负数)等。(具体内容会在《7.评估》中介绍)

优化器(Optimizer)

优化器用于根据损失函数的梯度更新模型的权重。常见的优化器有SGD、Adam、RMSprop等。

SGD:随机梯度下降,每次只用一组数据进行学习;Adam:最优梯度下降(一般情况下它最好)

以下代码可以直观的展示这些优化器是什么。

import torch
import torch.utils.data as Data
import torch.nn.functional as F
import matplotlib.pyplot as plt
#定义一些超参数 hyper parameters
LR=0.01
BATCH_SIZE=32
EPOCH=12
x=torch.unsqueeze(torch.linspace(-1,1,1000),dim=1)
y=x.pow(2)+0.1*torch.normal(torch.zeros(*x.size()))
torch_dataset=Data.TensorDataset(x,y)
loader=Data.DataLoader(dataset=torch_dataset, batch_size=BATCH_SIZE, shuffle=True)
class Net(torch.nn.Module):
    def __init__(self):
        super(Net,self).__init__()
        self.hidden=torch.nn.Linear(1,20)
        self.predict=torch.nn.Linear(20,1)
    def forward(self,x):
        x=F.relu(self.hidden(x))
        x=self.predict(x)
        return x
#建立4个不同的神经网络,用于后续四种不同优化器
net_SGD=Net()
net_Momentum=Net()
net_RMSprop=Net()
net_Adam=Net()
nets=[net_SGD, net_Momentum, net_RMSprop, net_Adam]
opt_SGD = torch.optim.SGD(net_SGD.parameters(), lr=LR)
opt_Momentum = torch.optim.SGD(net_Momentum.parameters(), lr=LR, momentum=0.8) #动量项用于加速收敛,尤其是在处理高曲率、小但一致的梯度或嘈杂的梯度时。动量可以帮助优化器克服局部极小值,并在凹谷中更快地收敛。
opt_RMSprop = torch.optim.RMSprop(net_RMSprop.parameters(), lr=LR, alpha=0.9)  #alpha 是梯度平方的指数衰减率,用于控制移动平均线的平滑程度
opt_Adam = torch.optim.Adam(net_Adam.parameters(), lr=LR, betas=(0.9, 0.99))   #betas 控制动量项和二阶矩估计的指数衰减率。
#以上所有参数都是默认值,可能需要根据实际情况进行修改
optimizers = [opt_SGD, opt_Momentum, opt_RMSprop, opt_Adam]
loss_func=torch.nn.MSELoss()
losses_his = [[], [], [], []]   # record loss
# training
for epoch in range(EPOCH):
    print('Epoch: ', epoch)
    for step, (b_x, b_y) in enumerate(loader):          # for each training step
        for net, opt, l_his in zip(nets, optimizers, losses_his):#一个个网络一种种opt优化器进行训练
            output = net(b_x)              # get output for every net
            loss = loss_func(output, b_y)  # compute loss for every net
            opt.zero_grad()                # clear gradients for next train
            loss.backward()                # backpropagation, compute gradients
            opt.step()                     # apply gradients
            l_his.append(loss.data.numpy())     # loss recoder

labels = ['SGD', 'Momentum', 'RMSprop', 'Adam']
for i, l_his in enumerate(losses_his):
    plt.plot(l_his, label=labels[i])
plt.legend(loc='best')
plt.xlabel('Steps')
plt.ylabel('Loss')
plt.ylim((0, 0.2))
plt.show()

搭建简易的pytorch框架的神经网络

在最简单的情况下,可以像搭积木一样堆叠各层,例如全连接层(Linear)、激活函数(ReLU)和Dropout 等层。PyTorch 提供了 nn.Sequential,允许你通过线性堆叠快速构建网络。

对于更复杂的任务,可能需要设计非线性结构,如残差网络(ResNet)卷积神经网络(CNN)递归神经网络(RNN)等。此时,线性堆叠无法满足需求,通常会通过继承 nn.Module来自定义网络的前向传播逻辑。

在这一篇中,我们仅对pytorch框架大致了解即可,下一篇内容才会涉及到更复杂的具体神经网络内容。因此,我把堆叠的代码放在这里,我在代码中也尽可能加入了详细的注释。神经网络也可以处理回归和分类两种任务

还有一些主要的库函数,如DataLoader等

回归任务

import torch
import torch.nn.functional as F #nn:神经网络模块
import matplotlib.pyplot as plt
x=torch.unsqueeze(torch.linspace(-1,1,100),dim=1) #torch只会处理二维的数据,unsqueeze把一维变成二维
y=x.pow(2)+0.2*torch.rand(x.size())               #后面的是噪点
class Net(torch.nn.Module):#从torch.nn.Module继承模块
    def __init__(self,n_features,n_hidden,n_output):#层的信息
        super(Net,self).__init__() #官方步骤(必须)
        self.hidden=torch.nn.Linear(n_features,n_hidden)#这一层中有多少个输入,多少个输出(隐藏层的神经元的个数)
        self.predict=torch.nn.Linear(n_hidden,n_output)
    def forward(self,x):          #前向传递的过程,搭建模型,x为输入信息
        x=F.relu(self.hidden(x))  #x经过hidden层后用relu加工
        x=self.predict(x)         #预测不用激励函数因为线性的取值范围很大,若使用激励函数,结果可能被截断在一个小范围内
        return x
net=Net(1,10,1) #(n_features,n_hidden,n_output)
print(net)      #输出层结构

plt.ion() #将图片变为实时打印的过程
plt.show()
optimizer=torch.optim.SGD(net.parameters(),lr=0.5) #用优化器优化神经网络中的参数net.parameters()
loss_func=torch.nn.MSELoss()#均方差
for t in range(100):
    prediction=net(x)
    loss=loss_func(prediction,y)
    optimizer.zero_grad()  #优化步骤1:将所有参数的梯度降为0
    loss.backward()        #优化步骤2:当前次的反向传递
    optimizer.step()       #优化步骤3:以lr优化梯度
    if(t%5==0): #每拟合5次输出一次
        plt.cla()
        plt.scatter(x.data.numpy(),y.data.numpy())
        plt.plot(x.data.numpy(),prediction.data.numpy(),'r-',lw=5)
        plt.text(0.5,0,'loss=%.4f' % loss.data, fontdict={'size':20,'color':'red'})
        plt.pause(0.5)
plt.ioff()
plt.show()

输出: 

Net(
  (hidden): Linear(in_features=1, out_features=10, bias=True)
  (predict): Linear(in_features=10, out_features=1, bias=True)
)

分类任务

import torch
import torch.nn.functional as F
import matplotlib.pyplot as plt

# 构造伪数据
n_data = torch.ones(100, 2)    #创建了一个形状为 (100, 2) 的张量,这个张量有 100 行,每行有 2 个元素,每个元素的值都是 1。
x0 = torch.normal(2*n_data, 1)      # torch.normal()生成一个符合正态分布(也称高斯分布)的张量。
#torch.normal(2*n_data, 1) 表示生成一个形状和2*n_data相同(即(100, 2))的张量,其中每个元素都符合均值为2(因为n_data中的每个元素都是,乘2后每个元素变成2)、标准差为1的正态分布。
y0 = torch.zeros(100)               #类0,与后续“类1”做区分
x1 = torch.normal(-2*n_data, 1)     # class1 x data (tensor), shape=(100, 2)
y1 = torch.ones(100)                # class1 y data (tensor), shape=(100, 1)
x = torch.cat((x0, x1), 0).type(torch.FloatTensor) #合并x0x1作为全部的数据,在第0维(行的方向)上连接  # shape (200, 2) FloatTensor = 32-bit floating
y = torch.cat((y0, y1), ).type(torch.LongTensor)   #这个type为pytorch中标签默认的形式,一定要是64位的int #合并y0y1作为全部的标签 省略了维度参数,默认为在第0维 # shape (200,) LongTensor = 64-bit integer
plt.scatter(x.data.numpy()[:, 0], x.data.numpy()[:, 1], c=y.data.numpy(), s=100, lw=0, cmap='RdYlGn')
plt.show()#可视化当前两类数据点
class Net(torch.nn.Module):
    def __init__(self,n_features,n_hidden,n_output):
        super(Net,self).__init__()
        self.hidden=torch.nn.Linear(n_features,n_hidden)
        self.predict=torch.nn.Linear(n_hidden,n_output)
    def forward(self,x):
        x=F.relu(self.hidden(x))#做为一个function
        x=self.predict(x)
        return x
net=Net(2,10,2) #输入和输出都是两个特征,因为分为0或1两类,默认层中有10个神经元
#对于多分类问题,输出为[0,1]代表机器认为属于第二个大类,[1,0]代表机器认为属于第一个大类,[0,1,0]代表机器认为属于第二个大类,综上,哪一栏打上了tag1,机器就将他分到了哪一类
print(net)
plt.ion()
plt.show()
optimizer=torch.optim.SGD(net.parameters(),lr=0.02)
loss_func=torch.nn.CrossEntropyLoss() #回归问题用均方差(MSE),多分类问题用交叉熵(这种损失函数计算的是softmax,即每一类别的概率(概率和为1))
for t in range(100):
    out=net(x)
    loss=loss_func(out,y)
    optimizer.zero_grad() #优化步骤1:将所有参数的梯度降为0
    loss.backward()       #优化步骤2:当前次的反向传递
    optimizer.step()      #优化步骤3:以lr优化梯度
    if(t%5==0): #每2步输出一次
        # plot and show learning process
        plt.cla()#清空当前的绘图,这样在每次绘图时不会重叠之前的图像。
        prediction = torch.max(F.softmax(out, dim=1), 1)[1] #在第一个维度上(即列方向)使用激励函数softmax,将out转化为概率,“,1”即为找到最大值,索引为[1]即概率最大的位置。最大值的索引通常表示预测的类别,[0]就是最大值
        # 总之,这行代码的作用是从 out 张量中计算出每个样本的预测类别
        pred_y = prediction.data.numpy().squeeze() #.squeeze():移除单维度条目(即长度为1的维度),使数据更容易处理和可视化
        # torch.squeeze():移除大小为 1 的维度,可以选择性地在特定维度上进行。
        # torch.unsqueeze():在指定位置插入一个大小为 1 的维度。
        target_y = y.data.numpy()
        plt.scatter(x.data.numpy()[:, 0], x.data.numpy()[:, 1], c=pred_y, s=100, lw=0, cmap='RdYlGn')
        accuracy = float((pred_y == target_y).astype(int).sum()) / float(target_y.size)
        plt.text(1.5, -4, 'Accuracy=%.2f' % accuracy, fontdict={'size': 20, 'color':  'red'})
        plt.pause(0.5)
plt.ioff()
plt.show()

 输出:

Net(
  (hidden): Linear(in_features=2, out_features=10, bias=True)
  (predict): Linear(in_features=10, out_features=2, bias=True)
)

在这两段代码中,我们也能简单的找到一些搭建简易的pytorch框架神经网络的一些规律,如在网络的class中先一层层的定义层的信息,注意该层输入输出的特征数,下一层输入的特征数通常是该层输出的特征数;再定义与层数对应数量的前向传播函数,注意每一层使用的激励函数以及最后一层的预测x=self.predict(x);以及在训练循环中,optimizer.zero_grad() #优化步骤1:将所有参数的梯度降为0、loss.backward()       #优化步骤2:当前次的反向传递、optimizer.step()      #优化步骤3:以lr优化梯度,这三个优化步骤通常是必不可少的。

optimizer.zero_grad()

作用

  • 梯度清零:在每一次反向传播之前,将模型中所有参数的梯度(grad 属性)清零。

原因

  • 梯度累加:在像 PyTorch 这样的深度学习框架中,梯度是累加的。这意味着每次调用 loss.backward() 时,新的梯度会被累加到现有的梯度上。如果不在每次迭代开始时将梯度清零,梯度会不断累加,导致更新方向和步长不准确,从而影响模型的收敛性和性能。

loss.backward()

作用

  • 反向传播:计算损失函数相对于模型参数的梯度。

过程

  • 计算梯度:通过自动微分(Automatic Differentiation),框架会自动计算损失函数对每个参数的偏导数。
  • 构建计算图:在前向传播过程中,框架会构建一个计算图,记录所有操作,以便在反向传播时高效地计算梯度。

重要性

  • 优化目标:梯度告诉我们如何调整参数才能减少损失。没有梯度,优化器就无法知道如何更新参数。

optimizer.step()

作用

  • 参数更新:根据计算得到的梯度,调整模型的参数,以最小化损失函数。

工作机制

  • 优化算法:优化器(如 SGD、Adam、RMSprop 等)使用特定的优化算法,根据梯度和其他内部状态(如动量、适应性学习率等)来更新参数。
  • 学习率:优化器会根据预设的学习率(step size)来决定每次参数更新的幅度。

具体功能

  • 应用梯度:将计算得到的梯度应用到参数上,通常是沿梯度的反方向更新参数,以减小损失。
  • 参数调整:不同的优化器有不同的参数调整策略,例如 Adam 会根据梯度的一阶和二阶矩估计来调整学习率,从而加快收敛速度并提高稳定性。

快速搭建法

线性堆叠,这个网络的效果和上述分类问题的net是一样的。

import torch
net=torch.nn.Sequential(   #sequential的意思就是在其中一层层的累神经层
    torch.nn.Linear(2,10),
    torch.nn.ReLU(),       #做为一个class,要大写,区别于上面的function(激励函数)
    torch.nn.Linear(10,2)
)

保存和提取

分为两种方式保存和提取,分别为保存整个网络只保存每个点的状态(参数),保存和提取必须对应使用。使用状态保存和提取的方式时,必须先建立一样结构的网络,再使用提取的内容给它赋参数。

path1=r"./net.pth"
path2=r"./net_dict.pth"

import torch
torch.manual_seed(1) #可复现的随机
x=torch.unsqueeze(torch.linspace(-1,1,100),dim=1)#torch只会处理二维的数据,unsqueeze把一维变成二维
y=x.pow(2)+0.2*torch.rand(x.size())#后面的是噪点

def save():
    net=torch.nn.Sequential(
    torch.nn.Linear(1,10),
    torch.nn.ReLU(),
    torch.nn.Linear(10,1)
    )
    optimizer=torch.optim.SGD(net.parameters(),lr=0.5)
    loss_func=torch.nn.MSELoss()
    for t in range(100):
        prediction=net(x)
        loss=loss_func(prediction,y)
        optimizer.zero_grad() #优化步骤1:将所有参数的梯度降为0
        loss.backward()       #优化步骤2:当前次的反向传递
        optimizer.step()      #优化步骤3:以lr优化梯度
    torch.save(net,path1)             #保存整个网络
    torch.save(net.state_dict(),path2)#只保存每个点的状态(参数)

def restore_entire_net():
    net1=torch.load(path1)
    # pred1=net1(x)
def restore_dict_net(): #比上一种方式更快更轻便
    net2=torch.nn.Sequential(
    torch.nn.Linear(1,10),
    torch.nn.ReLU(),
    torch.nn.Linear(10,1)
    ) #这种方法必须先建立一样的结构,再赋参数
    net2.load_state_dict(torch.load(path2))

华为2024“智联杯”实战

任务书是这样的:

这个任务其实用上面所述的内容就可以很好的完成,我记得当时我们这部分是获得了前20名的,以下是代码:

train.py

import os
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
import joblib  # 用于加载标准化参数

# 数据加载和预处理
def load_and_preprocess_data(folder_path):
    data = []
    labels = []
    for file in os.listdir(folder_path):
        if file.endswith('.bin'):
            file_path = os.path.join(folder_path, file)
            label = int(file.split('_')[2])
            with open(file_path, 'rb') as f:
                signal = np.fromfile(f, dtype=np.float16)
            data.append(signal)
            labels.append(label)
    data = np.array(data)
    labels = np.array(labels)
    # 标准化处理
    scaler = StandardScaler()
    data = scaler.fit_transform(data)
    # 保存标准化参数
    scaler_path = r"./scaler.pkl"
    joblib.dump(scaler, scaler_path)
    return data, labels

# 构建数据集类
class MyDataset(Dataset):
    def __init__(self, data, labels):
        self.data = torch.tensor(data, dtype=torch.float32)
        self.labels = torch.tensor(labels, dtype=torch.long)

    def __len__(self):
        return len(self.data)

    def __getitem__(self, idx):
        return self.data[idx], self.labels[idx]

# 定义模型
class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.fc1 = nn.Linear(30720, 512)
        self.fc2 = nn.Linear(512, 256)
        self.fc3 = nn.Linear(256, 128) #增加隐藏层和神经元的数量
        self.fc4 = nn.Linear(128, 64)
        self.dropout = nn.Dropout(0.15)
        # self.dropout = nn.Dropout(0.1)
        self.fc5 = nn.Linear(64, 11)

    def forward(self, x):
        x = torch.relu(self.fc1(x))
        x = torch.relu(self.fc2(x))
        x = torch.relu(self.fc3(x))
        x = torch.relu(self.fc4(x))
        x = self.dropout(x)
        x = self.fc5(x)
        return x

# 训练模型
def train_model(model, train_loader, val_loader, criterion, optimizer, num_epochs, device):
    model.to(device)
    best_accuracy = 0.0
    for epoch in range(num_epochs):
        model.train()
        running_loss = 0.0
        for inputs, targets in train_loader:
            inputs, targets = inputs.to(device), targets.to(device)
            optimizer.zero_grad()
            outputs = model(inputs)
            loss = criterion(outputs, targets)
            loss.backward()
            optimizer.step()
            running_loss += loss.item()

        # 验证集上的评估
        model.eval()
        val_loss = 0.0
        total_correct = 0
        with torch.no_grad():
            for inputs, targets in val_loader:
                inputs, targets = inputs.to(device), targets.to(device)
                outputs = model(inputs)
                loss = criterion(outputs, targets)
                val_loss += loss.item()
                _, predicted = torch.max(outputs, 1)
                total_correct += (predicted == targets).sum().item()

        accuracy = total_correct / len(val_loader.dataset)
        print(f'Epoch {epoch + 1}, Loss: {running_loss/len(train_loader)}, Val Loss: {val_loss/len(val_loader)}, Val Accuracy: {accuracy}')\
        
        # 保存验证集上表现最好的模型
        if accuracy > best_accuracy:
            best_accuracy = accuracy
            best_model_wts = model.state_dict()

    # 加载最佳模型权重
    model.load_state_dict(best_model_wts)

# 加载数据
folder_path = r"D:\智联杯\本地调试的数据集\1.AI场景分类训练集(带标签)\train_set_remake"
data, labels = load_and_preprocess_data(folder_path)
X_train, X_val, y_train, y_val = train_test_split(data, labels, test_size=0.22, random_state=42)

# 构建数据加载器
train_dataset = MyDataset(X_train, y_train)
val_dataset = MyDataset(X_val, y_val)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=32)

# 定义模型、损失函数和优化器
model = MyModel()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.0001, weight_decay=1e-5) 

# 训练模型
num_epochs = 650
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
train_model(model, train_loader, val_loader, criterion, optimizer, num_epochs, device)

# 测试模型
model.eval()
with torch.no_grad():
    total_correct = 0
    for inputs, targets in val_loader:
        inputs, targets = inputs.to(device), targets.to(device)
        outputs = model(inputs)
        _, predicted = torch.max(outputs, 1)
        total_correct += (predicted == targets).sum().item()

accuracy = total_correct / len(val_loader.dataset)
print("模型在验证集上的准确率:", accuracy)

# 定义模型保存路径
model_path = r"./my_model.pth"
# 保存模型
torch.save(model.state_dict(), model_path)
print("模型已保存")

predict.py

import os
import numpy as np
import torch
import torch.nn as nn
import pandas as pd
from sklearn.preprocessing import StandardScaler
import joblib  # 用于加载标准化参数
from natsort import natsorted  # 用于自然排序

# 定义模型架构
class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.fc1 = nn.Linear(30720, 512)
        self.fc2 = nn.Linear(512, 256)
        self.fc3 = nn.Linear(256, 128)
        self.fc4 = nn.Linear(128, 64)
        self.dropout = nn.Dropout(0.15)
        self.fc5 = nn.Linear(64, 11)

    def forward(self, x):
        x = torch.relu(self.fc1(x))
        x = torch.relu(self.fc2(x))
        x = torch.relu(self.fc3(x))
        x = torch.relu(self.fc4(x))
        x = self.dropout(x)
        x = self.fc5(x)
        return x

# 加载并预处理数据
def load_and_preprocess_single_data(file_path):
    data = []
    with open(file_path, 'rb') as f:
        signal = np.fromfile(f, dtype=np.float16)
    data.append(signal)
    signal = np.array(data)
    # 加载标准化参数
    scaler_path = r"./scaler.pkl"
    scaler = joblib.load(scaler_path)
    signal = scaler.transform(data)
    return signal

# 预测函数
def predict(model, input_data):
    model.eval()
    with torch.no_grad():
        inputs = torch.tensor(input_data, dtype=torch.float32)
        outputs = model(inputs)
        _, predicted = torch.max(outputs, 1)
    return predicted.item()

# 定义模型并加载参数
model = MyModel()
model_path = r"./my_model.pth"
model.load_state_dict(torch.load(model_path))

# 读取要预测的文件夹路径
predict_folder_path = r"D:\智联杯\本地调试的数据集\2.AI场景分类测试集(无标签)"

# 存储所有预测结果
predictions = []

# 对文件夹中的每个文件进行预测
for file in natsorted(os.listdir(predict_folder_path)):
    if file.endswith('.bin'):
        file_path = os.path.join(predict_folder_path, file)
        input_data = load_and_preprocess_single_data(file_path)
        predicted_label = predict(model, input_data)
        predictions.append(predicted_label)

# 将预测结果保存到 CSV 文件
csv_output_path = r"./result.csv"
df_predictions = pd.DataFrame(predictions, columns=['PredictedLabel'])
df_predictions.to_csv(csv_output_path, index=False, header=False)  # header=False 避免写入表头

print(f'Predictions have been saved to {csv_output_path}')

其实就是用了数据预处理(标准化)、构建pytorch分类的网络类(线性层、dropout、激励函数)、训练循环、后向传播、学习率+调度、损失函数、优化器、保存和提取模型。准确率便能达到68%。

https://github.com/lzglzglzglzg/Wireless-program,这是当场第一的队伍的代码仓库,他们的准确率达到了惊人的99%。

迁移学习(Transfer Learning)

迁移学习的核心思想是使用在大规模数据集(如ImageNet)上预训练的模型,进行微调(fine-tuning)来适应新的任务。

# 冻结预训练模型的参数
for param in model.parameters():
    param.requires_grad = False

# 修改最后一层以适应新任务
model.fc = nn.Linear(model.fc.in_features, 10)  # 假设新任务有10个类别

这样,只需要训练模型最后的几层,利用预训练模型的知识。


总结

PyTorch 作为一种灵活的神经网络框架,支持从简单的线性堆叠到复杂的非线性网络拓扑构建。通过 nn.Sequential 可以快速搭建简单的模型,而通过继承 nn.Module,则可以自定义复杂的前向传播逻辑以适应各类任务需求。学习率、梯度下降、反向传播、超参数(如 epoch、batch)在网络训练中至关重要,Dropout 等正则化方法能有效防止过拟合。理解这些概念有助于高效地搭建和优化神经网络。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值