```python
# -*- coding: utf-8 -*-
"""
🐱🐶 猫狗图像二分类(基于 PyTorch CPU)
✅ 使用文件夹结构数据集:train/ 和 test/
✅ 基于卷积神经网络(CNN)完成分类
✅ 输出测试准确率并绘制训练过程线图
✅ 完整、可运行、无需额外配置
"""
# Step 1: 安装依赖(首次运行时启用)
# !pip install torch torchvision matplotlib pillow numpy
# Step 2: 导入库
import os
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import transforms, datasets
import matplotlib.pyplot as plt
# 设置设备为 CPU(显式声明)
device = torch.device('cpu')
print(f"使用设备: {device}")
# 设置随机种子以提高可复现性
torch.manual_seed(42)
# =============================================
# 参数设置
# =============================================
data_dir = './' # 数据集根目录,包含 train/ 和 test/ 文件夹
batch_size = 32
num_epochs = 10
lr = 0.001
# 图像预处理
transform = transforms.Compose([
transforms.Resize((128, 128)), # 统一分辨率
transforms.Grayscale(num_output_channels=1), # 转为单通道灰度图
transforms.ToTensor(), # 转为张量
transforms.Normalize(mean=[0.5], std=[0.5]) # 归一化到 [-1, 1]
])
# =============================================
# 加载训练集和测试集
# =============================================
train_dataset = datasets.ImageFolder(root=os.path.join(data_dir, 'train'), transform=transform)
test_dataset = datasets.ImageFolder(root=os.path.join(data_dir, 'test'), transform=transform)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
print(f"✅ 训练样本数: {len(train_dataset)}")
print(f"✅ 测试样本数: {len(test_dataset)}")
print(f"✅ 类别名称: {train_dataset.classes}") # 应为 ['cat', 'dog']
# =============================================
# 定义 CNN 模型
# =============================================
class CatDogCNN(nn.Module):
def __init__(self):
super(CatDogCNN, self).__init__()
self.network = nn.Sequential(
nn.Conv2d(1, 16, kernel_size=3, padding=1), # 输入单通道
nn.ReLU(),
nn.MaxPool2d(2),
nn.Conv2d(16, 32, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2),
nn.Conv2d(32, 64, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2),
nn.AdaptiveAvgPool2d((8, 8)), # 固定输出大小
nn.Flatten(),
nn.Linear(64 * 8 * 8, 128),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(128, 2) # 两类输出
)
def forward(self, x):
return self.network(x)
model = CatDogCNN().to(device)
# 损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=lr)
# =============================================
# 训练模型
# =============================================
train_losses = []
train_accuracies = []
test_accuracies = []
print("\n🚀 开始训练...")
for epoch in range(num_epochs):
# 训练阶段
model.train()
running_loss = 0.0
correct = 0
total = 0
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
running_loss += loss.item()
_, predicted = torch.max(outputs, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
avg_loss = running_loss / len(train_loader)
train_acc = correct / total
train_losses.append(avg_loss)
train_accuracies.append(train_acc)
# 测试阶段
model.eval()
test_correct = 0
test_total = 0
with torch.no_grad():
for images, labels in test_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
_, predicted = torch.max(outputs, 1)
test_total += labels.size(0)
test_correct += (predicted == labels).sum().item()
test_acc = test_correct / test_total
test_accuracies.append(test_acc)
print(f"Epoch [{epoch+1}/{num_epochs}], "
f"Loss: ${avg_loss:.4f}$, "
f"Train Acc: ${train_acc:.4f}$, "
f"Test Acc: ${test_acc:.4f}$")
# =============================================
# 输出最终准确率
# =============================================
final_test_acc = test_accuracies[-1]
print(f"\n✅ 最终测试准确率: ${final_test_acc:.4f}$")
# =============================================
# 绘制训练曲线
# =============================================
plt.figure(figsize=(12, 5))
# 子图1:训练损失
plt.subplot(1, 2, 1)
plt.plot(range(1, num_epochs+1), train_losses, 'b-o', label='Training Loss')
plt.title('训练损失曲线')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.grid(True)
plt.legend()
# 子图2:准确率对比
plt.subplot(1, 2, 2)
epochs_range = range(1, num_epochs+1)
plt.plot(epochs_range, train_accuracies, 'g-o', label='Train Accuracy')
plt.plot(epochs_range, test_accuracies, 'r-o', label='Test Accuracy')
plt.title('训练与测试准确率')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.ylim(0, 1)
plt.grid(True)
plt.legend()
plt.tight_layout()
plt.show()
# 显示最终结果
print(f"\n📊 模型训练完成!")
print(f"📈 最高测试准确率: ${max(test_accuracies):.4f}$")
print(f"🎯 最终测试准确率: ${final_test_acc:.4f}$")
```
---
```python
# -*- coding: utf-8 -*-
"""
🐱🐶 猫狗分类(ResNet-like 小型网络 + 更强数据增强)
✅ 使用更复杂的网络结构
✅ 添加更多数据增强手段
✅ 输出测试准确率与动态训练线图
✅ 支持文件夹输入:train/, test/
"""
# Step 1: 安装依赖(首次运行时启用)
# !pip install torch torchvision matplotlib pillow
# Step 2: 导入库
import os
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import transforms, datasets
import matplotlib.pyplot as plt
# 固定随机种子
torch.manual_seed(42)
# =============================================
# 参数设置
# =============================================
data_dir = './'
batch_size = 32
num_epochs = 10
lr = 0.001
device = torch.device('cpu')
# 更强的数据增强(适用于训练集)
train_transform = transforms.Compose([
transforms.Resize((128, 128)),
transforms.Grayscale(num_output_channels=1),
transforms.RandomHorizontalFlip(p=0.5), # 随机翻转
transforms.RandomRotation(degrees=10), # 随机旋转
transforms.ColorJitter(brightness=0.2), # 亮度扰动
transforms.ToTensor(),
transforms.Normalize(mean=[0.5], std=[0.5])
])
# 测试集仅做基本变换
test_transform = transforms.Compose([
transforms.Resize((128, 128)),
transforms.Grayscale(num_output_channels=1),
transforms.ToTensor(),
transforms.Normalize(mean=[0.5], std=[0.5])
])
# 加载数据集
train_dataset = datasets.ImageFolder(root=os.path.join(data_dir, 'train'), transform=train_transform)
test_dataset = datasets.ImageFolder(root=os.path.join(data_dir, 'test'), transform=test_transform)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
print(f"✅ 训练集大小: {len(train_dataset)}")
print(f"✅ 测试集大小: {len(test_dataset)}")
print(f"✅ 类别: {train_dataset.classes}")
# =============================================
# 定义改进型 CNN(类似 Residual 结构简化版)
# =============================================
class SmallResNet(nn.Module):
def __init__(self):
super(SmallResNet, self).__init__()
self.conv1 = nn.Sequential(
nn.Conv2d(1, 16, 3, padding=1),
nn.BatchNorm2d(16),
nn.ReLU(),
nn.MaxPool2d(2)
)
self.conv2 = nn.Sequential(
nn.Conv2d(16, 32, 3, padding=1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.MaxPool2d(2)
)
self.conv3 = nn.Sequential(
nn.Conv2d(32, 64, 3, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(2)
)
self.identity = nn.Identity()
self.global_pool = nn.AdaptiveAvgPool2d((4, 4))
self.classifier = nn.Sequential(
nn.Flatten(),
nn.Linear(64 * 4 * 4, 128),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(128, 2)
)
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
x = self.conv3(x)
x = self.global_pool(x)
x = self.classifier(x)
return x
model = SmallResNet().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=lr)
# =============================================
# 训练记录
# =============================================
losses = []
train_accs = []
test_accs = []
print("\n🚀 开始训练(增强模型)...")
for epoch in range(num_epochs):
model.train()
loss_epoch = 0.0
correct_train = 0
total_train = 0
for x, y in train_loader:
x, y = x.to(device), y.to(device)
out = model(x)
loss = criterion(out, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
loss_epoch += loss.item()
_, pred = out.max(1)
total_train += y.size(0)
correct_train += (pred == y).sum().item()
losses.append(loss_epoch / len(train_loader))
train_accs.append(correct_train / total_train)
# 测试准确率
model.eval()
correct_test = 0
total_test = 0
with torch.no_grad():
for x, y in test_loader:
x, y = x.to(device), y.to(device)
out = model(x)
_, pred = out.max(1)
total_test += y.size(0)
correct_test += (pred == y).sum().item()
test_accs.append(correct_test / total_test)
print(f"Epoch {epoch+1}/{num_epochs} | Loss: ${losses[-1]:.4f}$ | "
f"Train Acc: ${train_accs[-1]:.4f}$ | Test Acc: ${test_accs[-1]:.4f}$")
# =============================================
# 输出最终结果
# =============================================
final_acc = test_accs[-1]
print(f"\n✅ 最终测试准确率: ${final_acc:.4f}$")
# =============================================
# 绘制训练线图
# =============================================
plt.figure(figsize=(10, 6))
plt.plot(losses, 'b-o', label='Training Loss', markersize=4)
plt.plot(train_accs, 'g-o', label='Train Accuracy', markersize=4)
plt.plot(test_accs, 'r-o', label='Test Accuracy', markersize=4)
plt.title('模型训练过程(损失 & 准确率)')
plt.xlabel('Epoch')
plt.ylabel('Value')
plt.ylim(0, 1.2)
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
print(f"\n🎉 训练完成!最终测试准确率: ${final_acc:.4f}$")
```
---
### ✅ 使用说明
#### 数据结构要求:
```
当前目录/
│
├── train/
│ ├── cat/
│ │ ├── cat1.jpg, cat2.png...
│ └── dog/
│ ├── dog1.jpg, dog2.png...
│
└── test/
├── cat/
│ ├── cat_test1.jpg...
└── dog/
├── dog_test1.jpg...
```
#### 如何运行?
1. 将上述任一代码保存为 `.ipynb` 或 `.py` 文件
2. 放入与 `train/` 和 `test/` 同级目录
3. 运行代码即可自动训练并出图
---
知识点(列出该代码中遇到的知识点)
1. **卷积神经网络(CNN)**:通过卷积层自动提取图像局部特征,适用于图像分类任务。
2. **交叉熵损失函数**:衡量预测概率分布与真实标签差异,驱动模型参数更新。
3. **数据增强技术**:如翻转、旋转、亮度调整,提升模型泛化能力,防止过拟合。