九:前沿扩展与实战
如何使用torchvision中的预训练模型进行迁移学习?¶
使用torchvision.models中提供的预训练模型进行迁移学习,通常包含以下几个核心步骤:
-
加载预训练模型:选择与任务匹配的架构(如ResNet、EfficientNet),并加载在ImageNet上预训练的权重。
-
替换分类头:将模型最后的全连接层(
fc或classifier)替换为一个新的、输出类别数与自定义任务相符的线性层。新层的参数默认需要梯度,会自动成为可训练部分。 -
冻结骨干网络(可选但常见):将预训练基座的参数
requires_grad设为False,以保留其学到的通用特征,仅训练新的分类头。这可以大幅减少训练时间和过拟合风险。 -
微调:如果希望进一步提升性能,可以在训练后期解冻部分或全部骨干网络,以极小学习率进行全局微调。
import torchvision.models as models
import torch.nn as nn
# 加载预训练ResNet-50
model = models.resnet50(pretrained=True)
# 冻结基座参数
for param in model.parameters():
param.requires_grad = False
# 替换最后的全连接层,假设新任务有10个类别
num_ftrs = model.fc.in_features
model.fc = nn.Linear(num_ftrs, 10)
# 现在,只有model.fc的参数是requires_grad=True,将被优化器更新
给出一个使用预训练ResNet进行图像分类微调的完整流程。¶
以下是一个涵盖数据加载、模型准备、训练和评估的完整骨架代码。
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, models, transforms
# 1. 数据预处理
data_transforms = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
train_dataset = datasets.ImageFolder('path/to/train', data_transforms)
val_dataset = datasets.ImageFolder('path/to/val', data_transforms)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=32, shuffle=False)
# 2. 构建模型
model = models.resnet50(pretrained=True)
for param in model.parameters():
param.requires_grad = False
model.fc = nn.Linear(model.fc.in_features, len(train_dataset.classes))
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)
# 3. 训练配置
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.fc.parameters(), lr=1e-3)
# 4. 训练循环
for epoch in range(10):
model.train()
for inputs, labels in train_loader:
inputs, labels = inputs.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
# 验证
model.eval()
correct, total = 0, 0
with torch.no_grad():
for inputs, labels in val_loader:
outputs = model(inputs.to(device))
_, preds = torch.max(outputs, 1)
correct += (preds == labels.to(device)).sum().item()
total += labels.size(0)
print(f'Epoch {epoch}: Val Acc {100 * correct / total:.2f}%')
如何使用torchtext构建文本分类的Pipeline?¶
torchtext 提供了便捷的文本预处理、词汇表和迭代器构建工具。以下使用其新版API(0.12+)演示文本分类流程:
import torch
from torch import nn
from torchtext.datasets import AG_NEWS
from torchtext.data.utils import get_tokenizer
from torchtext.vocab import build_vocab_from_iterator
# 1. 数据加载
train_iter = AG_NEWS(split='train')
tokenizer = get_tokenizer('basic_english')
# 2. 构建词表
def yield_tokens(data_iter):
for _, text in data_iter:
yield tokenizer(text)
vocab = build_vocab_from_iterator(yield_tokens(train_iter), specials=['<unk>', '<pad>'])
vocab.set_default_index(vocab['<unk>'])
# 3. 文本和标签处理管道
text_pipeline = lambda x: vocab(tokenizer(x))
label_pipeline = lambda x: int(x) - 1
# 4. 定义模型(一个简单的EmbeddingBag分类器)
class TextClassificationModel(nn.Module):
def __init__(self, vocab_size, embed_dim, num_class):
super().__init__()
self.embedding = nn.EmbeddingBag(vocab_size, embed_dim, sparse=False)
self.fc = nn.Linear(embed_dim, num_class)
def forward(self, text, offsets):
embedded = self.embedding(text, offsets)
return self.fc(embedded)
# 5. DataLoader的collate函数
def collate_batch(batch):
label_list, text_list, offsets = [], [], [0]
for _label, _text in batch:
label_list.append(label_pipeline(_label))
processed_text = torch.tensor(text_pipeline(_text), dtype=torch.int64)
text_list.append(processed_text)
offsets.append(processed_text.size(0))
labels = torch.tensor(label_list, dtype=torch.int64)
offsets = torch.tensor(offsets[:-1]).cumsum(dim=0)
text = torch.cat(text_list)
return text, offsets, labels
# 之后可使用DataLoader和标准训练循环进行训练
torchaudio中如何加载音频文件并提取梅尔频谱图?¶
import torchaudio
import torchaudio.transforms as T
# 加载音频文件,返回波形和采样率
waveform, sample_rate = torchaudio.load('audio.wav')
# 重采样到16kHz(如果需要)
if sample_rate != 16000:
resampler = T.Resample(sample_rate, 16000)
waveform = resampler(waveform)
sample_rate = 16000
# 定义梅尔频谱变换
mel_spectrogram = T.MelSpectrogram(
sample_rate=16000,
n_fft=400, # 窗口大小
hop_length=160, # 帧移
n_mels=80 # 梅尔滤波器组数量
)
# 生成梅尔频谱图 (shape: [channels, n_mels, time])
mel_spec = mel_spectrogram(waveform)
# 通常转换为对数刻度
log_mel_spec = torch.log(mel_spec + 1e-9)
如何将Hugging Face的transformers模型与PyTorch训练循环结合?¶
你可以直接获取模型实例,将其作为标准的PyTorch nn.Module 进行训练。
from transformers import AutoModelForSequenceClassification, AutoTokenizer
from torch.utils.data import DataLoader
import torch
# 加载模型和分词器
model = AutoModelForSequenceClassification.from_pretrained('bert-base-uncased', num_labels=2)
tokenizer = AutoTokenizer.from_pretrained('bert-base-uncased')
# 准备数据 (假设texts和labels是列表)
encodings = tokenizer(texts, truncation=True, padding=True, return_tensors='pt')
dataset = torch.utils.data.TensorDataset(encodings['input_ids'], encodings['attention_mask'], torch.tensor(labels))
loader = DataLoader(dataset, batch_size=16, shuffle=True)
optimizer = torch.optim.AdamW(model.parameters(), lr=5e-5)
# 标准训练循环
model.train()
for epoch in range(3):
for batch in loader:
input_ids, attention_mask, labels = [b.to(model.device) for b in batch]
outputs = model(input_ids, attention_mask=attention_mask, labels=labels)
loss = outputs.loss
loss.backward()
optimizer.step()
optimizer.zero_grad()
使用Hugging Face Trainer和原版PyTorch训练循环的优劣对比。¶
总结:当你进行标准NLP任务微调时,使用Trainer能极大加速开发;当你进行算法研究或需要完全自定义训练流程时,自己写训练循环是必要的。
如何加载大规模数据集而不会耗尽内存?流式处理。¶
对于无法一次性装入内存的数据集,使用可迭代数据集(IterableDataset)进行流式加载。torchdata和WebDataset是常用工具,但纯PyTorch也能实现。
关键策略:
-
使用
IterableDataset:继承它并实现iter方法,每次仅加载并返回一个样本,不将所有数据保存在内存中。 -
配合
DataLoader:设置num_workers并行预取,避免I/O瓶颈。 -
流式读取文件:例如,逐行读取大的JSONL文件,而不是一次性
json.load整个文件。
from torch.utils.data import IterableDataset, DataLoader
class StreamingDataset(IterableDataset):
def __init__(self, file_path):
self.file_path = file_path
def __iter__(self):
with open(self.file_path, 'r') as f:
for line in f:
# 解析每一行,返回一个样本
sample = process(line)
yield sample
dataset = StreamingDataset('huge_data.jsonl')
loader = DataLoader(dataset, batch_size=32, num_workers=4)
注意:在分布式环境中,IterableDataset的数据分片需要特别处理,通常使用torch.utils.data.get_worker_info()来分配不同的数据块。
如何实现一个基于PyTorch的变分自编码器(VAE)?¶
VAE的关键在于重参数化技巧和损失函数,损失包括重构损失(如MSE或BCE)和KL散度损失。
import torch
import torch.nn as nn
import torch.nn.functional as F
class VAE(nn.Module):
def __init__(self, input_dim, latent_dim):
super().__init__()
# 编码器
self.fc1 = nn.Linear(input_dim, 400)
self.fc_mu = nn.Linear(400, latent_dim)
self.fc_logvar = nn.Linear(400, latent_dim)
# 解码器
self.fc3 = nn.Linear(latent_dim, 400)
self.fc4 = nn.Linear(400, input_dim)
def encode(self, x):
h = F.relu(self.fc1(x))
return self.fc_mu(h), self.fc_logvar(h)
def reparameterize(self, mu, logvar):
std = torch.exp(0.5 * logvar)
eps = torch.randn_like(std)
return mu + eps * std
def decode(self, z):
h = F.relu(self.fc3(z))
return torch.sigmoid(self.fc4(h)) # 假设输入在[0,1]
def forward(self, x):
mu, logvar = self.encode(x)
z = self.reparameterize(mu, logvar)
recon_x = self.decode(z)
return recon_x, mu, logvar
# 损失函数
def vae_loss(recon_x, x, mu, logvar):
# 重构损失:二元交叉熵(对于0-1归一化图像)
BCE = F.binary_cross_entropy(recon_x, x, reduction='sum')
# KL散度
KLD = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())
return BCE + KLD
实现一个简单的GAN,并解释训练不稳定时如何调参。¶
简单GAN实现(生成手写数字):
import torch
import torch.nn as nn
# 生成器
class Generator(nn.Module):
def __init__(self, latent_dim, img_shape):
super().__init__()
self.model = nn.Sequential(
nn.Linear(latent_dim, 256), nn.ReLU(),
nn.Linear(256, 512), nn.ReLU(),
nn.Linear(512, int(torch.prod(torch.tensor(img_shape)))), nn.Tanh()
)
def forward(self, z):
return self.model(z).view(-1, *img_shape)
# 判别器
class Discriminator(nn.Module):
def __init__(self, img_shape):
super().__init__()
self.model = nn.Sequential(
nn.Linear(int(torch.prod(torch.tensor(img_shape))), 512), nn.LeakyReLU(0.2),
nn.Linear(512, 256), nn.LeakyReLU(0.2),
nn.Linear(256, 1), nn.Sigmoid()
)
def forward(self, img):
return self.model(img.view(img.size(0), -1))
训练不稳定时调参:
-
学习率:降低两倍或使用Adam优化器(betas=(0.5, 0.999)是常用配置)。
-
加入噪声:向判别器的输入或标签添加微弱的高斯噪声,可以平滑分布。
-
使用标签平滑:将真实标签从1.0替换为0.9~1.0,虚假标签从0.0替换为0.0~0.1。
-
使用Wasserstein GAN (WGAN):将判别器改为评论家,使用Wasserstein距离和梯度惩罚,训练更稳定。
-
平衡生成器和判别器的训练频率:如果一方过强,减少其更新次数。
-
使用BatchNorm:在生成器和判别器中(除最后一层)使用BN有助于稳定。
在PyTorch中如何实现Transformer模型?nn.Transformer的用法。¶
PyTorch内置了nn.Transformer模块,但通常直接使用其组件nn.TransformerEncoder和nn.TransformerDecoder更灵活。
完整示例(序列到序列模型):
import torch
import torch.nn as nn
class Seq2SeqTransformer(nn.Module):
def __init__(self, src_vocab_size, tgt_vocab_size, d_model, nhead, num_encoder_layers, num_decoder_layers):
super().__init__()
self.src_embedding = nn.Embedding(src_vocab_size, d_model)
self.tgt_embedding = nn.Embedding(tgt_vocab_size, d_model)
self.pos_encoder = PositionalEncoding(d_model) # 自定义位置编码
encoder_layer = nn.TransformerEncoderLayer(d_model=d_model, nhead=nhead)
self.encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_encoder_layers)
decoder_layer = nn.TransformerDecoderLayer(d_model=d_model, nhead=nhead)
self.decoder = nn.TransformerDecoder(decoder_layer, num_layers=num_decoder_layers)
self.fc_out = nn.Linear(d_model, tgt_vocab_size)
def forward(self, src, tgt, src_mask, tgt_mask, memory_mask):
src = self.pos_encoder(self.src_embedding(src))
tgt = self.pos_encoder(self.tgt_embedding(tgt))
memory = self.encoder(src, src_key_padding_mask=src_mask)
output = self.decoder(tgt, memory, tgt_mask=tgt_mask, memory_key_padding_mask=memory_mask)
return self.fc_out(output)
注意:需要为源和目标序列创建正确的掩码(因果掩码和填充掩码)。
如何实现自定义的注意力机制?写出伪代码。¶
以下实现一个多头自注意力机制的伪代码,展示核心数学运算,可轻松定制为稀疏注意力、相对位置注意力等。
import torch
import torch.nn.functional as F
class CustomMultiHeadAttention(nn.Module):
def __init__(self, d_model, n_heads, dropout=0.1):
super().__init__()
self.d_model = d_model
self.n_heads = n_heads
self.head_dim = d_model // n_heads
self.q_linear = nn.Linear(d_model, d_model)
self.k_linear = nn.Linear(d_model, d_model)
self.v_linear = nn.Linear(d_model, d_model)
self.out_linear = nn.Linear(d_model, d_model)
def forward(self, query, key, value, mask=None):
B, T, C = query.shape
# 1. 线性变换并分头
Q = self.q_linear(query).view(B, T, self.n_heads, self.head_dim).transpose(1, 2) # (B, h, T, d)
K = self.k_linear(key).view(B, -1, self.n_heads, self.head_dim).transpose(1, 2)
V = self.v_linear(value).view(B, -1, self.n_heads, self.head_dim).transpose(1, 2)
# 2. 计算注意力分数(点积,缩放)
attn_scores = torch.matmul(Q, K.transpose(-2, -1)) / (self.head_dim ** 0.5)
# 3. 应用掩码(例如因果掩码或填充掩码)
if mask is not None:
attn_scores = attn_scores.masked_fill(mask == 0, float('-inf'))
# 4. Softmax得到注意力权重
attn_weights = F.softmax(attn_scores, dim=-1)
attn_weights = F.dropout(attn_weights, p=0.1, training=self.training)
# 5. 加权求和
out = torch.matmul(attn_weights, V) # (B, h, T, d)
out = out.transpose(1, 2).contiguous().view(B, T, C)
return self.out_linear(out)
这个模块封装了完整的注意力计算,你可以通过修改Q, K, V的来源或注意力分数的计算方式来定制自己的注意力机制。例如,相对位置注意力会在计算分数时添加一个可学习的位置偏置矩阵。
如何将PyTorch模型通过WebAssembly在浏览器中运行?¶
将PyTorch模型在浏览器中运行通常通过ONNX Runtime Web或TensorFlow.js间接实现,核心思路是先将模型导出为ONNX格式,再转换为WebAssembly兼容的格式。具体流程如下:
- 导出PyTorch模型为ONNX
import torch
dummy_input = torch.randn(1, 3, 224, 224)
torch.onnx.export(model, dummy_input, "model.onnx", opset_version=14)
- 使用onnx2js或直接通过ONNX Runtime Web加载
ONNX Runtime Web是一个JavaScript库,可以直接在浏览器中加载ONNX模型,并利用WebAssembly(CPU后端)或WebGL/WebGPU(GPU后端)进行推理。
<script src="https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js"></script>
<script>
async function run() {
const session = await ort.InferenceSession.create('./model.onnx');
const inputTensor = new ort.Tensor('float32', new Float32Array(1*3*224*224), [1,3,224,224]);
const output = await session.run({input: inputTensor});
}
</script>
- 性能优化
- 使用量化:将FP32模型量化为INT8,减小模型体积,加快推理。
- 选择WebGL后端:如果设备支持,可启用GPU加速。
- 使用ONNX图优化:在导出前进行常量折叠等优化。
注意事项
-
并非所有PyTorch算子都被ONNX支持,尤其是动态控制流。此时需要重写模型,用等价操作替代。
-
浏览器内存有限,大模型可能无法运行,需结合量化、剪枝等手段压缩模型。
如何使用torch.nn.utils.prune对一个卷积网络进行全局剪枝?¶
全局剪枝(Global Pruning)是对整个模型的所有参数统一设定一个稀疏度目标,自动去除那些全局重要性最低的连接,而不仅仅是逐层剪枝。PyTorch提供了torch.nn.utils.prune.global_unstructured函数来实现。
步骤:
- 定义需要剪枝的参数列表:收集模型中所有你想要参与剪枝的层(通常是卷积层和全连接层的权重)。
params_to_prune = []
for name, module in model.named_modules():
if isinstance(module, torch.nn.Conv2d):
params_to_prune.append((module, 'weight'))
- 指定剪枝方法:例如使用L1范数衡量重要性。
from torch.nn.utils import prune
prune.global_unstructured(
params_to_prune,
pruning_method=prune.L1Unstructured,
amount=0.5, # 稀疏度:50%的权重将被置零
)
- 验证稀疏度:剪枝后,权重的
weight_mask属性会保存掩码。可以通过torch.sum(module.weight == 0)查看零元素数量。
注意
-
全局剪枝一次性去除所有层中不重要的权重,能实现更好的压缩率,但可能导致某些层被过度剪枝。
-
剪枝后需要微调以恢复精度。
-
如果希望永久移除剪枝带来的掩码和计算图开销,可调用
prune.remove(module, 'weight'),但这样会将零权重物理保留(仍为零),后续通常需转换为稀疏格式才能真正加速。
什么是模型量化中的“per-channel”与“per-tensor”?¶
量化中的per-tensor和per-channel是两种确定量化参数(scale和zero_point)的粒度。
-
Per-tensor量化:对整个权重张量(例如一个卷积层的整个权重矩阵)使用同一组scale和zero_point。所有通道共享一个量化范围。优点是简单、计算快,但若不同通道的数值分布差异大,精度损失会较大。
-
Per-channel量化:对权重张量的每个输出通道独立计算scale和zero_point。例如,对Conv2d的
weight(out_channels, in_channels, H, W),会沿着out_channels维度为每个通道分别确定量化参数。这样能更好地适应不同通道的动态范围,显著提高量化精度,特别是对于深度可分离卷积或权重分布极不均匀的层。
在PyTorch中,可以通过torch.quantization.PerChannelMinMaxObserver等观察器来实现per-channel量化。通常,权重采用per-channel量化以保持精度,而激活由于依赖输入批次,更多采用per-tensor或动态量化。现代量化方案(如QAT)广泛使用per-channel量化来缩小与浮点模型的差距。
使用TensorRT对PyTorch模型进行推理优化,需要做哪些转换?¶
TensorRT是NVIDIA的深度学习推理优化器,需要对PyTorch模型进行转换:
-
导出PyTorch模型为ONNX 这是最通用的中间格式,确保
opset_version足够高,并使用do_constant_folding=True。 -
使用TensorRT的ONNX解析器 TensorRT提供了
trtexec命令行工具或C++/Python API (tensorrt.ONNXParser) 来加载ONNX模型。它会将ONNX图解析为TensorRT网络。 -
构建优化引擎
- 设置最大batch size、工作空间大小、精度模式(FP32, FP16, INT8)。
- 对于INT8,需要提供校准数据集来收集激活值的范围。
-
通过
builder.build_serialized_network生成一个优化后的引擎文件(.plan或.engine)。 -
推理 加载引擎,创建执行上下文,将输入输出绑定到GPU内存,然后调用
execute_v2进行高性能推理。
Python示例(简化流程):
import tensorrt as trt
# 1. 创建builder和network
builder = trt.Builder(TRT_LOGGER)
network = builder.create_network(1 << int(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH))
parser = trt.OnnxParser(network, TRT_LOGGER)
with open("model.onnx", "rb") as f:
parser.parse(f.read())
# 2. 配置优化
config = builder.create_builder_config()
config.set_memory_pool_limit(trt.MemoryPoolType.WORKSPACE, 1 << 30)
if use_fp16:
config.set_flag(trt.BuilderFlag.FP16)
# 3. 构建引擎
engine = builder.build_serialized_network(network, config)
with open("model.engine", "wb") as f:
f.write(engine)
注意事项
-
不支持的算子需要用
plugin或自定义层实现。 -
动态输入形状需要在构建时指定
-1维度,并通过optimization_profile设置最小/最优/最大形状。
如何将PyTorch模型导出为TorchScript并用于C++服务端推理?¶
TorchScript是PyTorch的序列化中间表示,能脱离Python在C++中高效运行。
-
导出TorchScript模型
-
使用
torch.jit.trace(适合静态图)或torch.jit.script(支持控制流)。
- 在C++中加载并推理
需要LibTorch(PyTorch的C++发行版)。
#include <torch/script.h>
torch::jit::script::Module module = torch::jit::load("model.pt");
std::vector<torch::jit::IValue> inputs;
inputs.push_back(torch::randn({1,3,224,224}));
auto output = module.forward(inputs).toTensor();
- 构建C++服务
将LibTorch链接到你的C++服务器程序(如使用
cmake),然后可以将模型加载到内存中,在请求到来时直接调用forward。由于没有Python解释器,启动极快,内存占用低,非常适合高并发、低延迟的服务。
注意事项
-
确保
traced或scripted模型在保存前已设置为eval()模式。 -
动态轴(如batch size)在
trace时会被部分固化,若需要可变batch,可在torch.jit.trace时不固定,但需后续通过torch.jit.freeze优化。
如何计算并优化模型的推理时延和吞吐?¶
定义:
-
时延 (Latency):处理单个请求的时间。
-
吞吐 (Throughput):单位时间内处理的请求数,常用
samples/sec或tokens/sec。
计算方法:
使用PyTorch Profiler或torch.cuda.Event测量GPU内核执行时间,排除CPU端数据加载干扰。
starter, ender = torch.cuda.Event(enable_timing=True), torch.cuda.Event(enable_timing=True)
starter.record()
output = model(input)
ender.record()
torch.cuda.synchronize()
latency = starter.elapsed_time(ender) # 毫秒
优化策略(从易到难):
-
算子融合:通过
torch.fx或torch.onnx.export合并Conv+BN+ReLU。 -
混合精度:使用FP16/BF16推理,利用Tensor Core加速。
-
量化:INT8量化,减小模型体积和内存带宽。
-
批处理:合并多个请求为batch,提高GPU利用率。
-
使用专用推理引擎:TensorRT、ONNX Runtime、vLLM等。
-
内存优化:固定内存(pinned memory)、零拷贝传输。
-
模型优化:剪枝、蒸馏、高效注意力(FlashAttention)。
如何在训练大型语言模型时使用激活检查点和ZeRO?¶
激活检查点(Gradient Checkpointing)在前向传播时丢弃大部分中间激活,反向传播时重新计算,以时间换空间。在PyTorch中通常对Transformer Block应用。
ZeRO(Zero Redundancy Optimizer)通过分片优化器状态、梯度和参数,消除多卡训练中的冗余显存。在DeepSpeed或PyTorch FSDP中实现。
结合使用:两者作用在不同显存区域,可以叠加。
- 激活检查点:对每个
TransformerBlock开启。
- ZeRO(以DeepSpeed ZeRO-2为例):在配置文件中设置
"zero_optimization": {"stage": 2}。
注意:
-
激活检查点会增加20-30%的计算时间。
-
ZeRO-2分片优化器和梯度,ZeRO-3分片参数,显存节省更显著,但通信量也更大。
典型训练GPT-2 1.5B的配置:
-
启用BF16混合精度。
-
每块Transformer Block使用梯度检查点。
-
使用DeepSpeed ZeRO-2 或 FSDP。
-
全局batch size通过梯度累积实现。
如何使用FSDP训练GPT-2规模模型的示例配置。¶
FSDP (Fully Sharded Data Parallel) 是PyTorch官方实现的ZeRO-3等效技术。以下是一个使用torch.distributed.fsdp训练GPT-2的示例配置骨架:
from torch.distributed.fsdp import (
FullyShardedDataParallel as FSDP,
MixedPrecision,
ShardingStrategy,
BackwardPrefetch,
StateDictType,
)
from torch.distributed.fsdp.wrap import transformer_auto_wrap_policy
from transformers import GPT2Model, GPT2Config
# 初始化分布式
dist.init_process_group(backend='nccl')
# 定义GPT-2模型
config = GPT2Config(vocab_size=50257, n_embd=768, n_head=12, n_layer=12)
model = GPT2Model(config)
# FSDP混合精度配置
mp_policy = MixedPrecision(
param_dtype=torch.bfloat16,
reduce_dtype=torch.bfloat16,
buffer_dtype=torch.bfloat16,
)
# 自动包装策略:每个Transformer Block被单独分片
auto_wrap_policy = functools.partial(
transformer_auto_wrap_policy,
transformer_layer_cls={GPT2Model.transformer.h[0].__class__}
)
# 构造FSDP模型
fsdp_model = FSDP(
model,
sharding_strategy=ShardingStrategy.FULL_SHARD, # ZeRO-3
auto_wrap_policy=auto_wrap_policy,
mixed_precision=mp_policy,
backward_prefetch=BackwardPrefetch.BACKWARD_PRE,
device_id=torch.cuda.current_device(),
limit_all_gathers=True,
use_orig_params=True, # 保持参数名与原始一致
)
# 之后进行标准训练循环
说明:
-
这里将每个Transformer层单独分片,最大化显存节省。
-
使用BF16混合精度,优化器为AdamW。
-
训练时务必使用
torch.cuda.amp.autocast。
如何在训练中监控并可视化注意力权重?¶
监控注意力权重有助于理解模型的决策过程。可以在目标注意力层注册forward_hook来捕获注意力矩阵。
attention_weights = {}
def save_attn_hook(module, input, output, layer_name):
# 假设该层的输出包含注意力权重(通常需要模型返回)
# 很多Transformer实现会返回注意力权重
pass # 具体实现依赖于模型结构
# 对于HuggingFace模型,可以在调用时设置output_attentions=True
outputs = model(inputs, output_attentions=True)
attn = outputs.attentions # 返回每层的注意力权重列表
# 可视化
import matplotlib.pyplot as plt
import seaborn as sns
sns.heatmap(attn[0][0, 0].detach().cpu(), cmap='Blues')
plt.title('Head 0 attention')
若需在训练过程中动态记录,可搭配wandb或TensorBoard,将注意力矩阵作为图像或热力图定期上传。
实现一个简单的“基于能量的模型”训练循环。¶
基于能量的模型(EBM)通过学习一个能量函数 E(x)E(x),使得真实样本能量低,虚假样本能量高。训练常使用对比散度或噪声对比估计。以下给出一个使用Langevin动力学采样和噪声对比训练的简化循环:
import torch
class EBM(torch.nn.Module):
def __init__(self, dim):
super().__init__()
self.fc = torch.nn.Sequential(
torch.nn.Linear(dim, 256), torch.nn.ReLU(),
torch.nn.Linear(256, 256), torch.nn.ReLU(),
torch.nn.Linear(256, 1)
)
def forward(self, x):
return self.fc(x).squeeze()
def langevin_sample(energy_fn, x_init, steps=100, step_size=0.1, noise_std=0.01):
x = x_init.detach().clone().requires_grad_(True)
for i in range(steps):
energy = energy_fn(x).sum()
grad = torch.autograd.grad(energy, [x])[0]
x = x - step_size * grad + noise_std * torch.randn_like(x)
return x.detach()
# 训练循环
ebm = EBM(input_dim)
optimizer = torch.optim.Adam(ebm.parameters())
for batch in dataloader:
real_samples = batch
# 生成负样本(从随机噪声开始进行Langevin采样)
neg_samples = torch.randn_like(real_samples)
neg_samples = langevin_sample(ebm, neg_samples)
# 能量损失:拉低真实样本能量,推高负样本能量
pos_energy = ebm(real_samples).mean()
neg_energy = ebm(neg_samples).mean()
loss = pos_energy - neg_energy
# 加入能量正则化(可选)
loss += 0.1 * (pos_energy**2 + neg_energy**2)
optimizer.zero_grad()
loss.backward()
optimizer.step()
要点:Langevin采样是EBM训练的关键,需要足够步数和合适步长才能生成有意义的负样本。训练不稳定时,可加入能量正则化或谱归一化。以上是简化版本,实际应用还需更多工程优化。
如何使用PyTorch进行时间序列预测?给出一个LSTM示例。¶
时间序列预测通过历史窗口数据推断未来值。LSTM(长短期记忆网络)是处理序列依赖的经典模型。下面是一个单变量多步预测的完整示例:
import torch
import torch.nn as nn
class LSTMForecaster(nn.Module):
def __init__(self, input_size=1, hidden_size=64, num_layers=2, output_steps=10):
super().__init__()
self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
self.linear = nn.Linear(hidden_size, output_steps) # 一次性输出未来多步
def forward(self, x):
# x 形状: (batch, seq_len, input_size)
out, (h_n, c_n) = self.lstm(x)
# 取最后时间步的隐状态进行预测
return self.linear(out[:, -1, :]) # (batch, output_steps)
训练步骤:
-
数据预处理:将一维时间序列切分为滑动窗口,前
lookback个点为输入,后output_steps个点为标签。 -
标准化:通常对数据进行 Z-Score 或 MinMax 归一化。
-
构建DataLoader并训练:
model = LSTMForecaster(input_size=1, hidden_size=64, output_steps=10)
criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
for X_batch, y_batch in train_loader:
optimizer.zero_grad()
pred = model(X_batch.unsqueeze(-1)) # 增加特征维度
loss = criterion(pred, y_batch)
loss.backward()
optimizer.step()
- 评估与反归一化:预测结果需逆变换回原始尺度。
关键技巧:
-
使用
batch_first=True提高代码可读性。 -
如果数据有多变量,可调整
input_size,并在 LSTM 前增加全连接映射或直接拼接。 -
对长序列,可结合梯度裁剪或引入 Attention 机制提升效果。
图神经网络如何在PyTorch Geometric中实现?简述消息传递。¶
PyTorch Geometric (PyG) 是图神经网络的常用库。其核心抽象是 MessagePassing 基类,通过 消息传递范式 实现节点表示更新。范式分三步:
-
消息计算:对每条边
(j → i),利用源节点j和目标节点i的特征计算消息m_ji。 -
消息聚合:对目标节点
i,聚合所有来自邻居的消息(例如求和、平均、最大值)。 -
节点更新:根据聚合后的消息和节点自身特征更新节点表示。
以下实现一个简单的图卷积层(GraphConv):
import torch
from torch_geometric.nn import MessagePassing
class SimpleGCN(MessagePassing):
def __init__(self, in_channels, out_channels):
super().__init__(aggr='add') # 聚合方式为求和
self.lin = torch.nn.Linear(in_channels, out_channels)
def forward(self, x, edge_index):
# x: 节点特征 (N, in_channels)
# edge_index: 边列表 (2, E)
return self.propagate(edge_index, x=x) # 触发消息传递
def message(self, x_j):
# x_j: 源节点的特征 (E, in_channels)
return x_j
def update(self, aggr_out):
# aggr_out: 聚合后的消息 (N, in_channels)
return self.lin(aggr_out)
使用时,构建 Data 对象(包含 x, edge_index, y),然后像普通模块一样训练。
如何实现一个简单的“神经辐射场”(NeRF)的训练?¶
NeRF 使用一个 MLP 表示 3D 场景,输入为 5D 坐标(空间位置 + 观测方向),输出颜色和密度。训练通过体渲染重建 2D 图像。简易步骤如下:
- 模型定义:
class NeRF(nn.Module):
def __init__(self, D=8, W=256):
super().__init__()
# 位置编码后的维度(未编码时直接使用坐标)
# 这里简化为不使用位置编码的版本
self.linears = nn.ModuleList([nn.Linear(3, W)] + [nn.Linear(W, W) for _ in range(D-1)])
self.alpha_out = nn.Linear(W, 1) # 密度
self.rgb_out = nn.Linear(W, 3) # 颜色
def forward(self, pts, viewdirs=None):
h = pts
for layer in self.linears:
h = F.relu(layer(h))
alpha = F.softplus(self.alpha_out(h)) # 保证密度 > 0
rgb = torch.sigmoid(self.rgb_out(h))
return rgb, alpha
-
体渲染函数:从相机光线采样点,沿光线积分颜色和密度。
-
训练循环:
for epoch in range(epochs):
rays_o, rays_d, target_colors = sample_rays() # 从图像生成光线
pts = rays_o + rays_d * t_vals # 采样空间点
pred_rgb, alpha = model(pts)
loss = F.mse_loss(pred_rgb, target_colors)
optimizer.zero_grad()
loss.backward()
optimizer.step()
完整实现需加入位置编码、分层采样(Coarse + Fine)等,但以上框架展示了核心逻辑。
在PyTorch中,如何处理可变长输入的自适应池化?¶
nn.AdaptiveAvgPool1d、AdaptiveMaxPool1d 等可将任意长度的时间/空间特征映射到固定输出大小,无需手动计算池化核和步长。对于序列数据,处理流程如下:
# 假设输入: (batch, channels, seq_len)
adaptive_pool = nn.AdaptiveAvgPool1d(output_size=100)
# 无论 seq_len 是多少,输出长度固定为 100
output = adaptive_pool(x) # (batch, channels, 100)
结合RNN/Transformer处理变长序列:
-
使用
pack_padded_sequence和pad_packed_sequence减少计算浪费。 -
在多头注意力中,通过
attention_mask忽略填充位置。 -
最后,用自适应池化将变长序列的编码器输出压缩为固定维度,再送入全连接层。
如何用torch.export进行模型导出?与TorchScript的区别。¶
torch.export (PyTorch 2.0+) 提供基于 AOT (Ahead-Of-Time) 编译 的导出方案,生成严格的静态图,与 torch.jit.trace/script 的对比如下:
使用示例:
import torch
model = MyModel()
example_input = (torch.randn(1, 3, 224, 224),)
exported_program = torch.export.export(model, example_input)
# 保存
torch.export.save(exported_program, "model.pt2")
使用PyTorch的“functorch”进行函数变换(如vmap, grad)。¶
functorch 提供函数变换工具,可对 PyTorch 函数进行矢量化、求导等操作。
vmap:将单个样本的操作自动扩展为批量操作,无需改写循环。
import torch.func as F
def predict(x, w):
return x @ w
batch_predict = F.vmap(predict, in_dims=(0, None)) # 沿x的batch维度映射,w共享
outputs = batch_predict(X_batch, weight)
grad:计算标量函数对输入的梯度。
- 组合使用:
vmap(grad(func))可一次性计算多个输入样本的梯度。
适用场景:需要编写无状态的纯函数逻辑,自动批量化;或者计算 per-sample gradient 用于隐私保护训练。
如何将PyTorch模型转换为TFLite用于移动端?¶
流程为 PyTorch → ONNX → TensorFlow → TFLite(或直接使用 ai-edge-torch 等新工具)。
-
PyTorch → ONNX:
torch.onnx.export(model, dummy_input, "model.onnx") -
ONNX → TensorFlow SavedModel:使用
onnx-tf工具。 -
SavedModel → TFLite:使用
TFLiteConverter。
import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model("saved_model_dir")
tflite_model = converter.convert()
with open("model.tflite", "wb") as f: f.write(tflite_model)
进阶:若希望动态尺寸,需在转换时指定 input_shapes。对精度敏感任务,可应用训练后量化 (converter.optimizations = [tf.lite.Optimize.DEFAULT])。注意:不是所有PyTorch算子都能无缝转换,复杂模型需要验证。
如何为训练数据集建立高效的TFRecord或WebDataset格式?¶
-
TFRecord (TensorFlow原生格式):使用
tensorflow.io.TFRecordWriter。首先将数据序列化为tf.train.Example,然后写入文件。PyTorch 可通过tf.data.TFRecordDataset读取,但跨框架不推荐。 -
WebDataset:基于 tar 文件存储,非常适合 PyTorch 大数据集流式训练。
import webdataset as wds
# 写入:将样本保存为图片和标签并打包成 tar
# 读取
dataset = wds.WebDataset("data-{0000..0099}.tar").decode("torchrgb").to_tuple("jpg;png", "cls")
loader = torch.utils.data.DataLoader(dataset, batch_size=32)
- WebDataset 支持直接从云存储流式加载,内置混洗和分布式支持。
选择建议:如果只使用 PyTorch,优先选择 WebDataset;如果需要跨框架,TFRecord 格式更通用。
在联邦学习场景,如何使用PyTorch实现模型聚合?¶
联邦学习通常采用 FedAvg 算法。服务器下发全局模型,各客户端本地训练后上传模型权重(或梯度),服务器进行聚合。
def fed_avg(global_model, client_weights_list, client_sizes):
# 计算加权平均
global_dict = global_model.state_dict()
total_samples = sum(client_sizes)
for key in global_dict.keys():
global_dict[key] = sum(
client_state[key] * client_sizes[i] / total_samples
for i, client_state in enumerate(client_weights_list)
)
global_model.load_state_dict(global_dict)
实现要点:
-
安全性:使用差分隐私(DP-SGD)或安全聚合协议(如同态加密)。
-
通信效率:只传输模型更新而非完整模型,并进行梯度压缩。
-
PyTorch生态工具:
PySyft,Flower,FedML等提供了高层API。
如何实现一个“扩散模型”的训练步骤?损失函数怎么写?¶
扩散模型的核心是通过逐步添加噪声和去噪来学习数据分布。以 DDPM 为例,训练步骤如下:
-
前向扩散(加噪):对干净图像 x0x0,随机采样时间步 tt,生成带噪图像 xt=αˉtx0+1−αˉtϵxt=αˉtx0+1−αˉtϵ。
-
模型预测:网络预测添加的噪声 ϵ^ϵ^ 或直接预测原始 x0x0。
-
损失函数:预测噪声的均方误差。
训练循环骨架:
for imgs in dataloader:
t = torch.randint(0, T, (imgs.size(0),))
noise = torch.randn_like(imgs)
noisy_imgs = alpha_bar[t].view(-1,1,1,1) * imgs + (1 - alpha_bar[t]).view(-1,1,1,1) * noise
pred_noise = model(noisy_imgs, t)
loss = F.mse_loss(pred_noise, noise)
optimizer.zero_grad()
loss.backward()
optimizer.step()
其中 alpha_bar 是预设的噪声调度参数。
使用PyTorch的“DTensor”进行张量并行编程的思路。¶
DTensor 是 PyTorch 分布式张量,用于描述张量在多设备上的分片(sharding)布局,是 张量并行(Tensor Parallelism) 的底层抽象。
编程思路:
-
定义设备网格:
device_mesh = DeviceMesh("cuda", torch.arange(num_gpus)) -
定义分片布局:指定每个维度如何分布(如
Shard(0)表示沿第0维分片)。 -
创建分布式张量:
dtensor = distribute_tensor(tensor, device_mesh, [Shard(0)]) -
执行运算:PyTorch 操作会自动推导出输出 DTensor 的分片布局,并在必要时进行通信。
示例:在2卡上对线性层权重进行列切分。
from torch.distributed.tensor import distribute_tensor, Shard
device_mesh = DeviceMesh("cuda", [0, 1])
weight = torch.randn(1024, 512)
dt = distribute_tensor(weight, device_mesh, [Shard(1)]) # 每卡持有 (1024, 256)
# 前向计算时,输入复制到两卡,各自计算,然后 all-gather 输出。
实际应用中,通常封装为高阶 API(如 PyTorch 的 dtensor.nn)或使用基于 DTensor 的模型并行库。
综合实战:设计一个从数据处理、模型训练到部署的全流程方案,针对一个实际业务场景。¶
业务场景:基于电商评论的情感分析,判断用户对商品的正负向情感。
全流程设计:
- 数据层:
- 收集带标签的评论数据,存储为 CSV/Parquet。
- 使用
torchtext或transformers的 tokenizer 进行分词、构建词表(或直接使用预训练BERT tokenizer)。 -
实现自定义
Dataset和流式加载DataLoader,支持多进程。 -
模型层:
- 选用 ALBERT 等轻量预训练模型,冻结底层,只微调顶层 classifier。
-
使用
transformers.AutoModelForSequenceClassification加载,或自己用 PyTorch 搭建 LSTM+Attention 分类器。 -
训练层:
- 训练循环中包含:混合精度、梯度累积、学习率 warmup + cosine decay。
- 监控准确率、F1-score(使用
torchmetrics)。 -
使用
W&B记录实验,并开启torch.profiler定位瓶颈。 -
优化与导出:
- 对模型进行动态量化(
quantize_dynamic)以减小尺寸。 - 导出为 TorchScript 或 ONNX,并进行图优化。
-
若部署到移动端,进一步转换为 TFLite。
-
部署:
- 使用 TorchServe 或 Triton Inference Server 搭建在线 API,启用动态 batching。
- 容器化(Docker),通过 K8s 部署,配置自动扩缩容。
-
设置输入过滤(防SQL注入)和输出监控。
-
持续迭代:
- 收集线上用户反馈(点赞/点踩),构建数据飞轮。
- 定期将 Bad Case 加入训练集,自动触发模型重训(MLOps管道)。
关键点:整个流程强调自动化和可观测性,确保模型能够稳定地持续优化。