跟着大佬学图像分类系列,→ 传送门 ←
本博客图像分类系列文章传送门:
图像分类是学习目标检测的“量变”内容,那么,废话不多说,开搞!
AlexNet 是2012年 ISLVRC 2012 竞赛的冠军网络,将分类准确率由传统的 70%+ 提升到了 80%+。是由 Hinton 和他的学生 Alex Krizhevsky 设计完成的网络。
(ISLVRC 是用于图像分类的数据集,属于 ImageNet 的子集,其包含 1,281,167 张训练集;50,000 张验证集合 100,000 张测试集)
number | Input_size | output_size | kernels | kernels_size | padding | stride |
---|---|---|---|---|---|---|
Conv1 | [224, 224, 3] | [55, 55, 96] | 48 * 2 | 11 | [1, 2] | 4 |
MaxPooling1 | [55, 55, 96] | [27, 27, 96] | \ | 3 | \ | 2 |
Conv2 | [27, 27, 96] | [27, 27, 256] | 128 * 2 | 5 | [2, 2] | 1 |
MaxPooling2 | [27, 27, 256] | [13, 13, 256] | \ | 3 | \ | 2 |
Conv3 | [13, 13, 256] | [13, 13, 384] | 192 * 2 | 3 | [1, 1] | 1 |
Conv4 | [13, 13, 384] | [13, 13, 384] | 192 * 2 | 3 | [1, 1] | 1 |
Conv5 | [13, 13, 384] | [13, 13, 256] | 128 * 2 | 3 | [1, 1] | 1 |
MaxPooling3 | [13, 13, 256] | [6, 6, 256] | \ | 3 | \ | 2 |
FC1 | 6*6*256 (展平) | \ | \ | 2048 | \ | \ |
FC2 | 2048 | \ | \ | 2048 | \ | \ |
FC3 | 2048 | \ | \ | 1000 | \ | \ |
input:一张大小为 224x224 的RGB图片
卷积核大小为 11x11
卷积核的个数为 96 个(一层48,有两层)
步长为4
padding 为 [1, 2]
output:根据公式,输出为 [55, 55, 96] 的特征矩阵,96为这一层卷积核的个数,如果没有用GPU并行跑两层,这里就应该是48(建议去看一下卷积与池化的原理)
(output_size = (input_size - kernel_size + 2*padding)/stride + 1)
(池化层同理)
input:一张大小为 27x27 的RGB图片
卷积核大小为 5x5
卷积核的个数为 384 个(一层192,有两层)
步长为1
padding 为 [2, 2]
output:根据公式,输出为 [27, 27, 256] 的特征矩阵
(output_size = (input_size - kernel_size + 2*padding)/stride + 1)
(池化层同理)
input:一张大小为 13x13 的RGB图片
卷积核大小为 3x3
卷积核的个数为 384 个(一层192,有两层)
步长为1
padding 为 [1, 1]
output:根据公式,输出为 [13, 13, 384] 的特征矩阵
(output_size = (input_size - kernel_size + 2*padding)/stride + 1)
input:一张大小为 13x13 的RGB图片
卷积核大小为 3x3
卷积核的个数为 384 个(一层192,有两层)
步长为1
padding 为 [1, 1]
output:根据公式,输出为 [13, 13, 384] 的特征矩阵
(output_size = (input_size - kernel_size + 2*padding)/stride + 1)
input:一张大小为 13x13 的RGB图片
卷积核大小为 3x3
卷积核的个数为 256 个(一层128,有两层)
步长为1
padding 为 [1, 1]
output:根据公式,输出为 [13, 13, 384] 的特征矩阵
(output_size = (input_size - kernel_size + 2*padding)/stride + 1)
(池化层同理)
input:最后一层卷积层展平后的结果,即 66256
output:2048(全连接层的神经元个数)
本代码使用的数据集来自 “花分类” 数据集,→ 传送门 ←(具体内容看 data_set文件夹下的 README.md)
import torch.nn as nn
import torch
class AlexNet(nn.Module):
# num_classes表示模型可以分类的数量,如花数据集中,1000表示可以训练1000种花来识别(可通过传值来改变num_classes)
# init_weights为初始化权重
def __init__(self, num_classes=1000, init_weights=False):
super(AlexNet, self).__init__()
'''
nn.Sequential 可以将模型的各个层打包成一个新的结构,能让模型看起来更清晰
如果不用 nn.Sequential,要表示各个层就需要
self.conv1 = nn.Conv2d( )
self.pool1 = nn.MaxPool2d( )
......
self.fc = nn.Linear( )
看起来会比较累赘
'''
# features :5层卷积 + 3层池化
self.features = nn.Sequential(
# 3=通道数(深度),48=kernel数量
nn.Conv2d(3, 48, kernel_size=11, stride=4, padding=nn.ZeroPad2d(1,2,1,2)),
# inplace 降低内存使用
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Conv2d(48, 128, kernel_size=5, stride=1, padding=2),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Conv2d(128, 192, kernel_size=3, stride=1, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(192, 192, kernel_size=3, stride=1, padding=1),
nn.ReLU(inplace=True),
nn.Conv2d(192, 128, kernel_size=3, stride=1, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
)
# classifier :3层全连接层
self.classifier = nn.Sequential(
nn.Dropout(p=0.5), # 随机失活
nn.Linear(6 * 6 * 128, 2048),
nn.ReLU(inplace=True),
nn.Dropout(p=0.5),
nn.Linear(2048, 2048),
nn.ReLU(inplace=True),
nn.Linear(2048, num_classes),
)
if init_weights:
# 如果用户需要初始化权重,调用_initialize_weights()方法
self._initialize_weights()
# 前馈
def forward(self, x):
x = self.features(x)
x = torch.flatten(x, start_dim=1) # 展平成一维向量
x = self.classifier(x)
return x
# 初始化权重
def _initialize_weights(self):
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
if m.bias is not None:
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.Linear):
nn.init.normal_(m.weight, 0, 0.01)
nn.init.constant_(m.bias, 0)
import torch
import torch.nn as nn
from torchvision import transforms, datasets, utils
import torch.optim as optim
from model import AlexNet
import os
import json
import time
# 根据电脑配置,判断是否使用 GPU
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)
# 数据预处理
data_transforms = {
"train":transforms.Compose([
transforms.RandomResizedCrop(224), # 随机裁剪为 224 * 224 大小
transforms.RandomHorizontalFlip(), # 随机翻转
transforms.ToTensor(), # 转换为 tensor 类型
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) # 标准化处理
]),
"val":transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
}
image_path = "./data/data_set/flower_data/" # 获取图像路径
# 训练集路径
train_dataset = datasets.ImageFolder(root=image_path + "/train", transform=data_transforms["train"])
train_num = len(train_dataset)
# 数据集各类别的索引 '花名':索引号
flower_list = train_dataset.class_to_idx
cla_dict = dict((val, key) for val, key in flower_list.items()) # 将类别放入字典
json_str = json.dumps(cla_dict, indent=4) # 将字典转为 json 格式
# 保存 json 文件
with open('class_indices.json', 'w') as json_file:
json_file.write(json_str)
batch_size = 32
# 加载训练集
train_loader = torch.utils.data.DataLoader(
train_dataset,
batch_size=batch_size,
shuffle=True, # shuffle=True 表示随机去 batch
num_workers=0 # num_worker设为0,意味着每一轮迭代时,dataLoader 不再自主加载数据到RAM(windows都是设为0)
)
# 验证集(测试)
validate_dataset = datasets.ImageFolder(root = image_path + '/val', transform=data_transforms["val"])
validate_num = len(validate_dataset)
# 加载验证集
validate_loader = torch.utils.data.DataLoader(
validate_dataset,
batch_size=batch_size,
shuffle=False,
num_workers=0
)
''' 训练模型 '''
net = AlexNet(num_classes=5, init_weights=True)
net.to(device) # 使用 GPU 或 CPU
loss_funcation = nn.CrossEntropyLoss() # 损失函数类型
# param = list(net.parameters()) # 查看模型的参数
optimizer = optim.Adam(net.parameters(), lr=0.0002) # 优化器类型
save_path = './AlexNet.pth' # 训练好后的模型存放路径
best_acc = 0.0 # 记录最好的结果
for epoch in range(10):
'''train'''
net.train() # train() 方法会执行 Dropout
running_loss = 0.0 # 统计训练过程中的平均损失
start_time = time.perf_counter() # 记录训练一次 epoch 所需的时间
for step, data in enumerate(train_loader, start=0):
images, labels = data # 得到图像及对应的分类
optimizer.zero_grad() # 清空梯度信息
outputs = net(images.to(device)) # 放入网络正向传播,得到训练结果
loss = loss_funcation(outputs, labels.to(device)) # 计算损失
loss.backward() # 反向传播
optimizer.step() # 更新模型参数
running_loss += loss.item()
# 打印训练进度
rate = (step + 1) / len(train_loader)
a = "*" * int(rate * 50)
b = "." * int((1 - rate) * 50)
print("\rtrain loss: {:^3.0f}%[{}->{}]{:.3f}".format(int(rate * 100), a, b, loss), end="")
print()
print(time.perf_counter() - start_time)
'''val'''
net.eval() # rain() 方法不会执行 Dropout
acc = 0.0
with torch.no_grad():
for data_test in validate_loader:
test_images, test_labels = data_test
outputs = net(test_images.to(device))
predict_y = torch.max(outputs, dim=1)[1] # 这种分类其实输出的是一个概率,要将概率转换为最贴近的类别序号
acc += (predict_y == test_labels.to(device)).sum().item() # 预测值与实际值相等,acc+1
accurate_test = acc / validate_num # 计算正确率
# 保存最好结果
if accurate_test > best_acc:
best_acc = accurate_test
torch.save(net.state_dict(), save_path)
print('[epoch %d] train_loss: %.3f test_accuracy: %.3f' % (epoch + 1, running_loss / step, acc / validate_num))
print("Finished !")
import torch
from model import AlexNet
import numpy
from PIL import Image
from torchvision import transforms
import matplotlib.pyplot as plt
import json
data_transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
# load image
img = Image.open("./tulip.jpg")
plt.imshow(img) # show img
img = data_transform(img)
img = torch.unsqueeze(img, dim=0)
try:
json_file = open('/class_indicts.json', 'r') # 打开之前保存的“花分类”文件
class_indicts = json.load(json_file)
except Exception as e:
print(e)
exit(-1)
# 初始化网络
model = AlexNet(num_classes=5)
# 加载在 model.py 中训练好的模型
model_weight_path = "./AlexNet.pth"
model.load_state_dict(torch.load(model_weight_path))
model.eval()
with torch.no_grad():
# 获取 img 的预测结果
output = torch.squeeze(model(img))
predict = torch.softmax(output, dim=0)
predict_class = torch.argmax(predict).numpy()
print(class_indicts[str(predict_class)], predict[predict_class].item())
plt.show()
代码连接 https://github.com/WZMIAOMIAO/deep-learning-for-image-processing