猫狗大战数据集分类识别-PyTorch代码实训
二分类任务
数据集文件目录结构图
pythonProject/
│
├── cat_recognition.py
│
└── kagglecatsanddogs_5340/
└── PetImages/
├── Cat/...
└── Dog/...
Cat和Dog文件夹中的图片的后缀均为.jpg
代码1(实现二分类问题)
import torch
import torch.nn
from torch.utils.data import Dataset, DataLoader, Subset, random_split
import torchvision.datasets
from torchvision import transforms, models
from PIL import Image
from torch import nn, optim
import matplotlib.pyplot as plt
transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize((0.5, ), (0.5, ))
])
root_dir = './kagglecatsanddogs_5340/PetImages'
dataset = torchvision.datasets.ImageFolder(root=root_dir, transform=transform)
n_train = int(0.8 * len(dataset))
n_test = len(dataset) - n_train
train_dataset, test_dataset = random_split(dataset, [n_train, n_test])
# dataloader = DataLoader(dataset, batch_size=4, shuffle=True)
train_loader = DataLoader(dataset=train_dataset, batch_size=16, shuffle=True)
test_loader = DataLoader(dataset=test_dataset, batch_size=16, shuffle=True)
print("Finish Reading the Dataset")
# MyNet
class MyNet(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, stride=1, padding=1)
self.relu1 = nn.ReLU()
self.maxpool1 = nn.MaxPool2d(kernel_size=2)
self.conv2 = nn.Conv2d(in_channels=16, out_channels=32, kernel_size=3, stride=1, padding=1)
self.relu2 = nn.ReLU()
self.maxpool2 = nn.MaxPool2d(kernel_size=2)
self.fc1 = nn.Linear(in_features=32*56*56, out_features=512)
self.relu_fc1 = nn.ReLU()
self.fc2 = nn.Linear(in_features=512, out_features=2)
def forward(self, x):
x = self.conv1(x)
x = self.relu1(x)
x = self.maxpool1(x)
x = self.conv2(x)
x = self.relu2(x)
x = self.maxpool2(x)
x = torch.flatten(x, 1)
x = self.fc1(x)
x = self.relu_fc1(x)
x = self.fc2(x)
return x
model = MyNet()
# model = models.resnet50(pretrained=True)
# for param in model.parameters():
# param.requires_grad = False
# model.fc = nn.Linear(2048, 2)
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(params=model.parameters(), lr=1e-4)
# train
def train(epoch): # epoch: 方便打印
running_loss = 0.0
running_total = 0
running_correct = 0
for batch_idx, data in enumerate(train_loader, 0): # 给train_loader元素编号,从0开始
inputs, targets = data # inputs和targets是“数组”的形式
optimizer.zero_grad() # 消除优化器中原有的梯度
outputs = model(inputs)
loss = criterion(outputs, targets) # 对比输出结果和“答案”
loss.backward()
optimizer.step() # 优化网络参数
running_loss += loss.item() # .item(): 取出tensor中特定位置的具体元素值并返回该值(Tensor to int or float)
_, predicted = torch.max(outputs.data, dim=1) # 找到每个样本预测概率最高的类别的标签值(即预测结果)
# dim=0计算tensor中每列的最大值的索引,dim=1表示每行的最大值的索引
running_total += inputs.shape[0] # .shape[0]: 读取矩阵第一维度的长度
running_correct += (predicted == targets).sum().item()
if batch_idx % 300 == 299:
print('[%d, %5d]: loss: %.3f , acc: %.2f %%'
% (epoch + 1, batch_idx + 1, running_loss / 300, 100 * running_correct / running_total))
running_loss = 0.0
running_total = 0
running_correct = 0
# test *
def test(epoch):
correct = 0
total = 0
with torch.no_grad():
for data in test_loader:
images, labels = data
outputs = model(images)
_, predicted = torch.max(outputs.data, dim=1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
acc = correct / total
print('[%d / %d]: Accuracy on test set: %.1f %% ' % (epoch + 1, 3, 100 * acc))
return acc
# main
for epoch in range(3):
train(epoch)
acc_test = test(epoch)
acc_list_test.append(acc_test)
print("----------Finish the model training process.----------")
结果1
(之前调整过epoch,且由于电脑性能限制,完成过多epoch的训练耗时较长,故仅进行了几轮训练)
代码2(用于理解dataloader内的结构)
# 理解dataloader
from torchvision import transforms
from torchvision.datasets import ImageFolder
from torch.utils.data import DataLoader
# 数据预处理
transform = transforms.Compose([transforms.Resize((224,224)), transforms.ToTensor()])
# 加载数据集
dataset = ImageFolder('./kagglecatsanddogs_5340/PetImages', transform=transform)
# 创建 DataLoader
loader = DataLoader(dataset, batch_size=4)
# 通过迭代的方式访问 DataLoader 中的元素
for i, (images, labels) in enumerate(loader):
# if i == 0: # 仅显示第一个批次的数据
print(f"第{i + 1}个批次的图像张量:")
print(images.shape) # 显示图像张量的形状
print("对应的标签:", labels)
# break
# 获取数据集中的特定样本(假设索引为10)
sample_idx = 10
image, label = dataset[sample_idx]
print(f"索引 {sample_idx} 的图像张量:")
print(image.shape)
print("对应的标签:", label)
结果2
ImageFolder方法会自动读取文件目录中的图片,并将其打上标签:[‘0’, ‘1’]。
问题
loss的含义;loss值的大小怎么看?
损失函数用来优化模型。在此二分类任务中,采用了交叉熵损失函数。损失值越小,表示模型对类别预测的准确性越高。
在训练过程中,目标是通过优化算法使损失值逐渐减小。
较大的损失值可能表示模型尚未充分学习数据的特征,或者模型架构、超参数选择不合适。在训练过程中,需要检查和调整模型,以使损失值逐渐减小。
batch_size对于训练速度和训练模型分类正确率的影响?
增大batch_size可以减少迭代次数。对相同的数据量,处理速度比小的batch_size更快。
但过大的batch_size可能会让内存容量撑不住,同时对参数的修正会变缓。
总结:
- batch_size设的大一些,收敛得快,也就是需要训练的次数少,准确率上升的也很稳定,但是实际使用起来精度不高;
- batch_size设的小一些,收敛得慢,可能准确率来回震荡,因此需要把基础学习速率降低一些,但是实际使用起来精度较高。
如何设计神经网络的结构?
设计神经网络的思路:
- 先设计一个过拟合的模型
- 再消除过拟合带来的问题
对于datasets.[数据集名]的参数transform,.ToTensor()和.Resize()谁先谁后?有影响吗?
推荐的操作顺序是先进行Resize
操作,然后再进行ToTensor
操作。
如果先ToTensor后Resize,那么最终得到的张量尺寸是转换前的原始大小。因为调整大小操作通常需要基于图像的像素信息来进行,而且模型通常要输入固定大小的张量。
怎样认识CNN提取了猫狗图像中的哪些特征用于分类任务?
一种方法是打印feature map来可视化网络提取的特征。
当batch_size = 16时,对搭建的卷积网络中的几个层进行可视化:
- conv1
- maxpool1
- conv2
归一化的作用?如何归一化?
归一化的作用:
- 加速训练过程,加速收敛
- 提高模型的稳定性
- 改善模型的泛化能力
所以往往需要使用代码求解数据集的均值和标准差,用于归一化时设置参数。
Learning Rate的含义?
本质上是“步长”。
学习率 大 | 学习率 小 | |
---|---|---|
学习速度 | 快 | 慢 |
理想状态下的使用时间点 | 刚开始训练时 | 一定轮数过后(接近结束时) |
不足 | 有可能会出现震荡的情况 | 容易过拟合;收敛速度慢 |
代码中为什么要声明全局变量(global)?
在Python中,如果想要在函数内部修改一个定义在函数外部的变量,需要使用 global
关键字来声明这个变量是全局的。否则,Python会认为是在函数内部创建了一个与全局变量同名的局部变量。
*什么是钩子函数?
钩子函数是一种回调机制,允许程序在执行的特定点插入用户定义的代码。
在PyTorch中,hook方法有四种:
torch.Tensor.register_hook()
torch.nn.Module.register_forward_hook()
torch.nn.Module.register_backward_hook()
torch.nn.Module.register_forward_pre_hook().
使用.register_forward_hook可以导出卷积特征图。
框选识别
这里使用opencv中提供的.CascadeClassifier()方法,引入haarcascade_frontalcatface.xml文件和haarcascade_frontalcatface_extended.xml文件,用于自动框选出图像中被识别出来的猫脸。
其中的.xml文件可以通过此网址下载:https://github.com/opencv/opencv/tree/master/data/haarcascades
import numpy as np
import cv2
cat_cascade = cv2.CascadeClassifier('haarcascade_frontalcatface.xml')
cat_ext_cascade = cv2.CascadeClassifier('haarcascade_frontalcatface_extended.xml')
SF=1.05 # try different values of scale factor like 1.05, 1.3, etc
N=3 # try different values of minimum neighbours like 3,4,5,6
def processImage(image_dir,image_filename):
# read the image
img = cv2.imread(image_dir+'/'+image_filename)
# convery to gray scale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# this function returns tuple rectangle starting coordinates x,y, width, height
cats = cat_cascade.detectMultiScale(gray, scaleFactor=SF, minNeighbors=N)
#print(cats) # one sample value is [[268 147 234 234]]
cats_ext = cat_ext_cascade.detectMultiScale(gray, scaleFactor=SF, minNeighbors=N)
#print(cats_ext)
# draw a blue rectangle on the image
for (x,y,w,h) in cats:
img = cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
# draw a green rectangle on the image
for (x,y,w,h) in cats_ext:
img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
# save the image to a file
cv2.imwrite('out'+image_filename,img)
for idx in range(1,7):
processImage('cats/',str(idx)+'.jpg')
processImage('.','dog.jpg')
运行结果如下:(准确率没有预期中高)