{"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":29193,"databundleVersionId":2318453,"sourceType":"competition"}],"dockerImageVersionId":30787,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport pandas as pd\nimport numpy as np\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom PIL import Image\nimport os\nimport matplotlib.pyplot as plt\nimport torchvision.models as models\nimport torchvision\nfrom tqdm import tqdm\nimport seaborn as sns","metadata":{"execution":{"iopub.status.busy":"2024-10-15T03:33:37.663580Z","iopub.execute_input":"2024-10-15T03:33:37.664093Z","iopub.status.idle":"2024-10-15T03:33:37.670945Z","shell.execute_reply.started":"2024-10-15T03:33:37.664053Z","shell.execute_reply":"2024-10-15T03:33:37.669930Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 导入数据集","metadata":{}},{"cell_type":"code","source":"labels_dataframe = pd.read_csv('/kaggle/input/classify-leaves/train.csv')\nlabels_dataframe.head(5)","metadata":{"execution":{"iopub.status.busy":"2024-10-15T03:33:37.673269Z","iopub.execute_input":"2024-10-15T03:33:37.673680Z","iopub.status.idle":"2024-10-15T03:33:37.708770Z","shell.execute_reply.started":"2024-10-15T03:33:37.673620Z","shell.execute_reply":"2024-10-15T03:33:37.707811Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 观察数据集","metadata":{}},{"cell_type":"code","source":"labels_dataframe.describe()","metadata":{"execution":{"iopub.status.busy":"2024-10-15T03:33:37.710086Z","iopub.execute_input":"2024-10-15T03:33:37.710484Z","iopub.status.idle":"2024-10-15T03:33:37.743535Z","shell.execute_reply.started":"2024-10-15T03:33:37.710429Z","shell.execute_reply":"2024-10-15T03:33:37.742581Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"使用pd.describe()函数生成描述性统计数据，统计数据集的集中趋势，分散和行列的分布情况，不包括 NaN值。可以看到训练集总共有18353张图片，标签有176类","metadata":{}},{"cell_type":"code","source":"# 显示出每种类别的叶子有多少个图 min:51 max:353\ndef barw(ax):\n    for p in ax.patches:\n        val = p.get_width()\n        x = p.get_x() + p.get_width()\n        y = p.get_y() + p.get_height() / 2\n        ax.annotate(round(val,2), (x, y))\nplt.figure(figsize = (15, 40))\n# sns.countplot() 用于画类别特征的频数条形图\n#（x轴是count数，y轴是类别。）\nax0 = sns.countplot(y = labels_dataframe['label'], order = labels_dataframe['label'].value_counts().index)\nbarw(ax0)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-10-15T03:33:37.746845Z","iopub.execute_input":"2024-10-15T03:33:37.747169Z","iopub.status.idle":"2024-10-15T03:33:40.538152Z","shell.execute_reply.started":"2024-10-15T03:33:37.747136Z","shell.execute_reply":"2024-10-15T03:33:40.537196Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 数据预处理","metadata":{}},{"cell_type":"markdown","source":"把label标签按字母排个序，这里仅显示前10个。     \n先用set()对label去重，然后转成list再排序","metadata":{}},{"cell_type":"code","source":"leaves_labels = sorted(list(set(labels_dataframe['label'])))\nn_class = len(leaves_labels)\nprint(n_class)\nleaves_labels[:10]","metadata":{"execution":{"iopub.status.busy":"2024-10-15T03:33:40.540788Z","iopub.execute_input":"2024-10-15T03:33:40.541199Z","iopub.status.idle":"2024-10-15T03:33:40.550546Z","shell.execute_reply.started":"2024-10-15T03:33:40.541164Z","shell.execute_reply":"2024-10-15T03:33:40.549764Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"编写字典 {label：num}  \nnum用range()生成      \n先用zip()捆绑label：num键值对，再用dict()生成字典   ","metadata":{}},{"cell_type":"code","source":"class_to_num = dict(zip(leaves_labels, range(n_class)))\nclass_to_num","metadata":{"execution":{"iopub.status.busy":"2024-10-15T03:33:40.551769Z","iopub.execute_input":"2024-10-15T03:33:40.552046Z","iopub.status.idle":"2024-10-15T03:33:40.571446Z","shell.execute_reply.started":"2024-10-15T03:33:40.552016Z","shell.execute_reply":"2024-10-15T03:33:40.570504Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"再转换回来，方便最后预测的时候使用      \nitems()同时返回字典键值对","metadata":{}},{"cell_type":"code","source":"num_to_class = {v : k for k, v in class_to_num.items()}\nnum_to_class","metadata":{"execution":{"iopub.status.busy":"2024-10-15T03:33:40.572753Z","iopub.execute_input":"2024-10-15T03:33:40.573067Z","iopub.status.idle":"2024-10-15T03:33:40.587608Z","shell.execute_reply.started":"2024-10-15T03:33:40.573034Z","shell.execute_reply":"2024-10-15T03:33:40.586701Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"现在已经制作了种类字典信息，但数据集还没有划分训练集，测试集以及验证集。接下来的代码要逐步弄清楚    \n我会分解LeavesData类的函数并解析代码         ","metadata":{}},{"cell_type":"markdown","source":"+ 声明类并继承Dataset\n+ init（）       \n    init()会根据mode决定生成是“训练数据集”还是“验证数据集”还是“测试数据集”，三者合一","metadata":{}},{"cell_type":"code","source":"class LeavesData(Dataset):\n    def __init__(self, csv_path, file_path, mode = 'train', valid_ratio = 0.2, resize_height = 256, resize_width = 256):\n        self.resize_height = resize_height\n        self.resize_width = resize_width\n        self.file_path = file_path\n        self.mode = mode\n        # 读取csv文件\n        # 利用pandas读取csv文件\n        self.data_info = pd.read_csv(csv_path, header = None) # header = none是去掉表头部分\n        # 计算length\n        self.data_len = len(self.data_info.index) - 1\n        # 该长度用于后面iloc切数据 我们设的valid_ratio=0.2，所以训练集是整个数据集的0.8倍\n        self.train_len = int(self.data_len * (1 - valid_ratio))\n        \n        \n        # 参数解析：\n        # csv_path是表格文件路径，目的是得到图片路径字符串\n        # file_path是图片真正路径，相当于拿到真实图片\n        # mode决定是“训练数据集”还是“验证数据集”还是“测试数据集”\n        # valid_ratio划分验证集占训练集的比例\n        # resize_height,resize_width规范化图片尺寸\n        # 利用read_csv获取表格，去掉表头，因为第一行不是数据\n        \n        \n        # 根据mode生成对应的数据集\n        \n        if mode == 'train':\n            # 第一步，取图片和标签\n            # train_image 第一列包含图像文件的名称\n            self.train_image = np.asarray(self.data_info.iloc[1:self.train_len, 0])\n            self.train_label = np.asarray(self.data_info.iloc[1:self.train_len, 1])\n            self.image_arr = self.train_image\n            self.label_arr = self.train_label\n            # 这里为了统一描述三种数据集的image和label（测试集没有label），定义了image_arr与label_arr\n            # iloc()是切片函数，先切行再切列\n            # asarray()将csv变成ndarray\n            # [1:self.train_len]是训练集长度\n        \n        elif mode == 'valid':\n            self.valid_image = np.asarray(self.data_info.iloc[self.train_len:,0])\n            self.valid_label = np.asarray(self.data_info.iloc[self.train_len:,1])\n            self.image_arr = self.valid_image\n            self.label_arr = self.valid_label\n            # [self.train_len:, ]是训练集长度\n        \n        elif mode == 'test':\n            self.test_image = np.asarray(self.data_info.iloc[1:, 0])\n            self.image_arr = self.test_image\n        \n        # 打印\n        # 这里定义了real_len表示数据集长度\n        self.real_len = len(self.image_arr)\n        print('Finished reading the {} set of Leaves Dataset ({} samples found)'.format(mode, self.real_len))\n    \n    # getitem()可以用索引访问元素，这里return每一个index对应的image数据(并对图像进行增广)和对应的label\n    # 读取图像\n    # single_image_name 从 image_arr（保存的是数据集图片路径）中得到index对应图像的路径名\n    # img_as_img通过Image.open()读取图像\n    def __getitem__(self, index):\n        # 从image_arr中得到索引对应的文件名\n        single_image_name = self.image_arr[index]\n        # 读取图像文件\n        img_as_img = Image.open(self.file_path + single_image_name)\n        \n        # 图片增广\n        # 通常训练集需要做图像增强处理，而验证集和测试集不需要\n        # transforms.Compose([])是对图片进行变换操作\n        # Resize()调整图像尺寸\n        # RandomHorizontalFlip()随机水平翻转，图像增强的操作之一\n        # ToTensor(),转化成张量形式\n        if self.mode == 'train':\n            transform = torchvision.transforms.Compose([\n                torchvision.transforms.RandomResizedCrop(224, scale=(0.08, 1.0), # 从图片的中心点剪裁出24*24的图片\n                                                         ratio=(3.0 / 4.0, 4.0 / 3.0)),\n                torchvision.transforms.RandomHorizontalFlip(), # 左右翻转\n                torchvision.transforms.ColorJitter(brightness=0.4, contrast=0.4,\n                                                   saturation=0.4),\n                # 加入随机噪音\n                torchvision.transforms.ToTensor(),\n                torchvision.transforms.Normalize([0.485, 0.456, 0.406], # 对图片的每个通道做均值和方差\n                                                 [0.229, 0.224, 0.225])\n            ])\n        else:\n            # valid 和 test 不做数据增强\n            transform = torchvision.transforms.Compose([\n                torchvision.transforms.Resize(256),\n                torchvision.transforms.CenterCrop(224),\n                # 加入随机噪音\n                torchvision.transforms.ToTensor(),\n                torchvision.transforms.Normalize([0.485, 0.456, 0.406], # 依然 对图片的每个通道做均值和方差\n                                                 [0.229, 0.224, 0.225])\n            ])\n        img_as_img = transform(img_as_img)\n        if self.mode == 'test':\n            return img_as_img\n        else:\n            # 得到图像的string label\n            label = self.label_arr[index]\n            # number label\n            number_label = class_to_num[label]\n\n            return img_as_img, number_label # 返回每一个index对应的图片数据和对应的label\n        \n    def __len__(self):\n            return self.real_len","metadata":{"execution":{"iopub.status.busy":"2024-10-15T03:33:40.589097Z","iopub.execute_input":"2024-10-15T03:33:40.589418Z","iopub.status.idle":"2024-10-15T03:33:40.608194Z","shell.execute_reply.started":"2024-10-15T03:33:40.589373Z","shell.execute_reply":"2024-10-15T03:33:40.607166Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 调用LeavesData类生成数据集\n定义一下不同数据集的csv_path，并通过更改mode修改数据集类的实例对象   \n可以看到训练集有多少照片","metadata":{}},{"cell_type":"code","source":"train_path = '/kaggle/input/classify-leaves/train.csv'\ntest_path = '/kaggle/input/classify-leaves/test.csv'\nimg_path = '/kaggle/input/classify-leaves/'\ntrain_dataset = LeavesData(train_path, img_path, mode = 'train')\nval_dataset = LeavesData(train_path, img_path, mode = 'valid')\ntest_dataset = LeavesData(test_path, img_path, mode = 'test')\nprint(train_dataset)\nprint(val_dataset)\nprint(test_dataset)","metadata":{"execution":{"iopub.status.busy":"2024-10-15T03:33:40.609904Z","iopub.execute_input":"2024-10-15T03:33:40.610350Z","iopub.status.idle":"2024-10-15T03:33:40.657791Z","shell.execute_reply.started":"2024-10-15T03:33:40.610306Z","shell.execute_reply":"2024-10-15T03:33:40.656849Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 定义DataLoader用于训练模型\n调用torch.utils.data.DataLoader        \ndataset:数据集名称          \nbatch_size:批量大小        \nshuffle:是否打乱数据          \nnum_workers:设置自己电脑GPU核心数      ","metadata":{}},{"cell_type":"code","source":"train_loader = torch.utils.data.DataLoader(\n    dataset = train_dataset,\n    batch_size = 128,\n    shuffle = False,\n    num_workers = 5)\n\nval_loader = torch.utils.data.DataLoader(\n    dataset = val_dataset,\n    batch_size = 128,\n    shuffle = False,\n    num_workers = 5)\n\ntest_loader = torch.utils.data.DataLoader(\n    dataset = test_dataset,\n    batch_size = 128,\n    shuffle = False,\n    num_workers = 5)","metadata":{"execution":{"iopub.status.busy":"2024-10-15T03:33:40.659051Z","iopub.execute_input":"2024-10-15T03:33:40.659433Z","iopub.status.idle":"2024-10-15T03:33:40.670040Z","shell.execute_reply.started":"2024-10-15T03:33:40.659388Z","shell.execute_reply":"2024-10-15T03:33:40.669106Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 给大家展示一下数据长啥样\ndef im_convert(tensor):\n    \"\"\" 展示数据\"\"\"\n    \n    image = tensor.to(\"cpu\").clone().detach()\n    image = image.numpy().squeeze()\n    image = image.transpose(1,2,0)\n    image = image.clip(0, 1)\n\n    return image\n\nfig=plt.figure(figsize=(20, 12))\ncolumns = 4\nrows = 2\nnum_workers=0\ndataiter = iter(val_loader)\ninputs, classes = next(dataiter)\n\nfor idx in range (columns*rows):\n    ax = fig.add_subplot(rows, columns, idx+1, xticks=[], yticks=[])\n    ax.set_title(num_to_class[int(classes[idx])])\n    plt.imshow(im_convert(inputs[idx]))\nplt.show()\n","metadata":{"execution":{"iopub.status.busy":"2024-10-15T03:33:40.671246Z","iopub.execute_input":"2024-10-15T03:33:40.671751Z","iopub.status.idle":"2024-10-15T03:33:43.629824Z","shell.execute_reply.started":"2024-10-15T03:33:40.671716Z","shell.execute_reply":"2024-10-15T03:33:43.628381Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 看一下是在cpu还是GPU上\ndef get_device():\n    return 'cuda' if torch.cuda.is_available() else 'cpu'\n\ndevice = get_device()\nprint(device)","metadata":{"scrolled":true,"execution":{"iopub.status.busy":"2024-10-15T03:33:43.636829Z","iopub.execute_input":"2024-10-15T03:33:43.637289Z","iopub.status.idle":"2024-10-15T03:33:43.677964Z","shell.execute_reply.started":"2024-10-15T03:33:43.637241Z","shell.execute_reply":"2024-10-15T03:33:43.676892Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## fine-tuning\n### 冻结模型前面的层\n只训练顶层的模型参数","metadata":{}},{"cell_type":"code","source":"def set_parameter_requires_grad(model, feature_extracting):\n    if feature_extracting:\n        model = model\n        for param in model.parameters():\n            param.requires_grad = False","metadata":{"execution":{"iopub.status.busy":"2024-10-15T03:33:43.679287Z","iopub.execute_input":"2024-10-15T03:33:43.679963Z","iopub.status.idle":"2024-10-15T03:33:43.692066Z","shell.execute_reply.started":"2024-10-15T03:33:43.679914Z","shell.execute_reply":"2024-10-15T03:33:43.690907Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### ResNet预训练模型\nnum_classes是输出层维度，在该项目应为176     \nfc是full connect全连接层       \nfc.in_features        ","metadata":{}},{"cell_type":"code","source":"# 调用ResNet并保留原模型参数\ndef res_model(num_classes, feature_extract = False, use_pretrained = True):\n    model_ft = models.resnet34(pretrained = use_pretrained)\n    set_parameter_requires_grad(model_ft, feature_extract)\n    num_ftrs = model_ft.fc.in_features\n    model_ft.fc = nn.Sequential(nn.Linear(num_ftrs, num_classes))\n    return model_ft","metadata":{"execution":{"iopub.status.busy":"2024-10-15T03:33:43.698378Z","iopub.execute_input":"2024-10-15T03:33:43.699161Z","iopub.status.idle":"2024-10-15T03:33:43.705494Z","shell.execute_reply.started":"2024-10-15T03:33:43.699121Z","shell.execute_reply":"2024-10-15T03:33:43.704708Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 定义超参数\nweight_decay 权重衰减","metadata":{}},{"cell_type":"code","source":"# 超参数\nlearning_rate = 3e-5\nweight_decay = 1e-3\nnum_epoch = 50\nmodel_path = './pre_res_model.ckpt'\nmodel_path","metadata":{"execution":{"iopub.status.busy":"2024-10-15T03:33:43.707118Z","iopub.execute_input":"2024-10-15T03:33:43.707849Z","iopub.status.idle":"2024-10-15T03:33:43.720560Z","shell.execute_reply.started":"2024-10-15T03:33:43.707814Z","shell.execute_reply":"2024-10-15T03:33:43.719369Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 把模型放到GPU上","metadata":{}},{"cell_type":"code","source":"model = res_model(176)# 176种叶子\nmodel = model.to(device)\nmodel.device = device\nprint(model)","metadata":{"execution":{"iopub.status.busy":"2024-10-15T03:33:43.722026Z","iopub.execute_input":"2024-10-15T03:33:43.727519Z","iopub.status.idle":"2024-10-15T03:33:45.254215Z","shell.execute_reply.started":"2024-10-15T03:33:43.727469Z","shell.execute_reply":"2024-10-15T03:33:45.253095Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 定义loss以及optim(优化器；例如SGD，Adam)","metadata":{}},{"cell_type":"code","source":"criterion = nn.CrossEntropyLoss()\noptimizer = torch.optim.Adam(model.parameters(), lr = learning_rate, weight_decay = weight_decay)","metadata":{"execution":{"iopub.status.busy":"2024-10-15T03:33:45.255593Z","iopub.execute_input":"2024-10-15T03:33:45.256018Z","iopub.status.idle":"2024-10-15T03:33:45.263736Z","shell.execute_reply.started":"2024-10-15T03:33:45.255972Z","shell.execute_reply":"2024-10-15T03:33:45.262348Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 训练网络\n下列代码逻辑务必熟练掌握  \n\n1.请始终记得DataLoader迭代器返回的是什么?     \n2.数据要先放在GPU再使用     \n3.forward() —> loss() —> zero_grad() —>backward() —> step()    \n4.loss和acc如何直观打印出来？   \n5.验证集和测试集要固定模型参数with torch.no_grad()，因为不能再训练。           model.train()和model.eval()的区别主要在有无dropout与BN(Batch Normalization)     \n6. model.state_dict()模型一旦进化就会被保存，保存的是模型参数     ","metadata":{}},{"cell_type":"code","source":"n_epochs = num_epoch\n\nbest_acc = 0.0\nfor epoch in range(n_epochs):\n    # ---------- Training ----------\n    # Make sure the model is in train mode before training.\n    model.train() \n    # These are used to record information in training.\n    train_loss = []\n    train_accs = []\n    # Iterate the training set by batches.\n    for batch in tqdm(train_loader):\n        # A batch consists of image data and corresponding labels.\n        imgs, labels = batch\n        imgs = imgs.to(device)\n        labels = labels.to(device)\n        # Forward the data. (Make sure data and model are on the same device.)\n        logits = model(imgs)\n        # Calculate the cross-entropy loss.\n        # We don't need to apply softmax before computing cross-entropy as it is done automatically.\n        loss = criterion(logits, labels)\n        \n        # Gradients stored in the parameters in the previous step should be cleared out first.\n        optimizer.zero_grad()\n        # Compute the gradients for parameters.\n        loss.backward()\n        # Update the parameters with computed gradients.\n        optimizer.step()\n        \n        # Compute the accuracy for current batch.\n        acc = (logits.argmax(dim=-1) == labels).float().mean()\n\n        # Record the loss and accuracy.\n        train_loss.append(loss.item())\n        train_accs.append(acc)\n        \n    # The average loss and accuracy of the training set is the average of the recorded values.\n    train_loss = sum(train_loss) / len(train_loss)\n    train_acc = sum(train_accs) / len(train_accs)\n\n    # Print the information.\n    print(f\"[ Train | {epoch + 1:03d}/{n_epochs:03d} ] loss = {train_loss:.5f}, acc = {train_acc:.5f}\")\n    \n    \n    # ---------- Validation ----------\n    # Make sure the model is in eval mode so that some modules like dropout are disabled and work normally.\n    model.eval()\n    # These are used to record information in validation.\n    valid_loss = []\n    valid_accs = []\n    \n    # Iterate the validation set by batches.\n    for batch in tqdm(val_loader):\n        imgs, labels = batch\n        # We don't need gradient in validation.\n        # Using torch.no_grad() accelerates the forward process.\n        with torch.no_grad():\n            logits = model(imgs.to(device))\n            \n        # We can still compute the loss (but not the gradient).\n        loss = criterion(logits, labels.to(device))\n\n        # Compute the accuracy for current batch.\n        acc = (logits.argmax(dim=-1) == labels.to(device)).float().mean()\n\n        # Record the loss and accuracy.\n        valid_loss.append(loss.item())\n        valid_accs.append(acc)\n        \n    # The average loss and accuracy for entire validation set is the average of the recorded values.\n    valid_loss = sum(valid_loss) / len(valid_loss)\n    valid_acc = sum(valid_accs) / len(valid_accs)\n\n    # Print the information.\n    print(f\"[ Valid | {epoch + 1:03d}/{n_epochs:03d} ] loss = {valid_loss:.5f}, acc = {valid_acc:.5f}\")\n    \n    # if the model improves, save a checkpoint at this epoch\n    if valid_acc > best_acc:\n        best_acc = valid_acc\n        torch.save(model.state_dict(), model_path)\n        print('saving model with acc {:.3f}'.format(best_acc))","metadata":{"execution":{"iopub.status.busy":"2024-10-15T03:33:45.265814Z","iopub.execute_input":"2024-10-15T03:33:45.266204Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_loss","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 模型评估\n### 导入训练好的模型\n先实例化一个model对象，再把训练好的参数放进去","metadata":{}},{"cell_type":"code","source":"model = res_model(176)\nmodel = model.to(device)\nmodel.load_state_dict(torch.load(model_path))\nmodel.eval()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### 预测","metadata":{}},{"cell_type":"code","source":"saveFileName = './submission.csv'\npredictions = []\n\nfor batch in tqdm(test_loader):\n    imgs = batch\n    with torch.no_grad():\n        imgs_hat = model(imgs.to(device))\n    predictions.extend(imgs_hat.argmax(dim=-1).cpu().numpy().tolist())\n\npreds = []\nfor i in predictions:\n    preds.append(num_to_class[i])\n    \ntest_data = pd.read_csv(test_path)\ntest_data['label'] = pd.Series(preds)\nsubmission = pd.concat([test_data['image'], test_data['label']], axis=1)\nsubmission.to_csv(saveFileName, index=False)\nprint(\"Done!!!!!!!!!!!!!!!!!!!!!!!!!!!\")\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}