4. 基于深度学习的钢材表面缺陷分类
任务目标
构建一个基于预训练CNN(ResNet18)的钢材表面缺陷分类器,对NEU-DET数据集中6类常见缺陷(划痕、麻点、夹杂、氧化皮、裂纹、轧制氧化坑)进行图像分类。最终交付可运行的训练与推理脚本,并给出验证精度的具体方法。
环境与依赖
- Python 3.8+,PyTorch 1.10+,torchvision 0.11+
- 数据集:NEU-DET(东北大学钢材表面缺陷数据集),包含1800张灰度图像(300×300),每类300张
- 目录结构:
data/下按train/与val/划分,每个子目录内按类别存放图像
关键完整代码
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms, models
import os
# ---------- 配置 ----------
DATA_ROOT = "data" # 含 train/ 与 val/ 子目录
BATCH_SIZE = 32
EPOCHS = 10
LR = 1e-4
NUM_CLASSES = 6
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# ---------- 数据预处理 ----------
train_transform = transforms.Compose([
transforms.Grayscale(num_output_channels=3), # 灰度图转3通道以适配ImageNet预训练
transforms.Resize((224, 224)),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
])
val_transform = transforms.Compose([
transforms.Grayscale(num_output_channels=3),
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
])
# ---------- 数据加载 ----------
train_dataset = datasets.ImageFolder(os.path.join(DATA_ROOT, "train"), train_transform)
val_dataset = datasets.ImageFolder(os.path.join(DATA_ROOT, "val"), val_transform)
train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=2)
val_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=2)
# ---------- 模型构建 ----------
def build_model(num_classes):
model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1)
# 冻结前几层,只微调最后两个stage和全连接层
for name, param in model.named_parameters():
if "layer4" not in name and "fc" not in name:
param.requires_grad = False
model.fc = nn.Linear(model.fc.in_features, num_classes)
return model
model = build_model(NUM_CLASSES).to(DEVICE)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=LR)
# ---------- 训练 ----------
def train_one_epoch():
model.train()
total_loss, correct, total = 0, 0, 0
for images, labels in train_loader:
images, labels = images.to(DEVICE), labels.to(DEVICE)
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
total_loss += loss.item() * images.size(0)
_, preds = torch.max(outputs, 1)
correct += (preds == labels).sum().item()
total += labels.size(0)
return total_loss / total, correct / total
# ---------- 验证 ----------
def validate():
model.eval()
correct, total = 0, 0
with torch.no_grad():
for images, labels in val_loader:
images, labels = images.to(DEVICE), labels.to(DEVICE)
outputs = model(images)
_, preds = torch.max(outputs, 1)
correct += (preds == labels).sum().item()
total += labels.size(0)
return correct / total
# ---------- 主流程 ----------
if __name__ == "__main__":
best_acc = 0.0
for epoch in range(EPOCHS):
train_loss, train_acc = train_one_epoch()
val_acc = validate()
print(f"Epoch {epoch+1}/{EPOCHS} | Train Loss: {train_loss:.4f} | Train Acc: {train_acc:.4f} | Val Acc: {val_acc:.4f}")
if val_acc > best_acc:
best_acc = val_acc
torch.save(model.state_dict(), "steel_defect_model.pth")
print(f"Best validation accuracy: {best_acc:.4f}")
运行方式
- 下载NEU-DET数据集,按类别整理为
data/train/<类别名>/与data/val/<类别名>/结构(建议按8:2划分训练/验证集) - 安装依赖:
pip install torch torchvision - 执行训练:
python train.py,模型权重保存为steel_defect_model.pth
验证方法与判定标准
训练完成后观察终端输出的验证集准确率。判定通过的标准:
- 验证集准确率 ≥ 85%(在NEU-DET标准划分下,ResNet18微调通常可达90%以上)
- 训练过程中验证损失不再下降且准确率趋于平稳,说明模型已收敛
- 使用
torch.load加载权重后,对单张缺陷图像执行前向推理,输出类别索引与对应缺陷名称一致
推理示例
def predict(image_path, model, class_names):
from PIL import Image
img = Image.open(image_path).convert("L")
img = val_transform(img).unsqueeze(0).to(DEVICE)
model.eval()
with torch.no_grad():
output = model(img)
_, pred = torch.max(output, 1)
return class_names[pred.item()]
# class_names 按 ImageFolder 中类别字母序排列
class_names = ["crazing", "inclusion", "patches", "pitted_surface", "rolled_in_scale", "scratches"]
print(predict("test_scratches.jpg", model, class_names)) # 预期输出: scratches
常见问题:若验证准确率明显偏低(<70%),优先检查数据划分是否按类别均衡、是否启用了正确的ImageNet归一化参数、以及是否因显存不足导致batch size被迫调小。灰度图转3通道时务必使用
Grayscale(num_output_channels=3),否则预训练权重无法正确加载。