pytorch实现猫狗识别
pytorch猫狗识别实现
不要轻视任何一个环节
写在前面:看官方文档很重要!
数据集
制作数据集
按照下图整理数据集文件结构,cat里面只放猫图、dog里面只放狗图
读取数据集
这里也可以通过重写
from torch.utils.data import Dataset
里面的Dataset类,自定义__init__
、__getitem__
、__len__
来加载,这里我直接用的 ImageFolder
trainData = ImageFolder(trainPath,transform=trainTransform)
valData = ImageFolder(valPath,transform=valTransform)
预处理(加载数据集)
torchvsion里提供了很多专门处理图像的方法,这里我们用transforms来整理
预览数据集,发现原数据集中的图片大小不一,通过resize统一大小(至于为什么选用(500,360)纯纯随手尝试,可以直接重置为正方形的),为了让训练效果更好,这里做了一下数据增强(包括随机翻转、调整图片饱和度和随机剪裁图片大小)
trainTransform = transforms.Compose([
transforms.Resize(size=(500, 360)), # 重置图象大小
transforms.RandomHorizontalFlip(), # 随机水平翻转
# transforms.RandomCrop(size=imgSize), # 随机裁剪大小,输入网络中的图片为 (3, 224, 224)
transforms.ColorJitter(brightness=0.5, contrast=0.5, hue=0.5), # 改变图片颜色饱和度(数据增强)
transforms.RandomResizedCrop(size=imgSize),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
# transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
])
valTransform = transforms.Compose([
transforms.Resize([imgSize,imgSize]),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
验证集我没有加上整理 -> 原因?
其实这里的加载分成了两部分:一个是获取数据集(ImageFolder
)、一个是生成迭代器(DataLoader
)
trainData = ImageFolder(trainPath,transform=trainTransform)
valData = ImageFolder(valPath,transform=valTransform)
trainLoad = DataLoader(trainData,batch_size=batchSize,shuffle=True)
valLoad = DataLoader(valData,batch_size=batchSize)
构建自己的网络
推荐教程:https://mofanpy.com/tutorials/machine-learning/torch/CNN
CNN
流程如下:
- 卷积层(
Conv2d
) - 激励函数(
ReLU
) - 池化<向下采样>(
pool
)
重复上述过程,最后将多维的张量展平(view
)得到特征图,到全连接层(Linear
)中进行分类得到输出
pytorch给我们提供了非常方便的接口,只需要继承torch.nn.Module
这个类,重载__init__
和forward
这两个魔术方法就能构造自己的网络模型
卷积中的图片大小要对应哇
class Net(torch.nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = torch.nn.Sequential(
torch.nn.Conv2d(in_channels=3, # 此时图片为 (3, 224, 224)
out_channels=16,
kernel_size=3,
stride=1, # 步长
padding=1, # padding=(kernel_size-1)/2从而保持图片大小不变(在步长为 1 时)
), # 输出图片 (16, 224, 224)
torch.nn.ReLU(),
torch.nn.MaxPool2d(2) # 2x2下采样,输出图片 (16, 112, 112)
)
self.conv2 = torch.nn.Sequential(
torch.nn.Conv2d(in_channels=16,
out_channels=32,
kernel_size=3,
stride=1,
padding=1,
),
torch.nn.ReLU(),
torch.nn.MaxPool2d(2)
)
self.conv3 = torch.nn.Sequential(
torch.nn.Conv2d(in_channels=32,
out_channels=64,
kernel_size=3,
stride=1,
padding=1,
),
torch.nn.ReLU(),
torch.nn.MaxPool2d(2)
)
self.dense = torch.nn.Sequential(
torch.nn.Linear(64 * 28 * 28, 128), # 第一个参数是图片的体积(长宽高),第二个参数是输出节点
torch.nn.ReLU(),
torch.nn.Linear(128, 2)
)
# 向前传播
def forward(self, x):
conv1_out = self.conv1(x)
conv2_out = self.conv2(conv1_out)
conv3_out = self.conv3(conv2_out)
res = conv3_out.view(conv3_out.size(0), -1) # 展平多维图像组,(batchSize: 64 * 28 * 28)
out = self.dense(res)
return out
开始训练
设置超参数
参数皆可自定义
imgSize = 224
batchSize = 64
epochSize = 18
lr = 0.001
生成模型、优化器、计算损失函数
model = Net() # 创建模型
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
lossFunc = torch.nn.CrossEntropyLoss() # 必须先把求损失的库函数定义到一个自己命名的函数再调用(需要初始化!)
训练
把每个epoch都输入网络,并格式化输出loss和accuracy(不要因为懒得搞就不写,它们的输出方便排错)
for epoch in range(epochSize):
trainLoss = 0.0
trainAcc = 0
print("Epoch {}/{}".format((epoch+1), epochSize))
print("-" * 10)
for batch, data in enumerate(trainLoad, start=1):
X, y = data
X, y = Variable(X),Variable(y)
# X, y = X.to(device), y.to(device) 这个是把网络输入gpu上运算
out = model(X)
_, predicted1 = torch.max(out.data, 1)
trainCorrect = (predicted1 == y).sum()
trainAcc += trainCorrect.item()
loss = lossFunc(out, y)
optimizer.zero_grad() # 梯度下降
loss.backward() # 损失函数反向传播
optimizer.step() # 优化器更新x
trainLoss += loss.item()
if batch%10 == 0:
print('Train Loss: {:.6f}, it\'s Acc:{:.6f}'.format(trainLoss / len(trainData), trainAcc / len(trainData)))
trainLoss = 0
trainAcc = 0
加速训练(在gpu上跑)
分成两步:
-
网络输入gpu
model = Net() model.cuda() # 把model放进去
-
数据输入gpu
for batch, data in enumerate(trainLoad, start=1): X, y = data # X, y = Variable(X),Variable(y) X, y = X.to(device), y.to(device) # 把张量扔进gpu
报错处理
RuntimeError: stack expects each tensor to be equal size
我在读取数据集的时候统一设置了大小,但是在训练过程仍然出现了这个问题,可以看到获取的信息告诉我在卷积时两者大小不匹配了
got [3, 224, 299] at entry 0 and [3, 224, 240] at entry 1
考虑是数据增强的处理没做到位,将重置图片大小放在最后
RuntimeError: CUDA out of memory
gpu没地方了,调小batch、关掉其他应用、清理torch缓存
遇到loss不收敛
发现在epoch=1时就出现了loss在附近徘徊不收敛的问题
- 优化器用Adam
- 学习率不合适,可以从大往小调(可以采用lr /= 5 去迭代)