4. 深度学习基础(二):PyTorch/TensorFlow框架安装与入门、张量操作、自动求导机制、构建第一个全连接网络
4.1 框架选择:PyTorch 还是 TensorFlow?
做材料计算这几年,我经常被问到这个问题。说实话,两个框架都能干活。但我个人习惯用 PyTorch,原因很简单——调试方便,代码写起来更像 Python。
TensorFlow 2.x 其实也进步很大,Keras 接口很友好。但如果你刚开始学,我建议从 PyTorch 入手。为什么?因为材料科学领域的新论文,十篇里有八篇用的是 PyTorch。你想想看,复现论文的时候,省多少事。
我的建议: 初学者选 PyTorch,做产品部署考虑 TensorFlow。两个都会更好,但别同时学,会乱。
4.2 安装与环境配置
安装这事,说简单也简单,说坑也多。我曾经在一个服务器上折腾了整整一下午,就为了把 CUDA 版本和 PyTorch 版本对上。
先确认你的显卡:
# 查看显卡驱动
nvidia-smi
# 查看 CUDA 版本
nvcc --version
然后安装 PyTorch:
# CPU 版本(没显卡用这个)
pip install torch torchvision torchaudio
# CUDA 11.8 版本(推荐)
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
注意: 别用 conda 装 PyTorch,除非你特别清楚自己在做什么。我踩过这个坑——conda 经常给你装 CPU 版本,你跑了一天才发现 GPU 根本没干活。
验证安装:
import torch
print(torch.__version__)
print(torch.cuda.is_available()) # True 就对了
4.3 张量操作——深度学习的基本功
张量,说白了就是多维数组。标量是 0 维,向量是 1 维,矩阵是 2 维,再往上就是高维张量。材料科学里,晶体结构、电荷密度、应力应变场,这些数据天然就是张量形式。
4.3.1 创建张量
import torch
# 从列表创建
t1 = torch.tensor([1.0, 2.0, 3.0])
# 全零张量
t2 = torch.zeros(3, 4)
# 随机张量
t3 = torch.randn(2, 3) # 标准正态分布
# 指定数据类型和设备
t4 = torch.ones(2, 2, dtype=torch.float32, device='cuda')
小技巧: 做材料数据预处理时,我习惯先把数据转成 numpy,再用 torch.from_numpy() 转成张量。这样能利用 numpy 强大的数组操作,又能在 GPU 上跑。
4.3.2 张量运算
加减乘除、矩阵乘法、索引切片,这些和 numpy 几乎一样。但有几个地方要注意:
# 矩阵乘法(这个容易搞混)
a = torch.randn(3, 4)
b = torch.randn(4, 5)
c = torch.mm(a, b) # 或者 a @ b
# 逐元素乘法
d = a * a
# 维度变换
e = a.view(2, 6) # 改变形状
f = a.permute(1, 0) # 转置
嗯,这里要特别说一下 view 和 reshape 的区别。view 要求内存连续,reshape 不要求。我遇到过 view 报错,换成 reshape 就好了。所以保险起见,用 reshape。
4.4 自动求导机制——深度学习引擎的核心
为什么深度学习能火?自动求导功不可没。你想想看,以前算梯度要手推公式,一个三层网络就够你推导半天。现在?PyTorch 帮你全干了。
4.4.1 计算图与 autograd
PyTorch 的自动求导基于动态计算图。什么意思?就是你每执行一步操作,它都记录下这个操作,形成一个图。反向传播时,沿着这个图往回走,用链式法则算梯度。
# 开启梯度追踪
x = torch.tensor([2.0], requires_grad=True)
y = x ** 2 + 3 * x + 1
# 反向传播
y.backward()
# 查看梯度
print(x.grad) # 输出: tensor([7.]),因为 dy/dx = 2x + 3 = 7
核心概念: requires_grad=True 告诉 PyTorch 这个变量需要算梯度。只有需要更新的参数才开启,输入数据不需要。我刚开始学的时候,给输入数据也开了,白白浪费内存。
4.4.2 梯度清零与 detach
训练循环里,每次反向传播前都要清零梯度。为什么?因为 PyTorch 默认累加梯度。这在某些场景有用(比如梯度累积),但大多数时候你需要清零。
# 标准训练循环
optimizer.zero_grad() # 清零梯度
loss.backward() # 反向传播
optimizer.step() # 更新参数
还有一个常用的操作是 detach。它从计算图中分离出一个张量,不再追踪梯度。我在做迁移学习时经常用——冻结预训练模型的部分参数。
4.5 构建第一个全连接网络
好了,理论说够了,我们来动手。用 PyTorch 构建一个预测材料弹性模量的全连接网络。
4.5.1 网络结构设计
全连接网络,也叫多层感知机(MLP)。结构很简单:输入层 -> 隐藏层 -> 输出层。每层之间是全连接的,每个神经元都连接到下一层的所有神经元。
import torch.nn as nn
import torch.nn.functional as F
class ElasticNet(nn.Module):
def __init__(self, input_dim=10, hidden_dim=64, output_dim=1):
super(ElasticNet, self).__init__()
self.fc1 = nn.Linear(input_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, hidden_dim)
self.fc3 = nn.Linear(hidden_dim, output_dim)
self.dropout = nn.Dropout(0.2)
def forward(self, x):
x = F.relu(self.fc1(x))
x = self.dropout(x)
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
设计经验: 隐藏层维度我一般取 2 的幂次,64、128、256。这不是玄学,是 GPU 对 2 的幂次有优化。另外,dropout 加在激活函数后面,不是前面。
4.5.2 训练流程
训练一个网络,核心就三步:前向传播、计算损失、反向传播更新参数。
# 初始化
model = ElasticNet()
criterion = nn.MSELoss() # 回归任务用均方误差
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# 训练循环
for epoch in range(100):
# 前向传播
outputs = model(inputs)
loss = criterion(outputs, targets)
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
if epoch % 10 == 0:
print(f'Epoch {epoch}, Loss: {loss.item():.4f}')
这里有个坑——loss.item()。我见过有人直接用 loss 打印,结果打印出一个张量,控制台刷屏。item() 把单元素张量转成 Python 数值,干净利落。
4.5.3 完整示例:预测合金弹性模量
假设我们有 1000 个合金样本,每个样本有 10 个特征(成分、温度等),要预测弹性模量。
# 生成模拟数据
import numpy as np
np.random.seed(42)
X = np.random.randn(1000, 10).astype(np.float32)
y = np.random.randn(1000, 1).astype(np.float32)
# 转成张量
X_tensor = torch.from_numpy(X)
y_tensor = torch.from_numpy(y)
# 训练
model = ElasticNet()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
for epoch in range(200):
pred = model(X_tensor)
loss = F.mse_loss(pred, y_tensor)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if epoch % 50 == 0:
print(f'Epoch {epoch}, MSE: {loss.item():.6f}')
注意: 真实项目中,数据要归一化、要划分训练集测试集、要加验证集。上面这个只是演示流程,别直接拿去用。我曾经犯过这个错——模型在训练集上表现完美,测试集上一塌糊涂。
4.6 本章知识体系
下面这张图总结了本章的核心内容,从框架选择到完整网络构建,一条线串起来:
4.7 常见问题与避坑指南
| 问题 | 原因 | 解决方案 |
|---|---|---|
| GPU 显存不足 | batch size 太大或网络太深 | 减小 batch size,或使用梯度累积 |
| loss 不下降 | 学习率太大或太小 | 尝试 lr=0.01, 0.001, 0.0001 |
| 梯度爆炸 | 网络太深或激活函数不合适 | 加梯度裁剪 torch.nn.utils.clip_grad_norm_ |
| 过拟合 | 模型太复杂,数据太少 | 加 dropout、正则化、数据增强 |
我曾经在训练一个预测材料断裂韧性的网络时,loss 怎么都降不下去。折腾了两天,最后发现是数据没归一化。特征值范围从 0.001 到 10000,网络根本学不动。所以,数据预处理真的很重要。
好了,这一章的内容就到这里。框架装好了,张量会操作了,自动求导理解了,第一个网络也跑通了。接下来就是不断练习,用真实材料数据去训练、去调参、去优化。这条路没有捷径,但每一步都算数。
公众号:蓝海资料掘金营,微信deep3321