pytorch 图片分类学习笔记

# Import需要的套件
import os
import numpy as np
import cv2
import torch
import torch.nn as nn
import torchvision.transforms as transforms
import pandas as pd
from torch.utils.data import DataLoader, Dataset
import time

# Read image 利用 OpenCV(cv2) 读入照片并存放在 numpy array 中
def readfile(path, label):
    # label 是一个 boolean variable, 代表需不需要回传 y 值
    image_dir = sorted(os.listdir(path))  # os.listdir(path)将path路径下的文件名以列表形式读出
    # print(os.listdir(path))
    # print(image_dir)
    x = np.zeros((len(image_dir), 128, 128, 3), dtype=np.uint8)
    y = np.zeros((len(image_dir)), dtype=np.uint8)
    for i, file in enumerate(image_dir):
        img = cv2.imread(os.path.join(path, file))  # os.path.join(path, file) 路径名合并
        x[i, :, :] = cv2.resize(img, (128, 128))
        if label:
            y[i] = int(file.split("_")[0])
    if label:
        return x, y
    else:
        return x


# 分别将 training set、validation set、testing set 用 readfile 函式读进来
workspace_dir = 'Q:/food-11'
print("Reading data")
print("...")
train_x, train_y = readfile(os.path.join(workspace_dir, "training"), True)
print("Size of training data = {}".format(len(train_x)))
val_x, val_y = readfile(os.path.join(workspace_dir, "validation"), True)
print("Size of validation data = {}".format(len(val_x)))
test_x = readfile(os.path.join(workspace_dir, "testing"), False)
print("Size of Testing data = {}".format(len(test_x)))
print("Reading data complicated")

''' Dataset '''
print("Dataset")
print("...")
# training 时做 data augmentation
# transforms.Compose 将图像操作串联起来
train_transform = transforms.Compose([
    transforms.ToPILImage(),
    transforms.RandomHorizontalFlip(),  # 随机将图片水平翻转
    transforms.RandomRotation(15),  # 随机旋转图片 (-15,15)
    transforms.ToTensor(),  # 将图片转成 Tensor, 并把数值normalize到[0,1](data normalization)
])
# testing 时不需做 data augmentation
test_transform = transforms.Compose([
    transforms.ToPILImage(),
    transforms.ToTensor(),
])


class ImgDataset(Dataset):
    def __init__(self, x, y=None, transform=None):
        self.x = x
        # label is required to be a LongTensor
        self.y = y
        if y is not None:
            self.y = torch.LongTensor(y)
        self.transform = transform

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

    def __getitem__(self, index):
        X = self.x[index]
        if self.transform is not None:
            X = self.transform(X)
        if self.y is not None:
            Y = self.y[index]
            return X, Y
        else:  # 如果没有标签那么只返回X
            return X


batch_size = 32
train_set = ImgDataset(train_x, train_y, train_transform)
val_set = ImgDataset(val_x, val_y, test_transform)
train_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True)
val_loader = DataLoader(val_set, batch_size=batch_size, shuffle=False)
print("Dataset complicated")

''' Model '''
print("Model")
print("...")


class Classifier(nn.Module):
    '''
        这是一个使用 PyTorch 框架定义的分类器模型。它包含两个部分:

        卷积神经网络 (CNN):它由五个卷积层和池化层构成。其中卷积层使用3×3的卷积核,池化层使用2×2的最大池化。第一层输入大小为[3, 128, 128],最后一层输出大小为[512, 4, 4],即512个通道,每个通道大小为4×4。

        全连接神经网络 (FCN):它由三个全连接层构成,分别是大小为512×4×4到1024,1024到512,512到11的线性变换。最后一层的输出大小为11,这个模型的任务是将输入图像分为11个类别。

        在前向传播函数forward中,输入图像x经过卷积神经网络得到特征图out,然后将out展平成大小为[batch_size, 512×4×4]的一维张量,最后通过全连接神经网络输出预测结果。
    '''
    def __init__(self):
        super(Classifier, self).__init__()
        # torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding)
        # torch.nn.MaxPool2d(kernel_size, stride, padding)
        # input 维度 [3, 128, 128]
        self.cnn = nn.Sequential(
            nn.Conv2d(3, 64, 3, 1, 1),  # [64, 128, 128]
            nn.BatchNorm2d(64),
            nn.ReLU(),
            nn.MaxPool2d(2, 2, 0),  # [64, 64, 64]

            nn.Conv2d(64, 128, 3, 1, 1),  # [128, 64, 64]
            nn.BatchNorm2d(128),
            nn.ReLU(),
            nn.MaxPool2d(2, 2, 0),  # [128, 32, 32]

            nn.Conv2d(128, 256, 3, 1, 1),  # [256, 32, 32]
            nn.BatchNorm2d(256),
            nn.ReLU(),
            nn.MaxPool2d(2, 2, 0),  # [256, 16, 16]

            nn.Conv2d(256, 512, 3, 1, 1),  # [512, 16, 16]
            nn.BatchNorm2d(512),
            nn.ReLU(),
            nn.MaxPool2d(2, 2, 0),  # [512, 8, 8]

            nn.Conv2d(512, 512, 3, 1, 1),  # [512, 8, 8]
            nn.BatchNorm2d(512),
            nn.ReLU(),
            nn.MaxPool2d(2, 2, 0),  # [512, 4, 4]
        )
        self.fc = nn.Sequential(
            nn.Linear(512 * 4 * 4, 1024),
            nn.ReLU(),
            nn.Linear(1024, 512),
            nn.ReLU(),
            nn.Linear(512, 11)
        )

    def forward(self, x):
        out = self.cnn(x)
        out = out.view(out.size()[0], -1)
        return self.fc(out)


print("Model complicated")


''' Training '''
print("Training")
print("...")
# 使用training set訓練,並使用validation set尋找好的參數

# model = Classifier().cuda()
device = 'cuda' if torch.cuda.is_available() else 'cpu'
model=Classifier().to(device)

loss = nn.CrossEntropyLoss()  # 因為是 classification task,所以 loss 使用 CrossEntropyLoss
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)  # optimizer 使用 Adam
num_epoch = 3  # 迭代30次

for epoch in range(num_epoch):
    epoch_start_time = time.time()
    train_acc = 0.0
    train_loss = 0.0
    val_acc = 0.0
    val_loss = 0.0

    model.train()  # 確保 model 是在 train model (開啟 Dropout 等...)
    for i, data in enumerate(train_loader):
        optimizer.zero_grad()  # 用 optimizer 將 model 參數的 gradient 歸零
        # train_pred = model(data[0].cuda())  # 利用 model 得到預測的機率分佈 這邊實際上就是去呼叫 model 的 forward 函數
        train_pred = model(data[0])  # 利用 model 得到預測的機率分佈 這邊實際上就是去呼叫 model 的 forward 函數
        # batch_loss = loss(train_pred, data[1].cuda())  # 計算 loss (注意 prediction 跟 label 必須同時在 CPU 或是 GPU 上)
        batch_loss = loss(train_pred, data[1])  # 計算 loss (注意 prediction 跟 label 必須同時在 CPU 或是 GPU 上)
        batch_loss.backward()  # 利用 back propagation 算出每個參數的 gradient
        optimizer.step()  # 以 optimizer 用 gradient 更新參數值

        train_acc += np.sum(np.argmax(train_pred.cpu().data.numpy(), axis=1) == data[1].numpy())
        train_loss += batch_loss.item()

    model.eval()
    with torch.no_grad():
        # torch.no_grad() 是一个上下文管理器,用于禁用梯度计算。在上下文管理器中,不会为 autograd 维护计算图,这意味着无法进行反向传播。

        # 使用 torch.no_grad() 有两个主要目的:

        # 防止因为评估模型时的内存消耗过多而导致内存不足,从而避免程序异常终止。
        # 取消梯度在测试或验证阶段的计算,从而加速模型的计算。
        # 在模型的测试或验证阶段,我们不需要计算模型参数的梯度,而且取消梯度的计算可以提高模型的性能和速度,因此在这些情况下使用 torch.no_grad() 是非常常见的。
        for i, data in enumerate(val_loader):
            # val_pred = model(data[0].cuda())
            # batch_loss = loss(val_pred, data[1].cuda())
            val_pred = model(data[0])
            # 这行代码是用训练好的模型 model 对验证集中的输入数据 data[0] 进行前向传播得到输出结果 val_pred。具体来说,data[0] 是一个 batch 的输入数据,model 负责将其转换为输出结果。输出结果 val_pred 的形状通常是 [batch_size, num_classes],其中 batch_size 是数据集中当前 batch 的大小,num_classes 则是分类问题中的类别数。 val_pred 中的每个元素是对应类别的得分,可以用 softmax 函数将其转换为概率分布。
            batch_loss = loss(val_pred, data[1])
            # 这行代码计算了当前训练过程中在验证集上的损失。具体来说,val_pred 是模型在验证集上的预测结果,data[1] 是验证集中的标签数据,而 loss 是之前定义的损失函数,它将这两个参数作为输入,计算出当前的损失值并返回给 batch_loss 变量。

            # 在这个损失值上的梯度不需要计算,因为验证集不参与参数的更新。因此,我们可以使用 torch.no_grad() 上下文管理器,该上下文管理器将暂时关闭梯度的计算,避免浪费内存和计算资源,从而更高效地计算验证损失值。


            val_acc += np.sum(np.argmax(val_pred.cpu().data.numpy(), axis=1) == data[1].numpy())
            # 这行代码计算了验证集上的准确率,具体来说,它首先通过 val_pred.cpu().data.numpy() 将模型在验证集上的预测结果转化为 numpy 数组,接着使用 numpy.argmax 函数在第二个轴(即行)上找到最大值所在的下标,也就是模型认为最可能的类别。这里假设预测的结果是一个大小为 (batch_size, num_classes) 的矩阵,第二个轴对应着 num_classes 个类别,因此 np.argmax(val_pred.cpu().data.numpy(), axis=1) 返回的是长度为 batch_size 的数组,每个元素的取值范围是 0 到 num_classes-1。

            # 接着,代码使用 == 运算符比较预测结果和真实标签 data[1].numpy() 是否相等,这里注意到 data[1] 存储的是验证集中样本的标签,因此 data[1].numpy() 返回的是长度为 batch_size 的数组,每个元素的取值范围也是 0 到 num_classes-1,代表样本真实的类别。

            # 最后,np.sum 统计两个数组中相等元素的个数,也就是模型正确预测的样本数,这个数值最终用于计算准确率。
            val_loss += batch_loss.item()
            # 这行代码的作用是将当前 validation batch 的损失值累加到总的 validation 损失值中。batch_loss.item() 返回一个 tensor 对象的数值部分,该值表示当前 batch 的损失值。然后,使用 "+=" 运算符将这个值累加到 val_loss 变量中,表示当前 epoch 的总 validation 损失值。

        # 將結果 print 出來
        print('[%03d/%03d] %2.2f sec(s) Train Acc: %3.6f Loss: %3.6f | Val Acc: %3.6f loss: %3.6f' % \
              (epoch + 1, num_epoch, time.time() - epoch_start_time, \
               train_acc / train_set.__len__(), train_loss / train_set.__len__(), val_acc / val_set.__len__(),
               val_loss / val_set.__len__()))
        # 这部分代码用于输出训练过程的信息,包括每一次迭代的训练集准确率(Train Acc)和训练集损失(Loss),以及验证集准确率(Val Acc)和验证集损失(loss)。

        # [%03d/%03d]:表示当前训练次数,以及总训练次数。
        # %2.2f sec(s):表示本次训练的耗时,以秒为单位,保留小数点后两位。
        # Train Acc: %3.6f:表示训练集准确率,保留小数点后六位。
        # Loss: %3.6f:表示训练集损失,保留小数点后六位。
        # |:表示分隔符,用于将训练集信息和验证集信息分开。
        # Val Acc: %3.6f:表示验证集准确率,保留小数点后六位。
        # loss: %3.6f:表示验证集损失,保留小数点后六位。
        # 其中 %03d、%3.6f、%2.2f、%d、%f 等都是格式化字符串的语法,用于指定输出格式。详细的格式化字符串语法可以参考 Python 的官方文档。

# for epoch in range(num_epoch)::循环 num_epoch 次进行训练。
# train_acc 和 train_loss:定义训练集上的准确率和损失,初始化为0.0。
# val_acc 和 val_loss:定义验证集上的准确率和损失,初始化为0.0。
# model.train():将模型设置为训练模式。此时会启用dropout和batch normalization等。
# for i, data in enumerate(train_loader)::对训练数据进行循环迭代,其中 train_loader 是一个 DataLoader 对象,用于加载训练数据。
# optimizer.zero_grad():将模型梯度清零。
# train_pred = model(data[0]):用模型预测输出。
# batch_loss = loss(train_pred, data[1]):计算损失。
# batch_loss.backward():反向传播计算梯度。
# optimizer.step():更新模型参数。
# train_acc += np.sum(np.argmax(train_pred.cpu().data.numpy(), axis=1) == data[1].numpy()):计算训练集上的准确率。
# train_loss += batch_loss.item():累计训练集上的损失。
# model.eval():将模型设置为评估模式,此时会关闭dropout和batch normalization等。
# with torch.no_grad()::在验证集上进行验证时,不需要计算梯度,使用 torch.no_grad() 上下文管理器可以提高计算效率。
# 这部分代码是用来验证模型的表现情况,并将训练过程中的一些参数输出。这部分代码也是嵌套在一个 for 循环中的。

# 首先,使用了一个 torch.no_grad() 上下文管理器来关闭梯度计算。这个上下文管理器确保在验证过程中不会计算任何梯度。接下来,代码使用一个 for 循环枚举验证数据加载器中的数据,然后计算模型在当前批次上的预测和损失值。和之前训练过程一样,这里的 model(data[0]) 表示对当前的输入数据进行模型预测,然后用 loss(val_pred, data[1]) 计算模型预测结果和标签之间的损失值。最后,计算当前批次的精确度和损失值,并将其加到验证集上。

# 最后,使用 print() 函数输出训练过程中的一些参数,包括当前训练的 epoch 数,训练用时,训练集上的准确率和损失值,验证集上的准确率和损失值。这里用到了字符串的格式化技巧来将参数按照指定的格式输出。其中 %03d 表示输出一个三位数的整数,不足三位时前面用 0 补齐;%2.2f 表示输出一个两位数的浮点数,小数点后保留两位,不足两位时前面用 0 补齐;%3.6f 表示输出一个三位数的浮点数,小数点后保留六位,不足三位时前面用空格补齐。

# 这段代码是一个典型的训练神经网络的过程,采用的是经典的Mini-batch SGD(随机梯度下降)方法。代码中通过for循环迭代num_epoch次来进行训练,其中num_epoch是指训练轮数,也就是将整个训练集数据集训练num_epoch次。

# 在每次训练中,首先通过train_loader来获取mini-batch大小的训练数据,然后将模型的梯度清零,将训练数据传递给模型进行前向传播(model(data[0])),然后计算损失函数值(loss(train_pred, data[1]))。接着根据损失函数值通过反向传播计算梯度,并用优化器(optimizer)更新模型的权重(optimizer.step())。

# 在训练过程中,通过train_acc和train_loss记录每个mini-batch的准确率和损失函数值,用于评估模型的训练效果。

# 训练结束后,通过val_loader来获取验证集数据,然后将模型设为评估模式,即model.eval(),计算模型在验证集上的准确率和损失函数值。最后输出每轮训练和验证的准确率和损失函数值,以便用户了解模型的训练效果。




# randi.chewning4@hotmail.com
# 继续解析剩下的代码 
# 在完成了对训练集的遍历后,代码进入了验证集的遍历阶段。这个过程跟训练过程类似,只不过模型的状态被设置为评估模式(model.eval()),且没有进行梯度更新。

# 具体而言,代码通过一个for循环遍历验证集的所有数据样本,对每个样本,将其特征数据(data[0])送入模型中,得到该样本的预测结果(val_pred),并将其与该样本的真实标签(data[1])一起传入损失函数(loss)中计算该样本的损失值(batch_loss)。这里同样注意要将预测结果和真实标签从GPU中转移到CPU上,然后将这个batch的预测准确率和损失值加入到对应的累计变量中(val_acc和val_loss)。

# 最后,代码将本次epoch的训练准确率、训练损失、验证准确率和验证损失打印输出,同时epoch计数器加1(epoch+1)。

# 完整代码的作用是进行神经网络的训练和验证,并输出每个epoch的训练准确率、训练损失、验证准确率和验证损失。其中,训练集用于更新模型参数,验证集用于评估模型性能,训练过程中通过反向传播算法更新模型的参数。

train_val_x = np.concatenate((train_x, val_x), axis=0)
train_val_y = np.concatenate((train_y, val_y), axis=0)
train_val_set = ImgDataset(train_val_x, train_val_y, train_transform)
train_val_loader = DataLoader(train_val_set, batch_size=batch_size, shuffle=True)

# model_best = Classifier().cuda()
model_best=Classifier().to(device)
loss = nn.CrossEntropyLoss()  # 因為是 classification task,所以 loss 使用 CrossEntropyLoss
optimizer = torch.optim.Adam(model_best.parameters(), lr=0.001)  # optimizer 使用 Adam
num_epoch = 3

for epoch in range(num_epoch):
    epoch_start_time = time.time()
    train_acc = 0.0
    train_loss = 0.0

    model_best.train()
    for i, data in enumerate(train_val_loader):
        optimizer.zero_grad()
        # train_pred = model_best(data[0].cuda())
        # batch_loss = loss(train_pred, data[1].cuda())
        train_pred = model_best(data[0])
        batch_loss = loss(train_pred, data[1])
        batch_loss.backward()
        optimizer.step()

        train_acc += np.sum(np.argmax(train_pred.cpu().data.numpy(), axis=1) == data[1].numpy())
        train_loss += batch_loss.item()

        # 將結果 print 出來
    print('[%03d/%03d] %2.2f sec(s) Train Acc: %3.6f Loss: %3.6f' % \
          (epoch + 1, num_epoch, time.time() - epoch_start_time, \
           train_acc / train_val_set.__len__(), train_loss / train_val_set.__len__()))

print("Training complicated")


''' Testing '''
print("Testing")
print("...")
test_set = ImgDataset(test_x, transform=test_transform)
test_loader = DataLoader(test_set, batch_size=batch_size, shuffle=False)
model_best.eval()
prediction = []
with torch.no_grad():
    for i, data in enumerate(test_loader):
        # test_pred = model_best(data.cuda())
        test_pred = model_best(data)
        test_label = np.argmax(test_pred.cpu().data.numpy(), axis=1)
        for y in test_label:
            prediction.append(y)
# 將結果寫入 csv 檔
with open("predict.csv", 'w') as f:
    f.write('Id,Category\n')
    for i, y in enumerate(prediction):
        f.write('{},{}\n'.format(i, y))
print("Testing complicated")

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值