赞
踩

AlexNet网络结构特点:
1.首次利用GPU进行网络加速训练 (GPU处理速度是CPU的20-50倍)
2.使用了ReLU激活函数,而不是传统的Sigmoid激活函数(缺点:求导麻烦、容易梯度消失)以及tanh激活函数

3.使用了LRN(Local Response Normalization)局部相应归一化
4.在全连接层的前两层中使用了Dropout随机失活神经元操作,以减少过拟合
解释一下什么叫过拟合
拟合的函数完美预测训练集,但对新数据的测试集预测结果差。
过度的拟合了训练数据,而没有考虑到泛化能力
根本原因
特征维度过多,模型假设过于复杂,参数过多,训练数据过少,噪声过多
解决方法
使用Dropout的方式在网络正向传播过程中随机失活一部分神经元

Dropout会在每一层中随机失活一部分神经元,可以理解为变相的减少了网络训练的参数,从而达到减少过拟合的作用
在将AlexNet网络结构之前。首先回顾一下卷积之后的矩阵尺寸大小计算公式
N = (W - F + 2P) / S + 1
N:卷积之后的矩阵尺寸大小
W:输入图片的大小W * W
F:卷积核的大小F * F
P:padding(补0)的像素p
S:滑动的步长
第一个卷积层

之所以有上下两部分,是因为有两块GPU进行并行运算
先看一下Conv1 就是框选出来的这一部分
输入图片是一个高为224 宽为224 深度为3的彩色图像
卷积核大小是11*11
所采用的步距是4
padding:[1,2] 在特征矩阵的左边加上1列0 左边加上2列0 上面加上一列0 下面加上2列0
采用卷积核的个数是2*48=96 一层有两个,一共48层
(224-11+2+1)/4 +1 =55
那么第一个卷积层输入:224*224*3
第一个卷积层输出:55*55*96(输出的深度是卷积核的个数)
第一个池化层

第一个池化层的输入:55*55*96
池化层池化核的尺寸:3
padding:0
步长:2
(55-3+2*0)/2+1=27
第一个池化层的输出:27*27*96 (池化操作只会改变特征矩阵的高度和宽度,不会改变深度)
第二个卷积层:

卷积核的个数:2*128=256(也是输出矩阵的深度)
卷积核的尺寸:5*5
padding:[2,2]
步长:1
第二个卷积层输出矩阵的尺寸:
(27-5+2+2)/1+1= 27
第二个卷积层的输入:[27,27,96]
第二个卷积层的输出:[27,27,256]
第二个池化层

卷积核的尺寸:3
padding:0
stride:2
池化后的矩阵尺寸:
(27-3+2*0)/2+1=13
第二个池化层输入:[27,27,256]
第二个池化层输出:[13,13,256]
第三个卷积层:

卷积核的个数:2*192=384
卷积核的尺寸:3
padding:[1,1]
stride:1
(13-3+1+1)/1+1=13
第三个卷积层输入:[13,13,256]
第三个卷积层输出:[13,13,384]
第四个卷积层

卷积核的个数:2*192=384
卷积核的大小:3
padding:[1,1]
步长:1
(13-3+1+1)/1+1=13
第四个卷积层的输入:[13,13,384]
第四个卷积层的输出:[13,13,384]
第五个卷积层

卷积核的个数:2*128=256
卷积核的大小:3
padding:[1,1]
步长:1
(13-3+1+1)/1+1=13
第五个卷积层的输入:[13,13,384]
第五个卷积层的输出:[13,13,256]
第三个池化层

池化核的大小:3
padding:0
stride:2
(13-3+2*0)/2+1=6
第三个池化层的输入:[13,13,256]
第三个池化层的输出:[6,6,256]
最后面是三个全连接层,将Maxpool输出的进行展平,然后和三个层进行连接
总结一下这个AlexNet网络结构:
一共是五个卷积层三个全连接层
分别是:
Conv1,Maxpool1,Conv2,Maxpool2,Conv3,Conv4,Conv5,Maxpool3,Full connection1,Full connection2,Full connection3
接下来是本次训练用到的数据集,是一个花分类的数据集

数据集下载地址:
https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz
将下载的数据集放在这个文件夹下面,并将压缩包解压

提前写好一个将数据集分为训练集和测试集的脚本,比例是9:1
下面是脚本的代码
- import os
- from shutil import copy, rmtree
- import random
-
-
- def mk_file(file_path: str):
- if os.path.exists(file_path):
- # 如果文件夹存在,则先删除原文件夹在重新创建
- rmtree(file_path)
- os.makedirs(file_path)
-
-
- def main():
- # 保证随机可复现
- random.seed(0)
-
- # 将数据集中10%的数据划分到验证集中
- split_rate = 0.1
-
- # 指向你解压后的flower_photos文件夹
- cwd = os.getcwd()
- data_root = os.path.join(cwd, "flower_data")
- origin_flower_path = os.path.join(data_root, "flower_photos")
- assert os.path.exists(origin_flower_path), "path '{}' does not exist.".format(origin_flower_path)
-
- flower_class = [cla for cla in os.listdir(origin_flower_path)
- if os.path.isdir(os.path.join(origin_flower_path, cla))]
-
- # 建立保存训练集的文件夹
- train_root = os.path.join(data_root, "train")
- mk_file(train_root)
- for cla in flower_class:
- # 建立每个类别对应的文件夹
- mk_file(os.path.join(train_root, cla))
-
- # 建立保存验证集的文件夹
- val_root = os.path.join(data_root, "val")
- mk_file(val_root)
- for cla in flower_class:
- # 建立每个类别对应的文件夹
- mk_file(os.path.join(val_root, cla))
-
- for cla in flower_class:
- cla_path = os.path.join(origin_flower_path, cla)
- images = os.listdir(cla_path)
- num = len(images)
- # 随机采样验证集的索引
- eval_index = random.sample(images, k=int(num*split_rate))
- for index, image in enumerate(images):
- if image in eval_index:
- # 将分配至验证集中的文件复制到相应目录
- image_path = os.path.join(cla_path, image)
- new_path = os.path.join(val_root, cla)
- copy(image_path, new_path)
- else:
- # 将分配至训练集中的文件复制到相应目录
- image_path = os.path.join(cla_path, image)
- new_path = os.path.join(train_root, cla)
- copy(image_path, new_path)
- print("\r[{}] processing [{}/{}]".format(cla, index+1, num), end="") # processing bar
- print()
-
- print("processing done!")
-
-
- if __name__ == '__main__':
- main()
把脚本文件放在这个文件夹里面

就在这个页面,按住shit+鼠标右键 ,在终端中打开

输入这行代码,然后敲回车

完成
生成了训练集文件夹和测试集文件夹

接下来是代码详解部分
AlexNet网络结构的搭建代码详解
- import torch.nn as nn
- import torch
-
-
- class AlexNet(nn.Module):
- def __init__(self, num_classes=1000, init_weights=False):
- super(AlexNet, self).__init__()
- self.features = nn.Sequential(#nn.Sequential能够将一系列层结构组合成一个新的结构 features用于提取图像特征的结构
- nn.Conv2d(3, 48, kernel_size=11, stride=4, padding=2), # input[3, 224, 224] output[48, 55, 55] 48是卷积核个数 batch channel high weight
- #padding参数解释:如果是int型,比如说1 就是上下左右都补1列0 如果传入一个tuple(元组)的话 比如传入(1,2),1代表上下方各补一行0,2代表左右两侧各补两列0
- nn.ReLU(inplace=True),#inplace这个参数可以理解为pytorch通过一种方法增加计算量,来降低内存使用容量的一种方法,可以通过这种方法在内存中载入更大的模型
- nn.MaxPool2d(kernel_size=3, stride=2), # output[48, 27, 27]
- nn.Conv2d(48, 128, kernel_size=5, padding=2), # output[128, 27, 27] 没有设置stride是因为这个卷积层的步长是1,而默认的步长就是1
- nn.ReLU(inplace=True),
- nn.MaxPool2d(kernel_size=3, stride=2), # output[128, 13, 13]
- nn.Conv2d(128, 192, kernel_size=3, padding=1), # output[192, 13, 13]
- nn.ReLU(inplace=True),
- nn.Conv2d(192, 192, kernel_size=3, padding=1), # output[192, 13, 13]
- nn.ReLU(inplace=True),
- nn.Conv2d(192, 128, kernel_size=3, padding=1), # output[128, 13, 13]
- nn.ReLU(inplace=True),
- nn.MaxPool2d(kernel_size=3, stride=2), # output[128, 6, 6]
- )
- self.classifier = nn.Sequential(#包含了三层全连接层 是一个分类器
- nn.Dropout(p=0.5),#p是每一层随机失活的比例 默认是0.5
- nn.Linear(128 * 6 * 6, 2048),#第一个全连接层,全连接层的节点个数是2048个
- nn.ReLU(inplace=True),
- nn.Dropout(p=0.5),
- nn.Linear(2048, 2048),#第二个全连接层
- nn.ReLU(inplace=True),
- nn.Linear(2048, num_classes),#第三个全连接层 输入的是数据集 类别的个数,默认是1000
- )
- if init_weights:#初始化权重
- self._initialize_weights()
-
- def forward(self, x):#正向传播过程
- x = self.features(x)
- x = torch.flatten(x, start_dim=1)#进行一个展平处理 将传进来的变量x进行展平,从index=1 这个维度开始 也就是channel
- 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')#kaiming_normal初始化权重方法
- if m.bias is not None:#如果偏置不为空的话,那么就用0去初始化
- nn.init.constant_(m.bias, 0)
- elif isinstance(m, nn.Linear):#如果这个实例是全连接层
- nn.init.normal_(m.weight, 0, 0.01)#采用normal,也就是正态分布来给我们的权重进行赋值,正态分布的均值等于0,方差是0.01
- nn.init.constant_(m.bias, 0)#将偏置初始化为0
训练模块的代码详解
- import os
- import sys
- import json
-
- import torch
- import torch.nn as nn
- from torchvision import transforms, datasets, utils
- import matplotlib.pyplot as plt
- import numpy as np
- import torch.optim as optim
- from tqdm import tqdm
-
- from model import AlexNet
-
-
- def main():
- device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")#指定训练过程中所指定的设备 如果有GPU设备就使用第一块GPU设备,如果没有的话,就使用CPU设备
- print("using {} device.".format(device))
-
- data_transform = {#数据预处理函数
- "train": transforms.Compose([transforms.RandomResizedCrop(224),#随机裁剪成224*224像素大小
- transforms.RandomHorizontalFlip(),#在水平方向随机反转
- transforms.ToTensor(),#转换成一个张量
- transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]),#进行一个标准化处理 output=(input-0.5)/0.5
- "val": transforms.Compose([transforms.Resize((224, 224)), # cannot 224, must (224, 224)
- transforms.ToTensor(),
- transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])}
-
- data_root = os.path.abspath(os.path.join(os.getcwd(), "../..")) # get data root path getcwd获取当前文件所在的目录 ..是返回上级目录 ../..是返回上上级目录 path.join是将两个目录连接在一起
- image_path = os.path.join(data_root, "data_set", "flower_data") # flower data set path
- assert os.path.exists(image_path), "{} path does not exist.".format(image_path)
- train_dataset = datasets.ImageFolder(root=os.path.join(image_path, "train"),
- transform=data_transform["train"])
- train_num = len(train_dataset)#记录训练集有多少张图片
-
- # {'daisy':0, 'dandelion':1, 'roses':2, 'sunflower':3, 'tulips':4} 雏菊 蒲公英 玫瑰 向日葵 郁金香
- flower_list = train_dataset.class_to_idx#获取分类名称所对应的索引
- cla_dict = dict((val, key) for key, val in flower_list.items())#遍历分类索引的字典,然后将key value反过来 变成 0 daisy
- # write dict into json file
- json_str = json.dumps(cla_dict, indent=4) #通过json.dumps将cla这个字典转化为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,
- num_workers=0)
-
- validate_dataset = datasets.ImageFolder(root=os.path.join(image_path, "val"),
- transform=data_transform["val"])
- val_num = len(validate_dataset)
- validate_loader = torch.utils.data.DataLoader(validate_dataset,
- batch_size=batch_size, shuffle=False,
- num_workers=0)
-
- print("using {} images for training, {} images for validation.".format(train_num,
- val_num))
- # test_data_iter = iter(validate_loader)
- # test_image, test_label = test_data_iter.next()
-
- # def imshow(img):
- # img = img / 2 + 0.5 # unnormalize
- # npimg = img.numpy()
- # plt.imshow(np.transpose(npimg, (1, 2, 0)))
- # plt.show()
-
- # print(' '.join('%5s' % cla_dict[test_label[j].item()] for j in range(4)))
- # imshow(utils.make_grid(test_image))
-
- net = AlexNet(num_classes=5, init_weights=True)
-
- net.to(device)
- loss_function = nn.CrossEntropyLoss()
- # pata = list(net.parameters())
- optimizer = optim.Adam(net.parameters(), lr=0.0002)
-
- epochs = 10
- save_path = './AlexNet.pth'#保存权重的路径
- best_acc = 0.0
- train_steps = len(train_loader)
- for epoch in range(epochs):
- # train
- net.train()#dropout只在训练集中使用 而不在验证集中使用
- running_loss = 0.0
- train_bar = tqdm(train_loader, file=sys.stdout)
- for step, data in enumerate(train_bar):
- images, labels = data
- optimizer.zero_grad()
- outputs = net(images.to(device))
- loss = loss_function(outputs, labels.to(device))
- loss.backward()#将损失反向传播
- optimizer.step()#更新节点参数
-
- # print statistics
- running_loss += loss.item()
-
- train_bar.desc = "train epoch[{}/{}] loss:{:.3f}".format(epoch + 1,
- epochs,
- loss)
-
- # validate
- net.eval()
- acc = 0.0 # accumulate accurate number / epoch
- with torch.no_grad():
- val_bar = tqdm(validate_loader, file=sys.stdout)
- for val_data in val_bar:
- val_images, val_labels = val_data
- outputs = net(val_images.to(device))
- predict_y = torch.max(outputs, dim=1)[1]
- acc += torch.eq(predict_y, val_labels.to(device)).sum().item()
-
- val_accurate = acc / val_num
- print('[epoch %d] train_loss: %.3f val_accuracy: %.3f' %
- (epoch + 1, running_loss / train_steps, val_accurate))
-
- if val_accurate > best_acc:
- best_acc = val_accurate
- torch.save(net.state_dict(), save_path)
-
- print('Finished Training')
-
-
- if __name__ == '__main__':
- main()
训练结果

预测模块的代码详解
- import os
- import json
-
- import torch
- from PIL import Image
- from torchvision import transforms
- import matplotlib.pyplot as plt
-
- from model import AlexNet
-
-
- def main():
- device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
-
- 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_path = "../tulip.jpg"
- assert os.path.exists(img_path), "file: '{}' dose not exist.".format(img_path)
- img = Image.open(img_path)
-
- plt.imshow(img)
- # [N, C, H, W]
- img = data_transform(img)
- # expand batch dimension
- img = torch.unsqueeze(img, dim=0)
-
- # read class_indict
- json_path = './class_indices.json'
- assert os.path.exists(json_path), "file: '{}' dose not exist.".format(json_path)
-
- with open(json_path, "r") as f:
- class_indict = json.load(f)
-
- # create model
- model = AlexNet(num_classes=5).to(device)
-
- # load model weights
- weights_path = "./AlexNet.pth"
- assert os.path.exists(weights_path), "file: '{}' dose not exist.".format(weights_path)
- model.load_state_dict(torch.load(weights_path))
-
- model.eval()
- with torch.no_grad():
- # predict class
- output = torch.squeeze(model(img.to(device))).cpu()
- predict = torch.softmax(output, dim=0)
- predict_cla = torch.argmax(predict).numpy()
-
- print_res = "class: {} prob: {:.3}".format(class_indict[str(predict_cla)],
- predict[predict_cla].numpy())
- plt.title(print_res)
- for i in range(len(predict)):
- print("class: {:10} prob: {:.3}".format(class_indict[str(i)],
- predict[i].numpy()))
- plt.show()
-
-
- if __name__ == '__main__':
- main()
预测结果

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。