赞
踩
写在前面:根据b站博主霹雳吧啦Wz 学习CNN,作为个人的学习记录。
目录
2012年才出现的AlexNet,在ImageNet LSVRC-2010比赛中实现了前1和前5的错误率分别为37.5%和17.0%,在ILSVRC-2012比赛中获得了15.3%的前5的获胜测试错误率。
论文:《ImageNet Classification with Deep Convolutional Neural Networks》
论文贡献:
- 在ILSVRC-2010和ILSVRC-2012比赛中使用的ImageNet子集上训练了迄今为止最大的卷积神经网络之一,并在这些数据集上获得了迄今为止报道过的最好的结果。
- 为了防止过拟合,首次使用ReLUs作为激活函数,并指出快速学习对在大数据集上训练的大模型的性能有很大的影响。
- 首次使用两个GPU进行训练,这将前1和前5的错误率分别降低了1.7%和1.2%,双gpu网络的训练时间比单gpu网络略短。
- 使用局部响应归一化(LRN),有助于快速收敛,增强了模型的泛化能力。(一般是在激活、池化后进行的一种处理方法)。
- 使用重叠池化层,降低了0.4%和0.3%的错误率。避免过拟合。
- 使用数据增强方式:平移和翻转;PCA进行降维。避免过拟合。
- 首次应用DropOut机制,大约使收敛所需的迭代次数翻了一倍。避免过拟合。
翻译:网络架构明确地显示了两个gpu之间的职责描述。一个GPU运行图形顶部的层部件,另一个运行图形底部的层部件。gpu只在特定的层进行通信。网络的输入是150,528维,网络剩余层中的神经元数量由253,440-186,624-64,896-64,896-43,264 - 4096-4096-1000给出。
由于这个模型太大,在这里,所有的卷积核个数和全连接层节点数都减半。
- 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.Conv2d(3, 48, kernel_size=11, stride=4, padding=2), # input[3, 224, 224] output[48, 55, 55]
- nn.ReLU(inplace=True),
- nn.MaxPool2d(kernel_size=3, stride=2), # output[48, 27, 27]
- nn.Conv2d(48, 128, kernel_size=5, padding=2), # output[128, 27, 27]
- 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), # 随机失活一半的神经元
- nn.Linear(128 * 6 * 6, 2048),
- nn.ReLU(inplace=True),
- nn.Dropout(p=0.5),
- nn.Linear(2048, 2048),
- nn.ReLU(inplace=True),
- nn.Linear(2048, num_classes), # 这里的num_classes是根据数据集内的种类决定
- )
- if init_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): # 如果在卷积层,那就使用kaiming_normal_方法初始化
- 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)
使用的是花分类数据集,分别有雏菊、蒲公英、玫瑰、向日葵、郁金香五种类别。首先新建一个名为"flower_data"的新文件夹,然后在浏览器下载该数据集,解压进"flower_data",使用split_data.py程序对数据集进行划分,变成训练集和验证集。
这是所有图片下的文件,一种类别的图片是一个文件夹,文件夹名称为类别名称:
只需要将自己的数据集按类别分开(例如daisy、dandelion、roses、sunflowers、tulips),放在一个文件夹内(例如flower_data)。然后使用split_data.py程序划分训练集和验证集。最后在训练程序和测试程序中找到变量num_classes的值,修改成自己数据集的种类个数。即可
训练程序里:
预测程序里:
split_data.py:
- 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()
如果有,默认使用第一块GPU;如果没有,那就用CPU。
- ### 1. 电脑如果有GPU的话,就会选择使用GPU,否则就用CPU
- device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
- print("using {} device.".format(device))
用transforms方式进行预处理,用compose将各种方法合在一起。
- ### 2. 图像预处理
- data_transform = {
- # 训练集图片预处理方式
- "train": transforms.Compose([transforms.RandomResizedCrop(224), # 将图像随机裁剪成224*224
- transforms.RandomHorizontalFlip(), # 水平翻转
- transforms.ToTensor(), # 转换成(C*H*W),并将像素值大小归一化[0,1]
- transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]), # 将三个通道的像素值标准化[-1,1]
- # 验证集图片预处理方式
- "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))])}
- ### 3. 找到训练集
- # 返回上一级目录
- data_root = os.path.abspath(os.path.join(os.getcwd(), "../..")) # get data root path
- # 在上一级目录下寻找data_set文件夹,然后进入其下一级目录flower_data
- 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)
- # image_path是指向flower_data文件夹,接着将这个文件夹下的train定义成训练集,然后对训练集的图像根据前面写的data_transform预处理方法处理图像,最后放进train_dataset中。
- train_dataset = datasets.ImageFolder(root=os.path.join(image_path, "train"),
- transform=data_transform["train"])
- train_num = len(train_dataset) # 获取训练集数量
- ### 4. 将类索引写入json文件
- # {'daisy':0, 'dandelion':1, 'roses':2, 'sunflower':3, 'tulips':4}
- flower_list = train_dataset.class_to_idx # 这里返回一个字典,key是文件夹名,value是顺序值。如上一行所示
- cla_dict = dict((val, key) for key, val in flower_list.items()) # 交换键值,主要因为后面的预测值是数字,想输出的时候是花的名称
- # write dict into json file 将类索引的字典写入json文件
- json_str = json.dumps(cla_dict, indent=4)
- with open('class_indices.json', 'w') as json_file:
- json_file.write(json_str)
生成的json文件如下图所示:
加载训练集
根据找训练集的方式去找到验证集,而且加载进来。
- ### 5. 加载进训练集和验证集
- batch_size = 32 # 设置批次大小
- nw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, 8]) # 设置加载数据时的进程数
- print('Using {} dataloader workers every process'.format(nw))
- # 加载训练集
- train_loader = torch.utils.data.DataLoader(train_dataset,
- batch_size=batch_size, shuffle=True,
- num_workers=nw)
- # 找到且加载验证集
- 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=4, shuffle=False,
- num_workers=nw)
-
- print("using {} images for training, {} images for validation.".format(train_num,
- val_num))
- 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) # 定义优化器
- ### 7. 训练
- epochs = 10 # 设置训练轮数
- save_path = './AlexNet.pth' # 模型权重保存路径
- best_acc = 0.0 # 用于记录最高的准确率
- train_steps = len(train_loader) # 训练步数
- for epoch in range(epochs):
- # train
- net.train()
- running_loss = 0.0 # 计算平均损失
- train_bar = tqdm(train_loader, file=sys.stdout) # 设置训练的进度条
- for step, data in enumerate(train_bar):
- images, labels = data # 将data分成图像和标签
- optimizer.zero_grad() # 梯度清零
- outputs = net(images.to(device)) # 正向传播获得输出
- loss = loss_function(outputs, labels.to(device)) # 计算损失值
- loss.backward() # 损失进行反向传播
- optimizer.step() # 更新权重w
-
- # 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 # 将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() # 将预测值与真实值进行对比,如果相等,eq(predict_y, val_label)返回为1,否则为0;然后用sum求和求出预测对了多少
-
- 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')
- 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():
- ### 1、寻找可使用的GPU,如果没有就用cpu
- device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
- ### 2、 定义图像预处理方式
- data_transform = transforms.Compose(
- [transforms.Resize((224, 224)), # 将图像resize成224*224的
- transforms.ToTensor(), # 变成C*H*W格式,并且归一化
- transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) # 标准化
-
- ### 3、加载进需要的预测的图像
- img_path = "rose.jpeg"
- assert os.path.exists(img_path), "file: '{}' dose not exist.".format(img_path)
- img = Image.open(img_path)
- ### 4、图像预处理
- plt.imshow(img) # 显示图像
- # [N, C, H, W]
- img = data_transform(img) # 预处理
- # expand batch dimension
- img = torch.unsqueeze(img, dim=0) # 拓展成四维,增加一个批次纬度
-
- ### 5、获取类别
- # 加载json文件获取类别
- 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)
-
- ### 6、定义模型,加载权重,然后预测
- # 定义模型
- model = AlexNet(num_classes=5).to(device)
-
- # 加载模型权重
- 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()) # 在json文件里找到索引对应的种类名称打印出来,而且加上概率值
- 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 版权所有,并保留所有权利。