当前位置:   article > 正文

图像分类:搭建AlexNet并定义加载训练特定数据集_alexnet——使用深度卷积神经网络进行图像分类数据集训练集

alexnet——使用深度卷积神经网络进行图像分类数据集训练集

目录

 

前言

AlexNet搭建

模型架构

卷积神经网络输出

层设置

模型代码

数据集定义加载

自定义数据集

数据集定义代码

读取txt文件

处理加载数据集

数据集加载代码

训练测试

训练过程

测试过程


前言

AlexNet模型是2012由Alex等人在ImageNet竞赛中提出,很优秀的机器学习分类的算法。也由此深度学习开始迅速发展,更多更深的神经网络被提出。在对一个简单图像分类任务下手时,我们要考虑到数据集的定义加载,然后搭建模型并对数据集进行训练测试。本文将讲一下其过程和遇到的一些问题。

AlexNet搭建

模型架构

在模型最初被提出时,其结构如下图所示:

该模型主要有以下几个优点:

(1)首次利用GPU进行网络加速训练;

(2)使用了Relu激活函数(f(x)=max(0,x)),而不是传统的sigmoid和tanh函数,训练速度有了较大提升;

(3)提出了LRN(局部响应归一化)层,抑制了反馈较小的神经元,增强了模型的泛化能力;

(4)使用了Dropout和数据增强进而有效减少过拟合发生。

卷积神经网络输出

Output=WF+2PS+1

  • 输入图片大小 W×W(一般情况下Width=Height)
  • Filter大小F×F
  • 步长 S
  • padding的像素数 P

下面以AlexNet第一层卷积层、池化层为例:

  1. #卷积层1
  2. nn.Conv2d(in_channels=3, out_channels=48, kernel_size=11, stride=4,padding=0), # input[3, 224, 224] output[48, 55, 55]
  3. nn.ReLU(inplace=True), # 直接修改覆盖原值,节省运算内存
  4. #池化层1
  5. nn.MaxPool2d(kernel_size=3, stride=2), # output[48, 27, 27]
  6. #全连接层1
  7. nn.Dropout(p=0.5), # Dropout 随机失活神经元,默认比例为0.5
  8. nn.Linear(128 * 6 * 6, 2048),
  9. nn.ReLU(inplace=True),

卷积层1的几个参数:
       in_channels=3:表示的是输入的通道数,由于是RGB型的,所以通道数是3
       out_channels=96:表示的是输出的通道数,设定输出通道数的96(这个是可以根据自己的需要来设置的)
       kernel_size=11:表示卷积核的大小是11x11的,也就是上面的 “F”, F=11
       stride=4:表示的是步长为4,也就是上面的S, S=4
       padding=0:表示的是填充值的大小为2,也就是上面的P, P=2

假定图像输入大小为224*224,根据计算公式可以算得Output=55,那么输出的size即为55*55。通道数则为自己设定的数值。

通过全连接层我们看到里面使用了Dropout。该层包含权重向量和激活函数,将一个图片先拉伸为一向量作为输入,与权重向量点乘后再通过激活函数进一步输出。

层设置

cove1d:用于文本数据,只对宽度进行卷积,对高度不进行卷积
cove2d:用于图像数据,对宽度和高度都进行卷积

maxpool意为最大池化层,MaxPoold即为二维最大池化操作。池化层用于特征融合和降维。

模型代码

  1. def __init__(self, num_classes=63, init_weights=False):
  2. super(AlexNet, self).__init__()
  3. # 用nn.Sequential()将网络打包成一个模块,精简代码
  4. self.features = nn.Sequential( # 卷积层提取图像特征
  5. #卷积层1
  6. nn.Conv2d(3, 48, kernel_size=11, stride=4), # input[3, 224, 224] output[48, 55, 55]
  7. nn.ReLU(inplace=True), # 直接修改覆盖原值,节省运算内存
  8. #池化层1
  9. nn.MaxPool2d(kernel_size=3, stride=2), # output[48, 27, 27]
  10. #卷积层2
  11. nn.Conv2d(48, 128, kernel_size=5, padding=2), # output[128, 27, 27]
  12. nn.ReLU(inplace=True),
  13. #池化层2
  14. nn.MaxPool2d(kernel_size=3, stride=2), # output[128, 13, 13]
  15. #卷积层3
  16. nn.Conv2d(128, 192, kernel_size=3, padding=1), # output[192, 13, 13]
  17. nn.ReLU(inplace=True),
  18. #卷积层4
  19. nn.Conv2d(192, 192, kernel_size=3, padding=1), # output[192, 13, 13]
  20. nn.ReLU(inplace=True),
  21. #卷积层5
  22. nn.Conv2d(192, 128, kernel_size=3, padding=1), # output[128, 13, 13]
  23. nn.ReLU(inplace=True),
  24. #池化层3
  25. nn.MaxPool2d(kernel_size=3, stride=2), # output[128, 6, 6]
  26. )
  27. self.classifier = nn.Sequential( # 全连接层对图像分类
  28. #全连接层1
  29. nn.Dropout(p=0.5), # Dropout 随机失活神经元,默认比例为0.5
  30. nn.Linear(128 * 6 * 6, 2048),
  31. nn.ReLU(inplace=True),
  32. #全连接层2
  33. nn.Dropout(p=0.5),
  34. nn.Linear(2048, 2048),
  35. nn.ReLU(inplace=True),
  36. #全连接层3
  37. nn.Linear(2048, num_classes),
  38. )
  39. # if init_weights:
  40. # self._initialize_weights()
  41. # 前向传播过程
  42. def forward(self, x):
  43. x = self.features(x)
  44. x = torch.flatten(x, start_dim=1) # 展平后再传入全连接层
  45. x = self.classifier(x)
  46. return x

数据集定义加载

自定义数据集

untils.data包括Dataset和Dataloader,前者为一抽象类。自己定义的数据集需要继承抽象类class torch.utils.data.Dataset,并且需要重载两个重要的函数:__len__ __getitem__

  1. def getitem(self, index):
  2. def len(self):

其中__len__应该返回数据集的大小,而__getitem__应该编写支持数据集索引的函数
这里重点看 getitem函数,getitem接收一个index,然后返回图片数据和标签,这个index通常指的是一个list的index,这个list的每个元素就包含了图片数据的路径和标签信息。

数据集中包括两个文件夹,一个是图片(image),一个是标签(label)。我们需要将其一一对应,那这时就有两种方法。一种是读取标签信息所在的txt,对于图片名称加上路径再和label拼接组成一个txt;另一种方法则是读取图片所在的文件夹,读取图片路径后然后根据图片名称在标签信息txt中筛选找到对应的label然后组成一个新的txt。新的txt就可以作为数据集被读取加载。

 

数据集定义代码

  1. # *************************************数据集的设置****************************************************************************
  2. root = os.getcwd() + '/data/' # 数据集的地址
  3. # 定义读取文件的格式
  4. def default_loader(path):
  5. return Image.open(path).convert('RGB') #对于彩色图像返回RGB,对于灰度图像模式为L
  6. class MyDataset(Dataset):
  7. # 创建自己的类: MyDataset,这个类是继承的torch.utils.data.Dataset
  8. # ********************************** #使用__init__()初始化一些需要传入的参数及数据集的调用**********************
  9. def __init__(self, txt,mulu, transform=None, target_transform=None, loader=default_loader):
  10. super(MyDataset, self).__init__()
  11. # 对继承自父类的属性进行初始化
  12. fh = open(txt,'r')
  13. imgs = []
  14. # 按照传入的路径和txt文本参数,以只读的方式打开这个文本
  15. for line in fh: # 迭代该列表#按行循环txt文本中的内
  16. line = line.strip('\n')
  17. line = line.rstrip('\n')
  18. # 删除 本行string 字符串末尾的指定字符,这个方法的详细介绍自己查询python
  19. words = line.split()
  20. words[0] = './data/'+str(mulu)+'/'+str(words[0]) + '.JPG'
  21. # 用split将该行分割成列表 split的默认参数是空格,所以不传递任何参数时分割空格
  22. imgs.append((words[0], int(words[4])))
  23. #根据原本txt的内容,words[0]是图片信息,words[4]是lable
  24. self.imgs = imgs
  25. self.transform = transform
  26. self.target_transform = target_transform
  27. self.loader = loader
  28. def __getitem__(self, index): # 这个方法是必须要有的,用于按照索引读取每个元素的具体内容
  29. fn, label = self.imgs[index] # fn是图片path #fn和label分别获得imgs[index]也即是刚才每行中word[0]和word[4]的信息
  30. # print(fn)
  31. # img = Image.open(fn)
  32. img = self.loader(fn) # 按照路径读取图片
  33. if self.transform is not None:
  34. img = self.transform(img) # 数据标签转换为Tensor
  35. return img, label # return回哪些内容,那么我们在训练时循环读取每个batch时,就能获得哪些内容
  36. def __len__(self): # 这个函数它返回的是数据集的长度,也就是多少张图片,要和loader的长度作区分
  37. return len(self.imgs)

读取txt文件

open函数使用:open(name[, mode[, buffering]])
  • name : 一个包含了你要访问的文件名称的字符串值。

  • mode : mode 决定了打开文件的模式:只读,写入,追加等。所有可取值见如下的完全列表。这个参数是非强制的,默认文件访问模式为只读(r)。

  • buffering : 如果 buffering 的值被设为 0,就不会有寄存。如果 buffering 的值取 1,访问文件时会寄存行。如果将 buffering 的值设为大于 1 的整数,表明了这就是的寄存区的缓冲大小。如果取负值,寄存区的缓冲大小则为系统默认。

fh=oprn(txt,'r')其中r代表以只读的方式打开文件。读取的时候逐行读取,删除首尾的换行符,由于原txt第一列是图片名称,第五列是label,所以在给第一列加上路径信息后即可和第五列进行拼接。输出的imgs即为我们想要得到的信息。

处理加载数据集

在导入数据集时我们要对其进行预处理,然后进一步加载。 处理过程有很多种方法,包括裁剪、翻转、加入噪声等方法。通过调用untils.data.Dataloader可以加载数据集。关于Dataloader的使用还涉及到下面几个参数:

batch_size:可以分批次读取

shuffle=True可以对数据进行随机读取,可以对数据进行洗牌操作(shuffling),打乱数据集内数据分布的顺序

num_workers=0  可以并行加载数据(利用多核处理器加快载入数据的效率

 

数据集加载代码

  1. #数据预处理
  2. data_transform = {
  3. "train": transforms.Compose([transforms.RandomResizedCrop(227), # 随机裁剪,再缩放成 224×224
  4. transforms.RandomHorizontalFlip(p=0.5), # 水平方向随机翻转,概率为 0.5, 即一半的概率翻转, 一半的概率不翻转
  5. transforms.ToTensor(),
  6. transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]),
  7. "test": transforms.Compose([transforms.Resize((227, 227)), # cannot 224, must (224, 224)
  8. transforms.ToTensor(),
  9. transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])}
  10. #导入训练集并进行预处理
  11. train_data = MyDataset(txt=root+'IR1_05-17.txt',mulu='img_05-17', transform=data_transform["train"])
  12. train_num = len(train_data)
  13. print(train_num)
  14. #导入测试集并进行预处理
  15. test_data = MyDataset(txt=root+'IR1_18.txt',mulu='img_2018', transform=data_transform["test"])
  16. test_num = len(test_data)
  17. print(test_num)
  18. #print(test_data[1])
  19. #加载训练集
  20. train_loader = DataLoader(train_data, # 导入的训练集
  21. batch_size=64, # 每批训练的样本数
  22. shuffle=True, # 是否打乱训练集
  23. num_workers=0) # 使用线程数,在windows下设置为0
  24. #加载测试集
  25. test_loader = DataLoader(test_data, # 导入的测试集
  26. batch_size=64, # 每批测试的样本数
  27. shuffle=True, # 是否打乱测试集
  28. num_workers=0) # 使用线程数,在windows下设置为0

训练测试

在这个过程中,要注意以下两点:

  • net.train():训练过程中开启 Dropout
  • net.eval(): 测试过程关闭 Dropout

训练过程

神经网络模型训练是经过前向传播计算Loss,根据其值进行反向推导,然后调整优化参数。下面损失函数选用了交叉熵误差。同时还保存了模型,以便在测试过程中加载模型,这样就可以将训练和测试独立,方便测试。

  1. net = AlexNet(num_classes) # 实例化网络(输出类型)
  2. net.to(device) # 分配网络到指定的设备(GPU/CPU)训练
  3. loss_function = nn.CrossEntropyLoss() # 交叉熵损失
  4. optimizer = optim.Adam(net.parameters(), lr) # 优化器(训练参数,学习率)
  5. s_path = './lunwen.pkl'
  6. best_acc = 0.0
  7. for epoch in range(63):
  8. ########################################## train ###############################################
  9. net.train() # 训练过程中开启 Dropout
  10. running_loss = 0.0 # 每个 epoch 都会对 running_loss 清零
  11. time_start = time.perf_counter() # 对训练一个 epoch 计时
  12. for step, data in enumerate(train_loader, start=0): # 遍历训练集,step从0开始计算
  13. images, labels = data # 获取训练集的图像和标签
  14. #print(images.size())
  15. optimizer.zero_grad() # 清除历史梯度
  16. outputs = net(images.to(device)) # 正向传播
  17. loss = loss_function(outputs, labels.to(device)) # 计算损失
  18. loss.backward() # 反向传播
  19. optimizer.step() # 优化器更新参数
  20. running_loss += loss.item()
  21. # 打印训练进度(使训练过程可视化)
  22. rate = (step + 1) / len(train_loader) # 当前进度 = 当前step / 训练一轮epoch所需总step
  23. a = "*" * int(rate * 50)
  24. b = "." * int((1 - rate) * 50)
  25. print("\rtrain loss: {:^3.0f}%[{}->{}]{:.3f}".format(int(rate * 100), a, b, loss), end="")
  26. print()
  27. print('%f s' % (time.perf_counter() - time_start))
  28. torch.save(net, s_path)

测试过程

当图片经过模型输出outputs是一个不同类别对应的概率矩阵,假定类别为63类,那么outputs第一行就代表第一张图的被预测成0-62类的概率 ,可经过softmax进一步确定。torch.max()输出值最大的位置,假使它和原标签一致,那么便预测准确,再比上总数即可得到准确率。

其性能可看MAE和RMSE,在sklearn中均有相应函数,当然注释中也根据其定义写了一段代码。

  1. #加载模型
  2. net=torch.load('./lunwen.pth')
  3. net.to(device) # 分配网络到指定的设备(GPU/CPU)训练
  4. net.eval() # 验证过程中关闭 Dropout
  5. best_acc = 0.0
  6. pre=[]
  7. lab=[]
  8. acc = 0.0
  9. with torch.no_grad():
  10. for test_data in test_loader:
  11. test_images, test_labels = test_data
  12. #print(test_labels)
  13. num_test = test_labels.cpu().numpy()
  14. #print(num_test)
  15. lab.extend(num_test)
  16. #print(test_labels.item())
  17. outputs = net(test_images.to(device))
  18. #print(outputs.size())
  19. #print(outputs)
  20. predict_y = torch.max(outputs, dim=1)[1] # 以output中值最大位置对应的索引(标签)作为预测输出
  21. #print(predict_y.size())
  22. #print(predict_y)
  23. num_y = predict_y.cpu().numpy()
  24. #print(num_y)
  25. pre.extend(num_y)
  26. #y = torch.max(outputs, dim=1)[0]
  27. #mse = mean_squared_error(test_labels, predict_y)
  28. #print("MSE: %.4f" % mse)
  29. #print(y,predict_y,test_labels)
  30. acc += (predict_y == test_labels.to(device)).sum().item()
  31. print(acc)
  32. test_accurate = acc / test_num
  33. # 保存准确率最高的那次网络参数
  34. if test_accurate > best_acc:
  35. best_acc = test_accurate
  36. print(lab)
  37. print(pre)
  38. # error = []
  39. # for i in range(len(lab)):
  40. # error.append(lab[i] - pre[i])
  41. # squaredError = []
  42. # absError = []
  43. # for val in error:
  44. # squaredError.append(val * val) # target-prediction之差平方
  45. # absError.append(abs(val)) # 误差绝对值
  46. # print("1MSE = ", sum(squaredError) / len(squaredError)) # 均方误差MSE
  47. # print("1RMSE = ", sqrt(sum(squaredError) / len(squaredError))) # 均方根误差RMSE
  48. # print("1MAE = ", sum(absError) / len(absError)) # 平均绝对误差MAE
  49. mae = mean_absolute_error(lab,pre)
  50. mse = mean_squared_error(lab, pre)
  51. rmse = math.sqrt(mse)
  52. print("MAE: %.2f MSE: %.2f RMSE:%.2f" %(mae,mse,rmse) )
  53. print(' test_accuracy: %.3f \n' %test_accurate)

 

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/92701
推荐阅读
相关标签
  

闽ICP备14008679号