深度学习 食物分类项目

1. 导入必要的库

import random
import torch
import torch.nn as nn
import numpy as np
import os
from PIL import Image  # 读取图片数据
from torch.utils.data import Dataset, DataLoader
from tqdm import tqdm
from torchvision import transforms
import time
import matplotlib.pyplot as plt
from model_utils.model import initialize_model
  • 作用: 导入项目所需的库和模块,包括随机数生成、PyTorch框架、数据处理、图像处理、进度条显示、数据增强、时间计算、绘图工具等。

2. 设置随机种子

def seed_everything(seed):
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)
    torch.backends.cudnn.benchmark = False
    torch.backends.cudnn.deterministic = True
    random.seed(seed)
    np.random.seed(seed)
    os.environ['PYTHONHASHSEED'] = str(seed)
  • 作用: 设置随机种子,确保实验的可重复性。通过固定随机种子,使得每次运行代码时生成的随机数相同,从而保证实验结果的一致性。

3. 设置随机种子为0

seed_everything(0)
  • 作用: 调用seed_everything函数,设置随机种子为0。

4. 定义图像大小

HW = 224
  • 作用: 定义图像的高度和宽度为224,通常用于输入到卷积神经网络(CNN)中的图像大小。

5. 定义数据增强和预处理

train_transform = transforms.Compose(
    [
        transforms.ToPILImage(),   # 将numpy数组或tensor转换为PIL图像
        transforms.RandomResizedCrop(224),  # 随机裁剪并调整大小
        transforms.RandomRotation(50),  # 随机旋转
        transforms.ToTensor()  # 转换为tensor
    ]
)

val_transform = transforms.Compose(
    [
        transforms.ToPILImage(),   # 将numpy数组或tensor转换为PIL图像
        transforms.ToTensor()  # 转换为tensor
    ]
)
  • 作用: 定义训练和验证时的数据预处理流程。训练时使用数据增强(如随机裁剪、旋转),验证时只进行简单的转换。

6. 定义数据集类

class food_Dataset(Dataset):
    def __init__(self, path, mode="train"):
        self.mode = mode
        if mode == "semi":
            self.X = self.read_file(path)
        else:
            self.X, self.Y = self.read_file(path)
            self.Y = torch.LongTensor(self.Y)  # 标签转为长整型

        if mode == "train":
            self.transform = train_transform
        else:
            self.transform = val_transform

    def read_file(self, path):
        if self.mode == "semi":
            file_list = os.listdir(path)
            xi = np.zeros((len(file_list), HW, HW, 3), dtype=np.uint8)
            for j, img_name in enumerate(file_list):
                img_path = os.path.join(path, img_name)
                img = Image.open(img_path)
                img = img.resize((HW, HW))
                xi[j, ...] = img
            print("读到了%d个数据" % len(xi))
            return xi
        else:
            for i in tqdm(range(11)):
                file_dir = path + "/%02d" % i
                file_list = os.listdir(file_dir)

                xi = np.zeros((len(file_list), HW, HW, 3), dtype=np.uint8)
                yi = np.zeros(len(file_list), dtype=np.uint8)

                for j, img_name in enumerate(file_list):
                    img_path = os.path.join(file_dir, img_name)
                    img = Image.open(img_path)
                    img = img.resize((HW, HW))
                    xi[j, ...] = img
                    yi[j] = i

                if i == 0:
                    X = xi
                    Y = yi
                else:
                    X = np.concatenate((X, xi), axis=0)
                    Y = np.concatenate((Y, yi), axis=0)
            print("读到了%d个数据" % len(Y))
            return X, Y

    def __getitem__(self, item):
        if self.mode == "semi":
            return self.transform(self.X[item]), self.X[item]
        else:
            return self.transform(self.X[item]), self.Y[item]

    def __len__(self):
        return len(self.X)
  • 作用: 定义自定义数据集类food_Dataset,用于加载和处理食物分类数据集。根据模式(训练、验证、半监督)加载数据,并进行相应的预处理。

7. 定义半监督数据集类

class semiDataset(Dataset):
    def __init__(self, no_label_loder, model, device, thres=0.99):
        x, y = self.get_label(no_label_loder, model, device, thres)
        if x == []:
            self.flag = False
        else:
            self.flag = True
            self.X = np.array(x)
            self.Y = torch.LongTensor(y)
            self.transform = train_transform

    def get_label(self, no_label_loder, model, device, thres):
        model = model.to(device)
        pred_prob = []
        labels = []
        x = []
        y = []
        soft = nn.Softmax()
        with torch.no_grad():
            for bat_x, _ in no_label_loder:
                bat_x = bat_x.to(device)
                pred = model(bat_x)
                pred_soft = soft(pred)
                pred_max, pred_value = pred_soft.max(1)
                pred_prob.extend(pred_max.cpu().numpy().tolist())
                labels.extend(pred_value.cpu().numpy().tolist())

        for index, prob in enumerate(pred_prob):
            if prob > thres:
                x.append(no_label_loder.dataset[index][1])   # 调用到原始的getitem
                y.append(labels[index])
        return x, y

    def __getitem__(self, item):
        return self.transform(self.X[item]), self.Y[item]

    def __len__(self):
        return len(self.X)
  • 作用: 定义半监督数据集类semiDataset,用于从无标签数据中生成伪标签数据。通过模型预测无标签数据的标签,并根据置信度阈值筛选出高置信度的样本。

8. 获取半监督数据加载器

def get_semi_loader(no_label_loder, model, device, thres):
    semiset = semiDataset(no_label_loder, model, device, thres)
    if semiset.flag == False:
        return None
    else:
        semi_loader = DataLoader(semiset, batch_size=16, shuffle=False)
        return semi_loader
  • 作用: 根据无标签数据加载器和模型生成半监督数据加载器。如果生成的半监督数据集为空,则返回None

9. 定义自定义模型

class myModel(nn.Module):
    def __init__(self, num_class):
        super(myModel, self).__init__()
        self.conv1 = nn.Conv2d(3, 64, 3, 1, 1)    # 64*224*224
        self.bn1 = nn.BatchNorm2d(64)
        self.relu = nn.ReLU()
        self.pool1 = nn.MaxPool2d(2)   #64*112*112

        self.layer1 = nn.Sequential(
            nn.Conv2d(64, 128, 3, 1, 1),    # 128*112*112
            nn.BatchNorm2d(128),
            nn.ReLU(),
            nn.MaxPool2d(2)   #128*56*56
        )
        self.layer2 = nn.Sequential(
            nn.Conv2d(128, 256, 3, 1, 1),
            nn.BatchNorm2d(256),
            nn.ReLU(),
            nn.MaxPool2d(2)   #256*28*28
        )
        self.layer3 = nn.Sequential(
            nn.Conv2d(256, 512, 3, 1, 1),
            nn.BatchNorm2d(512),
            nn.ReLU(),
            nn.MaxPool2d(2)   #512*14*14
        )

        self.pool2 = nn.MaxPool2d(2)    #512*7*7
        self.fc1 = nn.Linear(25088, 1000)   #25088->1000
        self.relu2 = nn.ReLU()
        self.fc2 = nn.Linear(1000, num_class)  #1000-11

    def forward(self, x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.pool1(x)
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.pool2(x)
        x = x.view(x.size()[0], -1)
        x = self.fc1(x)
        x = self.relu2(x)
        x = self.fc2(x)
        return x
  • 作用: 定义一个自定义的卷积神经网络模型myModel,用于食物分类任务。模型包含多个卷积层、池化层和全连接层。

10. 定义训练和验证函数

def train_val(model, train_loader, val_loader, no_label_loader, device, epochs, optimizer, loss, thres, save_path):
    model = model.to(device)
    semi_loader = None
    plt_train_loss = []
    plt_val_loss = []

    plt_train_acc = []
    plt_val_acc = []

    max_acc = 0.0

    for epoch in range(epochs):
        train_loss = 0.0
        val_loss = 0.0
        train_acc = 0.0
        val_acc = 0.0
        semi_loss = 0.0
        semi_acc = 0.0

        start_time = time.time()

        model.train()
        for batch_x, batch_y in train_loader:
            x, target = batch_x.to(device), batch_y.to(device)
            pred = model(x)
            train_bat_loss = loss(pred, target)
            train_bat_loss.backward()
            optimizer.step()  # 更新参数 之后要梯度清零否则会累积梯度
            optimizer.zero_grad()
            train_loss += train_bat_loss.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__()) #记录准确率,

        if semi_loader!= None:
            for batch_x, batch_y in semi_loader:
                x, target = batch_x.to(device), batch_y.to(device)
                pred = model(x)
                semi_bat_loss = loss(pred, target)
                semi_bat_loss.backward()
                optimizer.step()  # 更新参数 之后要梯度清零否则会累积梯度
                optimizer.zero_grad()
                semi_loss += train_bat_loss.cpu().item()
                semi_acc += np.sum(np.argmax(pred.detach().cpu().numpy(), axis=1) == target.cpu().numpy())
            print("半监督数据集的训练准确率为", semi_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_bat_loss = loss(pred, target)
                val_loss += val_bat_loss.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.dataset.__len__())
        plt_val_acc.append(val_acc / val_loader.dataset.__len__())

        if epoch%3 == 0 and plt_val_acc[-1] > 0.6:
            semi_loader = get_semi_loader(no_label_loader, model, device, thres)

        if val_acc > max_acc:
            torch.save(model, save_path)
            max_acc = val_loss

        print('[%03d/%03d] %2.2f sec(s) TrainLoss : %.6f | valLoss: %.6f Trainacc : %.6f | valacc: %.6f' % \
              (epoch, epochs, time.time() - start_time, plt_train_loss[-1], plt_val_loss[-1], plt_train_acc[-1], plt_val_acc[-1])
              )  # 打印训练结果。 注意python语法, %2.2f 表示小数位为2的浮点数, 后面可以对应。

    plt.plot(plt_train_loss)
    plt.plot(plt_val_loss)
    plt.title("loss")
    plt.legend(["train", "val"])
    plt.show()

    plt.plot(plt_train_acc)
    plt.plot(plt_val_acc)
    plt.title("acc")
    plt.legend(["train", "val"])
    plt.show()
  • 作用: 定义训练和验证函数train_val,用于训练模型并在验证集上评估模型性能。函数还支持半监督学习,通过无标签数据生成伪标签数据来增强训练集。

11. 定义数据路径

train_path = r"F:\pycharm\beike\classification\food_classification\food-11_sample\training\labeled"
val_path = r"F:\pycharm\beike\classification\food_classification\food-11_sample\validation"
no_label_path = r"F:\pycharm\beike\classification\food_classification\food-11_sample\training\unlabeled\00"
  • 作用: 定义训练集、验证集和无标签数据集的路径。

12. 加载数据集

python

复制

train_set = food_Dataset(train_path, "train")
val_set = food_Dataset(val_path, "val")
no_label_set = food_Dataset(no_label_path, "semi")

train_loader = DataLoader(train_set, batch_size=16, shuffle=True)
val_loader = DataLoader(val_set, batch_size=16, shuffle=True)
no_label_loader = DataLoader(no_label_set, batch_size=16, shuffle=False)
  • 作用: 加载训练集、验证集和无标签数据集,并将其转换为DataLoader对象,以便在训练过程中进行批量加载。

13. 初始化模型

# model = myModel(11)
model, _ = initialize_model("vgg", 11, use_pretrained=True)
  • 作用: 初始化模型。可以选择使用自定义模型myModel或预训练的VGG模型。

14. 定义优化器和损失函数

lr = 0.001
loss = nn.CrossEntropyLoss()
optimizer = torch.optim.AdamW(model.parameters(), lr=lr, weight_decay=1e-4)
  • 作用: 定义学习率、损失函数(交叉熵损失)和优化器(AdamW)。

15. 设置设备

device = "cuda" if torch.cuda.is_available() else "cpu"
  • 作用: 检查是否有可用的GPU,如果有则使用GPU,否则使用CPU。

16. 定义保存路径和训练参数

save_path = "model_save/best_model.pth"
epochs = 15
thres = 0.99
  • 作用: 定义模型保存路径、训练轮数和置信度阈值。

17. 开始训练

train_val(model, train_loader, val_loader, no_label_loader, device, epochs, optimizer, loss, thres, save_path)
  • 作用: 调用train_val函数,开始训练模型,并在验证集上评估模型性能。训练过程中会保存最佳模型,并绘制损失和准确率曲线。

总结

这段代码实现了一个食物分类项目,包括数据加载、模型定义、训练和验证等功能。通过半监督学习,利用无标签数据生成伪标签数据来增强训练集,从而提高模型性能。



# 导入必要的库
import random  # 用于生成随机数
import torch  # PyTorch深度学习框架
import torch.nn as nn  # PyTorch的神经网络模块
import numpy as np  # 用于数值计算
import os  # 用于操作系统相关的功能,如文件路径操作
from PIL import Image  # 用于读取和处理图像数据
from torch.utils.data import Dataset, DataLoader  # 数据集和数据加载器
from tqdm import tqdm  # 用于显示进度条
from torchvision import transforms  # 用于图像预处理和数据增强
import time  # 用于计算时间
import matplotlib.pyplot as plt  # 用于绘图
from model_utils.model import initialize_model  # 自定义模块,用于初始化模型

# 设置随机种子,确保实验的可重复性
def seed_everything(seed):
    torch.manual_seed(seed)  # 设置PyTorch的随机种子
    torch.cuda.manual_seed(seed)  # 设置GPU的随机种子
    torch.cuda.manual_seed_all(seed)  # 设置所有GPU的随机种子
    torch.backends.cudnn.benchmark = False  # 关闭cuDNN的自动优化
    torch.backends.cudnn.deterministic = True  # 确保cuDNN的结果是确定的
    random.seed(seed)  # 设置Python随机模块的种子
    np.random.seed(seed)  # 设置NumPy的随机种子
    os.environ['PYTHONHASHSEED'] = str(seed)  # 设置Python哈希种子

# 调用seed_everything函数,设置随机种子为0
seed_everything(0)

# 定义图像的高度和宽度
HW = 224

# 定义训练数据的预处理和数据增强
train_transform = transforms.Compose(
    [
        transforms.ToPILImage(),  # 将numpy数组或tensor转换为PIL图像
        transforms.RandomResizedCrop(224),  # 随机裁剪并调整大小
        transforms.RandomRotation(50),  # 随机旋转
        transforms.ToTensor()  # 转换为tensor
    ]
)

# 定义验证数据的预处理
val_transform = transforms.Compose(
    [
        transforms.ToPILImage(),  # 将numpy数组或tensor转换为PIL图像
        transforms.ToTensor()  # 转换为tensor
    ]
)

# 定义自定义数据集类
class food_Dataset(Dataset):
    def __init__(self, path, mode="train"):
        self.mode = mode  # 数据集模式(train, val, semi)
        if mode == "semi":
            self.X = self.read_file(path)  # 读取无标签数据
        else:
            self.X, self.Y = self.read_file(path)  # 读取有标签数据
            self.Y = torch.LongTensor(self.Y)  # 将标签转换为长整型

        if mode == "train":
            self.transform = train_transform  # 训练数据使用数据增强
        else:
            self.transform = val_transform  # 验证数据使用简单预处理

    # 读取文件数据
    def read_file(self, path):
        if self.mode == "semi":
            file_list = os.listdir(path)  # 列出文件夹下所有文件
            xi = np.zeros((len(file_list), HW, HW, 3), dtype=np.uint8)  # 初始化数组存储图像数据
            for j, img_name in enumerate(file_list):
                img_path = os.path.join(path, img_name)  # 获取图像路径
                img = Image.open(img_path)  # 打开图像
                img = img.resize((HW, HW))  # 调整图像大小
                xi[j, ...] = img  # 将图像数据存入数组
            print("读到了%d个数据" % len(xi))  # 打印读取的数据数量
            return xi
        else:
            for i in tqdm(range(11)):  # 遍历11个类别
                file_dir = path + "/%02d" % i  # 获取每个类别的文件夹路径
                file_list = os.listdir(file_dir)  # 列出文件夹下所有文件
                xi = np.zeros((len(file_list), HW, HW, 3), dtype=np.uint8)  # 初始化数组存储图像数据
                yi = np.zeros(len(file_list), dtype=np.uint8)  # 初始化数组存储标签数据
                for j, img_name in enumerate(file_list):
                    img_path = os.path.join(file_dir, img_name)  # 获取图像路径
                    img = Image.open(img_path)  # 打开图像
                    img = img.resize((HW, HW))  # 调整图像大小
                    xi[j, ...] = img  # 将图像数据存入数组
                    yi[j] = i  # 将标签数据存入数组
                if i == 0:
                    X = xi  # 初始化X
                    Y = yi  # 初始化Y
                else:
                    X = np.concatenate((X, xi), axis=0)  # 拼接图像数据
                    Y = np.concatenate((Y, yi), axis=0)  # 拼接标签数据
            print("读到了%d个数据" % len(Y))  # 打印读取的数据数量
            return X, Y

    # 获取数据集中的单个样本
    def __getitem__(self, item):
        if self.mode == "semi":
            return self.transform(self.X[item]), self.X[item]  # 返回无标签数据
        else:
            return self.transform(self.X[item]), self.Y[item]  # 返回有标签数据

    # 返回数据集的大小
    def __len__(self):
        return len(self.X)

# 定义半监督数据集类
class semiDataset(Dataset):
    def __init__(self, no_label_loder, model, device, thres=0.99):
        x, y = self.get_label(no_label_loder, model, device, thres)  # 获取伪标签数据
        if x == []:
            self.flag = False  # 如果没有生成伪标签数据,标志为False
        else:
            self.flag = True  # 如果生成了伪标签数据,标志为True
            self.X = np.array(x)  # 存储伪标签数据
            self.Y = torch.LongTensor(y)  # 存储伪标签
            self.transform = train_transform  # 使用训练数据的预处理

    # 获取伪标签数据
    def get_label(self, no_label_loder, model, device, thres):
        model = model.to(device)  # 将模型移动到指定设备
        pred_prob = []  # 存储预测概率
        labels = []  # 存储预测标签
        x = []  # 存储图像数据
        y = []  # 存储伪标签
        soft = nn.Softmax()  # 定义Softmax函数
        with torch.no_grad():  # 不计算梯度
            for bat_x, _ in no_label_loder:
                bat_x = bat_x.to(device)  # 将数据移动到指定设备
                pred = model(bat_x)  # 模型预测
                pred_soft = soft(pred)  # 计算Softmax概率
                pred_max, pred_value = pred_soft.max(1)  # 获取最大概率和对应标签
                pred_prob.extend(pred_max.cpu().numpy().tolist())  # 存储概率
                labels.extend(pred_value.cpu().numpy().tolist())  # 存储标签
        for index, prob in enumerate(pred_prob):
            if prob > thres:  # 如果概率大于阈值
                x.append(no_label_loder.dataset[index][1])  # 存储图像数据
                y.append(labels[index])  # 存储伪标签
        return x, y

    # 获取数据集中的单个样本
    def __getitem__(self, item):
        return self.transform(self.X[item]), self.Y[item]

    # 返回数据集的大小
    def __len__(self):
        return len(self.X)

# 获取半监督数据加载器
def get_semi_loader(no_label_loder, model, device, thres):
    semiset = semiDataset(no_label_loder, model, device, thres)  # 创建半监督数据集
    if semiset.flag == False:
        return None  # 如果没有生成伪标签数据,返回None
    else:
        semi_loader = DataLoader(semiset, batch_size=16, shuffle=False)  # 创建数据加载器
        return semi_loader

# 定义自定义模型
class myModel(nn.Module):
    def __init__(self, num_class):
        super(myModel, self).__init__()
        self.conv1 = nn.Conv2d(3, 64, 3, 1, 1)  # 第一层卷积
        self.bn1 = nn.BatchNorm2d(64)  # 批归一化
        self.relu = nn.ReLU()  # ReLU激活函数
        self.pool1 = nn.MaxPool2d(2)  # 最大池化

        self.layer1 = nn.Sequential(
            nn.Conv2d(64, 128, 3, 1, 1),  # 第二层卷积
            nn.BatchNorm2d(128),  # 批归一化
            nn.ReLU(),  # ReLU激活函数
            nn.MaxPool2d(2)  # 最大池化
        )
        self.layer2 = nn.Sequential(
            nn.Conv2d(128, 256, 3, 1, 1),  # 第三层卷积
            nn.BatchNorm2d(256),  # 批归一化
            nn.ReLU(),  # ReLU激活函数
            nn.MaxPool2d(2)  # 最大池化
        )
        self.layer3 = nn.Sequential(
            nn.Conv2d(256, 512, 3, 1, 1),  # 第四层卷积
            nn.BatchNorm2d(512),  # 批归一化
            nn.ReLU(),  # ReLU激活函数
            nn.MaxPool2d(2)  # 最大池化
        )

        self.pool2 = nn.MaxPool2d(2)  # 最大池化
        self.fc1 = nn.Linear(25088, 1000)  # 全连接层
        self.relu2 = nn.ReLU()  # ReLU激活函数
        self.fc2 = nn.Linear(1000, num_class)  # 输出层

    # 定义前向传播
    def forward(self, x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.relu(x)
        x = self.pool1(x)
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.pool2(x)
        x = x.view(x.size()[0], -1)  # 展平
        x = self.fc1(x)
        x = self.relu2(x)
        x = self.fc2(x)
        return x

# 定义训练和验证函数
def train_val(model, train_loader, val_loader, no_label_loader, device, epochs, optimizer, loss, thres, save_path):
    model = model.to(device)  # 将模型移动到指定设备
    semi_loader = None  # 初始化半监督数据加载器
    plt_train_loss = []  # 存储训练损失
    plt_val_loss = []  # 存储验证损失
    plt_train_acc = []  # 存储训练准确率
    plt_val_acc = []  # 存储验证准确率
    max_acc = 0.0  # 初始化最大准确率

    for epoch in range(epochs):  # 遍历每个epoch
        train_loss = 0.0  # 初始化训练损失
        val_loss = 0.0  # 初始化验证损失
        train_acc = 0.0  # 初始化训练准确率
        val_acc = 0.0  # 初始化验证准确率
        semi_loss = 0.0  # 初始化半监督损失
        semi_acc = 0.0  # 初始化半监督准确率

        start_time = time.time()  # 记录开始时间

        model.train()  # 设置模型为训练模式
        for batch_x, batch_y in train_loader:  # 遍历训练数据
            x, target = batch_x.to(device), batch_y.to(device)  # 将数据移动到指定设备
            pred = model(x)  # 模型预测
            train_bat_loss = loss(pred, target)  # 计算损失
            train_bat_loss.backward()  # 反向传播
            optimizer.step()  # 更新参数
            optimizer.zero_grad()  # 梯度清零
            train_loss += train_bat_loss.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__())  # 记录训练准确率

        if semi_loader != None:  # 如果有半监督数据
            for batch_x, batch_y in semi_loader:  # 遍历半监督数据
                x, target = batch_x.to(device), batch_y.to(device)  # 将数据移动到指定设备
                pred = model(x)  # 模型预测
                semi_bat_loss = loss(pred, target)  # 计算损失
                semi_bat_loss.backward()  # 反向传播
                optimizer.step()  # 更新参数
                optimizer.zero_grad()  # 梯度清零
                semi_loss += train_bat_loss.cpu().item()  # 累加半监督损失
                semi_acc += np.sum(np.argmax(pred.detach().cpu().numpy(), axis=1) == target.cpu().numpy())  # 计算准确率
            print("半监督数据集的训练准确率为", semi_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_bat_loss = loss(pred, target)  # 计算损失
                val_loss += val_bat_loss.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.dataset.__len__())  # 记录平均验证损失
        plt_val_acc.append(val_acc / val_loader.dataset.__len__())  # 记录验证准确率

        if epoch % 3 == 0 and plt_val_acc[-1] > 0.6:  # 每3个epoch且验证准确率大于0.6时
            semi_loader = get_semi_loader(no_label_loader, model, device, thres)  # 获取半监督数据加载器

        if val_acc > max_acc:  # 如果当前验证准确率大于最大准确率
            torch.save(model, save_path)  # 保存模型
            max_acc = val_loss  # 更新最大准确率

        # 打印训练结果
        print('[%03d/%03d] %2.2f sec(s) TrainLoss : %.6f | valLoss: %.6f Trainacc : %.6f | valacc: %.6f' % \
              (epoch, epochs, time.time() - start_time, plt_train_loss[-1], plt_val_loss[-1], plt_train_acc[-1], plt_val_acc[-1]))

    # 绘制训练和验证损失曲线
    plt.plot(plt_train_loss)
    plt.plot(plt_val_loss)
    plt.title("loss")
    plt.legend(["train", "val"])
    plt.show()

    # 绘制训练和验证准确率曲线
    plt.plot(plt_train_acc)
    plt.plot(plt_val_acc)
    plt.title("acc")
    plt.legend(["train", "val"])
    plt.show()

# 定义数据路径
train_path = r"F:\pycharm\beike\classification\food_classification\food-11_sample\training\labeled"
val_path = r"F:\pycharm\beike\classification\food_classification\food-11_sample\validation"
no_label_path = r"F:\pycharm\beike\classification\food_classification\food-11_sample\training\unlabeled\00"

# 加载数据集
train_set = food_Dataset(train_path, "train")  # 加载训练集
val_set = food_Dataset(val_path, "val")  # 加载验证集
no_label_set = food_Dataset(no_label_path, "semi")  # 加载无标签数据集

# 创建数据加载器
train_loader = DataLoader(train_set, batch_size=16, shuffle=True)  # 训练数据加载器
val_loader = DataLoader(val_set, batch_size=16, shuffle=True)  # 验证数据加载器
no_label_loader = DataLoader(no_label_set, batch_size=16, shuffle=False)  # 无标签数据加载器

# 初始化模型
# model = myModel(11)  # 使用自定义模型
model, _ = initialize_model("vgg", 11, use_pretrained=True)  # 使用预训练的VGG模型

# 定义学习率、损失函数和优化器
lr = 0.001  # 学习率
loss = nn.CrossEntropyLoss()  # 交叉熵损失函数
optimizer = torch.optim.AdamW(model.parameters(), lr=lr, weight_decay=1e-4)  # AdamW优化器

# 设置设备
device = "cuda" if torch.cuda.is_available() else "cpu"  # 使用GPU或CPU

# 定义模型保存路径和训练参数
save_path = "model_save/best_model.pth"  # 模型保存路径
epochs = 15  # 训练轮数
thres = 0.99  # 置信度阈值

# 开始训练
train_val(model, train_loader, val_loader, no_label_loader, device, epochs, optimizer, loss, thres, save_path)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值