第二个深度学习项目——轻量CNN上跑CIFAR-10数据集
一、 导包
import time
import torch.cuda
import torchvision
from torch.utils.data import DataLoader
from torch.utils.data import random_split,Subset
from torchvision import transforms
import torch.nn as nn
from torchvision import datasets
import numpy as np
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score
import matplotlib.pyplot as plt
import seaborn as sns
二、 数据增强
1、先填充再裁剪,使用上下左右随机移动
2、水平翻转
3、亮度 对比度 饱和度变化
4、50%概率随机擦除,与经典Cutout有一定差别,感觉使用经典Cutout效果应该会更好
这里注意验证机和测试集上面不做transformer
transformer_train = transforms.Compose(
[
transforms.RandomCrop(32, padding=4), # 先填充再裁剪,使用上下左右随机移动
transforms.RandomHorizontalFlip(p=0.5), # 0.5的概率水平反转
transforms.ColorJitter(brightness=0.2, # 亮度变化±20%
contrast=0.2, # 对比度变化±20%
saturation=0.2), # 饱和度变化±20%
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465),
(0.2470, 0.2435, 0.2616)),
transforms.RandomErasing(p=0.5, # 50%概率随机擦除
scale=(0.02, 0.33),
ratio=(0.3, 3.3))
]
)
transformer_test = transforms.Compose(
[
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465),
(0.2470, 0.2435, 0.2616)),
]
)
三、 定义模型
CIFAR-10 图像为32 * 32 * 3 这里使用轻量化的CNN网络
维度变化:3 * 32 * 32 - > 64 * 4 * 4
每次卷积之后都使用了批归一化使模型更加稳定,最后就是一个正常的全连接层
class myCNN(nn.Module):
def __init__(self,class_num):
super().__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(3,16,3,1,1),
nn.BatchNorm2d(16),
nn.ReLU(),
nn.Conv2d(16, 16, 3, 1, 1),
nn.BatchNorm2d(16),
nn.ReLU(),
nn.MaxPool2d(2) # 16 * 16 * 16
)
self.layer2 = nn.Sequential(
nn.Conv2d(16, 32, 3, 1, 1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.Conv2d(32, 32, 3, 1, 1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.MaxPool2d(2) # 32 * 8 * 8
)
self.layer3 = nn.Sequential(
nn.Conv2d(32, 64, 3, 1, 1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.Conv2d(64, 64, 3, 1, 1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(2) # 64 * 4 * 4
)
self.layer4 = nn.Sequential(
nn.Flatten(), # 在这里展开就不需要再在forward函数中重新展开
nn.Linear(64*4*4,512),
nn.ReLU(),
nn.Linear(512,256),
nn.ReLU(),
nn.Linear(256,class_num)
)
def forward(self, x):
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
return x
四、 训练函数
训练函数中每个epoch记录一次loss,所以画出的loss图像大多都很平
(模型参数保存好像写得有问题,没改,最优参数都没保存)
def train_val(model, train_loader, val_loader, optimizer, loss, epoches, device, save_path, dir_path):
model = model.to(device)
plt_train_loss = []
plt_val_loss = []
plt_train_acc = []
plt_val_acc = [] # 该列表用来记录每一个epoch的 loss 和 acc
max_acc = []
for epoch in range(epoches):
start_time = time.time()
train_loss = 0.0
val_loss = 0.0 # 用来记录每一个epoch的 loss 和 acc
train_acc = 0.0
val_acc = 0.0
model.train() # 模型调为训练模式
for batch_x, batch_y in train_loader:
x, target = batch_x.to(device), batch_y.to(device)
pred = model(x)
train_batch_loss = loss(pred, target)
optimizer.zero_grad() # 梯度清理
train_batch_loss.backward() # 反向传播
optimizer.step() # 更新梯度
train_loss += train_batch_loss.detach().cpu().item()
train_acc += np.sum(np.argmax(pred.detach().cpu().numpy(),
axis=1) == target.cpu().numpy())
plt_train_loss.append(train_loss/train_loader.__len__())
plt_train_acc.append(train_acc/train_loader.dataset.__len__())
model.eval()
with torch.no_grad():
for batch_x,batch_y in val_loader:
x, target = batch_x.to(device), batch_y.to(device)
pred = model(x)
val_batch_loss = loss(pred,target)
val_loss += val_batch_loss.detach().cpu().item()
val_acc += np.sum(np.argmax(pred.detach().cpu().numpy(),
axis=1) == target.cpu().numpy())
plt_val_loss.append(val_loss/val_loader.__len__())
plt_val_acc.append(val_acc/val_loader.dataset.__len__())
if val_acc > max_acc:
torch.save(model,save_path)
max_acc = val_acc
print("[%02d/%02d] %2.2f sec(s) Trainloss:%.6f |train_acc:%.2f | val_loss:%.6f | val_acc:%.2f"%
(epoch, epoches, time.time() - start_time,plt_train_loss[-1], plt_train_acc[-1], plt_val_loss[-1], plt_val_acc[-1]))
plt.figure()
plt.plot(plt_train_loss,label='traiin')
plt.plot(plt_val_loss,label='val')
plt.title('loss')
plt.legend() # 显示图例
plt.savefig("img/" + dir_path + 'loss_curve.png') # 保存
plt.close() # 关图,防止内存泄漏
# ---------- acc 曲线 ----------
plt.figure()
plt.plot(plt_train_acc, label='train')
plt.plot(plt_val_acc, label='val')
plt.title('acc')
plt.legend()
plt.savefig("img/" + dir_path + 'acc_curve.png')
plt.close()
五、 测试模型函数
def test_model(model, test_loader, device, dir_path, class_names=None):
"""
CIFAR-10 完整测试评估
"""
if class_names is None:
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer',
'dog', 'frog', 'horse', 'ship', 'truck']
model = model.to(device)
model.eval()
all_preds = []
all_targets = []
total_loss = 0.0
loss_fn = torch.nn.CrossEntropyLoss()
with torch.no_grad():
for batch_x, batch_y in test_loader:
x, target = batch_x.to(device), batch_y.to(device)
# 前向传播
pred = model(x)
# 计算loss
loss = loss_fn(pred, target)
total_loss += loss.item() * target.size(0)
# 收集预测和标签
pred_labels = pred.argmax(1).cpu().numpy()
all_preds.extend(pred_labels)
all_targets.extend(target.cpu().numpy())
# 转为numpy数组
all_preds = np.array(all_preds)
all_targets = np.array(all_targets)
# ========== 基础指标 ==========
# Top-1 Accuracy
top1_acc = accuracy_score(all_targets, all_preds)
# 平均Loss
avg_loss = total_loss / len(all_targets)
# ========== 详细分类报告 ==========
report = classification_report(
all_targets,
all_preds,
target_names=class_names,
digits=4
)
# ========== 混淆矩阵 ==========
cm = confusion_matrix(all_targets, all_preds)
# ========== 每类准确率 ==========
per_class_acc = cm.diagonal() / cm.sum(axis=1)
# ========== 打印结果 ==========
print("\n" + "=" * 50)
print(" CIFAR-10 Test Results")
print("=" * 50)
print(f"Test Loss : {avg_loss:.4f}")
print(f"Top-1 Accuracy : {top1_acc:.4f} ({top1_acc * 100:.2f}%)")
print(f"Total Samples : {len(all_targets)}")
print("-" * 50)
print("\nPer-class Accuracy:")
for i, (name, acc) in enumerate(zip(class_names, per_class_acc)):
print(f" {i:2d}. {name:12s}: {acc:.4f} ({acc * 100:5.2f}%)")
print("\n" + "-" * 50)
print("Classification Report:")
print(report)
# ========== 可视化混淆矩阵 ==========
plt.figure(figsize=(10, 8))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
xticklabels=class_names,
yticklabels=class_names)
plt.xlabel('Predicted Label')
plt.ylabel('True Label')
plt.title('CIFAR-10 Confusion Matrix')
plt.tight_layout()
plt.savefig('img/' + dir_path + 'confusion_matrix.png', dpi=150)
print("\nConfusion matrix saved to 'confusion_matrix.png'")
plt.show()
# ========== 找出最难分类的类别 ==========
worst_classes = np.argsort(per_class_acc)[:3]
print(f"\nTop 3 Most Difficult Classes:")
for idx in worst_classes:
print(f" {class_names[idx]}: {per_class_acc[idx]:.4f}")
print("=" * 50)
return {
'top1_acc': top1_acc,
'avg_loss': avg_loss,
'per_class_acc': per_class_acc,
'confusion_matrix': cm,
'predictions': all_preds,
'targets': all_targets
}
六、 训练模型
if __name__ == "__main__":
######定义超参数#######
train_size = 40000
val_size = 10000
batch_size = 64
lr = 0.001
momentum = 0.1
epoches = 32
loss = nn.CrossEntropyLoss()
device = 'cuda' if torch.cuda.is_available() else 'cpu'
dataPath = "data_CIFAR"
save_path = "model_save/best.model.pth"
full_data = torchvision.datasets.CIFAR10(dataPath, train=True, download=False)
train_indices, val_indices = random_split(
range(len(full_data)),
[train_size, val_size],
generator=torch.Generator().manual_seed(25)
)
train_data = Subset(datasets.CIFAR10(dataPath, train=True, transform=transformer_train, download=False),
train_indices.indices)
val_data = Subset(datasets.CIFAR10(dataPath, train=True, transform=transformer_test, download=False), # 或 None
val_indices.indices)
test_data = datasets.CIFAR10(dataPath, train=False, transform=transformer_test, download=False)
model = myCNN(10)
lr_lst = [0.001, 0.005, 0.0001]
batch_size_lst = [64, 128, 256]
for lr in lr_lst:
for batch_size in batch_size_lst:
train_loader = DataLoader(train_data, batch_size=batch_size, shuffle=True)
val_loader = DataLoader(val_data, batch_size=batch_size, shuffle=False)
test_loader = DataLoader(test_data, batch_size=batch_size, shuffle=False)
dir_path = f'lr_{lr}_epoches_{epoches}_bs_{batch_size}'
print(f"{'='*20}" , dir_path, f"{'='*20}")
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
save_path = "model_save/" + dir_path + "best.model.pth"
train_val(model, train_loader, val_loader, optimizer, loss, epoches, device, save_path, dir_path)
results = test_model(model, test_loader, device='cuda', dir_path=dir_path)
print(f"\nFinal Test Accuracy: {results['top1_acc'] * 100:.2f}%")
七、 结果
1、batch_size = 64,lr = 0.001
最后发现,模型在batch_size=64,lr=0.001 需要多个epoch才能收敛
此时train_acc=0.81 val_acc = 0.83
详细结果如下:

==================================================
CIFAR-10 Test Results
==================================================
Test Loss : 0.5250
Top-1 Accuracy : 0.8292 (82.92%)
Total Samples : 10000
--------------------------------------------------Per-class Accuracy:
0. airplane : 0.8520 (85.20%)
1. automobile : 0.9090 (90.90%)
2. bird : 0.6650 (66.50%)
3. cat : 0.5390 (53.90%)
4. deer : 0.8650 (86.50%)
5. dog : 0.8270 (82.70%)
6. frog : 0.9150 (91.50%)
7. horse : 0.8970 (89.70%)
8. ship : 0.9040 (90.40%)
9. truck : 0.9190 (91.90%)--------------------------------------------------
Classification Report:
precision recall f1-score supportairplane 0.8402 0.8520 0.8461 1000
automobile 0.9489 0.9090 0.9285 1000
bird 0.8636 0.6650 0.7514 1000
cat 0.8292 0.5390 0.6533 1000
deer 0.7621 0.8650 0.8103 1000
dog 0.6852 0.8270 0.7494 1000
frog 0.7888 0.9150 0.8472 1000
horse 0.8478 0.8970 0.8717 1000
ship 0.8915 0.9040 0.8977 1000
truck 0.8888 0.9190 0.9036 1000accuracy 0.8292 10000
macro avg 0.8346 0.8292 0.8259 10000
weighted avg 0.8346 0.8292 0.8259 10000
Confusion matrix saved to 'confusion_matrix.png'Top 3 Most Difficult Classes:
cat: 0.5390
bird: 0.6650
dog: 0.8270
2、batch_size = 256,lr = 0.0001
模型一上来就能达到较高的精度(也很奇怪,寻找答案中...所以导致loss和acc的图看起来也很奇怪),后来再用这一组参数,也没有这88的精度了(不知道为什么,以后再回来看看)

3、batch_size = 256,lr = 0.001
(后来重新单独训练的一组参数,也不是一上来就收敛到80多的精度,哈哈真奇怪)
loss 和acc 曲线看起来感觉还是挺正常的

八、 完整源码
import time
import matplotlib.pyplot as plt
import numpy as np
import torch.cuda
import torchvision
from torch.utils.data import DataLoader,Dataset
from torch.utils.tensorboard import SummaryWriter
from torch.utils.data import random_split,Subset
from torchvision import transforms
import torch.nn as nn
from torchvision import datasets
import sys
import os
from datetime import datetime
import numpy as np
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score
import matplotlib.pyplot as plt
import seaborn as sns
transformer_train = transforms.Compose(
[
transforms.RandomCrop(32, padding=4), # 先填充再裁剪,使用上下左右随机移动
transforms.RandomHorizontalFlip(p=0.5), # 0.5的概率水平反转
transforms.ColorJitter(brightness=0.2, # 亮度变化±20%
contrast=0.2, # 对比度变化±20%
saturation=0.2), # 饱和度变化±20%
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465),
(0.2470, 0.2435, 0.2616)),
transforms.RandomErasing(p=0.5, # 50%概率随机擦除
scale=(0.02, 0.33),
ratio=(0.3, 3.3))
]
)
transformer_test = transforms.Compose(
[
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465),
(0.2470, 0.2435, 0.2616)),
]
)
class myCNN(nn.Module):
def __init__(self,class_num):
super().__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(3,16,3,1,1),
nn.BatchNorm2d(16),
nn.ReLU(),
nn.Conv2d(16, 16, 3, 1, 1),
nn.BatchNorm2d(16),
nn.ReLU(),
nn.MaxPool2d(2) # 16 * 16 * 16
)
self.layer2 = nn.Sequential(
nn.Conv2d(16, 32, 3, 1, 1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.Conv2d(32, 32, 3, 1, 1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.MaxPool2d(2) # 32 * 8 * 8
)
self.layer3 = nn.Sequential(
nn.Conv2d(32, 64, 3, 1, 1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.Conv2d(64, 64, 3, 1, 1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(2) # 64 * 4 * 4
)
self.layer4 = nn.Sequential(
nn.Flatten(),
nn.Linear(64*4*4,512),
nn.ReLU(),
nn.Linear(512,256),
nn.ReLU(),
nn.Linear(256,class_num)
)
def forward(self, x):
x = self.layer1(x)
x = self.layer2(x)
x = self.layer3(x)
x = self.layer4(x)
return x
def train_val(model, train_loader, val_loader, optimizer, loss, epoches, device, save_path, dir_path):
model = model.to(device)
plt_train_loss = []
plt_val_loss = []
plt_train_acc = []
plt_val_acc = [] # 该列表用来记录每一个epoch的 loss 和 acc
max_acc = []
for epoch in range(epoches):
start_time = time.time()
train_loss = 0.0
val_loss = 0.0 # 用来记录每一个epoch的 loss 和 acc
train_acc = 0.0
val_acc = 0.0
model.train() # 模型调为训练模式
for batch_x, batch_y in train_loader:
x, target = batch_x.to(device), batch_y.to(device)
pred = model(x)
train_batch_loss = loss(pred, target)
optimizer.zero_grad() # 梯度清理
train_batch_loss.backward() # 反向传播
optimizer.step() # 更新梯度
train_loss += train_batch_loss.detach().cpu().item()
train_acc += np.sum(np.argmax(pred.detach().cpu().numpy(),
axis=1) == target.cpu().numpy())
plt_train_loss.append(train_loss/train_loader.__len__())
plt_train_acc.append(train_acc/train_loader.dataset.__len__())
model.eval()
with torch.no_grad():
for batch_x,batch_y in val_loader:
x, target = batch_x.to(device), batch_y.to(device)
pred = model(x)
val_batch_loss = loss(pred,target)
val_loss += val_batch_loss.detach().cpu().item()
val_acc += np.sum(np.argmax(pred.detach().cpu().numpy(),
axis=1) == target.cpu().numpy())
plt_val_loss.append(val_loss/val_loader.__len__())
plt_val_acc.append(val_acc/val_loader.dataset.__len__())
if val_acc > max_acc:
torch.save(model,save_path)
max_acc = val_acc
print("[%02d/%02d] %2.2f sec(s) Trainloss:%.6f |train_acc:%.2f | val_loss:%.6f | val_acc:%.2f"%
(epoch, epoches, time.time() - start_time,plt_train_loss[-1], plt_train_acc[-1], plt_val_loss[-1], plt_val_acc[-1]))
plt.figure()
plt.plot(plt_train_loss,label='traiin')
plt.plot(plt_val_loss,label='val')
plt.title('loss')
plt.legend() # 显示图例
plt.savefig("img1/" + dir_path + 'loss_curve.png') # 保存
plt.close() # 关图,防止内存泄漏
# ---------- acc 曲线 ----------
plt.figure()
plt.plot(plt_train_acc, label='train')
plt.plot(plt_val_acc, label='val')
plt.title('acc')
plt.legend()
plt.savefig("img1/" + dir_path + 'acc_curve.png')
plt.close()
def test_model(model, test_loader, device, dir_path, class_names=None):
"""
CIFAR-10 完整测试评估
"""
if class_names is None:
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer',
'dog', 'frog', 'horse', 'ship', 'truck']
model = model.to(device)
model.eval()
all_preds = []
all_targets = []
total_loss = 0.0
loss_fn = torch.nn.CrossEntropyLoss()
with torch.no_grad():
for batch_x, batch_y in test_loader:
x, target = batch_x.to(device), batch_y.to(device)
# 前向传播
pred = model(x)
# 计算loss
loss = loss_fn(pred, target)
total_loss += loss.item() * target.size(0)
# 收集预测和标签
pred_labels = pred.argmax(1).cpu().numpy()
all_preds.extend(pred_labels)
all_targets.extend(target.cpu().numpy())
# 转为numpy数组
all_preds = np.array(all_preds)
all_targets = np.array(all_targets)
# ========== 基础指标 ==========
# Top-1 Accuracy
top1_acc = accuracy_score(all_targets, all_preds)
# 平均Loss
avg_loss = total_loss / len(all_targets)
# ========== 详细分类报告 ==========
report = classification_report(
all_targets,
all_preds,
target_names=class_names,
digits=4
)
# ========== 混淆矩阵 ==========
cm = confusion_matrix(all_targets, all_preds)
# ========== 每类准确率 ==========
per_class_acc = cm.diagonal() / cm.sum(axis=1)
# ========== 打印结果 ==========
print("\n" + "=" * 50)
print(" CIFAR-10 Test Results")
print("=" * 50)
print(f"Test Loss : {avg_loss:.4f}")
print(f"Top-1 Accuracy : {top1_acc:.4f} ({top1_acc * 100:.2f}%)")
print(f"Total Samples : {len(all_targets)}")
print("-" * 50)
print("\nPer-class Accuracy:")
for i, (name, acc) in enumerate(zip(class_names, per_class_acc)):
print(f" {i:2d}. {name:12s}: {acc:.4f} ({acc * 100:5.2f}%)")
print("\n" + "-" * 50)
print("Classification Report:")
print(report)
# ========== 可视化混淆矩阵 ==========
plt.figure(figsize=(10, 8))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
xticklabels=class_names,
yticklabels=class_names)
plt.xlabel('Predicted Label')
plt.ylabel('True Label')
plt.title('CIFAR-10 Confusion Matrix')
plt.tight_layout()
plt.savefig('img1/' + dir_path + 'confusion_matrix.png', dpi=150)
print("\nConfusion matrix saved to 'confusion_matrix.png'")
plt.show()
# ========== 找出最难分类的类别 ==========
worst_classes = np.argsort(per_class_acc)[:3]
print(f"\nTop 3 Most Difficult Classes:")
for idx in worst_classes:
print(f" {class_names[idx]}: {per_class_acc[idx]:.4f}")
print("=" * 50)
return {
'top1_acc': top1_acc,
'avg_loss': avg_loss,
'per_class_acc': per_class_acc,
'confusion_matrix': cm,
'predictions': all_preds,
'targets': all_targets
}
if __name__ == "__main__":
######定义超参数#######
train_size = 40000
val_size = 10000
batch_size = 64
lr = 0.001
momentum = 0.1
epoches = 32
loss = nn.CrossEntropyLoss()
device = 'cuda' if torch.cuda.is_available() else 'cpu'
dataPath = "data_CIFAR"
save_path = "model_save/best.model.pth"
full_data = torchvision.datasets.CIFAR10(dataPath, train=True, download=False)
train_indices, val_indices = random_split(
range(len(full_data)),
[train_size, val_size],
generator=torch.Generator().manual_seed(25)
)
train_data = Subset(datasets.CIFAR10(dataPath, train=True, transform=transformer_train, download=False),
train_indices.indices)
val_data = Subset(datasets.CIFAR10(dataPath, train=True, transform=transformer_test, download=False), # 或 None
val_indices.indices)
test_data = datasets.CIFAR10(dataPath, train=False, transform=transformer_test, download=False)
model = myCNN(10)
lr_lst = [0.0001] # 这里根据需要自己调整
batch_size_lst = [128, 256]
for lr in lr_lst:
for batch_size in batch_size_lst:
train_loader = DataLoader(train_data, batch_size=batch_size, shuffle=True)
val_loader = DataLoader(val_data, batch_size=batch_size, shuffle=False)
test_loader = DataLoader(test_data, batch_size=batch_size, shuffle=False)
dir_path = f'lr_{lr}_epoches_{epoches}_bs_{batch_size}'
print(f"{'='*20}" , dir_path, f"{'='*20}")
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
save_path = "model_save/" + dir_path + "best.model.pth"
train_val(model, train_loader, val_loader, optimizer, loss, epoches, device, save_path, dir_path)
results = test_model(model, test_loader, device='cuda', dir_path=dir_path)
print(f"\nFinal Test Accuracy: {results['top1_acc'] * 100:.2f}%")
找到原因了,模型定义到循环外面去了,导致模型没有初始化,模型上一次训练的参数没有清除,相当于每次训练都在上一次的基础上跑模型,太搞笑了,一个小时的模型白跑
更多推荐

所有评论(0)