当前位置:   article > 正文

笔记小结:现代卷积神经网络之批量归一化_batchnormalization的作用

batchnormalization的作用

本文为李沐老师《动手学深度学习》笔记小结,用于个人复习并记录学习历程,适用于初学者

训练深层神经网络是十分困难的,特别是在较短的时间内使他们收敛更加棘手。 本节将介绍批量规范化(batch normalization),这是一种流行且有效的技术,可持续加速深层网络的收敛速度。

从零开始实现

张量的批量规范化函数
  1. import torch
  2. from torch import nn
  3. def batch_norm(X, gamma, beta, moving_mean, moving_var, eps, momentum):
  4. # 通过is_grad_enabled来判断当前模式是训练模式还是预测模式
  5. if not torch.is_grad_enabled():
  6. # 如果是在预测模式下,直接使用传入的移动平均所得的均值和方差
  7. X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps)
  8. else:
  9. assert len(X.shape) in (2, 4)
  10. if len(X.shape) == 2:
  11. # 使用全连接层的情况,计算特征维上的均值和方差
  12. mean = X.mean(dim=0)
  13. var = ((X - mean) ** 2).mean(dim=0)
  14. else:
  15. # 使用二维卷积层的情况,计算通道维上(axis=1)的均值和方差。
  16. # 这里我们需要保持X的形状以便后面可以做广播运算
  17. mean = X.mean(dim=(0, 2, 3), keepdim=True)
  18. var = ((X - mean) ** 2).mean(dim=(0, 2, 3), keepdim=True)
  19. # 训练模式下,用当前的均值和方差做标准化
  20. X_hat = (X - mean) / torch.sqrt(var + eps)
  21. # 更新移动平均的均值和方差
  22. moving_mean = momentum * moving_mean + (1.0 - momentum) * mean
  23. moving_var = momentum * moving_var + (1.0 - momentum) * var
  24. Y = gamma * X_hat + beta # 缩放和移位
  25. return Y, moving_mean.data, moving_var.data
批量规范化层
  1. class BatchNorm(nn.Module):
  2. # num_features:完全连接层的输出数量或卷积层的输出通道数。
  3. # num_dims:2表示完全连接层,4表示卷积层
  4. def __init__(self, num_features, num_dims):
  5. super().__init__()
  6. if num_dims == 2:
  7. shape = (1, num_features)
  8. else:
  9. shape = (1, num_features, 1, 1)
  10. # 参与求梯度和迭代的拉伸和偏移参数,分别初始化成10
  11. self.gamma = nn.Parameter(torch.ones(shape))
  12. self.beta = nn.Parameter(torch.zeros(shape))
  13. # 非模型参数的变量初始化为01
  14. self.moving_mean = torch.zeros(shape)
  15. self.moving_var = torch.ones(shape)
  16. def forward(self, X):
  17. # 如果X不在内存上,将moving_mean和moving_var
  18. # 复制到X所在显存上
  19. if self.moving_mean.device != X.device:
  20. self.moving_mean = self.moving_mean.to(X.device)
  21. self.moving_var = self.moving_var.to(X.device)
  22. # 保存更新过的moving_mean和moving_var
  23. Y, self.moving_mean, self.moving_var = batch_norm(
  24. X, self.gamma, self.beta, self.moving_mean,
  25. self.moving_var, eps=1e-5, momentum=0.9)
  26. return Y
使用批量规范化层作用于LeNet

批量规范化是在卷积层或全连接层之后、相应的激活函数之前应用的。

  1. net = nn.Sequential(
  2. nn.Conv2d(1, 6, kernel_size=5), BatchNorm(6, num_dims=4), nn.Sigmoid(),
  3. nn.AvgPool2d(kernel_size=2, stride=2),
  4. nn.Conv2d(6, 16, kernel_size=5), BatchNorm(16, num_dims=4), nn.Sigmoid(),
  5. nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(),
  6. nn.Linear(16*4*4, 120), BatchNorm(120, num_dims=2), nn.Sigmoid(),
  7. nn.Linear(120, 84), BatchNorm(84, num_dims=2), nn.Sigmoid(),
  8. nn.Linear(84, 10))
训练
准备工作

和之前多篇文章中提到的一样,不再赘述,只给出代码

  1. from IPython import display
  2. import torchvision
  3. from torch.utils import data
  4. from torchvision import transforms
  5. import matplotlib.pyplot as plt
  6. def load_data_fashion_mnist(batch_size, resize=None):
  7. """下载Fashion-MNIST数据集,然后将其加载到内存中"""
  8. trans = [transforms.ToTensor()]
  9. if resize:
  10. trans.insert(0, transforms.Resize(resize))
  11. trans = transforms.Compose(trans)
  12. mnist_train = torchvision.datasets.FashionMNIST(
  13. root="../data", train=True, transform=trans, download=0)
  14. mnist_test = torchvision.datasets.FashionMNIST(
  15. root="../data", train=False, transform=trans, download=0)
  16. return (data.DataLoader(mnist_train, batch_size, shuffle=True,
  17. num_workers=get_dataloader_workers()),
  18. data.DataLoader(mnist_test, batch_size, shuffle=False,
  19. num_workers=get_dataloader_workers()))
  20. def get_dataloader_workers():
  21. """使用4个进程来读取数据"""
  22. return 4
  23. batch_size = 128
  24. train_iter, test_iter = load_data_fashion_mnist(batch_size, resize=224)
  25. lr, num_epochs, batch_size = 1.0, 10, 256
  26. train_iter, test_iter = load_data_fashion_mnist(batch_size)
  27. def accuracy(y_hat, y): #@save
  28. """计算预测正确的数量"""
  29. if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
  30. y_hat = y_hat.argmax(axis=1) #找出输入张量(tensor)中最大值的索引
  31. cmp = y_hat.type(y.dtype) == y
  32. return float(cmp.type(y.dtype).sum())
  33. class Accumulator: #@save
  34. """在n个变量上累加"""
  35. def __init__(self, n):
  36. self.data = [0.0] * n
  37. def add(self, *args):
  38. self.data = [a + float(b) for a, b in zip(self.data, args)]
  39. def reset(self):
  40. self.data = [0.0] * len(self.data)
  41. def __getitem__(self, idx):
  42. return self.data[idx]
  43. import matplotlib.pyplot as plt
  44. from matplotlib_inline import backend_inline
  45. def use_svg_display():
  46. """使⽤svg格式在Jupyter中显⽰绘图"""
  47. backend_inline.set_matplotlib_formats('svg')
  48. def set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend):
  49. """设置matplotlib的轴"""
  50. axes.set_xlabel(xlabel)
  51. axes.set_ylabel(ylabel)
  52. axes.set_xscale(xscale)
  53. axes.set_yscale(yscale)
  54. axes.set_xlim(xlim)
  55. axes.set_ylim(ylim)
  56. if legend:
  57. axes.legend(legend)
  58. axes.grid()
  59. class Animator: #@save
  60. """在动画中绘制数据"""
  61. def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None,
  62. ylim=None, xscale='linear', yscale='linear',
  63. fmts=('-', 'm--', 'g-.', 'r:'), nrows=1, ncols=1,
  64. figsize=(3.5, 2.5)):
  65. # 增量地绘制多条线
  66. if legend is None:
  67. legend = []
  68. use_svg_display()
  69. self.fig, self.axes = plt.subplots(nrows, ncols, figsize=figsize)
  70. if nrows * ncols == 1:
  71. self.axes = [self.axes, ]
  72. # 使用lambda函数捕获参数
  73. self.config_axes = lambda: set_axes(
  74. self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
  75. self.X, self.Y, self.fmts = None, None, fmts
  76. def add(self, x, y):
  77. # 向图表中添加多个数据点
  78. if not hasattr(y, "__len__"):
  79. y = [y]
  80. n = len(y)
  81. if not hasattr(x, "__len__"):
  82. x = [x] * n
  83. if not self.X:
  84. self.X = [[] for _ in range(n)]
  85. if not self.Y:
  86. self.Y = [[] for _ in range(n)]
  87. for i, (a, b) in enumerate(zip(x, y)):
  88. if a is not None and b is not None:
  89. self.X[i].append(a)
  90. self.Y[i].append(b)
  91. self.axes[0].cla()
  92. for x, y, fmt in zip(self.X, self.Y, self.fmts):
  93. self.axes[0].plot(x, y, fmt)
  94. self.config_axes()
  95. display.display(self.fig)
  96. display.clear_output(wait=True)
  97. def evaluate_accuracy_gpu(net, data_iter, device=None): #@save
  98. """使用GPU计算模型在数据集上的精度"""
  99. if isinstance(net, nn.Module):
  100. net.eval() # 设置为评估模式
  101. if not device:
  102. device = next(iter(net.parameters())).device
  103. # 正确预测的数量,总预测的数量
  104. metric = Accumulator(2)
  105. with torch.no_grad():
  106. for X, y in data_iter:
  107. if isinstance(X, list):
  108. # BERT微调所需的(之后将介绍)
  109. X = [x.to(device) for x in X]
  110. else:
  111. X = X.to(device)
  112. y = y.to(device)
  113. metric.add(accuracy(net(X), y), y.numel())
  114. return metric[0] / metric[1]
  115. import time
  116. class Timer: #@save
  117. """记录多次运行时间"""
  118. def __init__(self):
  119. self.times = []
  120. self.start()
  121. def start(self):
  122. """启动计时器"""
  123. self.tik = time.time()
  124. def stop(self):
  125. """停止计时器并将时间记录在列表中"""
  126. self.times.append(time.time() - self.tik)
  127. return self.times[-1]
  128. def avg(self):
  129. """返回平均时间"""
  130. return sum(self.times) / len(self.times)
  131. def sum(self):
  132. """返回时间总和"""
  133. return sum(self.times)
  134. def cumsum(self):
  135. """返回累计时间"""
  136. return np.array(self.times).cumsum().tolist()
  137. def train_ch6(net, train_iter, test_iter, num_epochs, lr, device):
  138. """用GPU训练模型(在第六章定义)"""
  139. def init_weights(m):
  140. if type(m) == nn.Linear or type(m) == nn.Conv2d:
  141. nn.init.xavier_uniform_(m.weight)
  142. net.apply(init_weights)
  143. print('training on', device)
  144. net.to(device)
  145. optimizer = torch.optim.SGD(net.parameters(), lr=lr)
  146. loss = nn.CrossEntropyLoss()
  147. animator = Animator(xlabel='epoch', xlim=[1, num_epochs],
  148. legend=['train loss', 'train acc', 'test acc'])
  149. timer, num_batches = Timer(), len(train_iter)
  150. for epoch in range(num_epochs):
  151. # 训练损失之和,训练准确率之和,样本数
  152. metric = Accumulator(3)
  153. net.train()
  154. for i, (X, y) in enumerate(train_iter):
  155. timer.start()
  156. optimizer.zero_grad()
  157. X, y = X.to(device), y.to(device)
  158. y_hat = net(X)
  159. l = loss(y_hat, y)
  160. l.backward()
  161. optimizer.step()
  162. with torch.no_grad():
  163. metric.add(l * X.shape[0], accuracy(y_hat, y), X.shape[0])
  164. timer.stop()
  165. train_l = metric[0] / metric[2]
  166. train_acc = metric[1] / metric[2]
  167. if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:
  168. animator.add(epoch + (i + 1) / num_batches,
  169. (train_l, train_acc, None))
  170. test_acc = evaluate_accuracy_gpu(net, test_iter)
  171. animator.add(epoch + 1, (None, None, test_acc))
  172. print(f'loss {train_l:.3f}, train acc {train_acc:.3f}, '
  173. f'test acc {test_acc:.3f}')
  174. print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec '
  175. f'on {str(device)}')
  176. def try_gpu(i=0): #@save
  177. """如果存在,则返回gpu(i),否则返回cpu()"""
  178. if torch.cuda.device_count() >= i + 1:
  179. return torch.device(f'cuda:{i}')
  180. return torch.device('cpu')
训练

和以前一样,我们将在Fashion-MNIST数据集上训练网络。 这个代码与我们第一次训练LeNet时几乎完全相同,主要区别在于学习率大得多。

  1. begin = time.time()
  2. train_ch6(net, train_iter, test_iter, num_epochs, lr, try_gpu())
  3. end = time.time()
  4. print(end - begin)

这个结果,对比当时不用批量归一化层的LeNet,训练的收敛速度快了许多,loss变小了,train acc提高了许多,但是test acc没有提高太多,出现了过拟合。 

简洁实现

除了使用我们刚刚定义的BatchNorm,我们也可以直接使用深度学习框架中定义的BatchNorm。 该代码看起来几乎与我们上面的代码相同。

  1. net = nn.Sequential(
  2. nn.Conv2d(1, 6, kernel_size=5), nn.BatchNorm2d(6), nn.Sigmoid(),
  3. nn.AvgPool2d(kernel_size=2, stride=2),
  4. nn.Conv2d(6, 16, kernel_size=5), nn.BatchNorm2d(16), nn.Sigmoid(),
  5. nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(),
  6. nn.Linear(256, 120), nn.BatchNorm1d(120), nn.Sigmoid(),
  7. nn.Linear(120, 84), nn.BatchNorm1d(84), nn.Sigmoid(),
  8. nn.Linear(84, 10))

下面,我们使用相同超参数来训练模型。 请注意,通常高级API变体运行速度快得多,因为它的代码已编译为C++或CUDA,而我们的自定义代码由Python实现。

  1. begin = time.time()
  2. train_ch6(net, train_iter, test_iter, num_epochs, lr, try_gpu())
  3. end = time.time()

从结果可以看到,运行速度快了,并且过拟合也小了许多。 

小结

  • 在模型训练过程中,批量规范化利用小批量的均值和标准差,不断调整神经网络的中间输出,使整个神经网络各层的中间输出值更加稳定。
  • 批量规范化在全连接层和卷积层的使用略有不同。
  • 批量规范化层和暂退层一样,在训练模式和预测模式下计算不同。
  • 批量规范化有许多有益的副作用,主要是正则化。另一方面,”减少内部协变量偏移“的原始动机似乎不是一个有效的解释。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/神奇cpp/article/detail/908022
推荐阅读
相关标签
  

闽ICP备14008679号