{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.14","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":5441,"databundleVersionId":38425,"sourceType":"competition"}],"dockerImageVersionId":30787,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# 画像分類コンペ\npytorchというpythonで機械学習を行うためのライブラリを用いた実装\n\n## 目次\n1. ライブラリのインポート\n2. データの整理\n3. 前処理の定義\n4. データセットの作成\n5. モデルの定義\n6. 訓練手法の定義\n7. 訓練を実行\n8. テスト\n9. 提出csvの作成","metadata":{}},{"cell_type":"markdown","source":"## 初期設定","metadata":{}},{"cell_type":"code","source":"import glob\nimport os.path\nimport random\nimport numpy as np\nimport json\nimport pandas as pd\nfrom PIL import Image\nfrom tqdm import tqdm\nimport matplotlib.pyplot as plt\n%matplotlib inline\nimport zipfile\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport torch.utils.data as data\nimport torchvision\nfrom torchvision import models,transforms\nimport torch.nn.functional as F\n\nfrom sklearn.model_selection import train_test_split\n\nDEVICE =\"cuda\"\nBATCH_SIZE=128","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-13T12:07:07.9867Z","iopub.execute_input":"2024-11-13T12:07:07.987105Z","iopub.status.idle":"2024-11-13T12:07:08.667575Z","shell.execute_reply.started":"2024-11-13T12:07:07.987068Z","shell.execute_reply":"2024-11-13T12:07:08.666834Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## データの整理","metadata":{}},{"cell_type":"markdown","source":"まず、与えられたデータはzipだったので、それを展開します","metadata":{}},{"cell_type":"code","source":"os.listdir('../input/dogs-vs-cats-redux-kernels-edition')\nos.makedirs('../data', exist_ok=True)\nbase_dir = '../input/dogs-vs-cats-redux-kernels-edition'\ntrain_dir = '../data/train'\ntest_dir = '../data/test'\nwith zipfile.ZipFile(os.path.join(base_dir, 'train.zip')) as train_zip:\n    train_zip.extractall('../data')\n    \nwith zipfile.ZipFile(os.path.join(base_dir, 'test.zip')) as test_zip:\n    test_zip.extractall('../data')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-13T11:22:25.722941Z","iopub.execute_input":"2024-11-13T11:22:25.72334Z","iopub.status.idle":"2024-11-13T11:22:41.230031Z","shell.execute_reply.started":"2024-11-13T11:22:25.723296Z","shell.execute_reply":"2024-11-13T11:22:41.229124Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"展開したデータをリスト（配列）に格納して、さらに訓練データをvalidationデータに分割します。\nValidationデータとは、訓練の最中にそのモデルの性能を検証するために用いられます。テストデータにはラベル（正解）が付与されていないので使えない。訓練データはそれを学習に利用しているため過学習（興味のある人は調べて）に陥りやすいから","metadata":{}},{"cell_type":"code","source":"train_list = glob.glob(os.path.join(train_dir,'*.jpg'))\ntest_list = glob.glob(os.path.join(test_dir, '*.jpg'))\n\ntrain_list, val_list = train_test_split(train_list, test_size=0.1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-13T11:22:41.231467Z","iopub.execute_input":"2024-11-13T11:22:41.231867Z","iopub.status.idle":"2024-11-13T11:22:41.382021Z","shell.execute_reply.started":"2024-11-13T11:22:41.231825Z","shell.execute_reply":"2024-11-13T11:22:41.38125Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(train_list[:10])\nprint(test_list[:10])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-13T11:59:28.432023Z","iopub.execute_input":"2024-11-13T11:59:28.43242Z","iopub.status.idle":"2024-11-13T11:59:28.437695Z","shell.execute_reply.started":"2024-11-13T11:59:28.432383Z","shell.execute_reply":"2024-11-13T11:59:28.43676Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 前処理の定義","metadata":{}},{"cell_type":"markdown","source":"訓練用とvalidation, test用の二つの前処理を用意します。\n前処理とは、与えられた画像データを、\n1. CNNに与えられる形に変更させるための処理です。\n2. モデルが予測をしないように加工する\n3. 訓練のためにデータに多様性を持たせる処理\n\n(1)についてはPytorchでのデータ型（Tensor型）に変換する処理（ToTensor()）\n\n(2)については正規化（Normalize）\n\n(3)についてはランダムな位置で決められたサイズ切り抜き（RandomResizedCrop）ランダムに左右反転(RandomHorizontalFlip)を与えています。","metadata":{}},{"cell_type":"code","source":"class ImageTransform():\n    def __init__(self):\n        self.data_transform = {\n            'train': transforms.Compose([\n                # transforms.Resize((224,224)),\n                transforms.RandomResizedCrop(224),\n                transforms.RandomHorizontalFlip(),\n                transforms.ToTensor(),\n                transforms.Normalize((0.485,0.456,0.406),(0.229,0.224,0.225))\n            ]),\n            'test': transforms.Compose([\n                transforms.Resize(250),\n                transforms.CenterCrop(224),\n                transforms.ToTensor(),\n                transforms.Normalize((0.485,0.456,0.406),(0.229,0.224,0.225))\n            ])\n        }\n    def __call__(self, img, phase):\n        return self.data_transform[phase](img)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-13T11:24:18.261066Z","iopub.execute_input":"2024-11-13T11:24:18.261953Z","iopub.status.idle":"2024-11-13T11:24:18.271201Z","shell.execute_reply.started":"2024-11-13T11:24:18.261903Z","shell.execute_reply":"2024-11-13T11:24:18.269812Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## データセットの作成","metadata":{}},{"cell_type":"markdown","source":"ここでは、データセットを作成するために用いられるクラスを定義します\n\nデータセットとは、データの集まり\n\n一つの問題をデータとしますと、データセットは問題集ということになります。\n\n番号を決めると、とある一つの問題が示されるように、各データには番号があります\n### クラス\nクラスの基本について、クラスとは設計図です。\n\n設計図をもとに実体を作ることをインスタンス化と呼びます。\n\n車の設計図がクラスであり、それをもとに実際に作られる車がインスタンスと呼ばれます。設計図は一つでよいですが、車は何台もつくられますね。\n\npythonでは\n```python\nclass クラス名():\n    def 関数名1(self,引数名１, 引数名2)\n        ...\n    def 関数名2(self,引数名１, 引数名2)\n        ...\n```\nのようになります。\n\nselfは必ず書かなければいけない「おまじない」、です\n\n\n### Datasetクラスの実装\n#### __init__\nコンストラクタと呼び、インスタンス化をしたときに、実行されます。（）のなかで与えられている引数たちを定義し、そのインスタンスの詳細を決めます。\n\n例えば、車の色や大きさなどを引数で渡されたりします。\n\n今回は、ファイルのパス（場所）のリストと、先ほど定義した前処理、フェーズ（訓練 or テスト）が与えられます\n\n#### __len__\nデータの数を返すものです。発展的にはなりますが、data.Datasetの継承のルールにより定義しなければいけないものです。\n\n#### __getitem__(idx)\nこれは、このDatasetでidx番目のデータを返します。\n\nその際に、先ほど定義した前処理を実行しています\n\nここでは、モデルに対する入力データと、モデルが出力するべき正解データを与えます。\n\n例えば、猫の画像と、\"cat\"が返されます。\n\n####\n","metadata":{}},{"cell_type":"code","source":"class MyDataset(data.Dataset):\n    def __init__(self,file_path_list,transform,phase):\n        self.file_list = file_path_list\n        self.transform = transform\n        self.phase = phase\n        \n    def __len__(self):\n        return len(self.file_list)\n    \n    def __getitem__(self,idx):\n        img_path = self.file_list[idx]\n        img = Image.open(img_path)\n        img_transformed = self.transform(img,self.phase)\n        \n        label = img_path.split('/')[-1].split('.')[0]\n        if label == 'dog':\n            label=1\n        elif label == 'cat':\n            label=0\n        else:\n            # これは、phase==testの場合、この場合そのままidを渡すことになる\n            pass\n            \n        return img_transformed,label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-13T11:24:33.976003Z","iopub.execute_input":"2024-11-13T11:24:33.976897Z","iopub.status.idle":"2024-11-13T11:24:33.985534Z","shell.execute_reply.started":"2024-11-13T11:24:33.976845Z","shell.execute_reply":"2024-11-13T11:24:33.984448Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"実際にクラス（設計図）をインスタンス化しています。\n\nここでようやく、Datasetは実体化しました。\n\ndataloaderというのは、pytorchのルールに作らなければいけないものだと考えてください。DatasetはDataloaderの中に与えて、DataLoaderを通じて入力と正解データを得ます","metadata":{}},{"cell_type":"code","source":"transform = ImageTransform()\n\ntrain_dataset = MyDataset(train_list, transform=transform, phase='train')\ntrain_dataloader = data.DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=4, pin_memory=True)\n\nval_dataset = MyDataset(val_list, transform=transform, phase='test')\nval_dataloader = data.DataLoader(val_dataset,batch_size=BATCH_SIZE, shuffle = True, num_workers=4, pin_memory=True)\n\ntest_dataset = MyDataset(test_list,transform=transform,phase='test')\ntest_dataloader = data.DataLoader(test_dataset,batch_size=BATCH_SIZE, shuffle = True, num_workers=4, pin_memory=True)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-13T12:03:32.624711Z","iopub.execute_input":"2024-11-13T12:03:32.62558Z","iopub.status.idle":"2024-11-13T12:03:32.634081Z","shell.execute_reply.started":"2024-11-13T12:03:32.625528Z","shell.execute_reply":"2024-11-13T12:03:32.633051Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## モデルの定義","metadata":{}},{"cell_type":"markdown","source":"ここのコードは映してください（写経）\n\nそして、データの流れを理解する努力をしてみましょう。分からなかったら質問","metadata":{}},{"cell_type":"code","source":"class Cnn(nn.Module):\n    def __init__(self):\n        super(Cnn,self).__init__()\n        \n        \n    def forward(self,x):\n\n        return out","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = Cnn()\nmodel.train()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 訓練手法の定義\n","metadata":{}},{"cell_type":"markdown","source":"以下のコードはテンプレートです。ですが、引数で与えられる、optimizer, criterionは訓練の手法を定義する重要なものになります。下で解説します","metadata":{}},{"cell_type":"code","source":"from tqdm import tqdm\n\n# 分類問題用のtrain_model\ndef train_model(model, train_loader, val_loader, optimizer, criterion, epochs):\n    def _train(epoch):\n        epoch_loss = 0\n        epoch_accuracy = 0    \n        # tqdmで進捗バーを表示し、postfixで訓練の途中結果を表示\n        with tqdm(train_loader, desc=\"Training\", leave=True) as pbar:\n            for data, label in pbar:\n                data = data.to(DEVICE)\n                label = label.to(DEVICE)\n                \n                output = model(data)\n                loss = criterion(output, label)\n                \n                optimizer.zero_grad()\n                loss.backward()\n                optimizer.step()\n                \n                acc = (output.argmax(dim=1) == label).float().mean()\n                epoch_accuracy += acc / len(train_loader)\n                epoch_loss += loss / len(train_loader)\n                \n                # postfixでtrain_loss, acc, learning rateを表示\n                pbar.set_postfix({\n                    'Train Loss': epoch_loss.item(),\n                    'Train Accuracy': epoch_accuracy.item(),\n                    'Learning Rate': optimizer.param_groups[0]['lr']\n                })\n        \n        print(f\"Epoch: {epoch+1}, Train Accuracy: {epoch_accuracy:.4f}, Train Loss: {epoch_loss:.4f}\")\n    def _val(epoch):\n        # 検証の進行状況を表示\n        epoch_val_accuracy = 0\n        epoch_val_loss = 0\n        with torch.no_grad():\n            with tqdm(val_loader, desc=\"Validation\", leave=True) as pbar:\n                for data, label in pbar:\n                    data = data.to(DEVICE)\n                    label = label.to(DEVICE)\n                    \n                    val_output = model(data)\n                    val_loss = criterion(val_output, label)\n                    \n                    acc = (val_output.argmax(dim=1) == label).float().mean()\n                    epoch_val_accuracy += acc / len(val_loader)\n                    epoch_val_loss += val_loss / len(val_loader)\n                    \n                    # postfixでval_loss, val_accを表示\n                    pbar.set_postfix({\n                        'Val Loss': epoch_val_loss.item(),\n                        'Val Accuracy': epoch_val_accuracy.item()\n                    })\n        \n        print(f\"Epoch: {epoch+1}, Validation Accuracy: {epoch_val_accuracy:.4f}, Validation Loss: {epoch_val_loss:.4f}\")\n\n    \n\n    \n    model = model.to(DEVICE)\n\n    _val(-1)\n    \n    for epoch in range(epochs):\n\n        print(f\"Epoch {epoch+1}/{epochs}\")\n        \n        _train(epoch)\n        \n        _val(epoch)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-13T11:37:52.274528Z","iopub.execute_input":"2024-11-13T11:37:52.275431Z","iopub.status.idle":"2024-11-13T11:37:52.287995Z","shell.execute_reply.started":"2024-11-13T11:37:52.27539Z","shell.execute_reply":"2024-11-13T11:37:52.28705Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"criterionとは、AIモデルの予測（出力）と正解データがどれだけ離れている、違うかを計算する式となります。\n\n**損失関数**と呼ばれます\n\n今回はCrossEntropyLossという分類問題でいつも使われる損失関数を利用しています。\n\noptimizerとは、パラメータを更新するための計算式（アルゴリズム）を決めるものになります。\n\nこれも、最も一般的で、困ったらこれを使えと言われている、**Adam**を利用します。\n\n**lr**とは **Learning Rate**, **学習率**となります。\n\n学習率とは、パラメーターを更新する際に、どれぐらい変化をさせるのかを定義するものです。これが大きいと、例えばパラメーターが更新のたびに -0.5->1.5と大きく変化するために、なかなか最適化されずらいです。\n\nそこで、0.0005のように小さく設定しますと、微調整が可能になるというものです。","metadata":{}},{"cell_type":"code","source":"optimizer= optim.Adam(params = model.parameters(), lr=0.0005)\ncriterion = nn.CrossEntropyLoss()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-13T11:41:15.237366Z","iopub.execute_input":"2024-11-13T11:41:15.237897Z","iopub.status.idle":"2024-11-13T11:41:15.243623Z","shell.execute_reply.started":"2024-11-13T11:41:15.237858Z","shell.execute_reply":"2024-11-13T11:41:15.24268Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 訓練の実行","metadata":{}},{"cell_type":"markdown","source":"epochsとはデータセットを周回する回数です。\n\n問題集=Datasetの最初から最後までの問題を一通り勉強することを　**1 Epoch**といいます。\n\n10エポックだと10周することになります。\n\n注意点です。もし、周回の数を多くしすぎるすると、　**過学習**が起きます。これは、モデルが問題に過剰に適応してしまうことで起きてしまいます。\n\n過学習が起きるとモデルは入力に対して、答えを暗記するような形になってしまいます。\n\n一般的な特徴から予測をしなければいけないのに、答えを暗記するようになってしまうと正答率が下がってしまいます","metadata":{}},{"cell_type":"code","source":"epochs = 10\ntrain_model(model,train_dataloader,val_dataloader,optimizer,criterion,epochs)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-13T11:41:18.010639Z","iopub.execute_input":"2024-11-13T11:41:18.011062Z","iopub.status.idle":"2024-11-13T11:58:29.150712Z","shell.execute_reply.started":"2024-11-13T11:41:18.011015Z","shell.execute_reply":"2024-11-13T11:58:29.148957Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 提出csvの作成","metadata":{}},{"cell_type":"markdown","source":"与えられたtestデータを、訓練後のモデルで予測してそれをcsvに出力します。\n\npandasと呼ばれるライブラリがcsvへの出力に利用されます","metadata":{}},{"cell_type":"code","source":"dog_probs = []\nmodel.eval()\nwith torch.no_grad():\n    for data, fileid in test_dataloader:\n        data = data.to(DEVICE)\n        preds = model(data)\n        preds_list = F.softmax(preds, dim=1)[:, 1].tolist()\n        dog_probs += list(zip(list(fileid), preds_list))\n\ndog_probs.sort(key = lambda x : int(x[0]))\ndog_probs[:10]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-13T12:04:18.437957Z","iopub.execute_input":"2024-11-13T12:04:18.438349Z","iopub.status.idle":"2024-11-13T12:04:44.736231Z","shell.execute_reply.started":"2024-11-13T12:04:18.438305Z","shell.execute_reply":"2024-11-13T12:04:44.735202Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"idx = list(map(lambda x: x[0],dog_probs))\nprob = list(map(lambda x: x[1],dog_probs))\n\nsubmission = pd.DataFrame({'id':idx,'label':prob})\nsubmission","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-13T12:07:23.136302Z","iopub.execute_input":"2024-11-13T12:07:23.136924Z","iopub.status.idle":"2024-11-13T12:07:23.194022Z","shell.execute_reply.started":"2024-11-13T12:07:23.136885Z","shell.execute_reply":"2024-11-13T12:07:23.193044Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission.to_csv('result.csv',index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-13T12:07:24.274536Z","iopub.execute_input":"2024-11-13T12:07:24.274993Z","iopub.status.idle":"2024-11-13T12:07:24.326198Z","shell.execute_reply.started":"2024-11-13T12:07:24.274934Z","shell.execute_reply":"2024-11-13T12:07:24.325442Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## テスト","metadata":{}},{"cell_type":"markdown","source":"実際にモデルが行った予測を可視化してみます。\n\n正しく動いているかをこれで検証するのです。","metadata":{}},{"cell_type":"code","source":"import random\n\nid_list = []\nclass_ = {0: 'cat', 1: 'dog'}\n\nfig, axes = plt.subplots(2, 5, figsize=(20, 12), facecolor='w')\n\nfor ax in axes.ravel():\n    \n    i = random.choice(submission['id'].values)\n    \n    label = submission.loc[submission['id'] == i, 'label'].values[0]\n    if label > 0.5:\n        label = 1\n    else:\n        label = 0\n        \n    img_path = os.path.join(test_dir, '{}.jpg'.format(i))\n    img = Image.open(img_path)\n    \n    ax.set_title(class_[label])\n    ax.imshow(img)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-11-13T12:07:25.377252Z","iopub.execute_input":"2024-11-13T12:07:25.378149Z","iopub.status.idle":"2024-11-13T12:07:27.8721Z","shell.execute_reply.started":"2024-11-13T12:07:25.378108Z","shell.execute_reply":"2024-11-13T12:07:27.871135Z"}},"outputs":[],"execution_count":null}]}