本文正在更新中


卷积前置基础知识准备

1.输入图像的通道数=卷积核的通道数

2.一个卷积核卷积,只会产生一个feature map(即一个二维矩阵)

3.多个卷积核卷积,产生的矩阵个数=卷积核个数,即一个卷积核产生一个矩阵,将产生的feature map沿通道方向(即垂直方向)堆叠在一起,产生了三维的张量(输出) =>输出的通道数=卷积核的个数

4.卷积后矩阵尺寸计算公式:
 N=(W-F+2P)/S+1
 N:输出矩阵的高和宽(一般输入和输出的都是方阵)
 W:输入图像的尺寸(W*W)
 F:fliter(滤波器)的简写——卷积核(conv)的尺寸
 P:Padding(填充)
 S:Stride(步长)
卷积动态图

AlexNet论文精讲

论文结构

1.关于当时深度学习的一个介绍(有点像小版综述)
2.数据集介绍
3.网络结构
4.减少过拟合
5.学习的细节
6.实验结果
7.相关讨论

逐段精讲

正在更新

AlexNet算法、亮点精讲

正在更新

代码讲解(自主编写)

model.py(模型结构代码)

本质上就是搭积木
AlexNet模型图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
"""
完成对AlexNet的模型搭建
"""

# 导入模块和库
from torch import nn
from torch.nn import Sequential, Conv2d, ReLU, MaxPool2d, Flatten, Dropout, Linear
class AlexNet(nn.Module):
def __init__(self,classes_num):
super(AlexNet, self).__init__()
self.sequential=Sequential(
# 其实有些默认参数没必要依次写出来,但我为了加深理解,所以把每个参数都写出来了
# 特征提取
Conv2d(in_channels=3,out_channels=48,kernel_size=11,stride=4,padding=2),
ReLU(inplace=True), # inplace增加计算量,但减少内存的占用 (每卷积一次,非线性激活一次,给函数引入非线性)
MaxPool2d(kernel_size=3,stride=2,padding=0), # 最大池化其实不会改变通道数,会改变图片的尺寸,所以这里的参数没有通道(深度)
Conv2d(in_channels=48,out_channels=128,kernel_size=5,stride=1,padding=2),
ReLU(inplace=True),
MaxPool2d(kernel_size=3,stride=2,padding=0),
Conv2d(in_channels=128,out_channels=192,kernel_size=3,stride=1,padding=1),
ReLU(inplace=True),
Conv2d(in_channels=192,out_channels=192,kernel_size=3,stride=1,padding=1),
ReLU(inplace=True),
Conv2d(in_channels=192,out_channels=128,kernel_size=3,padding=1),
ReLU(inplace=True),
MaxPool2d(kernel_size=3,stride=2,padding=0),

# 展平
Flatten(), # 这个是必须的,观察模型的结构可以看出,卷积层与全连接层之间,是要展平成一维的张量(一维向量)的!!!
# 是从三个维度展平的(CHW),batch_size是不去动的

# 开始分类(通过线性)
Dropout(p=0.5), # 这是AlexNet的一个创新点,目的防止过拟合,采用以p的概率随机失活神经元(这里的p其实也算个超参数,也可以自己调整调整)
Linear(in_features=4608,out_features=2048), # 经过上面展平后,节点数是有4608个(可以计算的128*6*6(CHW))
ReLU(inplace=True),
Dropout(p=0.5),
Linear(in_features=2048,out_features=2048), # 注意:这里的输入之所以没减半,是因为Dropout随机失活,只是失活,但没有消失(这也是个易错点)
ReLU(inplace=True),
Linear(in_features=2048,out_features=classes_num)
# 注意每层都要激活操作,因为:激活是非线性的,如果不激活,每层就是纯线性的变换,连续多层线性和一层是等效的,那么则神经网络的”深度“就不起效果了
)

# 正向传播
def forward(self,x):
x=self.sequential(x)
return x

train.py(训练文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
"""
训练模型,得到模型最优化后的参数,并保存
"""

# 导入需要的库和模块
import torch
import torchvision
from torch.nn import CrossEntropyLoss
from torch.utils.tensorboard import SummaryWriter
# 从model.py文件中导入类
from model import *
from torch.utils.data import DataLoader
from torchvision.datasets import ImageFolder

# 首先要明白的是我建立的模型输入尺寸为224*224,所以最基本要保证输入尺寸为224以及tensor的数据类型
# 训练数据的转化
train_data_transforms=torchvision.transforms.Compose(
[
torchvision.transforms.RandomCrop(224), # 随机裁剪成尺寸为224
torchvision.transforms.RandomHorizontalFlip(), # 随机翻转
torchvision.transforms.ToTensor(), # 转换为tensor数据类型
torchvision.transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5)) # 标准化(归一化)至[0,1]1(这里主要防止梯度爆炸)
# 注意:随机裁剪、反转等都是增大数据集数量,可以实现数据增强,其目的就是防止过拟化
]
)
# 测试数据的转化
test_data_transforms=torchvision.transforms.Compose(
[
torchvision.transforms.Resize((224,224)),
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))
# 注意:测试集是不用进行反转之类的,只需要进行简单的尺寸变化和类型转换,以适应模型输入尺寸,因为测试集是用来测试模型的准确率
]
)

# 处理数据
train_dataset=ImageFolder(root='./new_data/train',transform=train_data_transforms)
test_dataset=ImageFolder(root='./new_data/val',transform=test_data_transforms)
print(len(train_dataset),len(test_dataset))

# 载入数据
train_loader=DataLoader(dataset=train_dataset,batch_size=10,shuffle=True,num_workers=0)
test_loader=DataLoader(dataset=test_dataset,batch_size=40,shuffle=False,num_workers=0)
# 这里的num_worker为主线程加载(windows只能为0),batch_size指的是每一批有多少数据
print(len(train_loader),len(test_loader))

# 定义新的训练设备
device=torch.device('cuda' if torch.cuda.is_available() else "cpu")
print(device)

# 创建网络模型
num_class=3
model=AlexNet(num_class)
model=model.to(device) # 将训练设备放到cuda上

# 损失函数,这里的多分类问题仍采用交叉熵损失函数
Loss= CrossEntropyLoss(weight=None)
Loss=Loss.to(device)

# 定义tensorboard(一种可视化训练过程的工具)
writer=SummaryWriter(log_dir="AlexNet")

# 定义优化器
learning_rate=0.0002 # 学习率
# 这里用的是Adam优化器
optimizer=torch.optim.Adam(model.parameters(), lr=learning_rate)

# 定义训练是用到的超参数
train_step=0
test_step=0
epoch=20 # 训练轮数

for i in range(epoch):
print('————————————第{}轮训练——————————————'.format(i+1))
for data in train_loader:
# 开始训练
model.train() # 这是必须的,由于有Dropout层,为了保护、固定参数
imgs,targets=data # 读取图片和标签
imgs=imgs.to(device)
targets=targets.to(device) # 转换设备
output=model(imgs)
loss_single=Loss(output,targets) # 计算误差(损失),方便下面反向传播,计算梯度

# 优化(反向传播、梯度下降)
optimizer.zero_grad() # 梯度请0,防止梯度累加,参数爆炸
loss_single.backward() # 反向传播,计算梯度
optimizer.step() # 优化
train_step += 1
# 打印数据
if train_step % 25==0:
print("训练次数:%d loss_single:%f" % (train_step, loss_single))
# 本轮训练结束

total_test_loss = 0
total_test_accuracy = 0
# 针对本轮的训练,对测试集进行测试,查看准确率
model.eval() # 一种训练的标志,对Dropout等有作用
with torch.no_grad(): # 关闭梯度,测试时是不能更新参数的,所以也就不需要梯度,为避免梯度在测试中被污染,直接关闭
# 以下步骤与训练基本一致,但不要优化,也就不需要计算梯度、反向传播之类
for test_data in test_loader:
test_imgs, test_targets = test_data
test_imgs = test_imgs.to(device)
test_targets = test_targets.to(device)
output_test = model(test_imgs)
loss_test_single = Loss(output_test, test_targets)
total_test_loss += loss_test_single # 统计一轮内的总损失

# 注意:output_test类型为一维张量,分别为每个类别的非线性概率
accuracy = (output_test.argmax(1) == test_targets).sum()
total_test_accuracy += accuracy

# 打印数据
print("经过第{}轮训练 Loss:{}".format(i + 1, total_test_loss))
print("经过第{}轮训练 准确率:{}".format(i + 1, total_test_accuracy / len(test_dataset)))

# add_scalar绘制图像
writer.add_scalar(tag="Loss", scalar_value=total_test_loss, global_step=i + 1)
writer.add_scalar(tag="Accuracy", scalar_value=(total_test_accuracy / len(test_dataset)), global_step=i + 1)

# 保存模型,便于后面demo直接加载调用模型
torch.save(model, "./model_method/AlexNet_method{}.pth".format(i + 1))
print("第{}轮模型保存成功".format(i + 1))

demo.py(预测文件)

在本次预测中,为了方便大家理解,我只加入了单张图片预测,后续模型将会加入视频、本地、网络摄像头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
"""
demo
"""

import torch
import torchvision
from PIL import Image


class predict:

def my_predict(self, img_path, model_method):
# 引入图片
img = Image.open(img_path) # 将图片转换为PIL形式,方便转换为tensor数据类型

# 图片转换
img = img.convert('RGB') # 以此来适应各种格式图片(png,jpg)
transforms = torchvision.transforms.Compose([torchvision.transforms.Resize((224, 224)),
torchvision.transforms.ToTensor()]
)
img = transforms(img)
img = torch.reshape(img, (1, 3, 224, 224)) # 转换为适合进入神经网络的类型格式
img = img.cuda() # 以CUDA设备来demo,因为神经网络模型参数等也为cuda训练,传入验证图片时也必须在cuda上

# 标签(三个类别)
labels = ['茶云纹','茶白星病','茶赤叶斑病']

# 建立模型,加载模型(训练得到的,选误差最小的)
my_lenet = torch.load(model_method)
my_lenet(img)

# 开始预测
my_lenet.eval()
with torch.no_grad(): # 保证模型参数数据不受影响(不需要用到梯度)
output = my_lenet(img) # output此时为一维张量,存放着各个类别的非线性概率(加和不为1)
predict = torch.softmax(output, dim=1)
predict = predict.to('cpu') # 注意:这里先要转换为cpu上运转,便于tensor数据类型转换为numpy(!!!debug一下午才找到,惨痛教训)
print("预测结果为:", labels[output.argmax(1)])
print("概率为:", predict[0][output.argmax(1)].numpy().item()) # item()可以直接显示数值

return labels[output.argmax(1)]


# 测试是否正常
if __name__ == "__main__":
pre = predict()
pre.my_predict(r"D:\pythonProject1\my_AlexNet_nn\img_4.png",r"D:\pythonProject1\my_AlexNet_nn\model_method\AlexNet_method4.pth")

正在更新中