# 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")
inceptionv3 图像分类 pytorch 图片分类pytorch
转载本文章为转载内容,我们尊重原作者对文章享有的著作权。如有内容错误或侵权问题,欢迎原作者联系我们进行内容更正或删除文章。

提问和评论都可以,用心的回复会被更多人看到
评论
发布评论
相关文章
-
使用inceptionv3训练国字号项目一遥感图像分类
使用inceptionv3训练国
program-1遥感分类 数据集 github tensorflow