# 9.1 圖像增廣
在5.6節(深度卷積神經網絡)里我們提到過,大規模數據集是成功應用深度神經網絡的前提。圖像增廣(image augmentation)技術通過對訓練圖像做一系列隨機改變,來產生相似但又不同的訓練樣本,從而擴大訓練數據集的規模。圖像增廣的另一種解釋是,隨機改變訓練樣本可以降低模型對某些屬性的依賴,從而提高模型的泛化能力。例如,我們可以對圖像進行不同方式的裁剪,使感興趣的物體出現在不同位置,從而減輕模型對物體出現位置的依賴性。我們也可以調整亮度、色彩等因素來降低模型對色彩的敏感度。可以說,在當年AlexNet的成功中,圖像增廣技術功不可沒。本節我們將討論這個在計算機視覺里被廣泛使用的技術。
首先,導入實驗所需的包或模塊。
``` python
%matplotlib inline
import time
import torch
from torch import nn, optim
from torch.utils.data import Dataset, DataLoader
import torchvision
from PIL import Image
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
```
## 9.1.1 常用的圖像增廣方法
我們來讀取一張形狀為$400\times 500$(高和寬分別為400像素和500像素)的圖像作為實驗的樣例。
``` python
d2l.set_figsize()
img = Image.open('../../img/cat1.jpg')
d2l.plt.imshow(img)
```
下面定義繪圖函數`show_images`。
``` python
# 本函數已保存在d2lzh_pytorch包中方便以后使用
def show_images(imgs, num_rows, num_cols, scale=2):
figsize = (num_cols * scale, num_rows * scale)
_, axes = d2l.plt.subplots(num_rows, num_cols, figsize=figsize)
for i in range(num_rows):
for j in range(num_cols):
axes[i][j].imshow(imgs[i * num_cols + j])
axes[i][j].axes.get_xaxis().set_visible(False)
axes[i][j].axes.get_yaxis().set_visible(False)
return axes
```
大部分圖像增廣方法都有一定的隨機性。為了方便觀察圖像增廣的效果,接下來我們定義一個輔助函數`apply`。這個函數對輸入圖像`img`多次運行圖像增廣方法`aug`并展示所有的結果。
``` python
def apply(img, aug, num_rows=2, num_cols=4, scale=1.5):
Y = [aug(img) for _ in range(num_rows * num_cols)]
show_images(Y, num_rows, num_cols, scale)
```
:-: 
### 9.1.1.1 翻轉和裁剪
左右翻轉圖像通常不改變物體的類別。它是最早也是最廣泛使用的一種圖像增廣方法。下面我們通過`torchvision.transforms`模塊創建`RandomHorizontalFlip`實例來實現一半概率的圖像水平(左右)翻轉。
``` python
apply(img, torchvision.transforms.RandomHorizontalFlip())
```

上下翻轉不如左右翻轉通用。但是至少對于樣例圖像,上下翻轉不會造成識別障礙。下面我們創建`RandomVerticalFlip`實例來實現一半概率的圖像垂直(上下)翻轉。
``` python
apply(img, torchvision.transforms.RandomVerticalFlip())
```
:-: 
在我們使用的樣例圖像里,貓在圖像正中間,但一般情況下可能不是這樣。在5.4節(池化層)里我們解釋了池化層能降低卷積層對目標位置的敏感度。除此之外,我們還可以通過對圖像隨機裁剪來讓物體以不同的比例出現在圖像的不同位置,這同樣能夠降低模型對目標位置的敏感性。
在下面的代碼里,我們每次隨機裁剪出一塊面積為原面積$10\% \sim 100\%$的區域,且該區域的寬和高之比隨機取自$0.5 \sim 2$,然后再將該區域的寬和高分別縮放到200像素。若無特殊說明,本節中$a$和$b$之間的隨機數指的是從區間$[a,b]$中隨機均勻采樣所得到的連續值。
``` python
shape_aug = torchvision.transforms.RandomResizedCrop(200, scale=(0.1, 1), ratio=(0.5, 2))
apply(img, shape_aug)
```
:-: 
### 9.1.1.2 變化顏色
另一類增廣方法是變化顏色。我們可以從4個方面改變圖像的顏色:亮度(`brightness`)、對比度(`contrast`)、飽和度(`saturation`)和色調(`hue`)。在下面的例子里,我們將圖像的亮度隨機變化為原圖亮度的$50\%$($1-0.5$)$\sim 150\%$($1+0.5$)。
``` python
apply(img, torchvision.transforms.ColorJitter(brightness=0.5))
```
:-: 
我們也可以隨機變化圖像的色調。
``` python
apply(img, torchvision.transforms.ColorJitter(hue=0.5))
```

類似地,我們也可以隨機變化圖像的對比度。
``` python
apply(img, torchvision.transforms.ColorJitter(contrast=0.5))
```

我們也可以同時設置如何隨機變化圖像的亮度(`brightness`)、對比度(`contrast`)、飽和度(`saturation`)和色調(`hue`)。
``` python
color_aug = torchvision.transforms.ColorJitter(
brightness=0.5, contrast=0.5, saturation=0.5, hue=0.5)
apply(img, color_aug)
```

### 9.1.1.3 疊加多個圖像增廣方法
實際應用中我們會將多個圖像增廣方法疊加使用。我們可以通過`Compose`實例將上面定義的多個圖像增廣方法疊加起來,再應用到每張圖像之上。
``` python
augs = torchvision.transforms.Compose([
torchvision.transforms.RandomHorizontalFlip(), color_aug, shape_aug])
apply(img, augs)
```
:-: 
## 9.1.2 使用圖像增廣訓練模型
下面我們來看一個將圖像增廣應用在實際訓練中的例子。這里我們使用CIFAR-10數據集,而不是之前我們一直使用的Fashion-MNIST數據集。這是因為Fashion-MNIST數據集中物體的位置和尺寸都已經經過歸一化處理,而CIFAR-10數據集中物體的顏色和大小區別更加顯著。下面展示了CIFAR-10數據集中前32張訓練圖像。
``` python
all_imges = torchvision.datasets.CIFAR10(train=True, root="~/Datasets/CIFAR", download=True)
# all_imges的每一個元素都是(image, label)
show_images([all_imges[i][0] for i in range(32)], 4, 8, scale=0.8);
```
:-: 
**為了在預測時得到確定的結果,我們通常只將圖像增廣應用在訓練樣本上,而不在預測時使用含隨機操作的圖像增廣**。在這里我們只使用最簡單的隨機左右翻轉。此外,我們使用`ToTensor`將小批量圖像轉成PyTorch需要的格式,即形狀為(批量大小, 通道數, 高, 寬)、值域在0到1之間且類型為32位浮點數。
``` python
flip_aug = torchvision.transforms.Compose([
torchvision.transforms.RandomHorizontalFlip(),
torchvision.transforms.ToTensor()])
no_aug = torchvision.transforms.Compose([
torchvision.transforms.ToTensor()])
```
接下來我們定義一個輔助函數來方便讀取圖像并應用圖像增廣。有關`DataLoader`的詳細介紹,可參考更早的3.5節圖像分類數據集(Fashion-MNIST)。
``` python
num_workers = 0 if sys.platform.startswith('win32') else 4
def load_cifar10(is_train, augs, batch_size, root="~/Datasets/CIFAR"):
dataset = torchvision.datasets.CIFAR10(root=root, train=is_train, transform=augs, download=True)
return DataLoader(dataset, batch_size=batch_size, shuffle=is_train, num_workers=num_workers)
```
### 9.1.2.1 使用圖像增廣訓練模型
> 原書本節使用的多GPU, 由于我這里卡比較緊張就不使用多GPU了...關于PyTorch多GPU的使用可參考8.4節。
我們在CIFAR-10數據集上訓練5.11節(殘差網絡)中介紹的ResNet-18模型。
我們先定義`train`函數使用GPU訓練并評價模型。
``` python
# 本函數已保存在d2lzh_pytorch包中方便以后使用
def train(train_iter, test_iter, net, loss, optimizer, device, num_epochs):
net = net.to(device)
print("training on ", device)
batch_count = 0
for epoch in range(num_epochs):
train_l_sum, train_acc_sum, n, start = 0.0, 0.0, 0, time.time()
for X, y in train_iter:
X = X.to(device)
y = y.to(device)
y_hat = net(X)
l = loss(y_hat, y)
optimizer.zero_grad()
l.backward()
optimizer.step()
train_l_sum += l.cpu().item()
train_acc_sum += (y_hat.argmax(dim=1) == y).sum().cpu().item()
n += y.shape[0]
batch_count += 1
test_acc = d2l.evaluate_accuracy(test_iter, net)
print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f, time %.1f sec'
% (epoch + 1, train_l_sum / batch_count, train_acc_sum / n, test_acc, time.time() - start))
```
然后就可以定義`train_with_data_aug`函數使用圖像增廣來訓練模型了。該函數使用Adam算法作為訓練使用的優化算法,然后將圖像增廣應用于訓練數據集之上,最后調用剛才定義的`train`函數訓練并評價模型。
``` python
def train_with_data_aug(train_augs, test_augs, lr=0.001):
batch_size, net = 256, d2l.resnet18(10)
optimizer = torch.optim.Adam(net.parameters(), lr=lr)
loss = torch.nn.CrossEntropyLoss()
train_iter = load_cifar10(True, train_augs, batch_size)
test_iter = load_cifar10(False, test_augs, batch_size)
train(train_iter, test_iter, net, loss, optimizer, device, num_epochs=10)
```
下面使用隨機左右翻轉的圖像增廣來訓練模型。
``` python
train_with_data_aug(flip_aug, no_aug)
```
輸出:
```
training on cuda
epoch 1, loss 1.3615, train acc 0.505, test acc 0.493, time 123.2 sec
epoch 2, loss 0.5003, train acc 0.645, test acc 0.620, time 123.0 sec
epoch 3, loss 0.2811, train acc 0.703, test acc 0.616, time 123.1 sec
epoch 4, loss 0.1890, train acc 0.735, test acc 0.686, time 123.0 sec
epoch 5, loss 0.1346, train acc 0.765, test acc 0.671, time 123.1 sec
epoch 6, loss 0.1029, train acc 0.787, test acc 0.674, time 123.1 sec
epoch 7, loss 0.0803, train acc 0.804, test acc 0.749, time 123.1 sec
epoch 8, loss 0.0644, train acc 0.822, test acc 0.717, time 123.1 sec
epoch 9, loss 0.0526, train acc 0.836, test acc 0.750, time 123.0 sec
epoch 10, loss 0.0433, train acc 0.851, test acc 0.754, time 123.1 sec
```
## 小結
* 圖像增廣基于現有訓練數據生成隨機圖像從而應對過擬合。
* 為了在預測時得到確定的結果,通常只將圖像增廣應用在訓練樣本上,而不在預測時使用含隨機操作的圖像增廣。
* 可以從torchvision的`transforms`模塊中獲取有關圖片增廣的類。
-----------
> 注:本節與原書有一些不同,[原書傳送門](https://zh.d2l.ai/chapter_computer-vision/image-augmentation.html)
- Home
- Introduce
- 1.深度學習簡介
- 深度學習簡介
- 2.預備知識
- 2.1環境配置
- 2.2數據操作
- 2.3自動求梯度
- 3.深度學習基礎
- 3.1 線性回歸
- 3.2 線性回歸的從零開始實現
- 3.3 線性回歸的簡潔實現
- 3.4 softmax回歸
- 3.5 圖像分類數據集(Fashion-MINST)
- 3.6 softmax回歸的從零開始實現
- 3.7 softmax回歸的簡潔實現
- 3.8 多層感知機
- 3.9 多層感知機的從零開始實現
- 3.10 多層感知機的簡潔實現
- 3.11 模型選擇、反向傳播和計算圖
- 3.12 權重衰減
- 3.13 丟棄法
- 3.14 正向傳播、反向傳播和計算圖
- 3.15 數值穩定性和模型初始化
- 3.16 實戰kaggle比賽:房價預測
- 4 深度學習計算
- 4.1 模型構造
- 4.2 模型參數的訪問、初始化和共享
- 4.3 模型參數的延后初始化
- 4.4 自定義層
- 4.5 讀取和存儲
- 4.6 GPU計算
- 5 卷積神經網絡
- 5.1 二維卷積層
- 5.2 填充和步幅
- 5.3 多輸入通道和多輸出通道
- 5.4 池化層
- 5.5 卷積神經網絡(LeNet)
- 5.6 深度卷積神經網絡(AlexNet)
- 5.7 使用重復元素的網絡(VGG)
- 5.8 網絡中的網絡(NiN)
- 5.9 含并行連結的網絡(GoogLeNet)
- 5.10 批量歸一化
- 5.11 殘差網絡(ResNet)
- 5.12 稠密連接網絡(DenseNet)
- 6 循環神經網絡
- 6.1 語言模型
- 6.2 循環神經網絡
- 6.3 語言模型數據集(周杰倫專輯歌詞)
- 6.4 循環神經網絡的從零開始實現
- 6.5 循環神經網絡的簡單實現
- 6.6 通過時間反向傳播
- 6.7 門控循環單元(GRU)
- 6.8 長短期記憶(LSTM)
- 6.9 深度循環神經網絡
- 6.10 雙向循環神經網絡
- 7 優化算法
- 7.1 優化與深度學習
- 7.2 梯度下降和隨機梯度下降
- 7.3 小批量隨機梯度下降
- 7.4 動量法
- 7.5 AdaGrad算法
- 7.6 RMSProp算法
- 7.7 AdaDelta
- 7.8 Adam算法
- 8 計算性能
- 8.1 命令式和符號式混合編程
- 8.2 異步計算
- 8.3 自動并行計算
- 8.4 多GPU計算
- 9 計算機視覺
- 9.1 圖像增廣
- 9.2 微調
- 9.3 目標檢測和邊界框
- 9.4 錨框
- 10 自然語言處理
- 10.1 詞嵌入(word2vec)
- 10.2 近似訓練
- 10.3 word2vec實現
- 10.4 子詞嵌入(fastText)
- 10.5 全局向量的詞嵌入(Glove)
- 10.6 求近義詞和類比詞
- 10.7 文本情感分類:使用循環神經網絡
- 10.8 文本情感分類:使用卷積網絡
- 10.9 編碼器--解碼器(seq2seq)
- 10.10 束搜索
- 10.11 注意力機制
- 10.12 機器翻譯