Files
Kaggle/Mnist-Vae/main.py
T

137 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import torch
from torch import nn, optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
import numpy as np
import matplotlib.pyplot as plt
# ---------- 模型定义(修正) ----------
class VAE(nn.Module):
def __init__(self):
super(VAE, self).__init__() # 修正:去掉多余的 self
self.encoder = nn.Sequential(
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, 64),
nn.ReLU(),
nn.Linear(64, 20), # 输出 mu 和 log_var(或 sigma
)
self.decoder = nn.Sequential(
nn.Linear(10, 64),
nn.ReLU(),
nn.Linear(64, 256),
nn.ReLU(),
nn.Linear(256, 784),
nn.Sigmoid(),
)
def forward(self, x):
hidden = self.encoder(x)
mu, log_var = hidden.chunk(2, dim=1) # 用 log_var 更稳定
# 重参数化:sigma = exp(0.5 * log_var)
std = torch.exp(0.5 * log_var)
eps = torch.randn_like(std)
z = mu + eps * std
x_hat = self.decoder(z)
# KL 散度(按 batch 和像素平均)
KL = -0.5 * torch.sum(1 + log_var - mu.pow(2) - log_var.exp())
KL = KL / (x.size(0) * 28 * 28) # 与重构损失尺度一致
return x_hat, KL
# ---------- 超参数设置 ----------
batch_size = 128
epochs = 20
learning_rate = 1e-3
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {device}")
# ---------- 数据加载 ----------
transform = transforms.Compose(
[
transforms.ToTensor(), # 将 [0,255] 转为 [0,1] 的 Tensor
transforms.Lambda(lambda x: x.view(-1)), # 展平为 784 维向量
]
)
train_dataset = datasets.MNIST(
root="./data", train=True, download=True, transform=transform
)
test_dataset = datasets.MNIST(
root="./data", train=False, download=True, transform=transform
)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
# ---------- 初始化模型、优化器 ----------
model = VAE().to(device)
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
# 重构损失(二元交叉熵,因为像素在 [0,1])
criterion = nn.BCELoss(reduction="sum") # sum 后与 KL 求和,再除以像素数
# ---------- 训练循环 ----------
for epoch in range(1, epochs + 1):
model.train()
total_loss = 0
total_recon = 0
total_kl = 0
for batch_idx, (data, _) in enumerate(train_loader):
data = data.to(device)
optimizer.zero_grad()
x_hat, kl = model(data)
recon_loss = criterion(x_hat, data) # 按 batch 求和(每个像素的 BCE 之和)
# 总损失 = 重构损失 + KL 散度(都已除以像素数,但 recon 未除,所以需统一)
# 此处将 recon 也除以像素数,使两项量级匹配
recon_loss = recon_loss / (data.size(0) * 28 * 28)
loss = recon_loss + kl
loss.backward()
optimizer.step()
total_loss += loss.item()
total_recon += recon_loss.item()
total_kl += kl.item()
avg_loss = total_loss / len(train_loader)
avg_recon = total_recon / len(train_loader)
avg_kl = total_kl / len(train_loader)
print(
f"Epoch {epoch:2d} | Avg Loss: {avg_loss:.4f} | Recon: {avg_recon:.4f} | KL: {avg_kl:.4f}"
)
# 每 5 个 epoch 生成一些样本看看效果(可选)
if epoch % 5 == 0:
model.eval()
with torch.no_grad():
# 从标准正态分布采样 16 个 latent code
sample_z = torch.randn(16, 10).to(device)
generated = model.decoder(sample_z).cpu().numpy()
# 显示
fig, axes = plt.subplots(4, 4, figsize=(6, 6))
for i, ax in enumerate(axes.flat):
ax.imshow(generated[i].reshape(28, 28), cmap="gray")
ax.axis("off")
plt.suptitle(f"Epoch {epoch} Generated Samples")
plt.show()
plt.close()
# ---------- 测试集评估(可选) ----------
model.eval()
test_loss = 0
with torch.no_grad():
for data, _ in test_loader:
data = data.to(device)
x_hat, kl = model(data)
recon = criterion(x_hat, data) / (data.size(0) * 28 * 28)
test_loss += (recon + kl).item()
print(f"Test Average Loss: {test_loss / len(test_loader):.4f}")