ResNet
一、数据集准备
该数据集包含了train、test、val三部分,每一部分都包含了猫狗照片,train包含了20000张,猫狗各占一半;val包含了2000张照片,猫狗各占一半;首先将猫狗照片分为两个文件夹,方便为其数据加标签。
import os
import shutil
import numpy as np
import pandas as pd
path_img=r'./train'
myfolder= os.listdir(path_img)
lenl=len(myfolder)
print(lenl)
cat_direc=r'./train/cat'
dog_direc=r'./train/dog'
for folder in myfolder:
if 'cat' in folder:
path=path_img+'/'+folder
newpath=cat_direc+'/'+folder
if not os.path.exists(cat_direc):
os.mkdir(cat_direc)
os.rename(path,newpath)
print('%s 存入成功'%(folder))
if 'dog' in folder:
path2=path_img+'/'+folder
newpath2=dog_direc+'/'+folder
if not os.path.exists(dog_direc):
os.mkdir(dog_direc)
os.rename(path2,newpath2)
print('%s 存入成功'%(folder))
二、数据集处理
1、使用datasets.ImageFolder函数对每一个文件夹进行打标签,结果如下。
['cat', 'dog']
{'cat': 0, 'dog': 1}
2、读取数据,分别读取训练集和验证集,并对数据进行裁剪、归一化等处理,以下是实现代码以
及json文件的内容。
(1)transforms.Compose 是对数据的增强方式的组合,ResNet默认的输入图片尺寸为224*224;
并将图片转化为Tensor,归一化。 为了方便对训练集和数据集采用不同的增强方法,定义了一个
data_transforms字典,key分别是‘train’和‘validation’,对应的value分别是它们的增强方法。
(2)datasets.ImageFolder传入(1)中的增强方法,通过给定路径进行读取数据,并且会根据不
同文件夹进行贴标签。
(3)torch.utils.data.DataLoader把dataset读取到的照片,按照一定顺序排列,设置batch_size与
num_workers
data_transforms = {
'train': transforms.Compose([
transforms.Resize([224, 224]),
transforms.CenterCrop(224),
transforms.ToTensor(), # range [0, 255] -> [0.0,1.0]
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
]),
'validation':transforms.Compose([
transforms.Resize([224,224]),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
}
data_dir = 'data'
train_dataset = datasets.ImageFolder(root=os.path.join(data_dir, "train"),
transform=data_transforms["train"])
validate_dataset = datasets.ImageFolder(root=os.path.join(data_dir, "val"),
transform=data_transforms["validation"])
# imgs,labels=train_dataset[0]
# print(imgs,labels)
classeslist=train_dataset.class_to_idx
cla_dict = dict((val, key) for key, val in classeslist.items())
#json.dumps() 是把python对象转换成json对象的一个过程,生成的是字符串。
json_str = json.dumps(cla_dict, indent=4)
with open('class_indices.json', 'w') as json_file:
json_file.write(json_str)
batch_size = 128
nw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, 8])
train_loader=torch.utils.data.DataLoader(train_dataset,
batch_size=batch_size,
shuffle=True,
num_workers=nw)
print(train_loader)
validate_loader=torch.utils.data.DataLoader(validate_dataset,
batch_size=batch_size,
shuffle=True,
num_workers=nw)
三、加载模型
1、载入ResNet模型,并且根据数据集进行修改,数据集只有两类,cat和dog,所以它
最后的FC层应该输出两类的结果概率,并决定图片的类型,我们需要修改ResNet的FC
层。
net=resnet34()
in_channel = net.fc.in_features
net.fc = nn.Linear(in_channel, 2)
2、设置损失函数以及优化器。
(1)损失函数采用的是交叉熵损失
(2)Adam优化器,两个参数分别是要优化的参数,以及学习率
loss_function = nn.CrossEntropyLoss()
params = [p for p in net.parameters() if p.requires_grad]
optimizer = optim.Adam(params, lr=0.0001)
四、训练模型
1、设置迭代次数以及权重文件保存路径。
epochs = 10
best_acc = 0.0
save_path = './resNet34.pth'
2、要主要训练与测试时net.train()与net.eval的区别,区别在于是否启用BN与Dropout。
3、tqdm的使用:
desc:进度条的描述信息,也称进度条的前缀
total:要监视的进度的总数
ascii:进度条显示的方式,bool ,为True时使用编码更新的方式展示
ncols(int):整个输出信息的宽度
nrows(int):进度条的高速
position(int):设置打印进度条的位置,可以设置多个bar
colour(str):进度条的颜色
for epoch in range(epochs):
net.train()
running_loss = 0.0
#进度条
train_bar = tqdm(train_loader, file=sys.stdout)
for step, data in enumerate(train_bar):
images, labels = data
optimizer.zero_grad()
logits = net(images.to(device))
loss = loss_function(logits, labels.to(device))
loss.backward()
optimizer.step()
# print statistics
running_loss += loss.item()
train_bar.desc = "train epoch[{}/{}] loss:{:.3f}".format(epoch + 1,
epochs,
loss)
4、结果:验证集的准确率约为90%
五、测试模型
1、首先对数据进行同样的处理
data_transform = transforms.Compose(
[transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
2、载入训练好的权重
model = resnet34(num_classes=2).to(device)
model.load_state_dict(torch.load(weights_path, map_location=device))
3、图片预测
img_path = './data/test/1.jpg'
plt.imshow(img)
model.eval()
with torch.no_grad():
# predict class
output = torch.squeeze(model(img.to(device))).cpu()
predict = torch.softmax(output, dim=0)
predict_cla = torch.argmax(predict).numpy()
for i in range(len(predict)):
print("class: {:10} prob: {:.3}".format(class_indict[str(i)],
predict[i].numpy()))
plt.show()
六、问题总结
1、Residual learning 的基本原理?
以下是残差模块的一个基本网络结构。
our formulation always learns residual functions; our identity shortcuts are never closed, and all information is always passed
through, with additional residual functions to be learned.
2、Batch Normailization 的原理,思考 BN、LN、IN 的主要区别。
(1)BN的原理
首先是计算数据B的均值→然后计算数据B的方差→对数据B进行标准化处理→进行平移和缩放处理,引入γ和β两个参数,通过这两
个参数,让我们的网络可以学习恢复到原始网络的特征分布。
(2)区别
a) BN:是对不同样本的同一特征通道进行归一化。
b) LN: 是对同一样本的所有特征通道进行归一化。
c) IN:是对每一个样本的每一个通道进行归一化。
3、为什么分组卷积可以提升准确率?即然分组卷积可以提升准确率,同时还能降低计算量,分数数量尽量多不行吗?
分组卷积将输入特征图按C分为g组,每一组进行常规卷积。分组卷积也叫过滤器组,由于过滤器关系是稀疏的,根据稀疏关系,有
些过滤器之间的关系是不需要学习的,这样就可以减少参数量,也可以更准确、更有效的训练网络。
分组数量尽量多的话,可能会使得有些关系学习不到。