当前位置:   article > 正文

基于VGG-Face的年龄估计(论文总结与代码解释)_vggface

vggface

目录

1.本文改进

2.模型结构

3.训练模型

4.预测数据

5.实验结果

6.模型比较

7.结论分析 

8.数据集分析 

9.完整代码 


1.本文改进

本文主要是使用VGG-Face模型(卷积层不变,改变全连接层)在Adience数据库上进行年龄估计。

在深度神经网络中,由于深度神经网络有数百万个参数,由于它们有若干层和数千个节点,因此过拟合问题变得更加严重。所有用于年龄分类和预测的数据库都相对较小。它们在大小上无法与其他用于人脸识别和图像分类任务的数据库相提并论。为了克服过度拟合的问题,我们通过使用在一个非常大的数据库上训练的人脸识别深度CNN模型来构建我们提出的用于年龄估计的深度CNN。 

2.模型结构

       有一些CNN模型被成功地训练用于人脸识别任务。在这篇论文中,使用在2015年提出的VGG-face模型,该模型在LFW]和YFT数据库上取得了最先进的结果。VGG-Face由11个层、8个卷积层和3个全连接层组成。如下图所示,每个卷积层后面都有一个整流层(ReLU函数),在每个卷积块的末端运行一个max pool层。 


 VGG-Face模型在2015年的Deep Face Recognition论文中已提出,本文就是使用该模型进行年龄估计。VGG-Face模型如下:


本文保持VGG- Face模型的卷积层不变,在前两个Conv的relu函数之后添加BatchNorm2d进行数据的归一化处理,这使得数据在进行Relu之前不会因为数据过大而导致网络性能的不稳定。 同时用四个新的全连接层替换全连接层,构建并重新训练VGG- Face模型用于年龄估计。 前三个全连接层之后是dropout=0.5层和relu层。 第一全连接层尺寸为4096,第二、第三全连接层尺寸为5000。 最后一个全连接层表示一个N-way类预测器,其中N表示数据库中标签(类)的数量,该模型输出层的输出大小表示年龄标签的数量为8。

注:为什么全连接层的类型也属于卷积层?因为全连接层是卷积层的一种特殊情况,其中过滤器的大小和输入数据是相同的。 卷积层可以通过改变卷积基转变为全连接层。 

下面是使用pytorch实现模型结构:

卷积层:

  1. def cnn_layers(in_channels, batch_norm=False): # 卷积层不变,不使用归一化处理
  2. # fmt: off
  3. config = [ # 卷积层布局
  4. 64, 64, "M", # 卷积、卷积、池化、
  5. 128, 128, "M", # 卷积、卷积、池化、
  6. 256, 256, 256, "M", # 卷积、卷积、卷积、池化、
  7. 512, 512, 512, "M", # 卷积、卷积、卷积、池化、
  8. 512, 512, 512, "M" # 卷积、卷积、卷积、池化、
  9. ] # 总共5个卷积层
  10. layers = [] # 创建一个存放层的空列表
  11. for v in config:
  12. # maxpool
  13. if v == "M": # 添加池化层
  14. layers += [nn.MaxPool2d(kernel_size=2, stride=2)] # 2*2大小的窗口,步幅为2
  15. # conv2d layers
  16. else: # 否则继续添加卷积层
  17. conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1) # 卷积层3*3
  18. # 在前两个卷积层relu函数后面添加归一化处理
  19. if batch_norm:
  20. layers += [
  21. conv2d,
  22. nn.BatchNorm2d(v),
  23. nn.ReLU(inplace=True),
  24. ]
  25. else:
  26. layers += [conv2d, nn.ReLU(inplace=True)] # 添加激活函数
  27. # update in_channels
  28. in_channels = v
  29. return nn.Sequential(*layers)
  30. # *作用在形参上,代表这个位置接收任意多个非关键字参数,转化成元组方式;*作用在实参上,代表的是将输入迭代器拆成一个个元素。

 全连接层:

  1. def fc_layers(num_classes):
  2. # fully connected layers of vgg
  3. return nn.Sequential(
  4. nn.Linear(512 * 7 * 7, 512),
  5. nn.ReLU(True),
  6. nn.Dropout(),
  7. nn.Linear(512, 512),
  8. nn.ReLU(True),
  9. nn.Dropout(),
  10. nn.Linear(512, num_classes),
  11. )

VGG16:

然后我们创建一个VGG16模型的类,全连接层和卷积层是必须要放入这个类中的,但是由于代码行数比较多,我们单独定义函数,再在类中调用。

  1. class vgg16(nn.Module): # nn.Module是nn中十分重要的类,包含网络各层的定义及forward方法
  2. def __init__(self, num_classes, channels=3):
  3. # __init__还是有个特殊之处,那就是它不允许有返回值
  4. # 一般把网络中具有可学习参数的层放在构造函数__init__()中。
  5. # 不具有可学习参数的层(如ReLU)可放在构造函数中
  6. # vgg16 module
  7. #
  8. # parameters -------------------------
  9. # - num_classes - number of outputs to predict要预测的输出数量
  10. # - channels - number of input channels (eg. RGB:3)
  11. # inheriting from module class
  12. # 从模块类继承
  13. super(vgg16, self).__init__() # 这是对继承自父类的属性进行初始化
  14. # metadata
  15. self.name = "vgg16"
  16. self.num_classes = num_classes # 左边为示例属性,右边的是_init_的参数
  17. # layers
  18. self.features = cnn_layers(channels) # 调用卷积层
  19. self.classifier = fc_layers(num_classes) # 调用全连接层
  20. self.init_weights() # 权重初始化
  21. # transfer to gpu if cuda found
  22. if torch.cuda.is_available():
  23. self.cuda()
  24. def forward(self, x):
  25. x = self.features(x)
  26. # input首先经过self.features(x)卷积层,此时的输出x是包含batchsize维度为4的tensor,
  27. # 即(batchsize,channels,x,y),x.size(0)指batchsize的值。
  28. x = x.view(x.size(0), -1)
  29. # 将前面多维度的tensor展平成一维,简化x = x.view(batchsize, -1)
  30. # 其中batchsize指转换后有几行,而-1指在不告诉函数有多少列的情况下,根据原tensor数据和batchsize自动分配列数。
  31. # 其实相当于x = torch.flatten(x, 1)。四维[n,c,h,w]转换成二维[n,c*h*w]
  32. x = self.classifier(x) # 分类器
  33. return x
  34. def memory_usage(self):
  35. # Get the total parameters of the model获取模型的总参数
  36. def multiply_iter(iterable):
  37. res = 1
  38. for x in iterable:
  39. res *= x
  40. return res
  41. def add_params(parameter):
  42. res = 0
  43. for x in parameter:
  44. res += multiply_iter(x.shape)
  45. return res
  46. feat = add_params(self.features.parameters()) # 卷积层的参数
  47. clsf = add_params(self.classifier.parameters()) # 全连接层的参数
  48. total = feat + clsf # 总参数
  49. mb_f = 4 / 1024 ** 2
  50. print("Conv : {0}".format(feat))
  51. print("FC : {0}".format(clsf))
  52. print("-----------------")
  53. print("Total : {0}".format(total))
  54. print("Memory : {0:.2f}MB".format(total * mb_f))
  55. print("")
  56. def init_weights(self): # 权重初始化
  57. for m in self.modules():
  58. # 来判断一个对象是否是一个已知的类型,类似type()
  59. # isinstance(object, classinfo) object -- 实例对象。
  60. # classinfo -- 可以是直接或间接类名、基本类型或者由它们组成的元组。
  61. if isinstance(m, nn.Conv2d): # 判断m是否为卷积层
  62. nn.init.kaiming_normal_(
  63. m.weight, mode="fan_out", nonlinearity="relu"
  64. )
  65. if m.bias is not None:
  66. nn.init.constant_(m.bias, 0)
  67. # tensor – n 维 torch.Tensor
  68. # a – 该层后面一层的整流函数中负的斜率 (默认为 0,此时为 Relu)
  69. # mode – ‘fan_in’ (default) 或者 ‘fan_out’。使用fan_in保持weights的方差在前向传播中不变;使用fan_out保持weights的方差在反向传播中不变。
  70. # nonlinearity – 非线性函数 (nn.functional 中的名字),推荐只使用 ‘relu’ 或 ‘leaky_relu’ (default)
  71. elif isinstance(m, nn.BatchNorm2d):
  72. nn.init.constant_(m.weight, 1) # 用值1填充m.weight
  73. nn.init.constant_(m.bias, 0) # 用值0填充m.bias
  74. elif isinstance(m, nn.Linear):
  75. nn.init.normal_(m.weight, 0, 0.01) # torch.nn.init.normal_(tensor, mean=0.0, std=1.0)
  76. # N(mean, std^2) 用正态分布的值填充m.weight
  77. nn.init.constant_(m.bias, 0)
  78. def load_weights(self, saved_dict, ignore_keys=[]):
  79. # indexable ordered dict
  80. state_dict = self.state_dict()
  81. saved_dict = list(saved_dict.items())
  82. # update state_dict where pretrained dict is similar
  83. for i, (key, val) in enumerate(state_dict.items()):
  84. space = " " * (25 - len(str(key))) + " "
  85. n_val = saved_dict[i][1]
  86. if (
  87. key not in ignore_keys
  88. and val.shape == n_val.shape
  89. ):
  90. state_dict[key] = n_val
  91. print(" " + str(key) + space + "Loaded")
  92. else:
  93. print(" " + str(key) + space + "Ignored")
  94. self.load_state_dict(state_dict)
  95. def freeze_cnn_layers(self, except_last=0):
  96. num_params = len(list(self.features.parameters())) # 卷积层总的参数个数
  97. state_keys = [key for key in self.features.state_dict()]
  98. """
  99. pytorch 中的 state_dict 是一个简单的python的字典对象,将每一层与它的对应参数建立映射关
  100. 系.(如model的每一层的weights及偏置等等)
  101. (注意,只有那些参数可以训练的layer才会被保存到模型的state_dict中,如卷积层,线性层等等)
  102. 优化器对象Optimizer也有一个state_dict,它包含了优化器的状态以及被使用的超参数(如lr,
  103. momentum,weight_decay等)
  104. """
  105. for i, param in enumerate(self.features.parameters()): # 把所有参数自动编号
  106. key = state_keys[i]
  107. space = " " * (25 - len(str(key))) + " "
  108. if num_params - i > except_last:
  109. param.requires_grad = False # 卷积层
  110. print(" " + str(key) + space + "Frozen")
  111. else:
  112. param.requires_grad = True # 全连接层
  113. print(" " + str(key) + space + "Active")

3.训练模型

       输入图像被缩放到256 x 256像素,然后随机裁剪成224 x 224像素的小块。 采用随机梯度下降法对网络进行优化,最小批数为256,动量值为0.9。 此外,权重衰减设置为10-3。 在训练过程中,使用0.6的dropout rate对网络参数进行正则化。 训练以0.1的学习率开始,然后当验证集的准确性结果没有改善时,学习率降低10倍。 新添加的全连接层之间的权值采用均值为零、标准差为10-2高斯分布初始化,而偏差初始化为零。

       RGB输入图像被馈送到网络的输入层。 然后将每一隐藏层的输出作为输入馈送到下一隐藏层,直到计算出网络输出层(最后一层)的概率。   随机梯度象限法优化并找到连接层的参数,使用于估计年龄的softmax-log-loss预测最小化。 同时,卷积层的参数保持不变。 换句话说,我们优化了全连接层的参数来预测被试的年龄,而不改变卷积层的参数,卷积层是为人脸识别任务而训练和优化的。

  1. import numpy
  2. import torch
  3. from torch.utils.data import DataLoader
  4. # Dataset是一个包装类,用来将数据包装为Dataset类,然后传入DataLoader中,我们再使用DataLoader这个类来更加快捷的对数据进行操作。
  5. # DataLoader是一个比较重要的类,它为我们提供的常用操作有:batch_size(每个batch的大小),
  6. # shuffle(是否进行shuffle操作), num_workers(加载数据的时候使用几个子进程)
  7. from torch.utils.data.sampler import SubsetRandomSampler
  8. from torchvision import datasets
  9. from torchvision import transforms
  10. # 计算机视觉常用工具包,包含常用图像预处理、常用数据集实现、常用模型预训练。
  11. # global variables 全局变量
  12. # can change from outside
  13. random_scale = (0.4, 1.0)
  14. mean = [0.5, 0.5, 0.5]
  15. std = [0.2, 0.2, 0.2]
  16. def get_transforms(): # 定义一个图像变换函数做预处理
  17. # global语句是一个声明,它保存了整个当前代码块。这意味着列出的标识符将被解释为全局标识符。
  18. # 如果没有global,就不可能给全局变量赋值,尽管自由变量可以在没有声明global的情况下引用globals。
  19. # 在全局语句中列出的名称不能在该全局语句文本前面的同一代码块中使用。
  20. # 全局语句中列出的名称不能定义为形式参数,也不能定义为for循环控制目标、类定义、函数定义或import语句。
  21. global std
  22. global mean
  23. global random_scale
  24. # Compose()类,这个类的主要作用是串联多个图片变换的操作
  25. # 将transforms列表里面的transform操作进行遍历
  26. train_transform = transforms.Compose( # 训练数据的预处理
  27. [
  28. transforms.RandomResizedCrop((224, 224), scale=random_scale), # 随机长宽比裁剪
  29. transforms.RandomHorizontalFlip(), # 依概率p水平翻转
  30. transforms.ToTensor(),
  31. # 将PIL Image或者 ndarray 转换为tensor,是将输入的数据shape W,H,C ——> C,W,H,并且归一化至[0-1]
  32. transforms.Normalize(mean=mean, std=std), # 用平均值和标准偏差归一化张量图像
  33. ]
  34. )
  35. valid_transform = transforms.Compose( # 验证数据的预处理
  36. [
  37. transforms.Resize((224, 224)), # 调整图像大小
  38. transforms.ToTensor(), # 归一化
  39. transforms.Normalize(mean=mean, std=std), # # 用平均值和标准偏差归一化张量图像
  40. ]
  41. )
  42. return (train_transform, valid_transform)
  43. def find_mean_std(train_dir):
  44. """
  45. Get the mean and std per channel
  46. very slow because of two passes
  47. parameters -------------------------
  48. - train_dir - path of training set 训练集路径
  49. returns ----------------------------
  50. - mean - mean of the dataset per channel
  51. - std - standard deviation per channel
  52. """
  53. pin_memory = True if torch.cuda.is_available() else False # 判断GPU是否可用
  54. train_transform = transforms.Compose( # Compose()类,这个类的主要作用是串联多个图片变换的操作
  55. # 将transforms列表里面的transform操作进行遍历
  56. [transforms.Resize((224, 224)), transforms.ToTensor()]
  57. ) # torchvision.datasets这个包中包含MNIST、FakeData、COCO、LSUN、ImageFolder、DatasetFolder、ImageNet、CIFAR等一些常用的数据集,;
  58. train_dataset = datasets.ImageFolder(train_dir, train_transform)
  59. # 在train_dir路径下的图像进行train_transform
  60. # ImageFolder是一个通用的数据加载器,它要求我们以下面这种格式来组织数据集的训练、验证或者测试图片。;
  61. train_loader = DataLoader( # 数据读取
  62. # DataLoader是一个比较重要的类,它为我们提供的常用操作有:batch_size(每个batch的大小),
  63. # # shuffle(是否进行shuffle操作), num_workers(加载数据的时候使用几个子进程)
  64. train_dataset,
  65. batch_size=1,
  66. num_workers=0,
  67. pin_memory=pin_memory,
  68. )
  69. mn = torch.Tensor([0, 0, 0]) # 创建一个一维张量
  70. st = torch.Tensor([0, 0, 0])
  71. count = len(train_loader) # 多少个图片
  72. for input, target in train_loader:
  73. mn += input.mean([0, 2, 3])
  74. mn = mn / count # 平均值
  75. for input, target in train_loader: # 三个通道
  76. ch0 = (input[0][0] - mn[0])
  77. ch1 = (input[0][1] - mn[1])
  78. ch2 = (input[0][2] - mn[2])
  79. st[0] += torch.mul(ch0, ch0).sum() / 50176
  80. st[1] += torch.mul(ch1, ch1).sum() / 50176
  81. st[2] += torch.mul(ch2, ch2).sum() / 50176
  82. # st = root(sum(x^2) / N)
  83. st = torch.sqrt(st / count)
  84. return (mn, st)
  85. def split_loader(
  86. train_dir, valid_frac=0.1, batch_size=32, shuffle=True,
  87. ):
  88. """
  89. Function for splitting and loading train and valid iterators
  90. 函数用于分割和加载训练和验证迭代器
  91. parameters -------------------------
  92. - train_dir - path of training set 训练集路径
  93. - valid_frac - fraction split of the training set used for validation训练集分割用于验证的
  94. - batch_size - how many samples per batch to load
  95. - shuffle - whether to shuffle the train or validation indices打乱数据
  96. returns ----------------------------
  97. - train_loader - training set iterator
  98. - valid_loader - validation set iterator
  99. """
  100. # valid frac range assert
  101. error_msg = "Error : valid_frac should be in the range [0, 1]"
  102. assert (valid_frac >= 0) and (valid_frac <= 1), error_msg
  103. # override if cuda is available
  104. pin_memory = True if torch.cuda.is_available() else False # 判断GPU是否可用
  105. # load as dataset
  106. train_transform, valid_transform = get_transforms()
  107. train_dataset = datasets.ImageFolder(train_dir, train_transform)
  108. valid_dataset = datasets.ImageFolder(train_dir, valid_transform)
  109. # get indices
  110. num_train = len(train_dataset)
  111. indices = list(range(num_train)) # 099的向量
  112. split = int(valid_frac * num_train) #
  113. # shuffle if required
  114. if shuffle:
  115. numpy.random.shuffle(indices) # [1,2,3]打乱成[2,3,1]
  116. # samplers
  117. train_idx, valid_idx = indices[split:], indices[:split] # 分开训练验证
  118. train_sampler = SubsetRandomSampler(train_idx)
  119. valid_sampler = SubsetRandomSampler(valid_idx)
  120. # 采样器都随机地从原始的数据集中抽样数据。抽样数据采用permutation。 生成任意一个下标重排,从而利用下标来提取dataset中的数据的方法
  121. # dataloaders 数据加载
  122. train_loader = DataLoader(
  123. train_dataset,
  124. batch_size=batch_size,
  125. sampler=train_sampler,
  126. num_workers=0, # 使用多进程加载的进程数,0代表不使用多进程
  127. # dataloader一次性创建num_worker个worker,(也可以说dataloader一次性创建num_worker个工作进程,worker也是普通的工作进程),
  128. # 并用batch_sampler将指定batch分配给指定worker,worker将它负责的batch加载进RAM。
  129. pin_memory=pin_memory,
  130. )
  131. valid_loader = DataLoader(
  132. valid_dataset,
  133. batch_size=batch_size,
  134. sampler=valid_sampler,
  135. num_workers=0,
  136. pin_memory=pin_memory,
  137. )
  138. return (train_loader, valid_loader)
  139. def separate_loader(
  140. train_dir, valid_dir, batch_size=32, shuffle=True,
  141. ):
  142. """
  143. Function for splitting and loading train and valid iterators
  144. parameters -------------------------
  145. - train_dir - path of training set
  146. - valid_dir - path of validation set
  147. - batch_size - how many samples per batch to load
  148. - shuffle - whether to shuffle the train or validation indices
  149. returns ----------------------------
  150. - train_loader - training set iterator
  151. - valid_loader - validation set iterator
  152. """
  153. # load as dataset
  154. train_transform, valid_transform = get_transforms()
  155. train_dataset = datasets.ImageFolder(train_dir, train_transform)
  156. valid_dataset = datasets.ImageFolder(valid_dir, valid_transform)
  157. # override if cuda is available
  158. pin_memory = True if torch.cuda.is_available() else False
  159. # dataloaders
  160. train_loader = DataLoader(
  161. train_dataset,
  162. shuffle=shuffle,
  163. batch_size=batch_size,
  164. num_workers=0,
  165. pin_memory=pin_memory,
  166. )
  167. valid_loader = DataLoader(
  168. valid_dataset,
  169. shuffle=False,
  170. batch_size=batch_size,
  171. num_workers=0,
  172. pin_memory=pin_memory,
  173. )
  174. return (train_loader, valid_loader)
  175. def test_loader(test_dir, batch_size=32, shuffle=False):
  176. """
  177. Function for loading test image iterators迭代器
  178. parameters -------------------------
  179. - test_dir - path of image folder
  180. - batch_size - how many samples per batch to load
  181. returns ----------------------------
  182. - test_loader - data iterator
  183. """
  184. # override if cuda is available
  185. pin_memory = True if torch.cuda.is_available() else False
  186. # load as dataset
  187. valid_transform = get_transforms()
  188. test_dataset = datasets.ImageFolder(test_dir, valid_transform)
  189. # dataloaders
  190. test_loader = DataLoader(
  191. test_dataset,
  192. batch_size=batch_size,
  193. num_workers=0,
  194. pin_memory=pin_memory,
  195. shuffle=shuffle,
  196. )
  197. return test_loader
  198. def load_pth(path):
  199. # just for code completeness
  200. device = "cuda:0" if torch.cuda.is_available() else "cpu"
  201. return torch.load(path, map_location=device)
  1. import time
  2. import shutil
  3. import torch
  4. # global variables全局变量
  5. best_acc1 = 0
  6. # Average Value Computer Class
  7. class AverageMeter(object): # AverageMeter类来管理一些变量的更新
  8. def __init__(self):
  9. self.reset()
  10. def reset(self): # 重置方法reset
  11. self.val = 0 # 精度
  12. self.avg = 0 # 平均值
  13. self.sum = 0 # 总和
  14. self.count = 0 # 图片总个数
  15. def update(self, val, n=1): # 变量更新
  16. self.val = val
  17. self.sum += val * n
  18. self.count += n
  19. self.avg = self.sum / self.count
  20. def _train(train_loader, model, criterion, optimizer, epoch):
  21. """
  22. One epoch train function
  23. parameters -------------------------
  24. - train_loader - train data generator object
  25. - model - torch model object
  26. - criterion - loss function object
  27. - optimizer - optimizer object
  28. - epoch - epoch number to train
  29. returns ----------------------------
  30. - None
  31. """
  32. # 对各个参数进行重置
  33. losses = AverageMeter()
  34. top1 = AverageMeter()
  35. # top1--就是你预测的label取最后概率向量里面最大的那一个作为预测结果,
  36. # 如果你的预测结果中概率最大的那个分类正确,则预测正确。否则预测错误
  37. top5 = AverageMeter()
  38. # top5-就是最后概率向量最大的前五名中,只要出现了正确概率即为预测正确。否则预测错误。
  39. cuda_exists = torch.cuda.is_available()
  40. len_train = len(train_loader) # 训练数据个数
  41. # switch to train mode
  42. model.train() # 训练模型
  43. print("")
  44. print("EPOCH : {}".format(epoch))
  45. for i, (input, target) in enumerate(train_loader): # 数据迭代读取的循环函数#, 遍历整个训练数据,自动将所有数据按顺序编号
  46. if cuda_exists:
  47. input = input.cuda(non_blocking=True)
  48. target = target.cuda(non_blocking=True)
  49. # compute output
  50. output = model(input) # 计算输出
  51. loss = criterion(output, target) # 计算损失
  52. # measure accuracy and record loss测量精度和记录损失
  53. acc1, acc5 = _accuracy(output, target, topk=(1, 5))
  54. losses.update(loss.item(), input.size(0))
  55. top1.update(acc1[0], input.size(0))
  56. top5.update(acc5[0], input.size(0))
  57. # compute gradient and do SGD step
  58. # 计算梯度和做随机梯度下降步长
  59. optimizer.zero_grad() # 梯度归0
  60. loss.backward() # 损失回传
  61. optimizer.step() # 步长
  62. # 20 bars to display progress
  63. bar = (20 * (i + 1)) // len_train
  64. print(
  65. "\r"
  66. "(" + str(i + 1) + "/" + str(len_train) + ")"
  67. "[" + "=" * bar + "_" * (20 - bar) + "] "
  68. "Loss: {loss.val:.4f} ({loss.avg:.4f}) "
  69. "Acc@1: {top1.val:.3f} ({top1.avg:.3f}) "
  70. "Acc@5: {top5.val:.3f} ({top5.avg:.3f})".format(
  71. loss=losses, top1=top1, top5=top5,
  72. ),
  73. end="",
  74. )
  75. print("")
  76. def _validate(valid_loader, model, criterion):
  77. """
  78. Validation function
  79. parameters -------------------------
  80. - valid_loader - validation data generator object
  81. - model - torch model object
  82. - criterion - loss function object
  83. returns ----------------------------
  84. - top1.avg - top 1 average accuracy
  85. """
  86. losses = AverageMeter()
  87. top1 = AverageMeter()
  88. top5 = AverageMeter()
  89. cuda_exists = torch.cuda.is_available()
  90. len_valid = len(valid_loader)
  91. # switch to evaluate mode
  92. model.eval()
  93. print("VALIDATION :")
  94. with torch.no_grad(): # 是一个上下文管理器
  95. for i, (input, target) in enumerate(valid_loader):
  96. if cuda_exists:
  97. input = input.cuda(non_blocking=True)
  98. target = target.cuda(non_blocking=True)
  99. # compute output
  100. output = model(input)
  101. loss = criterion(output, target)
  102. # measure accuracy and record loss
  103. acc1, acc5 = _accuracy(output, target, topk=(1, 5))
  104. losses.update(loss.item(), input.size(0))
  105. top1.update(acc1[0], input.size(0))
  106. top5.update(acc5[0], input.size(0))
  107. # 20 bars to display progress
  108. bar = (20 * (i + 1)) // len_valid
  109. print(
  110. "\r"
  111. "(" + str(i + 1) + "/" + str(len_valid) + ")"
  112. "[" + "=" * bar + "_" * (20 - bar) + "] "
  113. "Loss: {loss.val:.4f} ({loss.avg:.4f}) "
  114. "Acc@1: {top1.val:.3f} ({top1.avg:.3f}) "
  115. "Acc@5: {top5.val:.3f} ({top5.avg:.3f})".format(
  116. loss=losses, top1=top1, top5=top5,
  117. ),
  118. end="",
  119. )
  120. print("")
  121. return top1.avg
  122. def _accuracy(output, target, topk=(1,)):
  123. """
  124. Compute the accuracy over the k top predictions
  125. 计算前面k个预测的准确性
  126. parameters -------------------------
  127. - output - model output tensor
  128. - target - actual label tensor
  129. - topk - top k accuracy values to return
  130. returns ----------------------------
  131. - res - list of k top accuracies
  132. """
  133. num_classes = 1
  134. for dim in output.shape[1:]:
  135. num_classes *= dim
  136. with torch.no_grad(): # torch.no_grad() 是一个上下文管理器,被该语句 wrap 起来的部分将不会track 梯度。
  137. maxk = max(topk)
  138. maxk = min(maxk, num_classes)
  139. batch_size = target.size(0)
  140. _, pred = output.topk(maxk, 1, True, True)
  141. pred = pred.t()
  142. correct = pred.eq(target.view(1, -1).expand_as(pred))
  143. res = []
  144. for k in topk:
  145. if k < num_classes:
  146. correct_k = correct[:k].view(-1).float().sum(0, keepdim=True)
  147. res.append(correct_k.mul_(100.0 / batch_size))
  148. else:
  149. res.append([0, 0])
  150. return res
  151. def train(
  152. model,
  153. loaders,
  154. lr=0.01,
  155. momentum=0.9,
  156. weight_decay=1e-4,
  157. epochs=10,
  158. checkpoint=None,
  159. ):
  160. """
  161. The main worker function used to train network
  162. parameters -------------------------
  163. - model - torch nn module
  164. - loaders - tuple of train and validation DataLoader
  165. - lr - learning rate of model
  166. - momentum - weighted average coefficient (alpha)
  167. - weight_decay - decay of weights coefficient (eta)
  168. - epochs - number of iterations to train
  169. - checkpoint - checkpoint dict
  170. returns ----------------------------
  171. - None
  172. """
  173. global best_acc1
  174. # create model
  175. print("=> training", model.name)
  176. # unpack loaders
  177. train_loader, valid_loader = loaders
  178. # find device
  179. if torch.cuda.is_available():
  180. device = torch.device("cuda:0")
  181. print("=> found cuda compatible gpu")
  182. else:
  183. device = torch.device("cpu")
  184. print("=> no cuda devices found, using cpu for training")
  185. # device switches and optimization
  186. torch.backends.cudnn.benchmark = True
  187. # loss and optimizer
  188. criterion = torch.nn.CrossEntropyLoss().to(device=device)
  189. optimizer = torch.optim.SGD(
  190. model.parameters(), lr, momentum, weight_decay=weight_decay,
  191. )
  192. # resume from a checkpoint
  193. if checkpoint:
  194. start_epoch = checkpoint["epoch"]
  195. best_acc1 = checkpoint["best_acc1"]
  196. model.load_state_dict(checkpoint["state_dict"])
  197. optimizer.load_state_dict(checkpoint["optimizer"])
  198. print("=> loaded checkpoint", end=" ")
  199. print("with epoch = %d" % start_epoch, end=" ")
  200. print("and accuracy = %.2f" % best_acc1)
  201. else:
  202. start_epoch = 0
  203. crtm = time.ctime().split()[1:-1]
  204. print("=> checkpoints will be saved as checkpoint.pth")
  205. print("=> training started at %s-%s %s" % (crtm[0], crtm[1], crtm[2]))
  206. # training
  207. for epoch in range(start_epoch, epochs):
  208. # adjust learning rate
  209. lr_adj = lr * (0.1 ** (epoch // 30))
  210. for param_group in optimizer.param_groups:
  211. param_group["lr"] = lr_adj
  212. # train for one epoch
  213. _train(
  214. train_loader, model, criterion, optimizer, epoch,
  215. )
  216. # remember best accuracy
  217. acc1 = _validate(valid_loader, model, criterion)
  218. is_best = acc1 > best_acc1
  219. best_acc1 = max(acc1, best_acc1)
  220. # save checkpoint
  221. save_dict = {
  222. "epoch": epoch + 1,
  223. "arch": model.name,
  224. "best_acc1": best_acc1,
  225. "state_dict": model.state_dict(),
  226. "optimizer": optimizer.state_dict(),
  227. }
  228. torch.save(save_dict, "checkpoint.pth")
  229. def confusion_matrix(model, valid_loader):
  230. """
  231. Obtain confusion matrix from prediction
  232. and actual labels
  233. 由预测得到混淆矩阵 和实际的标签
  234. """
  235. len_valid = len(valid_loader)
  236. cuda_exists = True if torch.cuda.is_available() else False
  237. # confusion matrix of ncls * ncls
  238. ncls = model.num_classes
  239. conf_matrix = torch.zeros(ncls, ncls)
  240. # switch to evaluate mode
  241. model.eval()
  242. print("VALIDATION :")
  243. with torch.no_grad():
  244. for i, (input, target) in enumerate(valid_loader):
  245. # compute output
  246. if cuda_exists:
  247. input = input.cuda(non_blocking=True)
  248. target = target.cuda(non_blocking=True)
  249. output = model(input)
  250. _, preds = torch.max(output, 1)
  251. for t, p in zip(target.view(-1), preds.view(-1)):
  252. conf_matrix[t.long(), p.long()] += 1
  253. # 20 bars to display progress
  254. bar = (20 * (i + 1)) // len_valid
  255. print(
  256. "\r"
  257. "(" + str(i + 1) + "/" + str(len_valid) + ")"
  258. "[" + "=" * bar + "_" * (20 - bar) + "]",
  259. end="",
  260. )
  261. print("")
  262. # horiz normalization to get percentage
  263. norm_conf = []
  264. for row in conf_matrix:
  265. factor = float(row.sum())
  266. normed = [float(i) / factor for i in row]
  267. norm_conf.append(normed)
  268. return norm_conf

4.预测数据

测试图像被缩放到256x256像素。 然后提取大小为224x224的三幅图像。 第一幅图像是从原始测试图像的中心得到的。 第二幅图像和第三幅图像分别从原始测试图像的左下角右上角提取。 利用训练后的CCN网络,将提取的三幅图像输入模型,计算每幅图像的softmax概率输出向量。 获得对原始测试图像的类分数的最终概率向量,对三幅图像的输出分数向量进行平均。 这种方法减少了低分辨率和遮挡等低质量图像的影响。

5.实验结果

1-off精度,表示结果在左或右一个相邻的年龄标签的误差时的精度。根据我们的结果,在精确精度和1-off精度方面,所提议的工作显著优于最先进的结果。这些结果证实了所提出的工作的有效性。表2给出了建议模型的混淆矩阵。

 

 

6.模型比较

与修改的GoogleNet模型进行比较: 为了进一步证明所提工作的有效性,对GoogLeNet模型进行了图像训练 ,在ImageNet ILSVRC数据库上进行了重新训练、微调和测试。 我们修改和微调了GoogLeNet CNN来执行年龄预测,完全替换连接层和改变节点的数量。 在修改后的体系结构中,有四个完全连接的层   每层节点数分别为1024、2048、2048和8。 然后修改后的  GoogLeNet被重新训练和微调,同时在训练期间保持卷积层不变  。

 改进后的GoogLeNet CNN在年龄估计方面达到了45.07%。 表六世通过使用VGG-Face CNN和GoogLeNet CNN,给出了所提模型的性能 估计。   为了进行年龄估计,使用了经过训练的GoogLeNet CNN进行图像分类任务   提供合理的结果。 但是,从表VI的结果可以明显看出,使用CNN提取的特征针对人脸识别任务训练的CNN模型比使用CNN模型提取的特征更有效图像分类训练。  

7.结论分析 

在本文中,提出了一个基于面部图像的年龄估计模型,该模型使用深度CNN称为VGG-Face,它是在一个大型数据库上训练的人脸识别。 对VGG-Face CNN进行了改进和微调,以进行年龄估计。 提出的模型在Adience数据库上比之前的算法提高了9%,Adience数据库是最新的具有挑战性的年龄估计基准,由无约束的人脸图像组成。 GoogLeNet是在一个包含数百万训练图像的大型数据库上进行训练的,它在年龄估计方面的性能与提出的使用VGG-Face的模型不具有竞争力。 不仅训练图像的数量和训练数据库中被训练对象的数量影响年龄估计的性能,而且所使用的CNN的训练前任务也决定了网络的年龄估计性能。

8.数据集分析 


9.完整代码 

完整代码 :总共4个py文件:loader.py、models.py、worker.py、vgg16.py(下面代码)

  1. # 读取CSV文件
  2. import pandas as pd
  3. frames = []
  4. folder_path = "F:/研究生/数据集/Adience_adience/adience/"
  5. for i in range(5):
  6. temp_df = pd.read_csv(folder_path + "fold_" + str(i) + "_data.txt", delimiter="\t")
  7. """
  8. pandas提供了pd.read_csv()方法可以读取其中的数据并且转换成DataFrame数据帧(一个矩阵样式的数据表)。
  9. python的强大之处就在于他可以把不同的数据库类型,比如txt/csv/.xls/.sql转换成统一的DataFrame格式然后进行统一的处理。真是做到了标准化
  10. """
  11. frames.append(temp_df) # 加载5个文件,依次放入frames列表的末尾中
  12. df = pd.concat(frames) # 把各个文件数据根据不同的轴简单融合
  13. df.head() # 观察前5行的数据。括号内没有数字默认为5个,要加载任意个数需添加任意的数
  14. # print(df.head())
  15. # print(df)
  16. # for cleaning anomalies清理异常
  17. # 不同年龄放入不同的年龄区间段,字典
  18. map_dict = {
  19. "13": "(08, 12)",
  20. "2": "(00, 02)",
  21. "22": "(15, 20)",
  22. "23": "(25, 32)",
  23. "29": "(25, 32)",
  24. "3": "(00, 02)",
  25. "32": "(25, 32)",
  26. "34": "(25, 32)",
  27. "35": "(25, 32)",
  28. "36": "(38, 43)",
  29. "42": "(38, 43)",
  30. "45": "(38, 43)",
  31. "46": "(48, 53)",
  32. "55": "(48, 53)",
  33. "56": "(48, 53)",
  34. "57": "(60, 100)",
  35. "58": "(60, 100)",
  36. "(8, 23)": "(08, 12)",
  37. "(27, 32)": "(25, 32)",
  38. "(38, 42)": "(38, 43)",
  39. "(38, 48)": "(38, 43)",
  40. "(00, 02)": "(00, 02)",
  41. "(04, 06)": "(04, 06)",
  42. "(08, 12)": "(08, 12)",
  43. "(15, 20)": "(15, 20)",
  44. "(25, 32)": "(25, 32)",
  45. "(38, 43)": "(38, 43)",
  46. "(48, 53)": "(48, 53)",
  47. "(60, 100)": "(60, 100)"
  48. }
  49. def map_func(x): # 定义一个年龄分布函数
  50. if x in map_dict:
  51. return map_dict[x]
  52. else:
  53. return x
  54. df["age"] = df["age"].map(map_func) # map()函数
  55. # 重组目录
  56. import os
  57. import shutil
  58. folder_path = "F:/研究生/数据集/Adience_adience/adience/faces/" # 原始数据
  59. file_list = [] # 创建一个空文件列表
  60. formats = ["jpg", "png"]
  61. # 对数据集进行处理:指定文件路径-找文件夹包含的文件-把路径和文件名称连接起来-判断路径是否为目录-
  62. # pytorch数据加载程序无法识别目录结构。
  63. # 为了重组树,使它是可读的,每个文件都被移动到以其类命名的子文件夹下。
  64. for subdir in os.listdir(folder_path): # os.listdir用于返回指定的文件夹包含的文件或文件夹的名字的列表
  65. subpath = os.path.join(folder_path, subdir) # 连接两个或更多的路径名组件
  66. if os.path.isdir(subpath): # 判断某一路径是否为目录
  67. for f in os.listdir(subpath):
  68. filepath = os.path.join(subpath, f)
  69. part = f.split(".") # 拆分字符串。通过指定分隔符对字符串进行切片,并返回分割后的字符串列表(list)
  70. # os.path.split():按照路径将文件名和路径分割开
  71. if os.path.isfile(filepath) and part[-1] in formats: # 用于判断某一对象(需提供绝对路径)是否为文件
  72. file_list.append((subpath, f))
  73. im_len = len(file_list)
  74. print(im_len)
  75. for i, (filepath, filename) in enumerate(file_list): # 把所有文件路径和文件名按顺序编号
  76. # get the identifiers
  77. parts = filename.split(".") # 把文件名 filename按.分开
  78. user_id = filepath.split("/")[-1] # 把文件路径按/分开,去最后一个路径
  79. file_id = parts[-2] + "." + parts[-1] # 取文件名最后两个路径、
  80. face_id = int(parts[-3]) # 文件倒数第三个路径转化为整形
  81. # find class df是一个字典
  82. if df[(df["user_id"] == user_id) &(df["original_image"] == file_id) &(df["face_id"] == face_id)].empty:
  83. continue
  84. else:
  85. class_ = df[(df["user_id"] == user_id) & (df["original_image"] == file_id) & (df["face_id"] == face_id)]["age"].values[0]
  86. new_path = os.path.join('F:/研究生/数据集/Adience_adience/adience/faces/', class_)
  87. if not os.path.exists(new_path):
  88. os.makedirs(new_path)
  89. # move file
  90. new_path = os.path.join(new_path, filename)
  91. file_path=os.path.join(filepath, filename)
  92. shutil.move(file_path, new_path)
  93. # progress
  94. prog = (20 * (i + 1)) // im_len
  95. print("\r[" + "=" * prog + "_" * (20 - prog) + "]", end="")
  96. for subdir in os.listdir(folder_path):
  97. subpath = os.path.join(folder_path, subdir)
  98. if os.path.isdir(subpath):
  99. if subdir[0] != "(":
  100. shutil.rmtree(subpath) # 表示递归删除文件夹下的所有子文件夹和子文件。
  101. elif os.path.isfile(subpath):
  102. os.remove(subdir) # 删除subdir文件路径
  103. # 准备
  104. import torch
  105. import source.models as models
  106. import source.worker as worker
  107. import source.loader as loader
  108. # the mean and std of dataset are found by running this
  109. # 通过运行该程序,可以得到数据集的均值和标准差
  110. # takes some time to iterate twice
  111. # 需要一些时间迭代两次
  112. loader.find_mean_std("F:/研究生/数据集/Adience_adience/adience/faces/")
  113. loader.random_scale = (0.8, 1.0)
  114. loader.mean = [0.437, 0.340, 0.304]
  115. loader.std = [0.286, 0.252, 0.236]
  116. # Dataset Loader to feed into network
  117. # 数据集加载器将馈送到网络
  118. # 20% of data is used for validation
  119. loaders = loader.split_loader("F:/研究生/数据集/Adience_adience/adience/faces/", valid_frac=0.2, batch_size=32)
  120. # pretrained weights - for convolution layers
  121. state = loader.load_pth("F:/研究生/论文/vgg-age-master/vgg_face_dag.pth")
  122. # 模型初试化
  123. model = models.vgg16(num_classes=8) # 8
  124. model.load_weights(state) # 加载权重
  125. model.memory_usage() # 模型的总参数
  126. worker.train(model, loaders, lr=0.01, epochs=3) # 训练
  127. check = loader.load_pth("checkpoint.pth")
  128. model.load_weights(check["state_dict"])
  129. valid_loader = loaders[1]
  130. conf_mat = worker.confusion_matrix(model, valid_loader)
  131. # 混淆矩阵
  132. for row in conf_mat:
  133. for elem in row:
  134. print("%.2f"%(elem*100), end="\t")
  135. print("")
  136. ncls = len(conf_mat)
  137. tot_acc = 0
  138. for i in range(ncls):
  139. acc = conf_mat[i][i]
  140. # add left
  141. if i > 0:
  142. acc += conf_mat[i][i - 1]
  143. if i < ncls - 1:
  144. acc += conf_mat[i][i + 1]
  145. tot_acc += acc
  146. tot_acc = tot_acc / ncls
  147. print("%.2f" % (tot_acc * 100))
  148. # 相应的预测和目标标签随输入图像一起可视化。
  149. import os
  150. import torch
  151. import source.models as models
  152. import source.worker as worker
  153. import source.loader as loader
  154. import matplotlib.pyplot as plt
  155. import torchvision.transforms as transforms
  156. # from NewSource import model
  157. # from NewSource import valid_loader
  158. dire = "F:/研究生/数据集/Adience_adience/adience/faces/"
  159. classes = os.listdir(dire)
  160. classes.sort()
  161. idx_to_class = {i:classes[i] for i in range(len(classes))}
  162. print(idx_to_class)
  163. model.eval()
  164. batch_size = 32
  165. model.to(torch.device("cpu"))
  166. mean = loader.mean
  167. std = loader.std
  168. mn_inv = [-m/s for m, s in zip(mean, std)]
  169. sd_inv = [1/s for s in std]
  170. inv_transform = transforms.Normalize(mean=mn_inv, std=sd_inv)
  171. with torch.no_grad():
  172. for i, (input, target) in enumerate(valid_loader):
  173. output = model(input)
  174. _, preds = torch.max(output, 1)
  175. fig=plt.figure(figsize=(15, 15))
  176. columns = 4
  177. rows = 5
  178. for i in range(1, columns*rows + 1):
  179. pred_class = idx_to_class[int(preds[i])]
  180. real_class = idx_to_class[int(target[i])]
  181. ax = fig.add_subplot(rows, columns, i)
  182. ax.title.set_text("pred:" + pred_class + "," + "real:" + real_class)
  183. ax.axis("off")
  184. plt.imshow(inv_transform(input[i]).permute(1, 2, 0))
  185. break
  186. plt.show()

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

闽ICP备14008679号