目录
-
实验目的
Omniglot数据集介绍
简介:Omniglot 数据集包含来⾃50 个不同字⺟的 1623 个不同⼿写字符,如下图所示。
数据规模:共1623个类别,每个类别有20个样本,每个样本⼤⼩为 28*28。
数据下载:Data_200.mat⽂件,包含200个类、每类前15个样本作为训 练,后5个样本作为测试(已划分好)。
使用CNN和SVM进行图片识别,并进行区分不同的数字
-
实验原理
CNN:
- 数据处理
主要过程包括:读入数据,划分数据集(本案例中已划分好),生成批次数据,训练样本集打乱,校验数据有效性
- 模型设计
为了建立输入数字和输出数字标签之间的关系,我们需要通过建立卷积神经网络和多层全连接神经网络来建立之间的关系,全连接神经网络包括三种层:输入层,隐含层,输出层。将28*28图片像素作为输入,最终的标签预测作为输出,层之间采用relu激活函数进行激活。一个经典的全连接神经网络模型如下图所示:
使用经典的全连接神经网络可以提升一定的准确率,但其输入数据的形式导致丢失了图像像素间的空间信息,这影响了网络对图像内容的理解。对于计算机视觉问题,效果最好的模型仍然是卷积神经网络。卷积神经网络针对视觉问题的特点进行了网络结构优化,可以直接处理原始形式的图像数据,保留像素间的空间信息,因此更适合处理视觉问题。
卷积神经网络由多个卷积层和池化层组成,卷积层负责对输入进行扫描以生成更抽象的特征表示,池化层对这些特征表示进行过滤,保留最关键的特征信息。
卷积神经网络结构如下图:
下图为5*5的输入图像,padding=1,卷积核大小为3*3,stride=1,对输入图像进行卷积,结果如右图
本次实验中的model中卷积神经网络模型及全连接神经网络模型如下:
卷积神经网络是一个包含7层的卷积层的网络
这是一个三层的全连接层,中间增加了两个dropout来减少过拟合的情况,激活函数都使用relu激活函数
- 训练配置
损失函数:损失函数是模型优化的目标,通过loss来判断模型的准确性,模型的训练过程相同分为如下三步:
- 先根据输入数据正向计算预测输出。
- 再根据预测值和真实值计算损失。
- 最后根据损失反向传播梯度并更新参数。
在本例中,使用交叉熵验证:
交叉熵损失函数的设计是基于最大似然思想,交叉熵的公式如下:
交叉熵只计算有正确解的输出的对数,如果是错误的解,则不进行计算
优化算法:在深度学习神经网络模型中,通常使用标准的随机梯度下降算法更新参数,学习率代表参数更新幅度的大小,即步长。当学习率最优时,模型的有效容量最大,最终能达到的效果最好。学习率和深度学习任务类型有关,合适的学习率往往需要大量的实验和调参经验。
学习率的常用算法有:SGD,Momentum,Adam等,本实验中使用了Adam优化器,优化效果最好
- 训练过程
使用交叉熵损失函数进行loss计算,输入图片和对应label,进行训练
这三个函数分别是:梯度归零、反向传播、参数更新,训练之中上一个batch产生的梯度先清零,然后进行反向传播,梯度就会运算并累加到.grad属性里,最后执行一次优化步骤,通过梯度下降更新参数的值
- 模型保存
保存最后一次epoch的loss和weight
SVM:
支持向量机(support vector machines, SVM)是一种二分类模型,它的基本模型是定义在特征空间上的间隔最大的线性分类器,间隔最大使它有别于感知机;SVM还包括核技巧,这使它成为实质上的非线性分类器。SVM的的学习策略就是间隔最大化,可形式化为一个求解凸二次规划的问题,也等价于正则化的合页损失函数的最小化问题。SVM的的学习算法就是求解凸二次规划的最优化算法。
Wx+b=0即为分离超平面,将两种点分类,这是线性分类器,如果是更加复杂的情况,比如本实验中利用SVM算法对数据集中的数字进行分类,那么就是非线性分类的情况:
常用的核函数如下:
使用的SVC分类器的参数如下:
- C (default=1.0):惩罚因子,即是SVM优化目标( 1 2 ∣ ∣ w ∣ ∣ 2 + C ∑ ξ i ) (\frac{1}{2}||w||^2+C\sum \xi_i)( 21∣∣w∣∣ 2+C∑ξ i )中的松弛变量的系数,C值越大,对误分类的容错变小,过大的C值容易过拟合,C值越小,对误分类的容错变大,过小的C值容易欠拟合;
- kernel (default=‘rbf’):核函数选择,可选值:'linear’线性核函数, 'poly’多项式核函数, 'sigmoid’双曲正切核函数,‘rbf’高斯核函数, ‘precomputed’;
- degree(default=3):多项式核函数阶数,只对‘poly’有效;
- gamma(default=‘auto’):‘rbf’、‘poly’、'sigmoid’核函数的参数γ \gammaγ;
- coef0(default=0.0):核函数中的常量r rr,仅对‘poly’和‘sigmoid’有用;
- shrinking(default=True):是否进行启发式;
- probability(default=False):概率估计;
- tol(default=1e-3):迭代终止精度;
- cache_size:核函数的缓存空间;
- class_weight:类别权重,主要针对不同类别中样本数不平衡的情况;
- verbose(default=False):是否将训练的过程详细输出;
- max_iter(default=-1):最大迭代次数,默认-1是无限制的意思;
- decision_function_shape(default=‘ovr’):SVM是二分类的方法,扩展到多分类情况下需要一定的策略,包含‘ovo’、‘ovr’,默认为’ovr’;
- random_state(default=None):将样本顺序随机化,更有利于训练模型。
-
实验步骤和程序流程
CNN:
根据助教提供的demo跑通baseline,初始的baseline的acc为0.705
后续根据baseline进行改进网络模型提升准确率,初始的baseline的卷积神经网络只有两层卷积层,全连接层也是只有两层,所以初步的想法是网络模型过于简单,需要增加网络层数。
增加了网络层数之后,acc获得了很大的提升,在60epoch下提升到了0.751
运行结果如下:
分析了一下为什么acc会这么低?首先,epoch轮数较少,acc还未稳定,所以导致本次acc较低,前面最高已经可以达到0.84;其次,查看训练集上和测试集上的acc比较可以发现:训练集上的acc甚至可以高达0.992!但是测试集上却出现了0.751的准确率,说明有比较明显的过拟合现象,可以通过在全连接层之间添加dropout来减少过拟合
添加dropout之后的全连接层如下:
再次运行60epoch,查看结果:
结果相当的好,仅仅60epoch,就达到了0.862的准确率,并且训练集上的准确率也仅有0.889,loss=0.326,说明几乎没有出现过拟合现象。这时候加大epoch轮次,在进行训练:
acc达到0.883,loss也仅有0.825,增加epoch轮数是可以提高准确率的,但是提高的并不是很多。
所以还是要从其他方面进一步提高准确率,由于该实验是一个典型的小样本学习,所以可以使用元学习、数据增强等思路进一步提高准确率。
(我在进行数据增强的时候,希望将图片随机旋转10-15度,从而提高数据量,但是在进行处理的时候,出现了无法解决的问题,所以数据增强这个办法就搁置了)
SVM:
同样的,先根据助教给的baseline进行运行,运行的初始识别准确率为0.393,使用的kernel为线性,C值为1,kernel参数为默认
注意:一开始运行的时候会出现报错,原因是计算F1的时候,分母不能为0,因此只需要在report中设置参数zero-division=1即可解决
因此尝试进行改变kernel等参数:
将kernel改为高斯分布函数rbf(可能会更适合图片分类这种任务),并增大惩罚参数,重新进行训练:
发现准确率提高到了0.434
(在一开始的时候,没有搞清楚svm的原理,以为只需要增加训练批次就可以提高acc,所以在下面这里进行了修改:iteration>=100
但是发现结果并没有改变,所以感到十分疑惑,后来发现
其实每次训练模型,都是fit收敛的,所以增加轮次是没有用处的)
-
实验结果
CNN:最终准确率达到88.3%,loss为0.0825
SVM:
-
评价分析
综合比较cnn和svm,发现还是cnn的识别准确率更高,不管是网络的复杂度训练的时间等等,cnn都完胜svm,这可能就是cnn在图像识别分类领域如此热火的原因吧。
我在进行这两个模型的探索的时候,都有过失败的经历,这些idea虽然没有成功,但是我认为这对于我将来的学习非常有意义。
CNN:我在进行数据增强的时候,希望将图片随机旋转10-15度,从而提高数据量,进而提高准确率,但是在对图片进行处理的时候,出现了无法解决的问题,有关图片格式的问题,在上网搜索并尝试解决之后失败了,问题没法解决,所以数据增强这个办法就搁置了
附1:参考文献
- 飞桨PaddlePaddle-源于产业实践的开源深度学习平台
- (4条消息) CNN笔记:通俗理解卷积神经网络_cnn卷积神经网络_v_JULY_v的博客-CSDN博客
- 支持向量机(SVM)——原理篇 - 知乎 (zhihu.com)
最后附上源代码:
train.py:
import os.path
import torch
import torch.backends.cudnn as cudnn
from torch.utils.data import DataLoader
from utils.dataloader_cl import Dataset, dataset_collate
from utils.trainer import fit_one_epoch
from nets.model import Baseline
if __name__ == "__main__":
Cuda = False #使用CPU为False,GPU为True
# ------------------------------------------------------#
# pretrained_model_path 网络预训练权重文件路径
# ------------------------------------------------------#
pretrained_model_path = ''
# ------------------------------------------------------#
# input_shape 输入的shape大小
# ------------------------------------------------------#
input_shape = [28, 28]
batch_size = 32
Init_Epoch = 0
Epoch = 200
# ------------------------------------------------------#
# Init_lr 初始学习率
# ------------------------------------------------------#
Init_lr = 0.001
# ------------------------------------------------------------------#
# save_period 多少个epoch保存一次权值
# ------------------------------------------------------------------#
save_period = 5
# ------------------------------------------------------------------#
# save_dir 权值与日志文件保存的文件夹
# ------------------------------------------------------------------#
save_dir = 'logs/'
if not os.path.exists(save_dir):
os.makedirs(save_dir)
num_workers = 0
# ------------------------------------------------------#
# train_val_dataset_path 训练和测试文件路径
# ------------------------------------------------------#
train_val_dataset_path = 'dataset/NewDataset.mat'
# ------------------------------------------------------#
# 设置用到的显卡
# ------------------------------------------------------#
ngpus_per_node = torch.cuda.device_count()
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# ------------------------------------------------------#
# 创建模型
# ------------------------------------------------------#
model = Baseline()
if pretrained_model_path != '':
print('Load weights {}.'.format(pretrained_model_path))
pretrained_dict = torch.load(pretrained_model_path, map_location= device)
model.load_state_dict(pretrained_dict)
model_train = model.train()
if Cuda:
Generator_train = torch.nn.DataParallel(model)
cudnn.benchmark = True
Generator_train = Generator_train.cuda()
opt_model = torch.optim.Adam(model.parameters(), lr=Init_lr)
# ---------------------------------------#
# 构建数据集加载器。
# ---------------------------------------#
train_dataset = Dataset(train_val_dataset_path, input_shape, epoch_length=Epoch, is_train=True)
val_dataset = Dataset(train_val_dataset_path, input_shape, epoch_length=Epoch, is_train=False)
shuffle = True
train_gen = DataLoader(train_dataset, shuffle=shuffle, batch_size=batch_size, num_workers=num_workers,
pin_memory=True, drop_last=True, collate_fn=dataset_collate, sampler=None)
val_gen = DataLoader(val_dataset, shuffle=shuffle, batch_size=batch_size, num_workers=num_workers,
pin_memory=True, drop_last=True, collate_fn=dataset_collate, sampler=None)
# ---------------------------------------#
# 开始模型训练
# ---------------------------------------#
for epoch in range(Init_Epoch, Epoch):
epoch_step = train_dataset.length // batch_size
epoch_step_val = val_dataset.length // batch_size
train_gen.dataset.epoch_now = epoch
val_gen.dataset.epoch_now = epoch
fit_one_epoch(model_train, model, opt_model, epoch, epoch_step, epoch_step_val, train_gen, val_gen, Epoch, Cuda, save_period, save_dir)
model.py:
import cv2
import kornia
import numpy
from matplotlib import pyplot as plt
import numpy as np
import torch
import torch.nn as nn
from torch import Tensor
import torch.nn.functional as F
class Baseline(nn.Module):
def __init__(self):
super(Baseline, self).__init__()
self.conv_features = nn.Sequential(
nn.Conv2d(1, 16, kernel_size=3, padding=1),
nn.BatchNorm2d(16),
nn.ReLU(inplace=True),
nn.Conv2d(16, 32, kernel_size=3, padding=1),
nn.BatchNorm2d(32),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2),
nn.Conv2d(32, 32, kernel_size=3, padding=1),
nn.BatchNorm2d(32),
nn.ReLU(inplace=True),
nn.Conv2d(32, 64, kernel_size=3, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2),
nn.Conv2d(64, 64, kernel_size=3, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(inplace=True),
nn.Conv2d(64, 128, kernel_size=3, padding=1),
nn.BatchNorm2d(128),
nn.ReLU(inplace=True),
nn.Conv2d(128, 256, kernel_size=3, padding=1),
nn.BatchNorm2d(256),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=2),
)
self.classifier = nn.Sequential(
nn.Linear(2304, 512),
nn.ReLU(inplace=True),
nn.Dropout(p=0.5),
nn.Linear(512, 256),
nn.ReLU(inplace=True),
nn.Dropout(p=0.2),
nn.Linear(256, 200),
)
def forward(self, x): # size(x) == (B,1,28,28)
x = self.conv_features(x)
x = x.view(x.size(0), -1)
# print(x.shape)
x = self.classifier(x)
# x = F.softmax(x, dim=1)
return x
trainer.py:
import os
import cv2
import kornia
import numpy
from torch import Tensor
import torch.nn as nn
import torch
from tqdm import tqdm
def fit_one_epoch(model_train, model, opt_model, epoch, epoch_step, epoch_step_val, train_gen, val_gen, Epoch,
cuda, save_period, save_dir):
loss = 0
train_set = set()
print('Start Train')
criterion = nn.CrossEntropyLoss()
if cuda:
criterion = criterion.cuda()
pbar = tqdm(total=epoch_step, desc=f'Epoch {epoch + 1}/{Epoch}', postfix=dict, mininterval=0.3)
acc = 0
for iteration, batch in enumerate(train_gen):
if iteration >= epoch_step:
break
images, label = batch[0], batch[1] # image (B,C,H,W) label (B)
with torch.no_grad():
if cuda:
images = images.cuda()
label = label.cuda()
model_train.train()
prob_tensor = model_train(images)
class_index = torch.argmax(prob_tensor, dim=1)
acc = acc + (label == class_index).sum().item()
loss_value = criterion(prob_tensor, label)
opt_model.zero_grad()
loss_value.backward()
opt_model.step()
loss += loss_value.item()
pbar.set_postfix(**{'loss': loss / (iteration + 1),
'acc': acc / ((iteration + 1) * label.shape[0])
})
pbar.update(1)
print('Start test')
pbar.close()
pbar = tqdm(total=epoch_step_val, desc=f'Epoch {epoch + 1}/{Epoch}', postfix=dict, mininterval=0.3)
acc = 0
for iteration, batch in enumerate(val_gen):
if iteration >= epoch_step_val:
break
model_train.eval()
images, label = batch[0], batch[1]
for i in range(label.shape[0]):
train_set.add(int(label[i]))
with torch.no_grad():
if cuda:
images = images.cuda()
label = label.cuda()
prob_tensor = model_train(images)
class_index = torch.argmax(prob_tensor, dim=1)
acc = acc + (label == class_index).sum().item()
pbar.set_postfix(**{'acc': acc / ((iteration + 1) * label.shape[0]),
})
pbar.update(1)
pbar.close()
save_state_dict = model.state_dict()
# save_state_dict_gen = Generator.state_dict()
if (epoch + 1) % save_period == 0 or epoch + 1 == Epoch:
torch.save(save_state_dict, os.path.join(save_dir, "ep%03d-loss%.3f.pth" % (
epoch + 1, loss / epoch_step)))
torch.save(save_state_dict, os.path.join(save_dir, "last_epoch_weights.pth"))
train_svm.py:
# 运行前先按下面方法安装sklearn库
# pip install -U scikit-learn -i https://pypi.tuna.tsinghua.edu.cn/simple/
import os.path
import torch
import torch.backends.cudnn as cudnn
from torch.utils.data import DataLoader
from utils.dataloader_svm import Dataset, dataset_collate
from utils.trainer_svm import fit_one_epoch
from nets.model import Baseline
if __name__ == "__main__":
Cuda = False
# ------------------------------------------------------#
# pretrained_model_path 网络预训练权重文件路径
# ------------------------------------------------------#
pretrained_model_path = ''
# ------------------------------------------------------#
# input_shape 输入的shape大小
# ------------------------------------------------------#
input_shape = [28, 28]
batch_size = 32
num_workers = 0
# ------------------------------------------------------#
# train_val_dataset_path 训练和测试文件路径
# ------------------------------------------------------#
train_val_dataset_path = 'dataset/NewDataset.mat'
# ---------------------------------------#
# 构建数据集加载器。
# ---------------------------------------#
train_dataset = Dataset(train_val_dataset_path, input_shape, is_train=True)
val_dataset = Dataset(train_val_dataset_path, input_shape, is_train=False)
shuffle = False
train_gen = DataLoader(train_dataset, shuffle=shuffle, batch_size=train_dataset.__len__(), num_workers=num_workers,
pin_memory=True, drop_last=True, collate_fn=dataset_collate, sampler=None)
val_gen = DataLoader(val_dataset, shuffle=shuffle, batch_size=val_dataset.__len__(), num_workers=num_workers,
pin_memory=True, drop_last=True, collate_fn=dataset_collate, sampler=None)
# ---------------------------------------#
# 开始模型训练
# ---------------------------------------#
fit_one_epoch(train_gen, val_gen)
trainer_svm.py:
import os
import cv2
import kornia
import numpy as np
from sklearn.svm import SVC
from sklearn.metrics import accuracy_score, classification_report
def fit_one_epoch(train_gen, val_gen):
loss = 0
train_set = set()
print('Start Train')
model = SVC(kernel='rbf', C=10.0, gamma='scale')
for iteration, batch in enumerate(train_gen):
if iteration >= 1:#100
break
train_images, train_label = batch[0], batch[1] # image (B,C,H,W) label (B)
print(np.shape(train_images), np.shape(train_label))
model.fit(train_images, train_label)
print('Start test')
acc = 0
for iteration, batch in enumerate(val_gen):
if iteration >= 1:
break
val_images, val_label = batch[0], batch[1]
val_pred = model.predict(val_images)
accuracy = accuracy_score(val_label, val_pred)
report = classification_report(val_label, val_pred,zero_division=1)
print("Accuracy:", accuracy)
print("Classification report:", report)