狗类别实现过程
一. 将数据集按标签分类,将标签转换为数字表示,并制作数据集
二. 搭建网络框架,inception,或者ResNet
三. 选择优化函数,训练模型
数据集制作
首先分析数据集,题中已经很明确告诉有120 种狗,那么就有120种标签值,分别为0-119。
给了一个训练集,和一个csv文件,图像在训练集中,图像的标签在csv文件里,那么如何将图片和它的标签去捆绑,因为训练图像的流程就是拿图像训练得出预测值,和它标签进行损失计算。
首先观察csv文件:
训练集:
csv文件中给出了每张图片的标签值,那么要做的就是按照csv文件里的标签值,将图片复制到另一个文件夹中,进行分类,属于同一品中的狗放到同一文件夹中,然后用内置API ImageFolder
去自动设置标签。具体原理如下
左面是已经将训练集分类好的新训练集,将同一种类的图片归置到一起,那么ImageFolder会将每个文件夹中的照片赋值为相同的标签值,从0开始,例如第一个文件夹kuvasz 里的照片的标签全部赋值为0,下一个文件夹kerry_blue_terrier 标签赋值为1 ,它这种标签是自动生成的,不需要人为的加
那么要做的第一步,读取csv文件,创建一个字典,保证每张图片能够准确划分到属于它的类(文件夹)中,因为训练集中的图片要按照字典查找它的标签才能复制到它对应的文件夹中。
import pandas as pd
def read_train_csv():
df=pd.read_csv("/kaggle/input/dog-breed-identification/labels.csv")
id=df.iloc[0:,0].values #读取每张图片的id,也就是训练集图片名称,从第一行开始到最后一行,第一列,想要从pandas数据转化为列表,必须后缀.values
breed=df.iloc[0:,1].values #读取第二行标签值
return dict(zip(id,breed)) #将图片id和其对应的标签值捆绑,这里并不是图片,而是图片名称,图片要通过这个字典查询它应该放哪个文件夹中
定义复制函数
import shutil
import os
def copy_dir(data_dir,target_dir): #源地址和目标地址
os.makedirs(target_dir,exist_ok=True) #如果目标地址不存在,先创建
shutil.copy(data_dir,target_dir) #复制
定义分类函数,将训练集分类,将图片按照上面的字典进行分类
#这里源文件地址是 /kaggle/input/dog-breed-identification/train
#目的文件地址是output中 :/kaggle/working/train
def train_to_data(data_dir,target_dir): #训练集的地址,目标地址
dict=read_train_csv() #用上面的读取csv文件函数返回训练集的字典
for data in os.listdir(data_dir): #将放训练集图片的文件夹迭代一张一张拿出来读
label=dict.get(data.split('.')[0]) #因为我们用os读取出来是照片名称,格式为000bec180eb18c7604dcecc8fe0dba07.jpg,我们要前半部分作为value,去字典中查找key,那么查出来该img的label
fname=os.path.join(data_dir,data) #获取该图片的地址
copy_dir(fname,os.path.join(target_dir,label)) #将图片复制到对应的文件夹中,文件名称就是label
train_to_data("/kaggle/input/dog-breed-identification/train","/kaggle/working/train") #进行数据分类
#转移测试集,因为我们最后要预测测试集,所以也要把测试集封装成数据集,才能放到神经网络里面训练,否则光秃秃的测试集没法放进网络框架,和上面的一样
#但是将测试集都放到unknow文件中,这样ImageFolder会将其直接封装为标签值为0的数据集,因为我们要预测它标签值,所以这里赋值为多少都无所谓
for test_data in os.listdir("/kaggle/input/dog-breed-identification/test/"):
copy_dir(os.path.join("/kaggle/input/dog-breed-identification/test",test_data),"/kaggle/working/test/unknow")
训练集图片分类完成后,那么就开始制作训练数据集了
#首先进行图像处理,数据增强,所有图像识别必须要做的,因为该数据集每张的图片大小不同,所以要统一图片像素大小
import torchvision
train_transform=torchvision.transforms.Compose([torchvision.transforms.RandomResizedCrop(224,scale=(0.08,1.0),ratio=(3.0/4.0,4.0/3.0)), #设置裁剪出来的像素大小为224
torchvision.transforms.RandomHorizontalFlip(), #随机进行水平翻转
torchvision.transforms.ColorJitter(
brightness=0.4,
contrast=0.4,
saturation=0.4
), #调图像的色彩度
torchvision.transforms.ToTensor(), #必须有,将图像变为tensor向量,否则没法放进网络中运行,将维度[224,224,3] 变为 [3,224,224]
torchvision.transforms.Normalize([0.485,0.456,0.406], [0.229,0.224,0.225]) #将图像数据标准化,减小计算量
])
#对测试集进行图像处理 ,只需要改变尺寸,标准化数据就够了,因为只需预测
test_transform=torchvision.transforms.Compose([ torchvision.transforms.Resize(256),
torchvision.transforms.CenterCrop(224),
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])])
#制作数据集,ImageFolder的原理上面已经说过,但是切记ImageFolder的文件地址必须是你分类数据集的上一级!,例如分好类后在文件夹/train/下
#那么ImageFolder的文件地址就只能到train/为止,因为ImageFolder是按照文件夹的名称赋值标签值的,它要看文件夹的名称,不能直接定位到具体某一类的文件夹上
from torch.utils.data import DataLoader
train_mid_data=torchvision.datasets.ImageFolder('/kaggle/working/train/',transform=train_transform) #ImageFolder可以自动将对应文件夹中的图片进行标签赋值 但是切记,文件地址必须为上一级,否则会出错!
train_data=DataLoader(train_mid_data,batch_size=32,drop_last=True,shuffle=True) #数据集制作,32个为一组,打乱数据集顺序,不能让网络挨次训练某一类型数据
#制作测试集数据,方便后面直接扔到网络模型中预测
test_mid_data=torchvision.datasets.ImageFolder('/kaggle/working/test/',transform=test_transform)
test_data=DataLoader(test_mid_data,batch_size=32)
以上就是完整的数据集制作流程,具体的图像数据集制作可以看下面这篇文章
PyTorch数据集制作
网络模型搭建
这里做了三种网络的对比,第一种是 ResNet残差网络,第二种是LeNet网络
,第三种GoolgeNet网络,对比了性能,第三种最好
第三种使用的是inception模块 ,inception具体的原理是用不同的卷积维度,对同一张图片进行卷积,然后将不同卷积后的结果结合起来形成完整的一个结果。
完整的一个GoogleNet模型如下图:
实现很简单:
from torch import nn
from torch.nn import functional as F
class inception(nn.Module):
def __init__(self,inchannels,c1,c2,c3,c4):
super().__init__()
self.p1_1=nn.Conv2d(inchannels,c1,kernel_size=1)
self.p2_1=nn.Conv2d(inchannels,c2[0],kernel_size=1)
self.p2_2=nn.Conv2d(c2[0],c2[1],kernel_size=3,padding=1)
self.p3_1=nn.Conv2d(inchannels,c3[0],kernel_size=1)
self.p3_2=nn.Conv2d(c3[0],c3[1],kernel_size=5,padding=2)
self.p4_1=nn.MaxPool2d(kernel_size=3,padding=1,stride=1)
self.p4_2=nn.Conv2d(inchannels,c4,kernel_size=1)
def forward(self,x):
x1=F.relu(self.p1_1(x))
x2=F.relu(self.p2_2(F.relu(self.p2_1(x))))
x3=F.relu(self.p3_2(F.relu(self.p3_1(x))))
x4=F.relu(self.p4_2(F.relu(self.p4_1(x))))
return torch.cat((x1,x2,x3,x4),dim=1)
b1=nn.Sequential(nn.Conv2d(in_channels=3,out_channels=64,kernel_size=7,stride=2,padding=3),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3,stride=2,padding=1))
b2=nn.Sequential(nn.Conv2d(64,64,kernel_size=1),nn.ReLU(),
nn.Conv2d(64,192,kernel_size=3,padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3,stride=2,padding=1))
b3=nn.Sequential(inception(192,64,(96,128),(16,32),32),
inception(256,128,(128,192),(32,96),64),
nn.MaxPool2d(kernel_size=3,stride=2,padding=1)
)
b4=nn.Sequential(inception(480,192,(96,208),(16,48),64),
inception(512,160,(112,224),(24,64),64),
inception(512,128,(128,256),(24,64),64),
inception(512,112,(144,288),(32,64),64),
inception(528,256,(160,320),(32,128),128),
nn.MaxPool2d(kernel_size=3,stride=2,padding=1))
b5=nn.Sequential(inception(832,256,(160,320),(32,128),128),
inception(832,384,(192,384),(48,128),128),
nn.AdaptiveAvgPool2d((1,1)),
nn.Flatten())
#得到神经网络模型,上面模型是可以直接复制用的
def get_net():
net=nn.Sequential(b1,b2,b3,b4,b5,nn.Linear(1024,120))
return net
下面是一个简单模型框架也可以选择:
from torch import nn
from torch.nn import functional as F
class net(nn.Module):
def __init__(self):
super().__init__()
#224 224 3
self.conv1=nn.Conv2d(in_channels=3,out_channels=64,kernel_size=3,padding=1)
self.bn1=nn.BatchNorm2d(64)
self.pool1=nn.MaxPool2d(kernel_size=2,stride=2)
#112 112 64
self.conv2=nn.Conv2d(in_channels=64,out_channels=128,kernel_size=3,padding=1)
self.bn2=nn.BatchNorm2d(128)
self.pool2=nn.MaxPool2d(kernel_size=2,stride=2)
#56 56 128
self.conv3=nn.Conv2d(in_channels=128,out_channels=256,kernel_size=3,padding=1)
self.bn3=nn.BatchNorm2d(256)
self.pool3=nn.MaxPool2d(kernel_size=2,stride=2)
#28 28 256
self.conv4=nn.Conv2d(in_channels=256,out_channels=512,kernel_size=3,padding=1)
self.bn4=nn.BatchNorm2d(512)
self.pool4=nn.MaxPool2d(kernel_size=2,stride=2)
#14 14 512
self.conv5=nn.Conv2d(in_channels=512,out_channels=512,kernel_size=3,padding=1)
self.bn5=nn.BatchNorm2d(512)
self.pool5=nn.MaxPool2d(kernel_size=2,stride=2)
#7 7 512
self.Linear1=nn.Linear(in_features=7*7*512,out_features=240)
self.Linear2=nn.Linear(in_features=240,out_features=120)
def forward(self,x):
x=F.relu(self.pool1(self.bn1(self.conv1(x))))
x=F.relu(self.pool2(self.bn2(self.conv2(x))))
x=F.relu(self.pool3(self.bn3(self.conv3(x))))
x=F.relu(self.pool4(self.bn4(self.conv4(x))))
x=F.relu(self.pool5(self.bn5(self.conv5(x))))
x=x.flatten(start_dim=1)
x=F.relu(self.Linear1(x))
x=self.Linear2(x)
return x
def get_net():
net=net()
return net
做训练函数
import matplotlib.pyplot as plt
# 将程序放到GPU上跑,否则默认cpu会卡死
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
net=get_net().to(device) #将模型放到GPU上,必须把数据也放到相同GPU上才能运行
def train(net,train_data,epoch_num,lr,wd):
#这里只是用了一个简单的Adam梯度下降算法,这篇文章只是一个基础构建,可以在此基础上改进
optimer=torch.optim.Adamax(net.parameters(),
lr=lr,
weight_decay=wd)
num_batches=len(train_data) #用来后面的损失函数计算,得到单个图片的损失值
loss_function=nn.CrossEntropyLoss() #损失函数
loss_value=[] #记录损失值,用来画图,将该列表直接放到plt中就能画
for epoch in range(epoch_num): #进行大循环
epoch_loss=0 #每一次大循环都要重置损失值
for img,label in train_data:
img=img.to(device) #将数据都放在GPU上
label=label.to(device)
optimer.zero_grad() #一系列训练模板
y=net(img)
loss=loss_function(y,label)
epoch_loss+=loss.item()
loss.backward()
optimer.step()
loss_value.append(epoch_loss/(num_batches*32))
#画损失函数的图
plt.plot(np.squeeze(loss_value))
plt.ylabel('cost')
plt.xlabel('iterations (per tens)')
plt.show()
train(net,train_data,50,6e-5,5e-4) #开始训练,后面俩参数都可以自己调
其实训练的结果不是太好,损失值还是太大,可以通过调学习率去调节,这里只是用的一个最基本的参数值
进行预测测试集
前面已经将测试集数据已经做好了,直接放模型里预测,去观察题中给的需要提交的csv文件格式,可以知道需要提交的并不是你要预测这张图片属于哪种狗子,而是这张图片属于每一类狗子的概率,这里就必须要用softmax函数了,具体原理可以上网差,他会将所有情况的概率总和变为1,然后每一类的概率就是看其在这1中占比多少。
preds=[]
for i,_ in test_data:
out_put=torch.nn.functional.softmax(net(i.to(device)),dim=1) #将预测值进行softmax处理
preds.extend(out_put.cpu().detach().numpy()) #将得到的预测值变为numpy数组,因为只有numpy才能存到pandas文件中
ids=sorted(os.listdir("/kaggle/working/test/unknow")) #将测试集的顺序按id排序,因为提交的格式id就是按大小排序的
with open("/kaggle/working/submission.csv",'w') as f: #制作csv文件
f.write('id,'+','.join(train_mid_data.classes)+'\n') #先将标题标签做好
for i,output in zip(ids,preds):
f.write(i.split('.')[0]+','+','.join([str(num) for num in output])+'\n') #将每一个对应的id和预测的结果加入到文件中,其实这里逗号,csv文件都会识别,将其作为分割符号,所以在csv文件中也不会看见逗号