2024-2-29食物分类实战代码总结

import random
import torch
import torch.nn as nn
import numpy as np
import os
from torch.utils.data import DataLoader,Dataset
from PIL import Image
from tqdm import tqdm   #看进度条
from torchvision.transforms import transforms
from torch import optim
import time
import matplotlib.pyplot as plt

1. 导入需要的库

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)
#################################################################
seed_everything(0)
###############################################

2.  提高实验的可重复性

train_transform = transforms.Compose([
    transforms.ToPILImage(),
    transforms.RandomResizedCrop(224),#将图片随机放大,并切出224*224
    transforms.RandomResizedCrop(50),
    transforms.ToTensor()
])
#验证集增广方式 只需变换维度和张量
val_transform = transforms.Compose([
    transforms.ToPILImage(),
    transforms.ToTensor()
])

3. 用于数据增广, ToPILImage():将numpy的array和tensor转化为PILImage,RandomResizedCrop(224),将图片随机放大,并切出224*224大小,transforms.RandomResizedCrop(50):将图片在50度之内随机旋转,.ToTensor():将PILImage或array转化为tensor,训练集的图片需要进行数据增广,而验证集的图片只需转化为张量就行了。经过transform,实际的数据量没变,只不过每一个epoch里都会进行随机的变化,相当于增加了数据量

class FoodDataset(Dataset):

4. 写数据集 :三个函数

     def __init__(self,path,mode):
        x,y = self.read_file(path)
        if mode == "train":
            self.transform = train_transform
        else:
            self.transform = val_transform
        self.x = x
        self.y = torch.LongTensor(y)   #规定

5. 初始化函数:需要传入你想定义的数据集的路径,和你的这个数据集的类型是什么 。xy通过readfile函数赋值,再根据数据集类型,进行不同的数据增广方式,再将x,y转变为数据集自身的属性,而不是局部变量,注意对x 和y进行的操作

    def __getitem__(self,item):
        return self.transform(self.x[item]), self.y[item]

6. 取样品函数: 要对x进行数据增广,y是分类,不用增广 

    def __len__(self):
        return len(self.x)

7. 取长度函数

    def read_file(self,path):   #self在各个函数中传递
        for i in tqdm(range(11)):
            file_dir = path + "/%02d" % i  # python也太jb灵活了吧
            file_list = os.listdir(file_dir)  # listdir会把上一行文件夹中所有文件都读出来

            xi = np.zeros((len(file_list), HW, HW, 3),dtype = np.uint8)  # 用矩阵将图片存放在一起,这个函数的第一个参数是shape,是一个元组,剩余两个参数可以不填。不规定dtype,默认为浮点数,规定读成整数
            yi = np.zeros((len(file_list)))

            for j, each in enumerate(file_list):  # 调试可知file list是列表,而each就是图片名的字符串,j为list中的下标
                img_path = file_dir + "/" + each  # 字符串相相加  将图片读入计算机中
                img = Image.open(img_path)
                img = img.resize((HW, HW))  # 调试可知图片大小为512,要将其resize到224
                xi[j, ...] = img  # 省略号表示后面的参数自动等价
                yi[j] = i  # 将所取图片的下标也存进来,标签即i
                # print(each)

            if i == 0:  # 第一个文件夹里的图片保存为x,
                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

 8. 读文件函数:读取11个文件夹下的各个图片,i相当于打开了每个文件,用字符串相加的方式得到每个图片每个文件夹的地址,用os.listdir()函数可以得到文件夹下所有图片的名称,组成一个列表。之后初始化两个零矩阵,xi存储取出来的图片信息,yi用来存储取出的类型信息,j为图片名在列表中的下标,each为其图片名,再用字符串相加的方式将文件夹名与图片名相加得到地址,用函数将图片读到img中,此时图片为512*512,要将其改变大小,变为我们需要的224*224,将图片信息存储到xi中,xi第一个参数为个数/下标,后面的参数可代表图片信息,图片信息为224*224*3所以用代码表示方式,…表示后面的参数自动等同为图片的参数,再将所取下标存下来,接下来判断,如果读的是第一个文件夹,就让xy等于xi yi,否则,就让后面的文件夹中提取出来的信息并在xy上,并返回xy

class myModel(nn.Module):

 9. 写模型类

    def __init__(self,num_class):
        super(myModel, self).__init__()
        #3*224*224 --> 512*7*7


        self.layer1 = nn.Sequential(
            nn.Conv2d(3,64,3,1,1),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.MaxPool2d(2)
        )
        #64*112*112 -->
        # self.conv2 = nn.Conv2d(64, 128, 3, 1, 1)
        # self.bn = nn.BatchNorm2d(128)
        # self.relu2 = nn.ReLU()
        # self.pool2 = nn.MaxPool2d(2)
        self.layer2 = nn.Sequential(
            nn.Conv2d(64, 128, 3, 1, 1),
            nn.BatchNorm2d(128),
            nn.ReLU(),
            nn.MaxPool2d(2)
        )
        self.layer3 = nn.Sequential(
            nn.Conv2d(128, 256, 3, 1, 1),
            nn.BatchNorm2d(256),
            nn.ReLU(),
            nn.MaxPool2d(2)
        )
        self.layer4 = nn.Sequential(
            nn.Conv2d(256, 512, 3, 1, 1),
            nn.BatchNorm2d(512),
            nn.ReLU(),
            nn.MaxPool2d(2)
        )

        self.pool1 = nn.MaxPool2d(2)
        self.fc1 = nn.Linear(25088,1000)
        self.relu = nn.ReLU()
        self.fc2 = nn.Linear(1000,num_class)

10. 初始化函数:  对父类模型进行修改。Conv2d()接受5个参数,in_channel, out_channel, kernel_size, stride, padding, 第一层:输入3层,输出64层,用3*3的卷积核去卷,步长为1,并padding1,经过一系列处理,提取出图片中重要的特征,根据最后的参数512*7*7.写出最后的全连接层

    def forward(self,x):
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)
        x = self.pool1(x)
        x = x.view(x.size()[0], -1)
        x = self.fc1(x)
        x = self.relu(x)
        x = self.fc2(x)

        return x

11. 前向过程函数 

def train_val(model, train_loader, val_loader, device, epochs, optimizer, save_path):

12. 训练流程函数: 需要传入几个参数

    model = model.to(device)

    plt_train_loss = []
    plt_val_loss = []
    plt_train_acc = []
    plt_val_acc = []
    max_val_acc = 0.0

13. 先将模型放在gpu上,用列表记录 训练和验证的loss,分类模型还讲究成功率,所以要定义训练和验证的准确率和最大的准确率,用于保存最佳模型

    for epoch in range(epochs):     #开始训练
        train_loss = 0.0
        val_loss = 0.0
        train_acc = 0.0
        val_acc = 0.0
        start_time = time.time()

14. 开始训练,每一轮都要定义该轮的训练和验证的loss,acc,还要记录此轮开始的时间 

        model.train()   #将模型设置为训练模式
        for batch_x,batch_y in train_loader:      #一个epoch为训练所有数据,train_loader可以自动分批取出所有batch  thought:train_loader代表的还是群体数据,只不过将其打乱分批
            x, target = batch_x.to(device), batch_y.to(device)  #将数据放在gpu上
            pred = model(x)  #前向过程
            train_bat_loss = loss(pred, target)  #算出loss
            train_bat_loss.backward()    #回传过程,算出梯度
            optimizer.step()    #更新参数
            optimizer.zero_grad()   #梯度归零
            train_loss += train_bat_loss.cpu().item()  #batloss是张量在gpu上,先取下来,再用item()取其数值
            train_acc += np.sum(np.argmax(pred.detach().cpu().numpy(), axis= 1) == target.cpu().numpy()) #argmax()可以找到最大值所在的下标,直接写pred会报错,因为pred在gpu上,先从计算网上摘下来,放在cpu上,再转为numpy形式,注意在哪个维度上找最大值

        plt_train_loss.append(train_loss/train_loader.dataset.__len__())   #求平均loss值
        plt_train_acc.append(train_acc / train_loader.dataset.__len__())

15. 进入训练模式,在train_loader中取出特征和标签,将其放在gpu上并记为x,和target,将x经过模型,计算loss,此处为交叉熵损失,经过一系列操作后,更新了模型,将算出的loss取下来,取数值加到此轮的loss上,再计算准确率acc,计算方法:用np.argmax()找出pred的四份数据里的预测值最大的下标,并和实际比较,算出这四个中猜对的个数,(调试可知pred和前面那个都是array(4,),train_bat_loss()为tensor(),train_bat_loss.cpu().item()为float),通过loader将这一轮训练集数据取完之后,将得到的总的loss和acc将平均loss和acc记录在列表里

 model.eval()   #进入验证模式
        with torch.no_grad():           #与训练集不同,验证集不能积累梯度 数据只要经过模型就一定会产生梯度
            for batch_x, batch_y in val_loader:
                x, target = batch_x.to(device), batch_y.to(device)  # 将数据放在gpu上
                pred = model(x)  # 前向过程
                val_bat_loss = loss(pred, target)  # 算出loss
                # val_bat_loss.backward()  # 回传过程,算出梯度
                # optimizer.step()  # 更新参数
                # optimizer.zero_grad()  # 梯度归零      #不需要更新模型
                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__())

16. 验证流程 :和训练流程相似,只是不计算梯度,不更新参数了

            if val_acc > max_val_acc:   #更新模型
                torch.save(model,save_path)
                max_val_acc = val_acc

            print("[%03d/%03d]  %2.2f secs Trainloss: %.6f Trainacc: %.6f   |   Valloss: %.6f Valacc: %.6f"%(epoch, epochs, time.time() - start_time, plt_train_loss[-1],plt_train_acc[-1], plt_val_loss[-1],plt_val_acc[-1]))

 17.  存好最佳的模型

    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()

18. 画图

model = myModel(11)
lr = 0.001
optimizer = optim.AdamW(model.parameters(), lr, weight_decay=0.001)   #比sgd好用的优化器
device = "cuda" if torch.cuda.is_available() else "cpu"

epochs = 20
save_path = "model_save"

19. 定义的超参数 ,其中优化器改用AdamW

train_loader = DataLoader(train_set, batch_size=4, shuffle= True)
val_loader = DataLoader(val_set, batch_size= 4, shuffle= True)
# for bat_x,bat_y in train_loader:
#     print(bat_x)
#     pred = model(bat_x)

loss = nn.CrossEntropyLoss()  #交叉熵损失,直接调用就行:传入模型输出和标签

train_val(model, train_loader, val_loader, device, epochs, optimizer, save_path)

20. 主体部分 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值