ResNet网络(三部曲_3)

1 网络介绍

Deep Residual Learning for Image Recognition

论文地址:https://arxiv.org/abs/1512.03385

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2 具体应用

使用resnet50,进行猫狗二分类

2.1 网络搭建

ResNet50.py

# 定义 ResNet50 用于二分类任务的网络模型
from torch import nn
from torchvision import models


class ResNet50ForCatDog(nn.Module):
    def __init__(self):
        super(ResNet50ForCatDog, self).__init__()
        # 加载预训练的 ResNet50
        self.resnet50 = models.resnet50(pretrained=True)
        # 修改最后一层全连接层以适应二分类任务
        # num_ftrs是全连接层的输入神经元个数
        num_ftrs = self.resnet50.fc.in_features
        # 修改最后一层的全连接层,以符合自己的二分类【猫、狗】需求
        self.resnet50.fc = nn.Linear(num_ftrs, 2)

    def forward(self, x):
        return self.resnet50(x)

可以通过如下代码查看resnet50的结构

from torchvision import models
resnet50 = models.resnet50(pretrained=False)
print(resnet50)

在这里插入图片描述

pretrained=True 这个参数的作用是指示加载在大型数据集(如 ImageNet)上预训练好的模型权重。
使用预训练的模型有以下好处:
节省训练时间:预训练模型已经学习到了通用的图像特征,基于这样的模型进行微调,通常可以比从头开始训练更快地收敛到较好的结果。
利用已有知识:预训练模型在大规模数据上学习到的特征具有一定的通用性和鲁棒性,可以为新的任务提供有价值的初始特征表示。
提高性能:在许多情况下,使用预训练模型并进行适当的微调,可以获得比随机初始化权重并从头训练更好的性能。

2.2 网络训练

TrainResNet.py

import os
import torch
from torch import nn, optim
from Utils import write_to_txt
from ResNet50 import ResNet50ForCatDog
from DrawCurves import plot_metrics
from datetime import datetime
from torchvision import transforms, models
from torchvision.datasets import ImageFolder
from torch.utils.data import DataLoader

# 定义数据集
ROOT_TRAIN = r'train数据集_path'
ROOT_TEST = r'val数据集_path'
# 文档写入路径
WRITER_PATH="./train_process"
# 定义超参数
batch_size = 64
learning_rate = 0.001
num_epochs = 30

# 将图像RGB三个通道的像素值分别减去0.5,再除以0.5.从而将所有的像素值固定在[-1,1]范围内
normalize = transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
train_transform = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.RandomVerticalFlip(),  # 随机垂直旋转
    transforms.ToTensor(),  # 将0-255范围内的像素转为0-1范围内的tensor
    normalize])

val_transform = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    normalize])

train_dataset = ImageFolder(ROOT_TRAIN, transform=train_transform)
val_dataset = ImageFolder(ROOT_TEST, transform=val_transform)

train_dataloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
val_dataloader = DataLoader(val_dataset, batch_size=batch_size, shuffle=True)


# 定义训练函数
def train(model, train_loader, criterion, optimizer):
    model.train()
    running_loss = 0.0
    running_corrects = 0.0
    for inputs, labels in train_loader:
        inputs = inputs.to(device)
        labels = labels.to(device)

        optimizer.zero_grad()

        outputs = model(inputs)
        _, preds = torch.max(outputs, 1)
        loss = criterion(outputs, labels)

        loss.backward()
        optimizer.step()

        running_loss += loss.item() * inputs.size(0)
        running_corrects += torch.sum(preds == labels)

    epoch_loss = running_loss / len(train_loader.dataset)
    epoch_acc = running_corrects / len(train_loader.dataset)
    print(f"Train Loss: {epoch_loss:.4f}, Train Acc: {epoch_acc:.4f}")
    write_to_txt(WRITER_PATH,f"Train Loss: {epoch_loss:.4f}, Train Acc: {epoch_acc:.4f}")
    return epoch_loss, epoch_acc.item()


#  定义测试函数
def val(model, test_loader, criterion):
    model.eval()
    running_loss = 0.0
    running_corrects = 0.0
    with torch.no_grad():
        for inputs, labels in test_loader:
            inputs = inputs.to(device)
            labels = labels.to(device)

            outputs = model(inputs)
            _, preds = torch.max(outputs, 1)
            loss = criterion(outputs, labels)

            running_loss += loss.item() * inputs.size(0)
            running_corrects += torch.sum(preds == labels)
    epoch_loss = running_loss / len(test_loader.dataset)
    epoch_acc = running_corrects / len(test_loader.dataset)
    print(f"Test Loss: {epoch_loss:.4f}, Test Acc: {epoch_acc:.4f}")
    write_to_txt(WRITER_PATH,f"Test Loss: {epoch_loss:.4f}, Test Acc: {epoch_acc:.4f}")
    return epoch_loss, epoch_acc.item()


"""
只保测试结果最好的那一个模型
"""
# 开始训练模型
# 如果显卡可用,则用显卡进行训练
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print("当前设备{}".format(device))
write_to_txt(WRITER_PATH,f"当前设备{device}")
model = ResNet50ForCatDog()
model.to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

max_acc = 0.0
save_epoch=0
Loss_train = []
Acc_train = []
Loss_val = []
Acc_val = []
# 计时
start_time = datetime.now()
print("当前训练模型是ResNet50,猫狗二分类,预定训练轮次-{}".format(num_epochs))
write_to_txt(WRITER_PATH,f"当前训练模型是ResNet50,猫狗二分类,预定训练轮次-{num_epochs}")
for t in range(num_epochs):
    print("-----第{}轮训练开始-----".format(t + 1))
    write_to_txt(WRITER_PATH, f"-----第{t + 1}轮训练开始-----")
    train_loss, train_acc = train(model, train_dataloader, criterion, optimizer)
    val_loss, val_acc = val(model, val_dataloader, criterion)
    # 将损失值和正确率写入列表
    Loss_train.append(train_loss)
    Acc_train.append(train_acc)
    Loss_val.append(val_loss)
    Acc_val.append(val_acc)
    # 保存最好的模型权重文件
    if val_acc > max_acc:
        folder = '../save_models'
        if not os.path.exists(folder):
            os.mkdir('../save_models')
        max_acc = val_acc
        save_epoch=t
        print(f'save best model,第{t + 1}轮')
        write_to_txt(WRITER_PATH,f'save best model,第{t + 1}轮')
        torch.save(model.state_dict(), '../save_models/best_model.pth')
end_time = datetime.now()
print("start_time:{}".format(start_time))
print("end_time:{}".format(end_time))
print("{}训练总用时:{}".format(device, end_time - start_time))
plot_metrics(Loss_train, Acc_train, Loss_val, Acc_val, save_path_loss='loss.png', save_path_accuracy='acc.png')
print("Done!")
write_to_txt(WRITER_PATH,f'=========Done!=========')
write_to_txt(WRITER_PATH,f'start_time:{start_time}')
write_to_txt(WRITER_PATH,f'end_time:{end_time}')
write_to_txt(WRITER_PATH,f'{device}训练总用时:{end_time - start_time}')
write_to_txt(WRITER_PATH,f'保存最好模型为{save_epoch+1}轮次')
write_to_txt(WRITER_PATH,f"Train Loss: {Loss_train[save_epoch]:.4f}, Train Acc: {Acc_train[save_epoch]:.4f}")
write_to_txt(WRITER_PATH,f"Test Loss: {Loss_val[save_epoch]:.4f}, Test Acc: {Acc_val[save_epoch]:.4f}")
write_to_txt(WRITER_PATH,f'=========Done!=========')

from DrawCurves import plot_metrics
from Utils import write_to_txt
from ResNet50 import ResNet50ForCatDog
为自定义py文件

DrawCurves.py

绘制模型训练曲线

import matplotlib.pyplot as plt


def plot_metrics(train_losses, train_accuracies, test_losses, test_accuracies,
                 save_path_loss=None, save_path_accuracy=None):
    """
    绘制训练集和测试集的损失值及正确率曲线,并保存为图片文件(可选)。

    参数:
    - train_losses: 训练集损失值的列表或数组
    - train_accuracies: 训练集正确率的列表或数组
    - test_losses: 测试集损失值的列表或数组
    - test_accuracies: 测试集正确率的列表或数组
    - save_path_loss: 保存损失值图形的路径。如果为 None,则不保存图片
    - save_path_accuracy: 保存正确率图形的路径。如果为 None,则不保存图片
    """
    epochs = range(1, len(train_losses) + 1)

    # 绘制训练集和测试集损失值
    plt.figure(figsize=(10, 5))
    plt.plot(epochs, train_losses, 'b', label='Train Loss')
    plt.plot(epochs, test_losses, 'r', label='Test Loss')
    plt.xlabel('Epochs')
    plt.ylabel('Loss')
    plt.title('Train and Test Loss')
    plt.legend(loc='upper right')

    # 如果提供了保存路径,则保存图片
    if save_path_loss:
        plt.savefig(save_path_loss)
    plt.show()

    # 绘制训练集和测试集正确率
    plt.figure(figsize=(10, 5))
    plt.plot(epochs, train_accuracies, 'b', label='Train Accuracy')
    plt.plot(epochs, test_accuracies, 'r', label='Test Accuracy')
    plt.xlabel('Epochs')
    plt.ylabel('Accuracy')
    plt.title('Train and Test Accuracy')
    plt.legend(loc='upper right')

    # 如果提供了保存路径,则保存图片
    if save_path_accuracy:
        plt.savefig(save_path_accuracy)
    plt.show()

Utils.py

一个用于书写训练数据,一个用于生成指定范围内的随机数

import random


def write_to_txt(file_path, content):
    """
    将指定内容写入到指定的txt文件中,并确保每次写入的内容新起一行。

    参数:
    file_path (str): 文件的路径。
    content (str): 要写入文件的内容。
    """
    with open(file_path, "a") as file:  # 以追加模式打开文件
        file.write(content + "\n")  # 写入内容并换行


def generate_random_numbers(count, min_value, max_value):
    """
    生成指定数量的随机数,并在给定范围内。

    参数:
    count (int): 要生成的随机数个数。
    min_value (int): 随机数的最小值(包括)。
    max_value (int): 随机数的最大值(包括)。

    返回:
    list: 生成的随机数列表。
    """
    random_numbers = [random.randint(min_value, max_value) for _ in range(count)]
    return random_numbers

模型训练曲线图:
在这里插入图片描述
在这里插入图片描述
保留的最好模型信息
在这里插入图片描述

2.3 模型测试

TestModel.py

import torch
from ResNet50 import ResNet50ForCatDog
from torch.autograd import Variable
from torchvision import datasets, transforms,models
from torchvision.transforms import ToPILImage
from torchvision.datasets import ImageFolder
import matplotlib.pyplot as plt
from Utils import generate_random_numbers

ROOT_TEST = r'待测试数据集_path'

val_transform = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
])

val_dataset = ImageFolder(ROOT_TEST, transform=val_transform)
print(len(val_dataset))

# 如果显卡可用,则用显卡进行训练
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print("当前设备{}".format(device))

# 调用net里面的定义的网络模型, 如果GPU可用则将模型转到GPU
# 加载预训练的 ResNet-50 模型
model=ResNet50ForCatDog()
model.to(device)
# 加载模型train.py里面训练的模型
model.load_state_dict(torch.load('../save_models/best_model.pth', weights_only=True))

# 获取预测结果
classes = ['cat', 'dog']

# 把tensor转成Image,方便可视化
show = ToPILImage()
# 进入验证阶段
model.eval()
# 随机30个图片,用于测试
arr=generate_random_numbers(30,0,len(val_dataset))
# 对val_dataset里面的照片进行推理验证
for i in arr:
    x, y = val_dataset[i][0], val_dataset[i][1]
    # show(x).show()
    img = show(x)
    # 使用 matplotlib 显示图像并设置标题
    plt.imshow(img)
    plt.title(i)
    plt.axis('off')  # 不显示坐标轴
    # 保存图像到本地文件
    # plt.savefig(f'../imgs/image_{i}.png', bbox_inches='tight', pad_inches=0)
    plt.show()
    x = Variable(torch.unsqueeze(x, dim=0).float(), requires_grad=False).to(device)
    with torch.no_grad():
        pred = model(x)
        # print(pred)
        predicted, actual = classes[torch.argmax(pred[0])], classes[y]
        print(f'index: {i} ,Predicted: "{predicted}" ,Actual: "{actual}"')

测试结果:
在这里插入图片描述

2.4 小玩意儿

图形化界面+已训练好的模型=猫狗分类器【仅限于猫狗图片,其他图片无预测能力】
在这里插入图片描述
ClassifyGui.py

import tkinter as tk
from tkinter import filedialog
from PIL import Image, ImageTk
import torch
from torchvision import transforms
from ResNet50 import ResNet50ForCatDog

# 定义一个简单的猫狗识别模型
class_names = ['Cat', 'Dog']
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print("当前设备{}".format(device))
# 加载预训练的 ResNet-50 模型
model=ResNet50ForCatDog()
model.to(device)
# 加载模型里面训练的模型
model.load_state_dict(torch.load('./best_model.pth', weights_only=True,map_location=device))
model.eval()

# 图像预处理
normalize = transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
preprocess = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),  # 将0-255范围内的像素转为0-1范围内的tensor
    normalize])

def predict_image(image_path):
    """加载图像并进行预测"""
    image = Image.open(image_path)
    image = preprocess(image).unsqueeze(0)
    with torch.no_grad():
        output = model(image)
        _, predicted = torch.max(output, 1)
        return class_names[predicted.item()]

def open_image():
    """打开文件对话框选择图片"""
    file_path = filedialog.askopenfilename()
    if file_path:
        # 显示选中的图像
        img = Image.open(file_path)
        img.thumbnail((300, 150))  # 调整图像大小以适应展示区域
        img = ImageTk.PhotoImage(img)
        image_label.config(image=img)
        image_label.image = img
        
        # 预测图像类别
        result = predict_image(file_path)
        result_label.config(text=f'Prediction: {result}')

# 初始化图形界面
root = tk.Tk()
root.title("Cat vs Dog Classifier")
root.geometry("500x300")  # 设置窗口大小

# 创建界面组件
frame_top = tk.Frame(root, width=300, height=150)
frame_top.pack_propagate(False)  # 防止 Frame 自适应内容大小
frame_top.pack(pady=10)

image_label = tk.Label(frame_top, bg='gray')
image_label.pack(expand=True)

btn = tk.Button(root, text="Choose Image", command=open_image)
btn.pack(pady=10)

result_label = tk.Label(root, text="Prediction: ", font=("Arial", 14))
result_label.pack(pady=10)

# 运行图形界面
root.mainloop()

在这里插入图片描述
在这里插入图片描述

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值