基于AlexNet的猫狗识别
基于AlexNet的猫狗识别
前言
机器视觉的课程设计,数据集来自网站Kaggle:www.kaggle.com/datasets/to…
一、加载数据集
1、数据处理
设置每批数据中的图片数batch_size,每次训练256张图片;设置图片大小为resize,将每张图片的大小重塑为224∗224224 * 224224∗224,但实际似乎会变为225∗225225 * 225225∗225。
以上操作由函数from torchvision import transform完成。Compose(组成),指由Resize和ToTensro两个操作组成数据转换transform。
batch_size = 256 # 每批里面的样本数
resize = 224 # 重塑图片大小
transform = transforms.Compose([
transforms.Resize((resize, resize)), #重塑
transforms.ToTensor(), # 设置为Tensor数据格式
])
2、加载数据集
图片数据加载依靠函数from torchvision.datasets import ImageFolder。
ImageFolder假设所有的文件按文件夹保存,每个文件夹下存储同一个类别的图片,文件夹名为类名,其构造函数如下:
ImageFolder(root, transform=None, target_transform=None, loader=default_loader)
它主要有四个参数: root:在root指定的路径下寻找图片, transform:对PIL Image进行的转换操作, transform的输入是使用loader读取图片的返回对象, target_transform:对label的转换, loader:给定路径后如何读取图片,默认读取为RGB格式的PIL Image对象。 参考博客原文链接
本次课设实际加载数据集代码:
train_data = ImageFolder(r"训练数据集路径", transform=transform)
test_data = ImageFolder(r"测试数据集路径",transform=transform)
3、数据分批
数据集不能一次就训练完,需要分批训练,GPU性能不佳那么batch_size就设置得小一些。 到此数据加载完成。
# torch.utils.data.DataLoader 小批量读取数据
train_iter = torch.utils.data.DataLoader(
train_data, # 数据(特征,标签)
batch_size=batch_size, # 批量样本数
shuffle=True, # 打乱顺序,一般为True
num_workers=num_workers) # 额外进程,一般用于加速加载数据,windows系统设为0即可
test_iter = torch.utils.data.DataLoader(
test_data,
batch_size=batch_size,
shuffle=False,
num_workers=num_workers)
二、建立网络模型
网络结构不介绍,参考书籍《动手学深度学习》第四章第6节。
如数据为灰度图,则输入通道in_channels为1, nn.Conv2d(3, 96, 11, 4) 改为 nn.Conv2d(1, 96, 11, 4)。 彩图则代码不变。
课设为二分类问题,因此全连接层最后一层为 nn.Linear(4096, 2) n分类问题则改为nn.Linear(4096, n)
# %%建立模型
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
class AlexNet(nn.Module):
def __init__(self):
super(AlexNet, self).__init__()
self.conv = nn.Sequential(
# in_channels, out_channels, kernel_size, stride
nn.Conv2d(3, 96, 11, 4),
nn.ReLU(),
# kernel_size, stride
nn.MaxPool2d(3, 2),
# 减小卷积窗口,使用填充为2来使得输入与输出的高和宽一致,
# 且增大输出通道数
nn.Conv2d(96, 256, 5, 1, 2),
nn.ReLU(),
nn.MaxPool2d(3, 2),
# 连续3个卷积层,且使用更小的卷积窗口。除了最后的卷积层外,
# 进一步增大了输出通道数。
# 前两个卷积层后不使用池化层来减小输入的高和宽
nn.Conv2d(256, 384, 3, 1, 1),
nn.ReLU(),
nn.Conv2d(384, 384, 3, 1, 1),
nn.ReLU(),
nn.Conv2d(384, 256, 3, 1, 1),
nn.ReLU(),
nn.MaxPool2d(3, 2)
)
# 这里全连接层的输出个数比LeNet中的大数倍。使用丢弃层来缓解过拟合
self.fc = nn.Sequential(
nn.Linear(256 * 5 * 5, 4096),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096, 4096),
nn.ReLU(),
nn.Dropout(0.5),
# 输出层。由于这里使用Fashion-MNIST
# 所以用类别数为10,而非论文中的1000
nn.Linear(4096, 2)
)
def forward(self, img):
feature = self.conv(img)
output = self.fc(feature.view(img.shape[0], -1))
return output
net = AlexNet()
# 初始化模型参数,使之更容易收敛
for m in net.modules():
if isinstance(m,torch.nn.Linear): #判断是否同类型
torch.nn.init.xavier_normal_(m.weight)
三、训练
训练时优化算法:SGD, 损失函数:交叉熵函数(适用于一般分类问题) 模型准确率计算:正确分类次数/总分类次数正确分类次数/总分类次数正确分类次数/总分类次数 训练过程由Tensorboard记录。
# %%训练
# 超参数
lr, num_epochs = 0.1, 20 # lr为收敛步长,num_epochs为训练周期
optimizer = torch.optim.SGD(net.parameters(), lr=lr) # 选择优化函数
loss = torch.nn.CrossEntropyLoss() # 选择损失函数
writer = SummaryWriter('./log') # 设置路径
# 将模型加载到指定运算器中
net = net.to(device)
n_train = len(train_data)
n_test = len(test_data)
print("training on ", device)
print("训练样本数:{} , 测试样本数:{} ".format(n_train,n_test))
for epoch in range(num_epochs):
print(15 * '*' + '第{}轮训练'.format(epoch) + 15 * '*')
train_l_sum, train_acc_sum, n, batch_count = 0.0, 0.0, 0, 0
start = time.time()
for X, y in train_iter:
X = X.to(device)
y = y.to(device)
y_pre = net(X)
l = loss(y_pre, y) # y_hat预测target概率,y真实target,l此批数据的loss
# 梯度清零
optimizer.zero_grad()
l.backward()
optimizer.step() # 优化网络参数
# 输出每批训练结果
train_l_sum += l.cpu().item()
train_acc_sum += (y_pre.argmax(dim=1) == y).sum().cpu().item() # 计算正确样本数
n += y.shape[0] # n:累和每批样本数量,也就是训练样本数量
batch_count += 1 # 批次
if batch_count % 20 == 0:
print('训练次数{} , 准确率:{} , loos:{} , time:{} '.format(batch_count, train_acc_sum/n_train, train_l_sum,
time.time() - start))
writer.add_scalar('Train_loss', train_l_sum, batch_count+len(train_iter)*epoch)
writer.add_scalar('Train_acc', train_acc_sum, batch_count+len(train_iter)*epoch)
# 输出每轮测试结果
test_acc = evaluate_accuracy(test_iter, net)
print('测试acc:{} , time:{}'.format(test_acc,time.time()-start))
writer.add_scalar('Test_acc', test_acc, epoch)
torch.save(net.state_dict(), "model{}.pt".format(epoch))
# writer.add_graph(net, torch.rand(4, 3, resize, resize))
writer.close()
训练后感:训练时常常遇到欠拟合或过拟合的情况,此时可以尝试优化以下几个方面。
优化算法
学习率(先大后小)
参数初始化
数据标准化
四、应用
1、加载模型
首先,将训练时得网络结构移过来,不然会加载失败。
class AlexNet(nn.Module):
def __init__(self):
super(AlexNet, self).__init__()
self.conv = nn.Sequential(
# in_channels, out_channels, kernel_size, stride
nn.Conv2d(3, 96, 11, 4),
nn.ReLU(),
# kernel_size, stride
nn.MaxPool2d(3, 2),
# 减小卷积窗口,使用填充为2来使得输入与输出的高和宽一致,
# 且增大输出通道数
nn.Conv2d(96, 256, 5, 1, 2),
nn.ReLU(),
nn.MaxPool2d(3, 2),
# 连续3个卷积层,且使用更小的卷积窗口。除了最后的卷积层外,
# 进一步增大了输出通道数。
# 前两个卷积层后不使用池化层来减小输入的高和宽
nn.Conv2d(256, 384, 3, 1, 1),
nn.ReLU(),
nn.Conv2d(384, 384, 3, 1, 1),
nn.ReLU(),
nn.Conv2d(384, 256, 3, 1, 1),
nn.ReLU(),
nn.MaxPool2d(3, 2)
)
# 这里全连接层的输出个数比LeNet中的大数倍。使用丢弃层来缓解过拟合
self.fc = nn.Sequential(
nn.Linear(256 * 5 * 5, 4096),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(4096, 4096),
nn.ReLU(),
nn.Dropout(0.5),
# 输出层。由于这里使用Fashion-MNIST
# 所以用类别数为10,而非论文中的1000
nn.Linear(4096, 2)
)
def forward(self, img):
feature = self.conv(img)
output = self.fc(feature.view(img.shape[0], -1))
return output
再加载训练好的模型参数
net = AlexNet()
net.load_state_dict(load(('model .pt文件所在路径')))
2、应用模型
这部分仅本次课设适用。 传入图片路径path进行猫狗识别,并输出结果。
小细节:需要将图片格式先转为PIL , 再转为Tensor, 因为transforms.Resize((resize, resize))仅用于PIL图片。
def classfiey(path):
resize = 224
path = os.path.abspath(path)
transform = transforms.Compose([
transforms.ToPILImage(),
transforms.Resize((resize, resize)),
transforms.ToTensor(),
])
img = cv2.imread(path)
img_1 = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) #opencv读取图片会把图片格式变为BGR
img_2 = transform(img_1)
image = reshape(img_2, (1, 3, resize, resize)) # 转为合适大小
net.eval()
result = net(image)
selection = ['猫', '狗']
return selection[argmax(result)]
最后设计简单UI界面进行调用。

五、模型评价
模型评价的大部分内容(包括图片)参考自知乎文章,图片上的水印是该社区自动打的。
1、评价指标
对于分类问题,一般有评价指标:准确率,精确率,召回率,F1,ROC曲线,AUC曲线。
5.1.1、准确率
精确率和准确率是比较容易混淆的两个评估指标,两者是有区别的。精确率是一个二分类指标,而准确率能应用于多分类
,其常见计算公式为:

5.1.2、精确率、召回率、F1值

上述计算公式中的Positive与Negative是预测标签里的正例和反例,True与false代表预测正误;要注意,精确率和召回率是二分类指标,不适用多分类,由此得到P-R曲线以及ROC曲线均是二分类评估指标(因为其横纵轴指标均为二分类混淆矩阵计算得到),而准确率适用于多分类评估。(可以将多分类问题转换为二分类问题进行求解,将关注的类化为一类,其他所有类化为一类)参考自知乎文章
==个人分析:== 精确率:预测为正例的那些数据里预测正确的数据个数。适用于罪犯判死刑的情况,宁可漏判也不可误判。 召回率:正例中预测准确的。适用于地震预测情况,宁可误判也不可漏判。
显然二者偏重点不同,难以只用某一个指标评价模型,因此有了精确率和召回率的调和均值F1值。
如果你对二者有侧重点,可以进行加权。
a>1a>1a>1时,RRR比PPP重要,反之同理。
5.1.3、PR曲线

为更好观测精确率和召回率的关系,可画出PR曲线观测,曲线越靠近右上角越好,但一般不太好用,一般使用F1值或AUC值。
5.1.4、ROC曲线与AUC ROC曲线纵坐标是真正率,横坐标是假正率,如下图,去对应的计算公式为:
其特点为不受不平衡数据(反例>>正例)的影响,因此优先级高于PR曲线。

ROC曲线就是以true positive rate 和 false positive rate为轴,取不同的threshold点画点成线。有人问了threshold是什么。这么说吧,每个分类器作出的预测,都是基于一个probability score。一般默认的threshold都是0.5,如果probability>0.5,那么这个sample被模型分成正例了,反之则是反例。搬运自知乎文章
AUC是ROC曲线的积分面积,用一个值代替ROC曲线去衡量模型,ROC曲线越靠近左上角越好,所围面积也越大。
使用ROC曲线和AUC可以不设定threshold,减少一个超参数的影响。
2、本次设计模型评价结果
混淆矩阵:
Alex:

Vgg16:

综合评价:
Alex:
precision recall f1-score support
0 0.88 0.72 0.79 1011
1 0.76 0.90 0.82 1012
accuracy 0.81 2023
macro avg 0.82 0.81 0.81 2023
weighted avg 0.82 0.81 0.81 2023
Vgg16:
precision recall f1-score support
0 0.98 0.90 0.94 1011
1 0.91 0.98 0.94 1012
accuracy 0.94 2023
macro avg 0.94 0.94 0.94 2023
weighted avg 0.94 0.94 0.94 2023
其中0:猫,1:狗。 从上述结果可以看出,模型对于狗的识别优于猫的识别。
转载自:https://juejin.cn/post/7115037974630400031