222 lines
8.4 KiB
Python
222 lines
8.4 KiB
Python
import os
|
||
|
||
import numpy as np
|
||
import torch
|
||
import torch.nn as nn
|
||
from torch.optim.lr_scheduler import StepLR
|
||
from torchvision import models, transforms
|
||
from torch.utils.data import DataLoader
|
||
from torchvision.datasets import ImageFolder
|
||
from torch.optim import Adam
|
||
from tqdm import tqdm
|
||
from torchvision.transforms import functional
|
||
from FocalLoss import FocalLoss
|
||
from torchvision.models import ResNet18_Weights
|
||
from torch.cuda.amp import GradScaler, autocast
|
||
|
||
# 加载预训练的ResNet-18模型
|
||
model = models.resnet18(weights=ResNet18_Weights.IMAGENET1K_V1)
|
||
|
||
# 将模型移到GPU上(如果有)
|
||
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
||
model = model.to(device)
|
||
|
||
# 替换最后一层以适应二分类任务
|
||
num_features = model.fc.in_features
|
||
model.fc = nn.Linear(num_features, 2) # 假设是二分类问题
|
||
model.fc = model.fc.to(device)
|
||
|
||
# 冻结所有层
|
||
for param in model.parameters():
|
||
param.requires_grad = False
|
||
|
||
# 解冻最后一层,用于微调
|
||
# for param in model.fc.parameters():
|
||
# param.requires_grad = True
|
||
|
||
# 解冻最后几层
|
||
for name, param in model.named_parameters():
|
||
if "layer4" in name or "fc" in name: # 只解冻最后几层
|
||
param.requires_grad = True
|
||
|
||
def random_brightness(image):
|
||
"""
|
||
随机调整图像的亮度。
|
||
|
||
该函数通过生成一个随机的亮度因子,然后应用到图像上,以增加或减少图像的亮度。这种方法常用于数据增强,
|
||
旨在帮助模型更好地泛化,因为它教模型在不同亮度条件下处理图像。
|
||
|
||
参数:
|
||
image (Tensor): 输入的图像Tensor。
|
||
|
||
返回:
|
||
Tensor: 亮度经过随机调整后的图像Tensor。
|
||
"""
|
||
# 生成一个随机的亮度因子
|
||
brightness_factor = torch.rand(1).item()
|
||
# 调整图像的亮度
|
||
return functional.adjust_brightness(image, brightness_factor)
|
||
|
||
|
||
def random_contrast(image):
|
||
"""
|
||
随机调整图像的对比度。
|
||
|
||
该函数通过生成一个随机的对比度因子,然后应用到图像上,以增加或减少图像的对比度。
|
||
对比度因子是在0.5到1.5之间随机生成的,其中0.5是最低对比度,1.5是最高对比度。
|
||
|
||
参数:
|
||
image (Tensor): 输入的图像Tensor。
|
||
|
||
返回:
|
||
Tensor: 对比度经过随机调整后的图像Tensor。
|
||
"""
|
||
# 生成一个随机的对比度因子,其值在0.5到1.5之间。
|
||
contrast_factor = torch.rand(1).item() + 0.5
|
||
# 使用生成的对比度因子调整图像的对比度。
|
||
return functional.adjust_contrast(image, contrast_factor)
|
||
|
||
|
||
def random_hue(image):
|
||
"""
|
||
随机调整图像的色相。
|
||
|
||
色相的变化是由一个随机生成的因子决定的,这个因子是在-0.05到0.05之间均匀分布的。
|
||
这个函数的目的是为图像数据增强,使得模型在训练过程中看到更多变体,从而提高泛化能力。
|
||
|
||
参数:
|
||
image (Tensor): 输入的图像Tensor。
|
||
|
||
返回:
|
||
Tensor: 色相被随机调整后的图像Tensor。
|
||
"""
|
||
# 生成一个随机的色相调整因子,其值在-0.05到0.05之间。
|
||
hue_factor = (torch.rand(1).item() - 0.5) / 10
|
||
# 使用调整的色相因子来改变图像的色相。
|
||
return functional.adjust_hue(image, hue_factor)
|
||
|
||
|
||
def add_gaussian_noise(image):
|
||
"""
|
||
向图像数据添加高斯噪声。
|
||
|
||
该函数的目的是为了模拟现实世界中图像经常受到的各种噪声干扰。通过在图像中添加高斯噪声,
|
||
可以使模型在训练过程中更加鲁棒,提高其在真实场景下的泛化能力。
|
||
|
||
参数:
|
||
image: 输入的图像数据,作为一个torch张量。
|
||
|
||
返回值:
|
||
返回添加了高斯噪声的图像数据,与输入图像数据类型和形状相同。
|
||
|
||
添加的噪声是通过torch.randn_like函数生成的,确保了噪声的分布与图像数据相同,
|
||
并且乘以了一个小的常数0.1来控制噪声的强度。
|
||
"""
|
||
# 生成与图像数据相同形状的高斯噪声张量,并将其与图像数据相加
|
||
return image + torch.randn_like(image) * 0.1
|
||
|
||
|
||
data_transforms = {
|
||
'train': transforms.Compose([
|
||
transforms.RandomResizedCrop(224),
|
||
transforms.RandomHorizontalFlip(),
|
||
transforms.RandomRotation(degrees=15), # 随机旋转图像最多15度
|
||
transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1), # 随机改变图像的颜色
|
||
transforms.ToTensor(),
|
||
# transforms.Lambda(lambda x: functional.adjust_brightness(x, brightness_factor=torch.rand(1).item())), # 随机调整亮度
|
||
# transforms.Lambda(lambda x: functional.adjust_contrast(x, contrast_factor=torch.rand(1).item() + 0.5)), # 随机调整对比度
|
||
# transforms.Lambda(lambda x: functional.adjust_hue(x, hue_factor=(torch.rand(1).item()-0.5)/10)), # 随机调整色相
|
||
# transforms.Lambda(lambda x: x + torch.randn_like(x) * 0.1), # 添加随机高斯噪声
|
||
transforms.Lambda(random_brightness),
|
||
transforms.Lambda(random_contrast),
|
||
transforms.Lambda(random_hue),
|
||
transforms.Lambda(add_gaussian_noise),
|
||
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
|
||
]),
|
||
'val': transforms.Compose([
|
||
transforms.Resize(256),
|
||
transforms.CenterCrop(224),
|
||
transforms.ToTensor(),
|
||
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
|
||
]),
|
||
}
|
||
|
||
# 加载数据集
|
||
data_dir = '/Users/jasonwong/Downloads/二维码测试1/data'
|
||
image_datasets = {x: ImageFolder(os.path.join(data_dir, x), data_transforms[x])
|
||
for x in ['train', 'val']}
|
||
dataloaders = {x: DataLoader(image_datasets[x], batch_size=32, shuffle=True, num_workers=4)
|
||
for x in ['train', 'val']}
|
||
dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']}
|
||
|
||
#设置损失函数和优化器】
|
||
num_normal_samples = 7891
|
||
num_anomaly_samples = 846
|
||
class_sample_counts = [num_normal_samples, num_anomaly_samples] # 替换为实际样本数量
|
||
weights = 1.0 / np.array(class_sample_counts)
|
||
weights /= weights.sum() # 确保权重和为1
|
||
class_weights = torch.FloatTensor(weights).to(device) # 将权重转换为PyTorch张量,并移动到设备上
|
||
# criterion = nn.CrossEntropyLoss()
|
||
# optimizer = torch.optim.SGD(model.fc.parameters(), lr=0.001, momentum=0.9)
|
||
optimizer = Adam(model.parameters(),
|
||
lr=1e-4, # 学习率
|
||
betas=(0.9, 0.999), # beta_1 和 beta_2
|
||
eps=1e-08) # epsilon
|
||
scheduler = StepLR(optimizer, step_size=7, gamma=0.1)
|
||
|
||
# 初始化GradScaler
|
||
# scaler = GradScaler()
|
||
|
||
#训练模型
|
||
def train_model(model, dataloaders, dataset_sizes, device, num_epochs=10, save_path='path_to_save/full_model'):
|
||
# criterion = FocalLoss(gamma=2, alpha=[0.25, 0.75], reduction='mean')
|
||
# 加载加权交叉熵损失函数
|
||
criterion = nn.CrossEntropyLoss(weight=class_weights)
|
||
|
||
for epoch in range(num_epochs):
|
||
print(f'Starting epoch {epoch + 1}/{num_epochs}')
|
||
|
||
for phase in ['train', 'val']:
|
||
if phase == 'train':
|
||
model.train()
|
||
else:
|
||
model.eval()
|
||
|
||
running_loss = 0.0
|
||
running_corrects = 0
|
||
|
||
for inputs, labels in tqdm(dataloaders[phase], desc=f'Epoch {epoch + 1} {phase.capitalize()}'):
|
||
inputs = inputs.to(device)
|
||
labels = labels.to(device)
|
||
|
||
optimizer.zero_grad()
|
||
|
||
with torch.set_grad_enabled(phase == 'train'):
|
||
outputs = model(inputs)
|
||
_, preds = torch.max(outputs, 1)
|
||
loss = criterion(outputs, labels)
|
||
|
||
if phase == 'train':
|
||
loss.backward()
|
||
optimizer.step()
|
||
|
||
running_loss += loss.item() * inputs.size(0)
|
||
running_corrects += torch.sum(preds == labels.data)
|
||
|
||
epoch_loss = running_loss / dataset_sizes[phase]
|
||
epoch_acc = running_corrects.double() / dataset_sizes[phase]
|
||
|
||
print(f'{phase} Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f}')
|
||
|
||
scheduler.step()
|
||
|
||
torch.save({
|
||
'epoch': epoch + 1,
|
||
'model_state_dict': model.state_dict(),
|
||
'optimizer_state_dict': optimizer.state_dict(),
|
||
'loss': epoch_loss,
|
||
}, f'{save_path}_epoch_{epoch + 1}.pth')
|
||
|
||
|
||
if __name__ == '__main__':
|
||
train_model(model, dataloaders, dataset_sizes, device, num_epochs=10, save_path='') |