Mixup的实现很简单,单纯地将两张图片融合在一起,通过这种混合来增强模型的泛化性能。假设有两张图片 ( x i , y i ) (x_{i}, y_{i}) (xi,yi)和 ( x j , y j ) (x_{j}, y_{j}) (xj,yj), 其中 x x x表示图像, y y y是one-hot标签。 Mixup的核心公式如下所示,其中 λ \lambda λ服从beta分布, λ ∈ [ 0 , 1 ] \lambda \in [0,1] λ∈[0,1]。
x ~ = λ x i + ( 1 − λ ) x j y ~ = λ y i + ( 1 − λ ) y j \tilde{x} = \lambda x_{i} + (1 - \lambda) x_{j} \\ \tilde{y} = \lambda y_{i} + (1 - \lambda) y_{j} \\ x~=λxi+(1−λ)xjy~=λyi+(1−λ)yj
#下述代码来自于mmclassification/mmcls/models/utils/augment/cutmix.py
def mixup(self, img, gt_label):
# 将gt_label转换成one-hot编码方式
one_hot_gt_label = one_hot_encoding(gt_label, self.num_classes)
# lambda,服从beta分布
lam = np.random.beta(self.alpha, self.alpha)
# batch 中的图片打乱
batch_size = img.size(0)
index = torch.randperm(batch_size)
# 图像混合
mixed_img = lam * img + (1 - lam) * img[index, :]
# 标签混合
mixed_gt_label = lam * one_hot_gt_label + (
1 - lam) * one_hot_gt_label[index, :]
return mixed_img, mixed_gt_label
Cutout是卷积神经网络的一种最简单正则化技术,它去除输入图像的连续部分,有效地利用现有样本的部分遮挡来增强数据集。Cutout实现很简单,在训练时,将固定大小的零掩码(zero-mask)随机地应用在每一个epoch中的输入图像上。实验发现cutout的区域大小参数比形状参数更重要,因此,选择正方形作为cutout的区域应用于所有实验。当cutout应用于图像时,随机选择图像中的一个像素坐标作为中心点,然后在该位置周围放置cutout mask。允许并非cutout mask 的所有部分都包含在图像中。cutout论文发现允许部分patch位于图像边界外是cutout实现良好性能的关键。cutout的代码如下所示:
def cutout(img, shape, pad_val=0):
"""Randomly cut out a rectangle from the original img.
Args:
img (ndarray): Image to be cutout.
shape (tuple[int]): Expected cutout shape (h, w).
pad_val (float | tuple[int | float]): Values to be filled in the
cut area. Defaults to 0.
Returns:
ndarray: The cutout image.
"""
img_h, img_w = img.shape[:2]
y0 = np.random.uniform(img_h)
x0 = np.random.uniform(img_w)
y1 = int(max(0, y0 - cut_h / 2.))
x1 = int(max(0, x0 - cut_w / 2.))
y2 = min(img_h, y1 + cut_h)
x2 = min(img_w, x1 + cut_w)
patch_shape = (y2 - y1, x2 - x1)
img_cutout = img.copy()
patch = np.array(
pad_val, dtype=img.dtype) * np.ones(
patch_shape, dtype=img.dtype) # mask
img_cutout[y1:y2, x1:x2, ...] = patch
return img_cutout
假设 x ∈ R W × H × C x \in \mathbb{R}^{W \times H \times C} x∈RW×H×C和 y y y分别表示一个训练图像和它的标签。CutMix通过合并两个训练样本 ( x A , y A ) \left( x_{A}, y_{A}\right) (xA,yA)和 ( x B , y B ) \left( x_{B}, y_{B}\right) (xB,yB)生成一个新的训练样本 ( x ~ , y ~ ) \left( \tilde{x}, \tilde{y} \right) (x~,y~)。新生成的训练样本被用来训练模型。用公式表示为:
x ~ = M ⊙ x A + ( 1 − M ) ⊙ x B y ~ = λ y A + ( 1 − λ ) y B \begin{matrix} \tilde{x} = M \odot x_{A} + (1 - M) \odot x_{B} \\ \tilde{y} = \lambda y_{A} + (1-\lambda) y_{B} \qquad \quad \end{matrix} x~=M⊙xA+(1−M)⊙xBy~=λyA+(1−λ)yB
其中 M ∈ 0 , 1 W × H M \in {0,1}^{W \times H} M∈0,1W×H是一个二进制掩码,指示两个图像中删除和填充的位置。 1 1 1是一个全1的二进制编码。 ⊙ \odot ⊙表示按元素相乘。与Mixup相似,参数 λ \lambda λ属于beta分布 B e t a ( α , α ) Beta\left( \alpha, \alpha \right) Beta(α,α)。在CutMix中,设置 α = 1 \alpha=1 α=1,参数 λ \lambda λ服从均匀分布 λ ∼ U ( 0 , 1 ) \lambda \sim U\left( 0, 1 \right) λ∼U(0,1)。
# 下述代码来自于mmclassification/mmcls/models/utils/augment/cutmix.py
def cutmix(self, img, gt_label):
# 将gt_label转换成one-hot编码
one_hot_gt_label = one_hot_encoding(gt_label, self.num_classes)
# 参数lambda 生成
lam = np.random.beta(self.alpha, self.alpha)
# 将0~n-1(包括0和n-1)随机打乱后获得的数字序列
batch_size = img.size(0)
index = torch.randperm(batch_size)
# 生成mask M
(bby1, bby2, bbx1,
bbx2), lam = self.cutmix_bbox_and_lam(img.shape, lam)
# 填充
img[:, :, bby1:bby2, bbx1:bbx2] = \
img[index, :, bby1:bby2, bbx1:bbx2]
# 分类结果按照一定比例分配
mixed_gt_label = lam * one_hot_gt_label + (
1 - lam) * one_hot_gt_label[index, :]
return img, mixed_gt_label
要获得二进制掩码 M M M,首先获得裁剪区域的边界框坐标 B = ( r x , r y , r w , r h ) B=(r_{x}, r_{y}, r_{w}, r_{h}) B=(rx,ry,rw,rh),裁剪的区域 B B B在图像 x A x_{A} xA中移除,然后用从另一张图像 x B x_{B} xB中裁剪的区域 B B B进行填充。确保剪裁面积比为 r w r h W H = 1 − λ \frac{r_{w}r_{h}}{WH} = 1- \lambda WHrwrh=1−λ, 依据以下公式对bbox边界坐标进行均匀采样。
r x ∼ U n i f ( 0 , W ) , r w = W ( 1 − λ ) r y ∼ U n i f ( 0 , H ) , r h = H ( 1 − λ ) r_{x} \sim Unif(0,W) , r_{w} = W \sqrt{(1 - \lambda)} \\ r_{y} \sim Unif(0,H) , r_{h} = H \sqrt{(1 - \lambda)} rx∼Unif(0,W),rw=W(1−λ)ry∼Unif(0,H),rh=H(1−λ)
得到剪裁区域之后,二进制掩码 M M M中相对应的bbox B 区域填充为0,其他区域为1。
# 下述代码来自于mmclassification/mmcls/models/utils/augment/cutmix.py
def rand_bbox(self, img_shape, lam, margin=0., count=None):
"""Standard CutMix bounding-box that generates a random square bbox
based on lambda value. This implementation includes support for
enforcing a border margin as percent of bbox dimensions.
Args:
img_shape (tuple): Image shape as tuple
lam (float): Cutmix lambda value
margin (float): Percentage of bbox dimension to enforce as margin
(reduce amount of box outside image). Default to 0.
count (int, optional): Number of bbox to generate. Default to None
"""
ratio = np.sqrt(1 - lam)
img_h, img_w = img_shape[-2:] # H,W
cut_h, cut_w = int(img_h * ratio), int(img_w * ratio) # r_h, r_w
margin_y, margin_x = int(margin * cut_h), int(margin * cut_w)
cy = np.random.randint(0 + margin_y, img_h - margin_y, size=count) # r_x
cx = np.random.randint(0 + margin_x, img_w - margin_x, size=count) # r_y
yl = np.clip(cy - cut_h // 2, 0, img_h)
yh = np.clip(cy + cut_h // 2, 0, img_h)
xl = np.clip(cx - cut_w // 2, 0, img_w)
xh = np.clip(cx + cut_w // 2, 0, img_w)
return yl, yh, xl, xh # 左上顶点坐标(xl, yl) 右下顶点坐标:(xh, yh)