大二寒假刚自学完李沐老师的《动手学深度学习》,简单的做个应用,水平有限。
一.数据获取
网络爬虫获得数据集
使用github上开源项目 使用 爬虫工具image downloader
使用前 根据requirements.txt配置环境
相关python环境 anaconda
#激活环境
activate 你的环境名字
#进入Image-Downloader根目录
cd Image-Downloader-master
#安装环境
pip3 install -r requirements.txt
运行image_downloader_gui.py文件后就可以弹出以下界面
得到的图片
数据预处理
保留有效数据
去除损坏图片(testdetect.py)
格式归一化(根目录下reformat_images.py)
尺寸归一化
命名归一化
opencv人脸检测,Dlib关键点检测
cascade为人脸检测器,predictor关键点检测器,传统算法,效果一般般,,只是用来练手,在训练模型的时候还需要检查一遍数据,可以使用卷积+nin提取出特征
通过关键点检测提取出图片中的人脸特征,输入网络
使用torchvision包读取数据,数据增强,不过数据增强只使用随机缩放裁剪,随机翻转,有兴趣还可以,提高曝光度,改变色温,数据增强可以提高模型的泛化能力(比如模拟不同自然光照射下的的场景)。
## 优化目标使用交叉熵,优化方法使用带动量项的SGD,学习率迭代策略为step,每隔100个epoch,变为原来的0.1倍
image_size = 64 ##图像统一缩放大小
crop_size = 48 ##图像裁剪大小,即训练输入大小
nclass = 4 ##分类类别数
model = simpleconv3(nclass) ##创建模型
data_dir = './data' ##数据目录
## 模型缓存接口
if not os.path.exists('models'):
os.mkdir('models')
## 检查GPU是否可用,如果是使用GPU,否使用CPU
use_gpu = torch.cuda.is_available()
if use_gpu:
model = model.cuda()
print(model)
## 创建数据预处理函数,训练预处理包括随机裁剪缩放、随机翻转、归一化,验证预处理包括中心裁剪,归一化
data_transforms = {
'train': transforms.Compose([
transforms.RandomResizedCrop(48),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5])
]),
'val': transforms.Compose([
transforms.Resize(image_size),
transforms.CenterCrop(crop_size),
transforms.ToTensor(),
transforms.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5])
]),
}
## 使用torchvision的dataset ImageFolder接口读取数据
image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),
data_transforms[x]) for x in ['train', 'val']}
## 创建数据指针,设置batch大小,shuffle,多进程数量
dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x],
batch_size=64,
shuffle=True,
num_workers=4) for x in ['train', 'val']}
## 获得数据集大小
dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']}
## 优化目标使用交叉熵,优化方法使用带动量项的SGD,学习率迭代策略为step,每隔100个epoch,变为原来的0.1倍
criterion = nn.CrossEntropyLoss()
optimizer_ft = optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
step_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=100, gamma=0.1)##每隔step_size步,学习率变为gamma倍
模型搭建与训练
可以多尝试几个模型,自定义模型或者vgg,resnet,nin,Googlenet等多个经典模型比较并尝试解释
自定义模型
class simpleconv3(nn.Module):
## 初始化函数
def __init__(self,nclass):
super(simpleconv3,self).__init__()
self.conv1 = nn.Conv2d(3, 12, 3, 2) #输入图片大小为3*48*48,输出特征图大小为12*23*23,卷积核大小为3*3,步长为2
self.bn1 = nn.BatchNorm2d(12)
self.conv2 = nn.Conv2d(12, 24, 3, 2) #输入图片大小为12*23*23,输出特征图大小为24*11*11,卷积核大小为3*3,步长为2
self.bn2 = nn.BatchNorm2d(24)
self.conv3 = nn.Conv2d(24, 48, 3, 2) #输入图片大小为24*11*11,输出特征图大小为48*5*5,卷积核大小为3*3,步长为2
self.bn3 = nn.BatchNorm2d(48)
self.fc1 = nn.Linear(48 * 5 * 5 , 1200) #输入向量长为48*5*5=1200,输出向量长为1200
self.fc2 = nn.Linear(1200 , 128) #输入向量长为1200,输出向量长为128
self.fc3 = nn.Linear(128 , nclass) #输入向量长为128,输出向量长为nclass,等于类别数
## 前向函数
def forward(self, x):
## relu函数,不需要进行实例化,直接进行调用
## conv,fc层需要调用nn.Module进行实例化
x = F.relu(self.bn1(self.conv1(x)))
x = F.relu(self.bn2(self.conv2(x)))
x = F.relu(self.bn3(self.conv3(x)))
x = x.view(-1 , 48 * 5 * 5)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
resnet
class Reshape(nn.Module):
def __init__(self, *args):
super(Reshape, self).__init__()
class GlobalAvgPool2d(nn.Module):
# 全局平均池化层可通过将池化窗口形状设置成输入的高和宽实现
def __init__(self):
super(GlobalAvgPool2d, self).__init__()
def forward(self, x):
return F.avg_pool2d(x, kernel_size=x.size()[2:])
# 残差神经网络
class Residual(nn.Module):
def __init__(self, in_channels, out_channels, use_1x1conv=False, stride=1):
super(Residual, self).__init__()
self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1, stride=stride)
self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1)
if use_1x1conv:
self.conv3 = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride)
else:
self.conv3 = None
self.bn1 = nn.BatchNorm2d(out_channels)
self.bn2 = nn.BatchNorm2d(out_channels)
def forward(self, X):
Y = F.relu(self.bn1(self.conv1(X)))
Y = self.bn2(self.conv2(Y))
if self.conv3:
X = self.conv3(X)
return F.relu(Y + X)
def resnet_block(in_channels, out_channels, num_residuals, first_block=False):
if first_block:
assert in_channels == out_channels # 第一个模块的通道数同输入通道数一致
blk = []
for i in range(num_residuals):
if i == 0 and not first_block:
blk.append(Residual(in_channels, out_channels, use_1x1conv=True, stride=2))
else:
blk.append(Residual(out_channels, out_channels))
return nn.Sequential(*blk)
resnet = nn.Sequential(
nn.Conv2d(1, 64, kernel_size=7 , stride=2, padding=3),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
resnet.add_module("resnet_block1", resnet_block(64, 64, 2, first_block=True))
resnet.add_module("resnet_block2", resnet_block(64, 128, 2))
resnet.add_module("resnet_block3", resnet_block(128, 256, 2))
resnet.add_module("resnet_block4", resnet_block(256, 512, 2))
resnet.add_module("global_avg_pool", GlobalAvgPool2d()) # GlobalAvgPool2d的输出: (Batch, 512, 1, 1)
resnet.add_module("fc", nn.Sequential(Reshape(), nn.Linear(512, 7)))
我一直觉得resnet很好用,他可以显著提高模型精度
李沐老师讲的有一句很好 resnet可以表达为f(x)=x+g(x)
其中x是上一次的输出,g(x)是接下来要对上一次输出x的处理的过程,会使模型越来越逼近期望结果y
训练函数
from __future__ import print_function, division
import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim import lr_scheduler
import torchvision
from torchvision import datasets, models, transforms
import os
from net import simpleconv3
## 使用tensorboardX进行可视化
from tensorboardX import SummaryWriter
writer = SummaryWriter('logs') ## 创建一个SummaryWriter的示例,默认目录名字为runs
## 训练主函数
def train_model(model, criterion, optimizer, scheduler, num_epochs=25):
for epoch in range(num_epochs):
print('Epoch {}/{}'.format(epoch, num_epochs - 1))
for phase in ['train', 'val']:
if phase == 'train':
scheduler.step()
model.train(True) ## 设置为训练模式
else:
model.train(False) ## 设置为验证模式
running_loss = 0.0 ##损失变量
running_accs = 0.0 ##精度变量
number_batch = 0 ##
## 从dataloaders中获得数据
for data in dataloaders[phase]:
inputs, labels = data
if use_gpu:
inputs = inputs.cuda()
labels = labels.cuda()
optimizer.zero_grad() ##清空梯度
outputs = model(inputs) ##前向运行
_, preds = torch.max(outputs.data, 1) ##使用max()函数对输出值进行操作,得到预测值索引
loss = criterion(outputs, labels) ##计算损失
if phase == 'train':
loss.backward() ##误差反向传播
optimizer.step() ##参数更新
running_loss += loss.data.item()
running_accs += torch.sum(preds == labels).item()
number_batch += 1
## 得到每一个epoch的平均损失与精度
epoch_loss = running_loss / number_batch
epoch_acc = running_accs / dataset_sizes[phase]
## 收集精度和损失用于可视化
if phase == 'train':
writer.add_scalar('data/trainloss', epoch_loss, epoch)
writer.add_scalar('data/trainacc', epoch_acc, epoch)
else:
writer.add_scalar('data/valloss', epoch_loss, epoch)
writer.add_scalar('data/valacc', epoch_acc, epoch)
print('{} Loss: {:.4f} Acc: {:.4f}'.format(
phase, epoch_loss, epoch_acc))
writer.close()
return model