这次我们来看一个2026年最新的深度学习入门实战教程重点不是讲复杂理论而是带你在本地环境跑通CNN、RNN、Transformer、GAN等八大核心算法。如果你关心代码能不能运行、显存占用多少、数据集怎么准备、训练效果如何验证这篇文章可以直接收藏备用。深度学习入门最大的门槛往往不是数学公式而是环境配置、数据准备和调试排错。这个教程覆盖了从卷积神经网络到Transformer的完整实战流程每个算法都提供可运行的代码、标准数据集和效果验证方法。我们将使用PyTorch框架在CPU和GPU环境下分别测试重点关注模型训练的资源占用、收敛速度和实际效果。本文适合有一定Python基础想系统掌握深度学习实战的开发者。我们将从环境准备开始逐步完成图像分类、文本生成、序列预测、图像生成等典型任务每个部分都包含可复现的代码和效果对比。1. 核心能力速览能力项说明覆盖算法CNN、RNN、LSTM、GRU、Transformer、GAN、AutoEncoder、ResNet深度学习框架PyTorch 2.0兼容TensorFlow/Keras硬件要求GPU推荐8G显存CPU可运行但训练较慢显存占用小型模型1-2G大型模型4-8G按实际batch size调整数据集MNIST、CIFAR-10、IMDB、COCO等标准数据集启动方式Jupyter Notebook Python脚本支持命令行训练API接口支持模型导出和API服务部署批量任务支持数据预处理、模型训练、批量推理流水线适合场景深度学习入门、算法对比实验、课程项目开发2. 适用场景与使用边界这个实战教程主要解决深度学习初学者面临的几个核心问题代码跑不通、效果复现不了、不知道如何调试。通过八个经典算法的完整实现你可以快速建立对深度学习工作流的直观认识。适合的学习路径计算机视觉入门CNN → ResNet → GAN自然语言处理入门RNN → LSTM → Transformer生成模型研究AutoEncoder → GAN → Diffusion扩展需要避免的误区不要期望一次运行就达到论文中的SOTA效果小显存设备建议从MNIST/CIFAR-10等小数据集开始涉及人脸生成、文本生成等内容时务必遵守数据使用规范技术边界说明本教程基于公开数据集避免版权风险生成模型输出仅供学习研究商用需额外授权所有代码提供完整的错误处理和资源释放3. 环境准备与前置条件3.1 基础软件环境# Python环境推荐3.8-3.10 python --version # 输出Python 3.9.18 # 包管理工具 pip --version # 输出pip 23.3.13.2 深度学习框架选择# PyTorch安装根据CUDA版本选择 # CUDA 11.8 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 或CPU版本 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu # 验证安装 python -c import torch; print(torch.__version__); print(torch.cuda.is_available())3.3 额外依赖包# requirements.txt内容 matplotlib3.5.0 numpy1.21.0 pandas1.3.0 scikit-learn1.0.0 jupyter1.0.0 tqdm4.60.0 Pillow9.0.0 opencv-python4.5.03.4 硬件检查清单# hardware_check.py import torch import psutil print(fPyTorch版本: {torch.__version__}) print(fCUDA可用: {torch.cuda.is_available()}) if torch.cuda.is_available(): print(fGPU设备: {torch.cuda.get_device_name(0)}) print(f显存大小: {torch.cuda.get_device_properties(0).total_memory / 1024**3:.1f} GB) print(fCPU核心数: {psutil.cpu_count()}) print(f内存大小: {psutil.virtual_memory().total / 1024**3:.1f} GB)4. CNN卷积神经网络实战4.1 项目结构设计deep_learning_tutorial/ ├── data/ # 数据集目录 │ ├── mnist/ # 手写数字数据集 │ └── cifar10/ # 物体分类数据集 ├── models/ # 模型定义 │ ├── cnn.py # CNN网络结构 │ ├── rnn.py # RNN系列模型 │ └── transformer.py # Transformer模型 ├── utils/ # 工具函数 │ ├── data_loader.py # 数据加载 │ └── trainer.py # 训练循环 ├── notebooks/ # Jupyter笔记本 └── scripts/ # 训练脚本4.2 基础CNN模型实现# models/cnn.py import torch import torch.nn as nn import torch.nn.functional as F class SimpleCNN(nn.Module): def __init__(self, num_classes10): super(SimpleCNN, self).__init__() self.conv1 nn.Conv2d(1, 32, 3, padding1) # MNIST: 1通道 self.conv2 nn.Conv2d(32, 64, 3, padding1) self.pool nn.MaxPool2d(2, 2) self.fc1 nn.Linear(64 * 7 * 7, 128) # 经过两次池化: 28→14→7 self.fc2 nn.Linear(128, num_classes) self.dropout nn.Dropout(0.5) def forward(self, x): x self.pool(F.relu(self.conv1(x))) # [1,28,28] → [32,14,14] x self.pool(F.relu(self.conv2(x))) # [32,14,14] → [64,7,7] x x.view(-1, 64 * 7 * 7) # 展平 x F.relu(self.fc1(x)) x self.dropout(x) x self.fc2(x) return x # 模型测试 if __name__ __main__: model SimpleCNN() x torch.randn(1, 1, 28, 28) # 模拟MNIST输入 print(f输入形状: {x.shape}) output model(x) print(f输出形状: {output.shape}) print(f参数数量: {sum(p.numel() for p in model.parameters()):,})4.3 数据加载与预处理# utils/data_loader.py import torch from torchvision import datasets, transforms from torch.utils.data import DataLoader def get_mnist_loaders(batch_size64, downloadTrue): MNIST数据加载器 transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) train_dataset datasets.MNIST( ./data/mnist, trainTrue, downloaddownload, transformtransform ) test_dataset datasets.MNIST( ./data/mnist, trainFalse, downloaddownload, transformtransform ) train_loader DataLoader(train_dataset, batch_sizebatch_size, shuffleTrue) test_loader DataLoader(test_dataset, batch_sizebatch_size, shuffleFalse) return train_loader, test_loader def get_cifar10_loaders(batch_size64, downloadTrue): CIFAR-10数据加载器 transform_train transforms.Compose([ transforms.RandomHorizontalFlip(), transforms.RandomCrop(32, padding4), transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)) ]) transform_test transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)) ]) train_dataset datasets.CIFAR10( ./data/cifar10, trainTrue, downloaddownload, transformtransform_train ) test_dataset datasets.CIFAR10( ./data/cifar10, trainFalse, downloaddownload, transformtransform_test ) train_loader DataLoader(train_dataset, batch_sizebatch_size, shuffleTrue) test_loader DataLoader(test_dataset, batch_sizebatch_size, shuffleFalse) return train_loader, test_loader4.4 训练循环实现# utils/trainer.py import torch import torch.nn as nn from tqdm import tqdm import matplotlib.pyplot as plt class Trainer: def __init__(self, model, optimizer, criterion, devicecuda): self.model model self.optimizer optimizer self.criterion criterion self.device device self.model.to(device) self.train_losses [] self.train_accuracies [] self.test_losses [] self.test_accuracies [] def train_epoch(self, train_loader): self.model.train() running_loss 0.0 correct 0 total 0 pbar tqdm(train_loader, desc训练中) for batch_idx, (data, target) in enumerate(pbar): data, target data.to(self.device), target.to(self.device) self.optimizer.zero_grad() output self.model(data) loss self.criterion(output, target) loss.backward() self.optimizer.step() running_loss loss.item() _, predicted output.max(1) total target.size(0) correct predicted.eq(target).sum().item() pbar.set_postfix({ Loss: f{loss.item():.4f}, Acc: f{100.*correct/total:.2f}% }) epoch_loss running_loss / len(train_loader) epoch_acc 100. * correct / total return epoch_loss, epoch_acc def test_epoch(self, test_loader): self.model.eval() running_loss 0.0 correct 0 total 0 with torch.no_grad(): for data, target in test_loader: data, target data.to(self.device), target.to(self.device) output self.model(data) loss self.criterion(output, target) running_loss loss.item() _, predicted output.max(1) total target.size(0) correct predicted.eq(target).sum().item() epoch_loss running_loss / len(test_loader) epoch_acc 100. * correct / total return epoch_loss, epoch_acc def fit(self, train_loader, test_loader, epochs10): for epoch in range(epochs): print(f\nEpoch {epoch1}/{epochs}) train_loss, train_acc self.train_epoch(train_loader) test_loss, test_acc self.test_epoch(test_loader) self.train_losses.append(train_loss) self.train_accuracies.append(train_acc) self.test_losses.append(test_loss) self.test_accuracies.append(test_acc) print(f训练损失: {train_loss:.4f}, 训练准确率: {train_acc:.2f}%) print(f测试损失: {test_loss:.4f}, 测试准确率: {test_acc:.2f}%) def plot_results(self): fig, (ax1, ax2) plt.subplots(1, 2, figsize(12, 4)) ax1.plot(self.train_losses, label训练损失) ax1.plot(self.test_losses, label测试损失) ax1.set_xlabel(Epoch) ax1.set_ylabel(Loss) ax1.legend() ax1.grid(True) ax2.plot(self.train_accuracies, label训练准确率) ax2.plot(self.test_accuracies, label测试准确率) ax2.set_xlabel(Epoch) ax2.set_ylabel(Accuracy (%)) ax2.legend() ax2.grid(True) plt.tight_layout() plt.show()5. RNN循环神经网络实战5.1 文本数据处理流程# utils/text_processor.py import torch from torchtext.data import get_tokenizer from collections import Counter import re class TextProcessor: def __init__(self, max_vocab_size10000, min_freq2): self.tokenizer get_tokenizer(basic_english) self.max_vocab_size max_vocab_size self.min_freq min_freq self.vocab None self.vocab_size 0 def build_vocab(self, texts): 构建词汇表 counter Counter() for text in texts: tokens self.tokenizer(self.clean_text(text)) counter.update(tokens) # 保留最常见的词 vocab [pad, unk] # 填充符和未知词 vocab.extend([word for word, count in counter.most_common(self.max_vocab_size) if count self.min_freq]) self.vocab {word: idx for idx, word in enumerate(vocab)} self.vocab_size len(vocab) return self.vocab def clean_text(self, text): 文本清洗 text text.lower() text re.sub(r[^a-zA-Z\s], , text) return text def text_to_sequence(self, text, max_length100): 文本转序列 tokens self.tokenizer(self.clean_text(text)) sequence [self.vocab.get(token, self.vocab[unk]) for token in tokens] # 填充或截断 if len(sequence) max_length: sequence.extend([self.vocab[pad]] * (max_length - len(sequence))) else: sequence sequence[:max_length] return torch.tensor(sequence)5.2 LSTM模型实现# models/rnn.py import torch import torch.nn as nn class TextLSTM(nn.Module): def __init__(self, vocab_size, embedding_dim100, hidden_dim128, output_dim1, num_layers2, dropout0.5): super(TextLSTM, self).__init__() self.embedding nn.Embedding(vocab_size, embedding_dim, padding_idx0) self.lstm nn.LSTM(embedding_dim, hidden_dim, num_layersnum_layers, batch_firstTrue, dropoutdropout, bidirectionalTrue) self.fc nn.Linear(hidden_dim * 2, output_dim) # 双向LSTM self.dropout nn.Dropout(dropout) def forward(self, text, text_lengths): # text: [batch_size, seq_len] embedded self.embedding(text) # [batch_size, seq_len, emb_dim] # 打包序列处理变长输入 packed_embedded nn.utils.rnn.pack_padded_sequence( embedded, text_lengths.cpu(), batch_firstTrue, enforce_sortedFalse ) packed_output, (hidden, cell) self.lstm(packed_embedded) output, output_lengths nn.utils.rnn.pad_packed_sequence(packed_output, batch_firstTrue) # 取最后一个有效时间步的输出 hidden self.dropout(torch.cat((hidden[-2,:,:], hidden[-1,:,:]), dim1)) return self.fc(hidden)6. Transformer模型实战6.1 自注意力机制实现# models/transformer.py import torch import torch.nn as nn import math class MultiHeadAttention(nn.Module): def __init__(self, d_model, num_heads, dropout0.1): 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) self.dropout nn.Dropout(dropout) def scaled_dot_product_attention(self, q, k, v, maskNone): attn_scores torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.d_k) if mask is not None: attn_scores attn_scores.masked_fill(mask 0, -1e9) attn_weights torch.softmax(attn_scores, dim-1) attn_weights self.dropout(attn_weights) output torch.matmul(attn_weights, v) return output, attn_weights def forward(self, q, k, v, maskNone): batch_size, seq_len q.size(0), q.size(1) # 线性变换并分头 q self.w_q(q).view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2) k self.w_k(k).view(batch_size, seq_len, self.num_heads, self.d_k).transpose(1, 2) v self.w_v(v).view(batch_size, seq_len, 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, seq_len, self.d_model ) # 输出线性变换 output self.w_o(attn_output) return output, attn_weights6.2 Transformer编码器实现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, dropout) self.linear1 nn.Linear(d_model, dim_feedforward) self.linear2 nn.Linear(dim_feedforward, d_model) self.norm1 nn.LayerNorm(d_model) self.norm2 nn.LayerNorm(d_model) self.dropout nn.Dropout(dropout) self.activation nn.ReLU() def forward(self, src, src_maskNone): # 自注意力子层 attn_output, _ self.self_attn(src, src, src, src_mask) src src self.dropout(attn_output) src self.norm1(src) # 前馈网络子层 ff_output self.linear2(self.dropout(self.activation(self.linear1(src)))) src src self.dropout(ff_output) src self.norm2(src) return src class TransformerEncoder(nn.Module): def __init__(self, vocab_size, d_model512, num_heads8, num_layers6, dim_feedforward2048, dropout0.1, max_seq_len100): super(TransformerEncoder, self).__init__() self.token_embedding nn.Embedding(vocab_size, d_model) self.position_embedding nn.Embedding(max_seq_len, d_model) self.layers nn.ModuleList([ TransformerEncoderLayer(d_model, num_heads, dim_feedforward, dropout) for _ in range(num_layers) ]) self.dropout nn.Dropout(dropout) def forward(self, src, src_maskNone): batch_size, seq_len src.size() # 词嵌入 位置编码 token_emb self.token_embedding(src) positions torch.arange(seq_len, devicesrc.device).unsqueeze(0).expand(batch_size, seq_len) pos_emb self.position_embedding(positions) x self.dropout(token_emb pos_emb) # 通过编码器层 for layer in self.layers: x layer(x, src_mask) return x7. GAN生成对抗网络实战7.1 生成器和判别器设计# models/gan.py import torch import torch.nn as nn class Generator(nn.Module): def __init__(self, latent_dim100, img_channels1, feature_size64): super(Generator, self).__init__() self.main nn.Sequential( # 输入: latent_dim维噪声 nn.ConvTranspose2d(latent_dim, feature_size * 8, 4, 1, 0, biasFalse), nn.BatchNorm2d(feature_size * 8), nn.ReLU(True), # 状态: (feature_size*8) x 4 x 4 nn.ConvTranspose2d(feature_size * 8, feature_size * 4, 4, 2, 1, biasFalse), nn.BatchNorm2d(feature_size * 4), nn.ReLU(True), # 状态: (feature_size*4) x 8 x 8 nn.ConvTranspose2d(feature_size * 4, feature_size * 2, 4, 2, 1, biasFalse), nn.BatchNorm2d(feature_size * 2), nn.ReLU(True), # 状态: (feature_size*2) x 16 x 16 nn.ConvTranspose2d(feature_size * 2, feature_size, 4, 2, 1, biasFalse), nn.BatchNorm2d(feature_size), nn.ReLU(True), # 状态: (feature_size) x 32 x 32 nn.ConvTranspose2d(feature_size, img_channels, 4, 2, 1, biasFalse), nn.Tanh() # 输出: img_channels x 64 x 64 ) def forward(self, input): return self.main(input) class Discriminator(nn.Module): def __init__(self, img_channels1, feature_size64): super(Discriminator, self).__init__() self.main nn.Sequential( # 输入: img_channels x 64 x 64 nn.Conv2d(img_channels, feature_size, 4, 2, 1, biasFalse), nn.LeakyReLU(0.2, inplaceTrue), # 状态: feature_size x 32 x 32 nn.Conv2d(feature_size, feature_size * 2, 4, 2, 1, biasFalse), nn.BatchNorm2d(feature_size * 2), nn.LeakyReLU(0.2, inplaceTrue), # 状态: (feature_size*2) x 16 x 16 nn.Conv2d(feature_size * 2, feature_size * 4, 4, 2, 1, biasFalse), nn.BatchNorm2d(feature_size * 4), nn.LeakyReLU(0.2, inplaceTrue), # 状态: (feature_size*4) x 8 x 8 nn.Conv2d(feature_size * 4, feature_size * 8, 4, 2, 1, biasFalse), nn.BatchNorm2d(feature_size * 8), nn.LeakyReLU(0.2, inplaceTrue), # 状态: (feature_size*8) x 4 x 4 nn.Conv2d(feature_size * 8, 1, 4, 1, 0, biasFalse), nn.Sigmoid() ) def forward(self, input): return self.main(input).view(-1, 1).squeeze(1)7.2 GAN训练流程# scripts/train_gan.py import torch import torch.nn as nn import torch.optim as optim from torchvision.utils import save_image import os def train_gan(generator, discriminator, dataloader, device, epochs100, lr0.0002, latent_dim100, save_interval10): # 优化器 g_optimizer optim.Adam(generator.parameters(), lrlr, betas(0.5, 0.999)) d_optimizer optim.Adam(discriminator.parameters(), lrlr, betas(0.5, 0.999)) # 损失函数 criterion nn.BCELoss() # 创建输出目录 os.makedirs(gan_results, exist_okTrue) for epoch in range(epochs): for i, (real_imgs, _) in enumerate(dataloader): batch_size real_imgs.size(0) real_imgs real_imgs.to(device) # 真实标签和假标签 real_labels torch.ones(batch_size, devicedevice) fake_labels torch.zeros(batch_size, devicedevice) # 训练判别器 d_optimizer.zero_grad() # 真实图像损失 real_output discriminator(real_imgs) d_loss_real criterion(real_output, real_labels) # 生成假图像 z torch.randn(batch_size, latent_dim, 1, 1, devicedevice) fake_imgs generator(z) fake_output discriminator(fake_imgs.detach()) d_loss_fake criterion(fake_output, fake_labels) # 判别器总损失 d_loss d_loss_real d_loss_fake d_loss.backward() d_optimizer.step() # 训练生成器 g_optimizer.zero_grad() fake_output discriminator(fake_imgs) g_loss criterion(fake_output, real_labels) # 骗过判别器 g_loss.backward() g_optimizer.step() if i % 100 0: print(fEpoch [{epoch}/{epochs}], Step [{i}/{len(dataloader)}], fD_loss: {d_loss.item():.4f}, G_loss: {g_loss.item():.4f}) # 保存生成的图像 if epoch % save_interval 0: with torch.no_grad(): test_z torch.randn(64, latent_dim, 1, 1, devicedevice) generated generator(test_z) save_image(generated, fgan_results/epoch_{epoch}.png, nrow8, normalizeTrue)8. 资源占用与性能观察8.1 显存监控工具# utils/monitor.py import torch import psutil import GPUtil from threading import Thread import time class ResourceMonitor: def __init__(self, interval5): self.interval interval self.monitoring False self.data { gpu_usage: [], gpu_memory: [], cpu_usage: [], memory_usage: [] } def start_monitoring(self): self.monitoring True self.thread Thread(targetself._monitor_loop) self.thread.start() def stop_monitoring(self): self.monitoring False self.thread.join() def _monitor_loop(self): while self.monitoring: try: # GPU监控 gpus GPUtil.getGPUs() if gpus: gpu gpus[0] self.data[gpu_usage].append(gpu.load * 100) self.data[gpu_memory].append(gpu.memoryUtil * 100) # CPU和内存监控 self.data[cpu_usage].append(psutil.cpu_percent()) self.data[memory_usage].append(psutil.virtual_memory().percent) except Exception as e: print(f监控错误: {e}) time.sleep(self.interval) def print_summary(self): if self.data[gpu_usage]: print(fGPU平均使用率: {sum(self.data[gpu_usage])/len(self.data[gpu_usage]):.1f}%) print(fGPU平均显存: {sum(self.data[gpu_memory])/len(self.data[gpu_memory]):.1f}%) print(fCPU平均使用率: {sum(self.data[cpu_usage])/len(self.data[cpu_usage]):.1f}%) print(f内存平均使用率: {sum(self.data[memory_usage])/len(self.data[memory_usage]):.1f}%) # 使用示例 def train_with_monitor(model, train_loader, epochs10): monitor ResourceMonitor() monitor.start_monitoring() try: # 训练代码 for epoch in range(epochs): # ... 训练逻辑 pass finally: monitor.stop_monitoring() monitor.print_summary()8.2 不同模型的资源占用对比# benchmarks/model_benchmark.py import torch import time from models.cnn import SimpleCNN from models.rnn import TextLSTM from models.transformer import TransformerEncoder def benchmark_model(model, input_shape, devicecuda, iterations100): model.to(device) model.eval() # 预热 with torch.no_grad(): if len(input_shape) 4: # CNN输入 x torch.randn(1, *input_shape[1:], devicedevice) else: # RNN/Transformer输入 x torch.randint(0, 1000, input_shape, devicedevice) for _ in range(10): _ model(x) # 正式测试 torch.cuda.synchronize() if device cuda else None start_time time.time() with torch.no_grad(): for _ in range(iterations): _ model(x) torch.cuda.synchronize() if device cuda else None end_time time.time() avg_time (end_time - start_time) * 1000 / iterations # 毫秒 return avg_time # 测试不同模型 if __name__ __main__: device cuda if torch.cuda.is_available() else cpu # CNN基准测试 cnn_model SimpleCNN() cnn_time benchmark_model(cnn_model, (1, 1, 28, 28), device) print(fCNN推理时间: {cnn_time:.2f}ms) # LSTM基准测试 lstm_model TextLSTM(vocab_size10000, output_dim2) lstm_time benchmark_model(lstm_model, (1, 100), device) print(fLSTM推理时间: {lstm_time:.2f}ms) # Transformer基准测试 transformer_model TransformerEncoder(vocab_size10000) transformer_time benchmark_model(transformer_model, (1, 100), device) print(fTransformer推理时间: {transformer_time:.2f}ms)9. 常见问题与排查方法问题现象可能原因排查方式解决方案CUDA out of memory批大小过大/模型太大检查显存占用nvidia-smi减小batch_size/使用梯度累积训练损失不下降学习率不当/数据问题检查数据预处理/学习率调度调整学习率/检查数据质量梯度爆炸/消失网络层太深/初始化问题监控梯度范数使用梯度裁剪/更好的初始化验证集性能差过拟合/数据泄露检查训练-验证数据分布增加正则化/重新划分数据模型输出NaN数值不稳定/除零错误检查损失函数计算添加数值稳定项/检查输入范围9.1 具体问题排查示例# utils/debug_tools.py import torch def check_gradients(model): 检查梯度情况 total_norm 0 for p in model.parameters(): if p.grad is not None: param_norm p.grad.data.norm(2) total_norm param_norm.item() ** 2 total_norm total_norm ** 0.5 return total_norm def check_activations(model, input_data): 检查激活值分布 activations {} def hook_fn(name): def hook(module, input, output): activations[name] output.detach() return hook # 注册钩子 hooks [] for name, module in model.named_modules(): if isinstance(module, (nn.Conv2d, nn.Linear, nn.LSTM)): hook module.register_forward_hook