torchvision transforms V2

torchvison 0.17よりtransforms V2が正式版となりました。
transforms V2では、CutmixやMixUpなど新機能がサポートされるとともに高速化されているとのことです。基本的には、今まで（ここではV1と呼びます。）と互換性がありますが一部異なるところがあります。
主な変更点を書きたいと思います。

# import
V2への対応は、基本的には、importを以下のように変更すれば互換性をもって対応できます。

- transforms V1
```python
import torchvision.transforms as transforms
```

- transforms V2
```python
import torchvision.transforms.v2 as transforms
```

# ToTensor非推奨
ToTensorは、データをTensor型に変換するとともに0～1の間に正規化します。両方同時に行うので非常に便利でした。V2より非推奨になりました。Tensor型への変換と正規化を別々に行う必要があります。
PIL Imageを想定した対応方法です。
### Tensor型への変換
ToImageを利用します。イメージ用のTensorのサブクラスのImageに変換します。
numpyのデータやPIL Imageを変換することができます。

### 正規化
ToDtypeでデータを実数化し0～1の間に正規化します。引き数として、データ型のtorch.float32を指定し、正規化用にscale=Trueとします。

### 例
画像を読み込み0～1のTensor型に変換してみます。

- 画像読み込み
PILを利用し画像を読み込みます。

```python
from PIL import Image

#　画像の読み込み 
img = Image.open('dog-cat.jpg')
img
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212365/db821b2f-ea96-5721-ef0d-9c2440ceeb2f.png)

- transforms V1
ToTensorでTensor型に変換するとともに0～1に正規化します。

```python
import torchvision.transforms as transforms

img_ts = transforms.ToTensor()(img)
img_ts
```

```
tensor([[[0.0000, 0.0000, 0.0000,  ..., 0.0078, 0.0078, 0.0078],
         [0.0000, 0.0000, 0.0039,  ..., 0.0039, 0.0039, 0.0039],
         [0.0000, 0.0000, 0.0039,  ..., 0.0000, 0.0000, 0.0000],
         ...,
         [0.2706, 0.2627, 0.2549,  ..., 0.6784, 0.6745, 0.6706],
         [0.3255, 0.3216, 0.3176,  ..., 0.6824, 0.6745, 0.6706],
         [0.3451, 0.3490, 0.3451,  ..., 0.6784, 0.6706, 0.6667]],

        [[0.1882, 0.1843, 0.1765,  ..., 0.0941, 0.0941, 0.0941],
         [0.1843, 0.1843, 0.1804,  ..., 0.0902, 0.0902, 0.0902],
         [0.1843, 0.1843, 0.1804,  ..., 0.0824, 0.0824, 0.0824],
         ...,
         [0.3412, 0.3333, 0.3255,  ..., 0.6824, 0.6784, 0.6745],
         [0.3804, 0.3765, 0.3725,  ..., 0.6863, 0.6784, 0.6745],
         [0.3882, 0.3922, 0.3882,  ..., 0.6824, 0.6745, 0.6706]],

        [[0.4196, 0.4118, 0.4078,  ..., 0.2902, 0.2902, 0.2902],
         [0.4157, 0.4118, 0.4118,  ..., 0.2863, 0.2863, 0.2863],
         [0.4157, 0.4118, 0.4118,  ..., 0.2902, 0.2902, 0.2902],
         ...,
         [0.4275, 0.4118, 0.4039,  ..., 0.6627, 0.6588, 0.6549],
         [0.4157, 0.4118, 0.4078,  ..., 0.6667, 0.6588, 0.6549],
         [0.4039, 0.4078, 0.3961,  ..., 0.6627, 0.6549, 0.6510]]])
```

データ拡張例

```python
sample_transforms = transforms.Compose([
    transforms.RandomResizedCrop(size=(224, 224)),
    transforms.ToTensor()
])
```

データ拡張後の画像を確認してみます。ToPILImageでPILに変換しています。

```python
transforms.ToPILImage()(sample_transforms(img))
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212365/508c7b5f-2a54-129c-2a27-c02438902600.png)


- transforms V2
ToImageでTensor型(正式にはTensorのsubclassのImage)に変換します。ただし正規化は行われず0～255の整数型のままです。

```python
import torch
import torchvision.transforms.v2 as transforms

img_ts = transforms.ToImage()(img)
img_ts
```

```
Image([[[  0,   0,   0,  ...,   2,   2,   2],
        [  0,   0,   1,  ...,   1,   1,   1],
        [  0,   0,   1,  ...,   0,   0,   0],
        ...,
        [ 69,  67,  65,  ..., 173, 172, 171],
        [ 83,  82,  81,  ..., 174, 172, 171],
        [ 88,  89,  88,  ..., 173, 171, 170]],

       [[ 48,  47,  45,  ...,  24,  24,  24],
        [ 47,  47,  46,  ...,  23,  23,  23],
        [ 47,  47,  46,  ...,  21,  21,  21],
        ...,
        [ 87,  85,  83,  ..., 174, 173, 172],
        [ 97,  96,  95,  ..., 175, 173, 172],
        [ 99, 100,  99,  ..., 174, 172, 171]],

       [[107, 105, 104,  ...,  74,  74,  74],
        [106, 105, 105,  ...,  73,  73,  73],
        [106, 105, 105,  ...,  74,  74,  74],
        ...,
        [109, 105, 103,  ..., 169, 168, 167],
        [106, 105, 104,  ..., 170, 168, 167],
        [103, 104, 101,  ..., 169, 167, 166]]], dtype=torch.uint8, )
```

ToDtypeを利用し、実数型に変換するとともにscale=Trueで0～1に正規化します。

```python
img_ts = transforms.ToDtype(torch.float32, scale=True)(img_ts)
```

```
Image([[[0.0000, 0.0000, 0.0000,  ..., 0.0078, 0.0078, 0.0078],
        [0.0000, 0.0000, 0.0039,  ..., 0.0039, 0.0039, 0.0039],
        [0.0000, 0.0000, 0.0039,  ..., 0.0000, 0.0000, 0.0000],
        ...,
        [0.2706, 0.2627, 0.2549,  ..., 0.6784, 0.6745, 0.6706],
        [0.3255, 0.3216, 0.3176,  ..., 0.6824, 0.6745, 0.6706],
        [0.3451, 0.3490, 0.3451,  ..., 0.6784, 0.6706, 0.6667]],

       [[0.1882, 0.1843, 0.1765,  ..., 0.0941, 0.0941, 0.0941],
        [0.1843, 0.1843, 0.1804,  ..., 0.0902, 0.0902, 0.0902],
        [0.1843, 0.1843, 0.1804,  ..., 0.0824, 0.0824, 0.0824],
        ...,
        [0.3412, 0.3333, 0.3255,  ..., 0.6824, 0.6784, 0.6745],
        [0.3804, 0.3765, 0.3725,  ..., 0.6863, 0.6784, 0.6745],
        [0.3882, 0.3922, 0.3882,  ..., 0.6824, 0.6745, 0.6706]],

       [[0.4196, 0.4118, 0.4078,  ..., 0.2902, 0.2902, 0.2902],
        [0.4157, 0.4118, 0.4118,  ..., 0.2863, 0.2863, 0.2863],
        [0.4157, 0.4118, 0.4118,  ..., 0.2902, 0.2902, 0.2902],
        ...,
        [0.4275, 0.4118, 0.4039,  ..., 0.6627, 0.6588, 0.6549],
        [0.4157, 0.4118, 0.4078,  ..., 0.6667, 0.6588, 0.6549],
        [0.4039, 0.4078, 0.3961,  ..., 0.6627, 0.6549, 0.6510]]], )
```

データ拡張例
V1では最後にToTensorでTensor型に変換しましたが、V2でははじめにToImageでTensor型に変換することを推奨しています。
また、RandomResizedCropを利用する場合は、antialiasの既定値が変更されたようで明示的に指定します。

```python
sample_transforms = transforms.Compose([
    transforms.ToImage(),
    transforms.RandomResizedCrop(size=(224, 224), antialias=True),
    transforms.ToDtype(torch.float32, scale=True)
])
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212365/589f6783-de71-c1bc-a78b-0d207f9176a1.png)

標準化を行う場合は、V1,V2とも最後にNormalizeで行います。

# CutMix,MixUp
せっかくなのでCutMix、MixUpも試してみます。
以下の２つの画像を利用します。

```python
#　画像の読み込み 
img1 = Image.open('dog.jpg')
img1
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212365/7d6f0554-d884-bd79-b69c-11872a4ca545.png)

```python
#　画像の読み込み 
img2 = Image.open('cat.jpg')
img2
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212365/43f4af62-6369-be1f-f9e7-2cb0c1529110.png)

先ほど行ったデータ拡張を用いてTensor型にしておきます。

```python
img1_ts = sample_transforms(img1)
img2_ts = sample_transforms(img2)
```

### CutMix
CutMix,MixUpともミニバッチ学習時にデータ拡張することを想定されています。複数枚の画像を同時に変換する必要があります。
CutMixには、たとえば画像分類のクラス数を指定します。ここでは犬と猫の2を指定します。

```python
cutmix = transforms.CutMix(num_classes=2)
```
CutMixを行うとCutMix後のデータと各クラスの割合が返却されます。
ミニバッチ用に２つのデータをstackで結合しました。クラスは整数型のTensorで指定する必要があります。0を犬、1を猫として指定しています。

```python
cutmix_img_ts, cutmix_label = cutmix(torch.stack([img1_ts, img2_ts]), torch.LongTensor([0, 1]))
```

CutMix後の画像を表示してみます。

```python
transforms.ToPILImage()(cutmix_img_ts[0])
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212365/aa6794c1-fd8f-1dcc-b107-046af73e2160.png)

```python
transforms.ToPILImage()(cutmix_img_ts[1])
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212365/cd0154b8-9071-82a7-be3d-196c6c6786c7.png)

2つの画像がMixされていることがわかります。
クラスを確認してみます。

```python
cutmix_label
```

```
tensor([[0.7226, 0.2774],
        [0.2774, 0.7226]])
```

１枚目の画像は、犬が約0.72、猫が約0.28、２枚目の画像は逆になっています。画像を見るとその程度の割合になっていることがわかります。

### MixUp
CutMix同様にMixUpもミニバッチ学習時にデータ拡張することを想定されています。複数枚の画像を同時に変換する必要があります。
MixUpには、たとえば画像分類のクラス数を指定します。ここでは犬と猫の2を指定します。

```python
mixup = transforms.MixUp(num_classes=2)
```
MixUpを行うとMixUp後のデータと各クラスの割合が返却されます。
ミニバッチ用に２つのデータをstackで結合しました。クラスは整数型のTensorで指定する必要があります。0を犬、1を猫として指定しています。

```python
mixup_img_ts, mixup_label = mixup(torch.stack([img1_ts, img2_ts]), torch.LongTensor([0, 1]))
```

MixUp後の画像を表示してみます。

```python
transforms.ToPILImage()(mixup_img_ts[0])
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212365/d15649f1-3ff5-1122-b5fb-744044017a49.png)


```python
transforms.ToPILImage()(mixup_img_ts[1])
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/212365/76230530-28fd-f33f-eed7-a98120bdc26a.png)


2つの画像が合成されていることがわかります。
クラスを確認してみます。

```python
mixup_label
```

```
tensor([[0.3732, 0.6268],
        [0.6268, 0.3732]])
```

１枚目の画像は、犬が約0.37、猫が約0.63、２枚目の画像は逆になっています。画像を見ると１枚目は猫や強く、２枚目は犬が強く合成されていることがわかります。

実際には、ミニバッチ学習で利用されるので、ミニバッチが学習での利用方法は、PyTorchのページで確認してください。

https://pytorch.org/vision/stable/auto_examples/transforms/plot_cutmix_mixup.html#sphx-glr-auto-examples-transforms-plot-cutmix-mixup-py