PyTorch实战:从零构建八大核心神经网络模型(CNN/RNN/GAN/Transformer等)

PyTorch实战:从零构建八大核心神经网络模型(CNN/RNN/GAN/Transformer等) 在深度学习领域快速迭代的今天掌握核心神经网络架构是每一位开发者、研究者乃至学生绕不开的课题。面对网络上零散的资料和复杂的理论很多初学者感到无从下手甚至中途放弃。本文旨在打破这一困境通过一套结构化的实战路径带你系统性地理解并实践八大核心神经网络模型。无论你是零基础的编程新手还是有一定机器学习经验想快速查漏补缺的开发者都能从本文获得从理论到代码的完整闭环体验。我们将从最基础的概念讲起手把手搭建环境并用PyTorch框架逐一实现CNN、RNN、GAN、GNN、DQN、Transformer、LSTM和DBN每个模型都配有可运行的代码和直观的结果分析让你在动手实践中真正“学透”。1. 深度学习与神经网络核心概念扫盲在深入具体模型之前我们有必要统一认知理解一些最基础但至关重要的概念。这能帮助我们在后续的学习中不被术语所困扰直击模型设计的本质。1.1 什么是深度学习深度学习是机器学习的一个子领域其核心特征是使用包含多个处理层即“深度”的神经网络模型来学习数据的多层次抽象表示。你可以把它想象成一个多层的特征加工流水线原始数据如图像像素、文字序列输入第一层这一层可能学会识别一些边缘、角落这些特征再输入第二层组合成更复杂的图案如眼睛、轮子如此层层递进最高层就能识别出“猫”、“汽车”这样的高级语义概念。这种“端到端”的学习能力使得深度学习在图像识别、自然语言处理等领域取得了革命性突破。1.2 神经网络的基本构件无论多么复杂的神经网络都由一些基本单元构成神经元Neuron模仿生物神经元是网络的基本计算单元。它接收一组输入进行加权求和再加上一个偏置最后通过一个非线性函数激活函数产生输出。层Layer神经元的集合。通常包括输入层Input Layer接收原始数据。隐藏层Hidden Layer进行特征变换和抽象的核心部分可以有一层或多层。输出层Output Layer产生最终的预测结果如分类概率、回归值。权重Weight 偏置Bias连接神经元之间的可调参数。模型学习的过程本质上就是通过数据不断调整这些参数使得网络的输出尽可能接近真实值。激活函数Activation Function引入非线性因素的关键。如果没有激活函数无论多少层网络都等价于一个线性变换无法学习复杂模式。常见的激活函数有Sigmoid、Tanh、ReLU及其变种。1.3 前馈神经网络一切的开端前馈神经网络FNN也称为多层感知机MLP是最简单、最经典的神经网络结构。数据从输入层开始单向逐层向前传播经过隐藏层最终到达输出层层与层之间全连接。它是理解其他复杂网络的基础。例如一个用于手写数字识别MNIST数据集的简单MLP其输入是784个像素点28x28经过若干隐藏层后输出10个神经元分别代表数字0-9的概率。2. 环境准备与工具链搭建“工欲善其事必先利其器”。一个稳定、一致的开发环境是高效学习的前提。我们将使用Python和PyTorch作为主要工具。2.1 基础环境配置首先确保你的计算机上安装了Python。推荐使用Python 3.8至3.10版本这些版本与主流深度学习库的兼容性最好。你可以通过以下命令检查python --version # 或 python3 --version强烈建议使用虚拟环境如venv或conda来管理项目依赖避免包冲突。# 使用 venv (Linux/macOS) python3 -m venv dl_env source dl_env/bin/activate # 使用 venv (Windows) python -m venv dl_env dl_env\Scripts\activate # 使用 conda (需先安装Anaconda或Miniconda) conda create -n dl_env python3.9 conda activate dl_env2.2 核心库安装激活虚拟环境后安装PyTorch及其相关库。请根据你的操作系统和是否有GPUCUDA访问 PyTorch官网 获取最准确的安装命令。以下是一个适用于无GPU或通用环境的CPU版本安装示例pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu然后安装其他必要的科学计算和可视化库pip install numpy pandas matplotlib scikit-learn jupyter2.3 验证安装与开发工具创建一个Python脚本或直接在Jupyter Notebook中运行以下代码验证环境是否正常import torch import torchvision import numpy as np import matplotlib.pyplot as plt print(fPyTorch版本: {torch.__version__}) print(fCUDA是否可用: {torch.cuda.is_available()}) # 如果CUDA可用会显示True并可以查看设备信息 if torch.cuda.is_available(): print(fCUDA设备: {torch.cuda.get_device_name(0)}) # 简单测试张量运算 x torch.rand(5, 3) y torch.ones(5, 3) z x y print(f随机张量x:\n{x}) print(f加法结果z:\n{z})如果一切正常你将看到PyTorch版本号、CUDA状态以及张量计算的结果。推荐使用VS Code或PyCharm作为集成开发环境IDE它们对Python和Jupyter的支持都非常友好。3. 卷积神经网络图像世界的王者卷积神经网络是计算机视觉的基石它通过“卷积”这一核心操作高效地处理图像这类网格状数据。3.1 CNN的核心思想与组件CNN的设计灵感来源于生物的视觉皮层。其三大核心思想是局部连接不像全连接网络那样每个神经元连接所有输入CNN的神经元只连接输入数据的局部区域如3x3的像素块这大幅减少了参数量。权值共享同一个卷积核滤波器会滑过整个输入图像这意味着无论特征出现在图像的哪个位置都由同一组权重来检测这赋予了模型平移不变性。池化下采样通过最大池化或平均池化操作降低特征图的空间尺寸从而减少计算量并增强模型对微小位置变化的鲁棒性。一个典型的CNN由卷积层、激活层、池化层交替堆叠最后接上全连接层进行分类。3.2 使用PyTorch构建一个CNN让我们用PyTorch构建一个经典的LeNet-5简化版来识别MNIST手写数字。import torch.nn as nn import torch.nn.functional as F class SimpleCNN(nn.Module): def __init__(self): super(SimpleCNN, self).__init__() # 卷积层1: 输入通道1(灰度图)输出通道6卷积核5x5 self.conv1 nn.Conv2d(in_channels1, out_channels6, kernel_size5) # 池化层1: 2x2窗口步长为2 self.pool nn.MaxPool2d(kernel_size2, stride2) # 卷积层2: 输入通道6输出通道16卷积核5x5 self.conv2 nn.Conv2d(in_channels6, out_channels16, kernel_size5) # 全连接层 # 经过两次卷积和池化后特征图尺寸计算(28-51)/212 - (12-51)/24 # 所以是 16 * 4 * 4 256 self.fc1 nn.Linear(in_features16 * 4 * 4, out_features120) self.fc2 nn.Linear(in_features120, out_features84) self.fc3 nn.Linear(in_features84, out_features10) # 10类输出 def forward(self, x): # 卷积 - 激活(ReLU) - 池化 x self.pool(F.relu(self.conv1(x))) x self.pool(F.relu(self.conv2(x))) # 展平特征图为一维向量 x x.view(-1, 16 * 4 * 4) # 全连接层 x F.relu(self.fc1(x)) x F.relu(self.fc2(x)) x self.fc3(x) # 最后输出层通常不加激活函数配合CrossEntropyLoss使用 return x # 实例化模型 model SimpleCNN() print(model)接下来我们加载数据、定义损失函数和优化器并进行训练。import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader # 数据预处理和加载 transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) # MNIST的均值和标准差 ]) train_dataset datasets.MNIST(root./data, trainTrue, downloadTrue, transformtransform) test_dataset datasets.MNIST(root./data, trainFalse, downloadTrue, transformtransform) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue) test_loader DataLoader(test_dataset, batch_size1000, shuffleFalse) # 定义损失函数和优化器 criterion nn.CrossEntropyLoss() optimizer optim.SGD(model.parameters(), lr0.01, momentum0.9) # 训练循环 def train(model, device, train_loader, optimizer, epoch): model.train() for batch_idx, (data, target) in enumerate(train_loader): data, target data.to(device), target.to(device) optimizer.zero_grad() # 梯度清零 output model(data) # 前向传播 loss criterion(output, target) # 计算损失 loss.backward() # 反向传播 optimizer.step() # 更新参数 if batch_idx % 100 0: print(fTrain Epoch: {epoch} [{batch_idx * len(data)}/{len(train_loader.dataset)} ({100. * batch_idx / len(train_loader):.0f}%)]\tLoss: {loss.item():.6f}) # 测试函数 def test(model, device, test_loader): model.eval() test_loss 0 correct 0 with torch.no_grad(): # 测试时不计算梯度 for data, target in test_loader: data, target data.to(device), target.to(device) output model(data) test_loss criterion(output, target).item() # 累加损失 pred output.argmax(dim1, keepdimTrue) # 获取预测结果 correct pred.eq(target.view_as(pred)).sum().item() test_loss / len(test_loader.dataset) accuracy 100. * correct / len(test_loader.dataset) print(f\nTest set: Average loss: {test_loss:.4f}, Accuracy: {correct}/{len(test_loader.dataset)} ({accuracy:.2f}%)\n) return accuracy # 选择设备CPU或GPU device torch.device(cuda if torch.cuda.is_available() else cpu) model.to(device) # 开始训练和测试 for epoch in range(1, 6): # 训练5个epoch train(model, device, train_loader, optimizer, epoch) test(model, device, test_loader)运行这段代码你将看到模型在MNIST测试集上的准确率很快就能达到98%以上。这个简单的例子展示了CNN强大的特征提取能力。4. 循环神经网络与长短期记忆网络序列建模专家对于文本、语音、时间序列等具有前后依赖关系的序列数据前馈网络和CNN就力不从心了。循环神经网络应运而生。4.1 RNN的基本原理与局限RNN的核心思想是引入“循环”结构使网络具有记忆能力。它维护一个隐藏状态这个状态会在处理序列的每个元素时被更新并传递到下一个时间步从而捕获历史信息。import torch.nn as nn class SimpleRNN(nn.Module): def __init__(self, input_size, hidden_size, output_size): super(SimpleRNN, self).__init__() self.hidden_size hidden_size # RNN层 self.rnn nn.RNN(input_size, hidden_size, batch_firstTrue) # 全连接输出层 self.fc nn.Linear(hidden_size, output_size) def forward(self, x): # x shape: (batch_size, sequence_length, input_size) out, hidden self.rnn(x) # out: (batch, seq_len, hidden_size) # 我们取最后一个时间步的输出用于分类/预测 out self.fc(out[:, -1, :]) return out然而标准RNN存在梯度消失和梯度爆炸问题导致其难以学习长距离依赖关系。当序列很长时早期的信息在反向传播过程中梯度会变得极其微小或巨大从而无法有效更新网络参数。4.2 LSTM解决长距离依赖的利器长短期记忆网络通过引入精妙的“门控机制”解决了RNN的长期依赖问题。LSTM单元包含三个门遗忘门决定从细胞状态中丢弃哪些信息。输入门决定哪些新信息被存入细胞状态。输出门基于细胞状态决定输出什么。class SimpleLSTM(nn.Module): def __init__(self, input_size, hidden_size, output_size, num_layers1): super(SimpleLSTM, self).__init__() self.hidden_size hidden_size self.num_layers num_layers # LSTM层 self.lstm nn.LSTM(input_size, hidden_size, num_layers, batch_firstTrue) # 全连接输出层 self.fc nn.Linear(hidden_size, output_size) def forward(self, x): # 初始化隐藏状态和细胞状态 h0 torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device) c0 torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device) # LSTM前向传播 out, (hn, cn) self.lstm(x, (h0, c0)) # out: (batch, seq_len, hidden_size) # 取最后一个时间步的隐藏状态 out self.fc(out[:, -1, :]) return outPyTorch中nn.LSTM的使用与nn.RNN非常相似但它内部已经实现了复杂的门控计算。LSTM在机器翻译、文本生成、股票预测等任务上表现出色。5. 生成对抗网络无监督学习的魔术师GAN的核心思想非常巧妙它让两个神经网络——生成器和判别器——在相互对抗中共同进步。5.1 GAN的基本框架生成器接收一个随机噪声向量目标是生成足以“以假乱真”的数据如图像。判别器接收一张图片可能来自真实数据集也可能来自生成器目标是判断它是“真实的”还是“生成的”。这个过程就像一个伪造者生成器不断精进技艺以骗过鉴定专家判别器而鉴定专家也在不断学习以识别更高级的赝品。两者在动态博弈中达到纳什均衡此时生成器产生的数据分布与真实数据分布几乎一致。5.2 实现一个简单的GAN我们以实现一个生成手写数字的GAN为例。import torch import torch.nn as nn # 定义生成器 class Generator(nn.Module): def __init__(self, latent_dim, img_shape): super(Generator, self).__init__() self.img_shape img_shape def block(in_feat, out_feat, normalizeTrue): layers [nn.Linear(in_feat, out_feat)] if normalize: layers.append(nn.BatchNorm1d(out_feat, 0.8)) layers.append(nn.LeakyReLU(0.2, inplaceTrue)) return layers self.model nn.Sequential( *block(latent_dim, 128, normalizeFalse), *block(128, 256), *block(256, 512), *block(512, 1024), nn.Linear(1024, int(torch.prod(torch.tensor(img_shape)))), nn.Tanh() # 输出在[-1,1]之间与归一化后的输入匹配 ) def forward(self, z): img self.model(z) img img.view(img.size(0), *self.img_shape) return img # 定义判别器 class Discriminator(nn.Module): def __init__(self, img_shape): super(Discriminator, self).__init__() self.model nn.Sequential( nn.Linear(int(torch.prod(torch.tensor(img_shape))), 512), nn.LeakyReLU(0.2, inplaceTrue), nn.Linear(512, 256), nn.LeakyReLU(0.2, inplaceTrue), nn.Linear(256, 1), nn.Sigmoid(), # 输出一个0到1的概率表示图像为真的置信度 ) def forward(self, img): img_flat img.view(img.size(0), -1) validity self.model(img_flat) return validity # 超参数 latent_dim 100 img_shape (1, 28, 28) # MNIST图像形状 # 初始化模型 generator Generator(latent_dim, img_shape) discriminator Discriminator(img_shape) # 定义损失函数和优化器 adversarial_loss nn.BCELoss() # 二分类交叉熵损失 optimizer_G torch.optim.Adam(generator.parameters(), lr0.0002, betas(0.5, 0.999)) optimizer_D torch.optim.Adam(discriminator.parameters(), lr0.0002, betas(0.5, 0.999)) # 训练循环核心部分 for epoch in range(num_epochs): for i, (imgs, _) in enumerate(dataloader): # 真实图像和标签 real_imgs imgs.to(device) valid torch.ones(imgs.size(0), 1).to(device) # 真实标签为1 fake torch.zeros(imgs.size(0), 1).to(device) # 生成标签为0 # --------------------- # 训练判别器 # --------------------- optimizer_D.zero_grad() # 计算真实图像的损失 real_loss adversarial_loss(discriminator(real_imgs), valid) # 生成假图像 z torch.randn(imgs.size(0), latent_dim).to(device) gen_imgs generator(z) # 计算假图像的损失 fake_loss adversarial_loss(discriminator(gen_imgs.detach()), fake) # 判别器总损失 d_loss (real_loss fake_loss) / 2 d_loss.backward() optimizer_D.step() # ----------------- # 训练生成器 # ----------------- optimizer_G.zero_grad() # 生成器希望判别器将假图像判断为真 g_loss adversarial_loss(discriminator(gen_imgs), valid) g_loss.backward() optimizer_G.step()训练过程中可以定期保存生成器产生的图像观察其从噪声到清晰数字的演变过程非常直观。GAN的训练不稳定是出了名的需要仔细调整超参数和网络结构。6. Transformer与注意力机制革命性的序列模型Transformer完全摒弃了RNN和CNN的循环与卷积结构仅依赖自注意力机制来处理序列在并行计算和长距离依赖建模上取得了巨大成功成为当今NLP乃至CV领域的主流架构。6.1 自注意力机制详解自注意力机制的核心是让序列中的每个元素如一个单词与序列中的所有元素包括自己进行交互通过计算“注意力分数”来决定在编码当前元素时应该“关注”其他元素的多少信息。计算过程简述线性变换对输入序列的每个词嵌入向量通过三个不同的权重矩阵生成查询向量、键向量和值向量。计算注意力分数通过查询向量与所有键向量的点积得到未归一化的注意力分数。缩放与归一化将分数除以键向量维度的平方根为了稳定梯度然后通过Softmax函数归一化得到注意力权重和为1。加权求和用注意力权重对所有的值向量进行加权求和得到当前元素的输出。6.2 构建一个简化的Transformer编码器层虽然完整的Transformer包含编码器和解码器但其核心是相同的多头自注意力层和前馈网络。import torch.nn as nn import torch.nn.functional as F import math class MultiHeadAttention(nn.Module): def __init__(self, d_model, num_heads): super(MultiHeadAttention, self).__init__() assert d_model % num_heads 0 self.d_model d_model self.num_heads num_heads self.d_k d_model // num_heads self.W_q nn.Linear(d_model, d_model) self.W_k nn.Linear(d_model, d_model) self.W_v nn.Linear(d_model, d_model) self.W_o nn.Linear(d_model, d_model) def scaled_dot_product_attention(self, Q, K, V, maskNone): # Q, K, V shape: (batch_size, num_heads, seq_len, d_k) scores torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k) if mask is not None: scores scores.masked_fill(mask 0, -1e9) attn_weights F.softmax(scores, dim-1) output torch.matmul(attn_weights, V) return output, attn_weights def forward(self, query, key, value, maskNone): batch_size query.size(0) # 线性变换并分头 Q self.W_q(query).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2) K self.W_k(key).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2) V self.W_v(value).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2) # 计算注意力 attn_output, attn_weights self.scaled_dot_product_attention(Q, K, V, mask) # 合并多头 attn_output attn_output.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model) # 输出线性变换 output self.W_o(attn_output) return output, attn_weights class TransformerEncoderLayer(nn.Module): def __init__(self, d_model, num_heads, dim_feedforward2048, dropout0.1): super(TransformerEncoderLayer, self).__init__() self.self_attn MultiHeadAttention(d_model, num_heads) self.linear1 nn.Linear(d_model, dim_feedforward) self.dropout nn.Dropout(dropout) self.linear2 nn.Linear(dim_feedforward, d_model) self.norm1 nn.LayerNorm(d_model) self.norm2 nn.LayerNorm(d_model) self.dropout1 nn.Dropout(dropout) self.dropout2 nn.Dropout(dropout) self.activation F.relu def forward(self, src, src_maskNone): # 自注意力子层 src2, _ self.self_attn(src, src, src, src_mask) src src self.dropout1(src2) src self.norm1(src) # 前馈网络子层 src2 self.linear2(self.dropout(self.activation(self.linear1(src)))) src src self.dropout2(src2) src self.norm2(src) return src这个编码器层包含了Transformer的核心多头自注意力、残差连接、层归一化和前馈网络。基于此构建的模型如BERT、GPT彻底改变了自然语言处理领域。7. 图神经网络、深度Q网络与深度信念网络概览由于篇幅所限我们简要介绍另外三个重要网络的核心思想与应用场景。7.1 图神经网络图神经网络专门用于处理图结构数据即由节点和边构成的数据。其核心思想是通过聚合邻居节点的信息来更新当前节点的表示。核心操作消息传递、邻居聚合。典型架构图卷积网络、图注意力网络。应用场景社交网络分析、推荐系统、分子性质预测、交通流量预测。7.2 深度Q网络DQN是深度强化学习中的里程碑式算法它将深度学习与Q-Learning结合用于解决高维状态空间下的决策问题。核心思想使用深度神经网络来近似Q值函数即评估在某个状态下采取某个动作的长期收益。关键技术经验回放、目标网络用于稳定训练。应用场景游戏AI如Atari游戏、AlphaGo、机器人控制、资源调度。7.3 深度信念网络DBN是由多个受限玻尔兹曼机堆叠而成的生成模型可以通过无监督的逐层预训练来初始化深度网络的权重是深度学习早期复兴的重要推动力之一。核心思想每层RBM学习数据的不同抽象特征底层学习边缘高层学习更复杂的组合。训练方式对比散度算法进行无监督预训练然后用反向传播进行有监督微调。应用场景虽然现在较少用作最终模型但其预训练思想影响深远常用于特征提取、降维。8. 常见问题与实战排错指南在学习和实践过程中你一定会遇到各种问题。以下是一些高频问题的排查思路。问题现象可能原因排查与解决思路Loss值为NaN或无限大1. 学习率设置过高。2. 数据未归一化或存在异常值。3. 网络层中除法运算分母可能为0。4. 梯度爆炸。1. 降低学习率尝试1e-3, 1e-4等。2. 检查输入数据进行标准化减均值除标准差。3. 在代码中避免除零使用torch.clamp进行限制。4. 使用梯度裁剪torch.nn.utils.clip_grad_norm_。模型不收敛Loss居高不下1. 学习率过低。2. 模型架构过于简单无法拟合数据。3. 损失函数选用错误如分类问题用了MSE。4. 数据标签错误或噪声太大。1. 适当增加学习率或使用学习率调度器。2. 增加网络深度或宽度。3. 检查任务类型分类用交叉熵回归用MSE/MAE。4. 检查数据集进行数据清洗。过拟合训练集精度高测试集精度低1. 模型复杂度过高。2. 训练数据量不足。3. 训练轮次过多。1. 使用Dropout、权重衰减L2正则化。2. 进行数据增强如图像旋转、裁剪。3. 使用早停法在验证集性能不再提升时停止训练。GPU内存溢出1. Batch Size设置过大。2. 模型参数量或中间激活值过大。3. 存在内存泄漏如张量长期不释放。1. 减小Batch Size。2. 使用更小的模型或检查网络结构是否冗余。3. 确保在验证/测试时使用with torch.no_grad()及时将不需要的张量移出GPU.cpu()或删除del。GAN训练模式崩溃生成器只生成少数几种样本多样性极差。1. 尝试使用WGAN-GP、LSGAN等改进的损失函数。2. 调整生成器和判别器的学习率有时需要让判别器“弱”一些。3. 在判别器中使用谱归一化。9. 工程最佳实践与学习路线建议掌握了基础模型后如何将其应用于实际项目并持续精进9.1 项目开发最佳实践模块化与可复现性将数据加载、模型定义、训练循环、评估指标等拆分为独立模块。使用argparse或配置文件管理超参数并记录每次实验的完整配置和随机种子。版本控制务必使用Git管理代码。模型检查点、训练日志、可视化结果也应有序保存。持续监控与可视化使用TensorBoard或Weights Biases等工具实时监控训练过程中的Loss、准确率等指标可视化模型结构、参数分布和生成的样本。自动化测试为数据预处理、模型前向传播等关键函数编写单元测试确保代码修改不会引入错误。渐进式开发从一个极简的模型如线性层和少量数据开始确保pipeline能跑通再逐步增加复杂度。9.2 深入学习的路线图巩固基础彻底理解本文涉及的八大网络并能手推其前向传播过程。精读《深度学习》花书和《神经网络与深度学习》等经典教材的相关章节。跟进前沿关注顶级会议NeurIPS, ICML, ICLR, CVPR, ACL的最新论文。从复现经典论文代码开始如去GitHub找PyTorch实现。专精领域CV方向深入ResNet、EfficientNet、Vision Transformer、目标检测YOLO、Faster R-CNN、图像分割U-Net、Mask R-CNN。NLP方向深入BERT、GPT系列、T5等预训练模型掌握微调、提示学习、向量检索等技术。强化学习方向深入PPO、SAC、DDPG等算法。图学习方向深入GCN、GAT、GraphSAGE等模型。工程化能力学习模型量化、剪枝、蒸馏等模型压缩技术以及使用ONNX、TensorRT等进行模型部署了解如何在云平台或边缘设备上提供服务。从理解原理到跑通代码再到解决实际问题每一步都需要动手实践和反复思考。建议你以本文的代码为起点尝试更换数据集如CIFAR-10之于CNN、修改网络结构、调整超参数观察模型性能的变化这是积累经验最快的方式。遇到报错时善用搜索引擎和开源社区如Stack Overflow, PyTorch论坛绝大多数问题都有前人遇到过。