"""
## 参考
https://www.kaggle.com/abhinand05/vision-transformer-vit-tutorial-baseline
"""
!cp -r ../input/vittutorialillustrations/* ./ 

!pip install nb_black
%load_ext nb_black
"""
# Introduction

このノートは、タイトルからもわかるように、基本的には2つのパートに分かれています。

<a href="#Vision-Transformers:-A-gentle-introduction">1. Vision Transformer: 優しい紹介</a> <br>
<a href="#Vision-Transformer-Implementation-in-PyTorch">2. PyTorchでの実装</a>

**今回のコンテストのために、PyTorch での ViT の実装に入る前に、Vision Transformers の基本的な考え方とその仕組みについて簡単に説明します。**

コードだけに興味がある方は、このノートの第二章を読み飛ばしても構いません。 実装は、[rwightman/pytorch-image-models](https://github.com/rwightman/pytorch-image-models) ライブラリのお陰で大きく変わることはありませんが、このライブラリには、事前学習された重みを含むすべてのモデルの実装が含まれています。
"""
"""
# <font size=4 color='blue'>このノートブックが便利だと思ったら、私はもっとそのようなノートブックを書くためにやる気にさせるUpvoteを残してください。</font>
"""
"""
# Vision Transformers: A gentle introduction

Vision Transformersは、2020年10月下旬にGoogle Brainチームが発表した論文 [AN IMAGE IS WORTH 16X16 WORDS:
TRANSFORMERS FOR IMAGE RECOGNITION AT SCALE](https://arxiv.org/pdf/2010.11929.pdf) で初めて紹介されました。

ViTの仕組みを理解するためには、当然ながらtransformerがどのように機能し、どのような問題を解決したのかについての予備知識が必要です。ここでは、transformerがどのように機能するのかを簡単に紹介してから、目の前のトピックであるViTの詳細に入っていこうと思います。

![ViT-Illustration](vision-transformer.png)

もしあなたがNLP（自然言語処理）を初めて知り、トランスフォーマーモデルについてもっと知りたいと思っていて、実際にどのように機能するかについての公正な直観を得たいと思っているなら、 [Jay Allamar](https://jalammar.github.io/)の素晴らしいブログ記事をチェックしてみることをお勧めします。上の画像も彼のブログ記事からインスピレーションを得ています。
"""
"""
## Transformers: A brief overview

> **すでにTransformerを理解されている方は、ご自由に読み飛ばしてください。**

トランスフォーマーモデルは、私たちが知っているように自然言語処理に革命をもたらしました。最初に導入されたとき、トランスフォーマーモデルは複数のNLP記録を更新し、当時のState of the Artを押し進めていました。今では、現代のNLPタスクのデファクトスタンダードとなっており、LSTMやGRUのような前世代のモデルと比較すると、驚くほどのパフォーマンス向上をもたらします。

NLPの風景を一変させた最も重要な論文は、["Attention is all you need"](https://arxiv.org/pdf/1706.03762.pdf) という論文です。この論文で紹介されたのが、トランスフォーマーアーキテクチャです。

### **Motivations:**

当時、系列やNLPタスクのための既存のモデルは、ほとんどがRNNを使用していました。 **これらのネットワークの問題点は、長期的な依存関係を捉えることができないことでした。** 

LSTMやGRU - RNNの亜種は依存関係をキャプチャすることができましたが、それにも限界がありました。 

そこで、トランスフォーマーの背後にある主なインスピレーションは、この再帰を取り除き、ほぼすべての依存性をキャプチャすることでした。これは、self-attention（マルチヘッド）と呼ばれる注意メカニズムの変形を使用して達成されたもので、成功には非常に重要です。トランフォーマーモデルのもう一つの利点は、高度な並列化が可能であることです。  


### Transformer Architecture
**注: アーキテクチャ図には、説明中の対応するステップが注記されています。**

![TranformerArchitecture](transformer-arch.png)

- Transformerには、上図の左側にあるデコーダと右側にあるエンコーダの2つの部分があります。 
- ここでは機械翻訳をしていると想像してください。 
- エンコーダは入力データ（文）を受け取り、入力の中間表現を生成します。 
- デコーダはこの中間表現を段階的にデコードし、出力を生成します。しかし、違いはこれをどのように行っているかにあります。 
- ViTでは、エンコーダのセクションを理解するだけで十分です。 

> **注: ここでの説明は、アーキテクチャの背後にある直感についてのものです。より多くの数学的な詳細については、代わりにそれぞれの研究論文をチェックしてください。**

### Tranformers: Step by step overview
**(1)** 入力データは最初にベクトルに埋め込まれます。埋め込み層(embedding layer)は、各単語のために学習されたベクトル表現をつかむのに役立つ。

**(2)** 次の段階では、位置エンコーディングが入力embeddingに注入される。これは、Tranformerが入力として渡されるシーケンスの順序（例えば文）を知らないからです

**(3)** ここで、 multi-headed attentionが少し変わってきます。

**Multi-headed-attention architecture:**
![multi-headed-attn](multi-headed-attention.png)

**(4)** Multi-Headed Attentionは3つの学習可能なベクトルで構成されています。Query, Key、Valueの3つのベクトルである。これは、検索（クエリ）すると、検索エンジンがクエリとキーを比較し、値で応答するという情報の再利用に由来すると言われています。

**(5)** Q と K の表現は、ドット積行列の乗算を経て、ある単語が他のすべての単語にどれだけ注意を払わなければならないかを表すスコア行列を生成します。スコアが高ければ高いほど注目度が高く、逆もまた然りです。 

**(6)** その後、スコア行列は、Q と K ベクトルの次元に応じてスケールダウンされる。これは，乗算が爆発的な効果をもたらす可能性があるため，より安定した勾配を確保するためです． 

(マスクの部分については、デコーダのセクションに到達したときに説明します)

**(7)** 次に、注目度スコアを確率に変換するために、スコア行列をソフトマックス化します。明らかに、スコアが高いほど高くなり、低いほど低くなります。これにより、モデルがどの単語に注目すべきかを確実に判断できるようになります。 

**(8)** 次に、確率を含む結果の行列に値ベクトルを乗算します。これにより、モデルが学習した確率スコアの高い単語がより重要になる。スコアの低い単語は効果的にかき消されて無関係になる。 

**(9)** そして、QKベクトルとVベクトルの連結出力をLinear層に送り込み、さらに処理を行う。 

**(10)** シーケンス内の各単語に対してSelf-Attentionが行われる。1つは他のものに依存しないので、Self-Attentionモジュールのコピーを使用して、これを**multi-headed**化して同時にすべてを処理することができます。 

**(11)** その後、出力値ベクトルを連結し、入力層からの残差接続に加算し、その結果の再表現をLayernNormに渡して正規化する。(残差接続はネットワークを流れる勾配を助け、LayernNormは学習時間をわずかに短縮し、ネットワークを安定化させるのに役立ちます)

**(12)** さらに、出力は、より豊かな表現を得るために、point-wise feed forward networkに渡されます。  

**(13)**  出力は再びレイヤーノルム化され、前のレイヤーから残差が追加されます。 


**注意: これでエンコーダのセクションは終わりですが、Vision Transformer を完全に理解するにはこれで十分です。デコーダ部分はエンコーディングレイヤーと非常に似ているので、理解するのはあなたにお任せします。**

**(14)** エンコーダからの出力は、前の時間ステップ/ワードからの入力（もしあれば）と共にデコーダに送られ、出力はエンコーダからの出力と共に次のattention layerに送られる前に、マスクされたmulti headed attentionを受けます。 

**(15)** Masked multi headed attention はリークがないことを確実にするために、デコード中にネットワークがシーケンス内で後から来る単語への可視性を持つべきではないために必要である。これは、スコアマトリクスの系列内で後から来る単語のエントリをマスクすることによって行われます。シーケンス内の現在の単語と前の単語は 1 で追加され、未来の単語のスコアは -inf で追加されます。これにより、確率を得るためにsoftmaxを実行する際に、系列内の将来の単語が0にかき消され、残りの単語は保持されます。 

**(16)** ここにも残差接続があり、勾配の流れを改善しています。最後に、出力は線形層に送られ、確率の出力を得るためにソフトマックスされます。 
"""
"""
## How Vision Tranformers works?

Tranformerの内部の働きを高いレベルでカバーしたところで、いよいよVision Tranformersに取り組む準備が整いました。 

Tranformerを画像に適用することは、以下の理由から常に困難なことでした。
- 単語/文章/段落とは異なり、画像は基本的にはピクセルの形でより多くの情報を含んでいます。 
- 現在のハードウェアでも、画像内のすべてのピクセルに注目することは非常に困難です。 
- その代わりに、人気のある代替案は、局所的なattentionを利用することでした。 
- 実際、CNNは畳み込みによって非常に似たようなことをしていて、モデルの層を深くしていくと受容野は本質的に大きくなりますが、Tranformerは「Tranformer」の性質上、常にCNNよりも計算量が多くなります。もちろん、CNNが現在のコンピュータビジョンの進歩にどれだけ貢献しているかは知っている。

グーグルの研究者たちは論文の中で、コンピュータ・ビジョンの次の大きな一歩となりうる、これまでとは異なるものを提案している。彼らはCNNへの依存はもう必要ないかもしれないことを示しているのだ。では、Vision Tranformerについてもっと詳しく見ていこう。

### Vision Transformer Architecture

![vit-architecture](vit-arch.png)

**(1)** トランスフォーマーのエンコーダ部分だけを使用していますが、画像をネットワークに送り込む方法に違いがあります。


**(2)** 画像を固定サイズのパッチに分解しています。そのため、これらのパッチの1つは、論文で提案されているように、16x16または32x32の寸法にすることができます。パッチが多ければ多いほど、パッチ自体が小さくなるので、これらのネットワークを訓練するのがより簡単になります。したがって、私たちはタイトルにあるように、「画像は16x16ワードの価値がある」ということになります。 

**(3)** その後、パッチは展開され（平坦化され）、ネットワークへの更なる処理のために送られます。

**(4)** ここでのNNとは異なり、モデルはシーケンス内のサンプルの位置について何も考えていませんが、ここでは各サンプルは入力画像からのパッチです。 そのため、画像は**位置埋め込みベクトルと一緒に**エンコーダに送り込まれます。ここで注意しなければならないことは、位置埋め込みも学習可能なので、実際には位置に関係なくハードコードされたベクトルを送り込む必要はないということです。

**(5)** BERTのように開始時に特別なトークンもあります。

**(6)** 各画像パッチは、最初に大きなベクトルに展開（平坦化）され、学習可能な埋め込み行列と掛け合わされ、埋め込みパッチが作成されます。そして、これらの埋め込みパッチは位置埋め込みベクトルと結合され、それがトランスフォーマーに供給されます。 

> **注意：ここから先はすべて標準的なトランスフォーマーと同じです。**

**(7)** 唯一の違いは、デコーダの代わりにエンコーダからの出力が直接フィードフォワードニューラルネットワークに渡され、分類出力を得ることです。 

### Things to note:
- この論文は、ほとんどの場合、コンボリューションを完全に無視しています。 
- しかし、彼らは、画像パッチのコンボリューション埋め込みを使用するViTのいくつかのバリエーションを使用しています。しかし、それは性能にあまり影響を与えていないようです。  
- これを書いている時点では、Vision TransformersはImageNetの画像分類ベンチマークでトップになっています。 

<img src="benchmarks-chart.png" width="700">
<!-- ![BenchmarksChart](benchmarks-charpng) -->

- この論文には他にも興味深いことがたくさんありますが、私にとって目立っていて、潜在的にCNNよりもトランスフォーマーの力を示しているのは、下の画像のように、レイヤーに対するattention distanceを示していることです。 


<img src="attn-distance.png" width="300" height="300">
<br>

- 上のグラフは、トランスフォーマーがネットワークの開始層から離れた領域にすでにattentionを払う能力を持っていることを示唆しています。
"""
"""
# <font size=4 color='blue'>このノートブックが便利だと思ったら、私はもっとそのようなノートブックを書くためにやる気にさせるUpvoteを残してください。</font>
"""
"""
## Vision Transformer Implementation in PyTorch
ビジョントランスフォーマーを理解したところで、  [this competition](https://www.kaggle.com/c/cassava-leaf-disease-classification)のベースラインモデルを構築してみましょう。

まず、TPU と torch-image-models (timm) を使えるようにするために torch-xla をインストールします。
"""
!curl https://raw.githubusercontent.com/pytorch/xla/master/contrib/scripts/env-setup.py -o pytorch-xla-env-setup.py
!python pytorch-xla-env-setup.py --version 1.7
!pip install timm
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

plt.style.use("ggplot")

import torch
import torch.nn as nn
import torchvision.transforms as transforms

import torch_xla
import torch_xla.core.xla_model as xm
import torch_xla.distributed.xla_multiprocessing as xmp
import torch_xla.distributed.parallel_loader as pl

import timm

import gc
import os
import time
import random
from datetime import datetime

from PIL import Image
from tqdm.notebook import tqdm
from sklearn import model_selection, metrics
# For parallelization in TPUs
os.environ["XLA_USE_BF16"] = "1"
os.environ["XLA_TENSOR_ALLOCATOR_MAXSIZE"] = "100000000"
def seed_everything(seed):
    """
    Seeds basic parameters for reproductibility of results
    
    Arguments:
        seed {int} -- Number of the seed
    """
    random.seed(seed)
    os.environ["PYTHONHASHSEED"] = str(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)
    torch.backends.cudnn.deterministic = True
    torch.backends.cudnn.benchmark = False


seed_everything(1001)
# general global variables
DATA_PATH = "../input/cassava-leaf-disease-classification"
TRAIN_PATH = "../input/cassava-leaf-disease-classification/train_images/"
TEST_PATH = "../input/cassava-leaf-disease-classification/test_images/"
MODEL_PATH = (
    "../input/vit-base-models-pretrained-pytorch/jx_vit_base_p16_224-80ecf9dd.pth"
)

# model specific global variables
IMG_SIZE = 224
BATCH_SIZE = 16
LR = 2e-05
GAMMA = 0.7
N_EPOCHS = 10
df = pd.read_csv(os.path.join(DATA_PATH, "train.csv"))
df.head()
df.info()
df.label.value_counts().plot(kind="bar")
train_df, valid_df = model_selection.train_test_split(
    df, test_size=0.1, random_state=42, stratify=df.label.values
)
class CassavaDataset(torch.utils.data.Dataset):
    """
    Helper Class to create the pytorch dataset
    """

    def __init__(self, df, data_path=DATA_PATH, mode="train", transforms=None):
        super().__init__()
        self.df_data = df.values
        self.data_path = data_path
        self.transforms = transforms
        self.mode = mode
        self.data_dir = "train_images" if mode == "train" else "test_images"

    def __len__(self):
        return len(self.df_data)

    def __getitem__(self, index):
        img_name, label = self.df_data[index]
        img_path = os.path.join(self.data_path, self.data_dir, img_name)
        img = Image.open(img_path).convert("RGB")

        if self.transforms is not None:
            image = self.transforms(img)

        return image, label
# create image augmentations
transforms_train = transforms.Compose(
    [
        transforms.Resize((IMG_SIZE, IMG_SIZE)),
        transforms.RandomHorizontalFlip(p=0.3),
        transforms.RandomVerticalFlip(p=0.3),
        transforms.RandomResizedCrop(IMG_SIZE),
        transforms.ToTensor(),
        transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),
    ]
)

transforms_valid = transforms.Compose(
    [
        transforms.Resize((IMG_SIZE, IMG_SIZE)),
        transforms.ToTensor(),
        transforms.Normalize((0.485, 0.456, 0.406), (0.229, 0.224, 0.225)),
    ]
)
print("Available Vision Transformer Models: ")
timm.list_models("vit*")
class ViTBase16(nn.Module):
    def __init__(self, n_classes, pretrained=False):

        super(ViTBase16, self).__init__()

        self.model = timm.create_model("vit_base_patch16_224", pretrained=False)
        if pretrained:
            self.model.load_state_dict(torch.load(MODEL_PATH))

        self.model.head = nn.Linear(self.model.head.in_features, n_classes)

    def forward(self, x):
        x = self.model(x)
        return x

    def train_one_epoch(self, train_loader, criterion, optimizer, device):
        # keep track of training loss
        epoch_loss = 0.0
        epoch_accuracy = 0.0

        ###################
        # train the model #
        ###################
        self.model.train()
        for i, (data, target) in enumerate(train_loader):
            # move tensors to GPU if CUDA is available
            if device.type == "cuda":
                data, target = data.cuda(), target.cuda()
            elif device.type == "xla":
                data = data.to(device, dtype=torch.float32)
                target = target.to(device, dtype=torch.int64)

            # clear the gradients of all optimized variables
            optimizer.zero_grad()
            # forward pass: compute predicted outputs by passing inputs to the model
            output = self.forward(data)
            # calculate the batch loss
            loss = criterion(output, target)
            # backward pass: compute gradient of the loss with respect to model parameters
            loss.backward()
            # Calculate Accuracy
            accuracy = (output.argmax(dim=1) == target).float().mean()
            # update training loss and accuracy
            epoch_loss += loss
            epoch_accuracy += accuracy

            # perform a single optimization step (parameter update)
            if device.type == "xla":
                xm.optimizer_step(optimizer)

                if i % 20 == 0:
                    xm.master_print(f"\tBATCH {i+1}/{len(train_loader)} - LOSS: {loss}")

            else:
                optimizer.step()

        return epoch_loss / len(train_loader), epoch_accuracy / len(train_loader)

    def validate_one_epoch(self, valid_loader, criterion, device):
        # keep track of validation loss
        valid_loss = 0.0
        valid_accuracy = 0.0

        ######################
        # validate the model #
        ######################
        self.model.eval()
        for data, target in valid_loader:
            # move tensors to GPU if CUDA is available
            if device.type == "cuda":
                data, target = data.cuda(), target.cuda()
            elif device.type == "xla":
                data = data.to(device, dtype=torch.float32)
                target = target.to(device, dtype=torch.int64)

            with torch.no_grad():
                # forward pass: compute predicted outputs by passing inputs to the model
                output = self.model(data)
                # calculate the batch loss
                loss = criterion(output, target)
                # Calculate Accuracy
                accuracy = (output.argmax(dim=1) == target).float().mean()
                # update average validation loss and accuracy
                valid_loss += loss
                valid_accuracy += accuracy

        return valid_loss / len(valid_loader), valid_accuracy / len(valid_loader)
def fit_tpu(
    model, epochs, device, criterion, optimizer, train_loader, valid_loader=None
):

    valid_loss_min = np.Inf  # track change in validation loss

    # keeping track of losses as it happen
    train_losses = []
    valid_losses = []
    train_accs = []
    valid_accs = []

    for epoch in range(1, epochs + 1):
        gc.collect()
        para_train_loader = pl.ParallelLoader(train_loader, [device])

        xm.master_print(f"{'='*50}")
        xm.master_print(f"EPOCH {epoch} - TRAINING...")
        train_loss, train_acc = model.train_one_epoch(
            para_train_loader.per_device_loader(device), criterion, optimizer, device
        )
        xm.master_print(
            f"\n\t[TRAIN] EPOCH {epoch} - LOSS: {train_loss}, ACCURACY: {train_acc}\n"
        )
        train_losses.append(train_loss)
        train_accs.append(train_acc)
        gc.collect()

        if valid_loader is not None:
            gc.collect()
            para_valid_loader = pl.ParallelLoader(valid_loader, [device])
            xm.master_print(f"EPOCH {epoch} - VALIDATING...")
            valid_loss, valid_acc = model.validate_one_epoch(
                para_valid_loader.per_device_loader(device), criterion, device
            )
            xm.master_print(f"\t[VALID] LOSS: {valid_loss}, ACCURACY: {valid_acc}\n")
            valid_losses.append(valid_loss)
            valid_accs.append(valid_acc)
            gc.collect()

            # save model if validation loss has decreased
            if valid_loss <= valid_loss_min and epoch != 1:
                xm.master_print(
                    "Validation loss decreased ({:.4f} --> {:.4f}).  Saving model ...".format(
                        valid_loss_min, valid_loss
                    )
                )
            #                 xm.save(model.state_dict(), 'best_model.pth')

            valid_loss_min = valid_loss

    return {
        "train_loss": train_losses,
        "valid_losses": valid_losses,
        "train_acc": train_accs,
        "valid_acc": valid_accs,
    }
model = ViTBase16(n_classes=5, pretrained=True)
def _run():
    train_dataset = CassavaDataset(train_df, transforms=transforms_train)
    valid_dataset = CassavaDataset(valid_df, transforms=transforms_valid)

    train_sampler = torch.utils.data.distributed.DistributedSampler(
        train_dataset,
        num_replicas=xm.xrt_world_size(),
        rank=xm.get_ordinal(),
        shuffle=True,
    )

    valid_sampler = torch.utils.data.distributed.DistributedSampler(
        valid_dataset,
        num_replicas=xm.xrt_world_size(),
        rank=xm.get_ordinal(),
        shuffle=False,
    )

    train_loader = torch.utils.data.DataLoader(
        dataset=train_dataset,
        batch_size=BATCH_SIZE,
        sampler=train_sampler,
        drop_last=True,
        num_workers=8,
    )

    valid_loader = torch.utils.data.DataLoader(
        dataset=valid_dataset,
        batch_size=BATCH_SIZE,
        sampler=valid_sampler,
        drop_last=True,
        num_workers=8,
    )

    criterion = nn.CrossEntropyLoss()
    #     device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    device = xm.xla_device()
    model.to(device)

    lr = LR * xm.xrt_world_size()
    optimizer = torch.optim.Adam(model.parameters(), lr=lr)

    xm.master_print(f"INITIALIZING TRAINING ON {xm.xrt_world_size()} TPU CORES")
    start_time = datetime.now()
    xm.master_print(f"Start Time: {start_time}")

    logs = fit_tpu(
        model=model,
        epochs=N_EPOCHS,
        device=device,
        criterion=criterion,
        optimizer=optimizer,
        train_loader=train_loader,
        valid_loader=valid_loader,
    )

    xm.master_print(f"Execution time: {datetime.now() - start_time}")

    xm.master_print("Saving Model")
    xm.save(
        model.state_dict(), f'model_5e_{datetime.now().strftime("%Y%m%d-%H%M")}.pth'
    )
# Start training processes
def _mp_fn(rank, flags):
    torch.set_default_tensor_type("torch.FloatTensor")
    a = _run()


# _run()
FLAGS = {}
xmp.spawn(_mp_fn, args=(FLAGS,), nprocs=8, start_method="fork")
"""
## Thanks a lot for reading all the way

# <font size=4 color='blue'>If you find this notebook useful, leave an upvote, that motivates me to write more such notebooks.</font>
"""