{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pandas as pd \nimport numpy as np \nimport torch \nimport seaborn as sns \nimport matplotlib.pyplot as plt \nfrom sklearn.preprocessing import LabelEncoder,OneHotEncoder\nimport os \nfrom PIL import Image \nfrom torchvision import transforms\n# 首先导入包\nimport 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\n# This is for the progress bar.\nfrom tqdm import tqdm\nimport seaborn as sns\n","metadata":{"execution":{"iopub.status.busy":"2023-06-20T13:06:43.235657Z","iopub.execute_input":"2023-06-20T13:06:43.236584Z","iopub.status.idle":"2023-06-20T13:06:48.689564Z","shell.execute_reply.started":"2023-06-20T13:06:43.236542Z","shell.execute_reply":"2023-06-20T13:06:48.688551Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 加载原始数据的统计目录\ntrain_data = pd.read_csv('/kaggle/input/classify-leaves/train.csv')\ntrain_data.head()","metadata":{"execution":{"iopub.status.busy":"2023-06-20T13:06:48.691914Z","iopub.execute_input":"2023-06-20T13:06:48.693119Z","iopub.status.idle":"2023-06-20T13:06:48.767497Z","shell.execute_reply.started":"2023-06-20T13:06:48.693071Z","shell.execute_reply":"2023-06-20T13:06:48.766367Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 训练数据共18353张图片，176类叶子\ntrain_data.describe()","metadata":{"execution":{"iopub.status.busy":"2023-06-20T13:06:48.769391Z","iopub.execute_input":"2023-06-20T13:06:48.769746Z","iopub.status.idle":"2023-06-20T13:06:48.80267Z","shell.execute_reply.started":"2023-06-20T13:06:48.769717Z","shell.execute_reply":"2023-06-20T13:06:48.801795Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 统计训练数据label的类别和数量\ntrain_data['label'].value_counts()","metadata":{"execution":{"iopub.status.busy":"2023-06-20T13:06:48.804482Z","iopub.execute_input":"2023-06-20T13:06:48.804789Z","iopub.status.idle":"2023-06-20T13:06:48.817095Z","shell.execute_reply.started":"2023-06-20T13:06:48.804764Z","shell.execute_reply":"2023-06-20T13:06:48.81593Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 树叶的名字统计\nlabels_unique = train_data['label'].unique()\nlabels_unique","metadata":{"execution":{"iopub.status.busy":"2023-06-20T13:06:48.819012Z","iopub.execute_input":"2023-06-20T13:06:48.819497Z","iopub.status.idle":"2023-06-20T13:06:48.83257Z","shell.execute_reply.started":"2023-06-20T13:06:48.819455Z","shell.execute_reply":"2023-06-20T13:06:48.831426Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 对label进行编码，并将映射表保存下来\nlabelencoder = LabelEncoder()\nlabelencoder.fit(train_data['label'])\ntrain_data['label'] = labelencoder.transform(train_data['label'])\nlabel_map = dict(zip(labelencoder.classes_,labelencoder.transform(labelencoder.classes_)))\nlabel_inv_map = {v:k for k,v in label_map.items()}\nlabel_map","metadata":{"execution":{"iopub.status.busy":"2023-06-20T13:06:48.834035Z","iopub.execute_input":"2023-06-20T13:06:48.834449Z","iopub.status.idle":"2023-06-20T13:06:48.855384Z","shell.execute_reply.started":"2023-06-20T13:06:48.834411Z","shell.execute_reply":"2023-06-20T13:06:48.854224Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#查看前20类的统计数量和绘图查看\ntop20_trainData = train_data['label'].value_counts().sort_values(ascending=False).head(20)\nprint(top20_trainData)\nplt.figure(figsize=(15,10))\nsns.barplot(x=top20_trainData.index,y=top20_trainData)\nplt.xticks(rotation=70)\nplt.title(\"Top 20 categories of leaf statistics\")\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2023-06-20T13:06:48.856848Z","iopub.execute_input":"2023-06-20T13:06:48.857889Z","iopub.status.idle":"2023-06-20T13:06:49.341483Z","shell.execute_reply.started":"2023-06-20T13:06:48.857844Z","shell.execute_reply":"2023-06-20T13:06:49.340524Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_data = pd.read_csv('/kaggle/input/classify-leaves/test.csv')\ntest_data","metadata":{"execution":{"iopub.status.busy":"2023-06-20T13:06:49.3429Z","iopub.execute_input":"2023-06-20T13:06:49.3436Z","iopub.status.idle":"2023-06-20T13:06:49.367591Z","shell.execute_reply.started":"2023-06-20T13:06:49.343565Z","shell.execute_reply":"2023-06-20T13:06:49.366496Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 测试数据共8800张图\ntest_data.describe()","metadata":{"execution":{"iopub.status.busy":"2023-06-20T13:06:49.369079Z","iopub.execute_input":"2023-06-20T13:06:49.369689Z","iopub.status.idle":"2023-06-20T13:06:49.387295Z","shell.execute_reply.started":"2023-06-20T13:06:49.369655Z","shell.execute_reply":"2023-06-20T13:06:49.386172Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"folder_path = \"/kaggle/input/classify-leaves/\"\n\n# 拿出几张照片看看\nfig, ax = plt.subplots(\n    nrows=3,\n    ncols=4,\n    sharex=True,\n    sharey=True, \n    figsize=(18,12)\n)\n\nax = ax.flatten()\ntransform = transforms.Compose([\n            transforms.Resize((224,224)),\n            transforms.ToTensor()\n            ])\nfor i in range(12):\n    img_path = os.path.join(folder_path,train_data['image'][i])\n    data = Image.open(img_path)\n    data = transform(data)\n    ax[i].imshow(data.permute((2,1,0))) # 记住将维度变换一下\n    ax[i].set(title=train_data['label'][i])\n    ax[i].title.set_size(25)\n    \nax[0].set_xticks([])\nax[0].set_yticks([])\nplt.tight_layout()\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2023-06-20T13:06:49.391278Z","iopub.execute_input":"2023-06-20T13:06:49.39165Z","iopub.status.idle":"2023-06-20T13:06:51.406629Z","shell.execute_reply.started":"2023-06-20T13:06:49.391617Z","shell.execute_reply":"2023-06-20T13:06:51.405672Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"labels_dataframe = pd.read_csv('/kaggle/input/classify-leaves/train.csv')\nlabels_dataframe.head(5)","metadata":{"execution":{"iopub.status.busy":"2023-06-20T13:06:51.407755Z","iopub.execute_input":"2023-06-20T13:06:51.408257Z","iopub.status.idle":"2023-06-20T13:06:51.441259Z","shell.execute_reply.started":"2023-06-20T13:06:51.408225Z","shell.execute_reply":"2023-06-20T13:06:51.440189Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#function to show bar length\n\ndef barw(ax): \n    \n    for p in ax.patches:\n        val = p.get_width() #height of the bar\n        x = p.get_x()+ p.get_width() # x- position \n        y = p.get_y() + p.get_height()/2 #y-position\n        ax.annotate(round(val,2),(x,y))\n        \n#finding top leaves\n\nplt.figure(figsize = (15,30))\n#类别特征的频数条形图（x轴是count数，y轴是类别。）\nax0 =sns.countplot(y=labels_dataframe['label'],order=labels_dataframe['label'].value_counts().index)\nbarw(ax0)\nplt.show()\n","metadata":{"execution":{"iopub.status.busy":"2023-06-20T13:06:51.442816Z","iopub.execute_input":"2023-06-20T13:06:51.443159Z","iopub.status.idle":"2023-06-20T13:06:54.067535Z","shell.execute_reply.started":"2023-06-20T13:06:51.44313Z","shell.execute_reply":"2023-06-20T13:06:54.066431Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#把label文件排个序\nleaves_labels = sorted(list(set(labels_dataframe['label'])))\nn_classes = len(leaves_labels)\nprint(n_classes)\nleaves_labels[:10]","metadata":{"execution":{"iopub.status.busy":"2023-06-20T13:06:54.068974Z","iopub.execute_input":"2023-06-20T13:06:54.069417Z","iopub.status.idle":"2023-06-20T13:06:54.079263Z","shell.execute_reply.started":"2023-06-20T13:06:54.069386Z","shell.execute_reply":"2023-06-20T13:06:54.078393Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#把label转成对应的数字\nclass_to_num = dict(zip(leaves_labels, range(n_classes)))\nclass_to_num","metadata":{"execution":{"iopub.status.busy":"2023-06-20T13:06:54.080499Z","iopub.execute_input":"2023-06-20T13:06:54.081832Z","iopub.status.idle":"2023-06-20T13:06:54.101032Z","shell.execute_reply.started":"2023-06-20T13:06:54.081784Z","shell.execute_reply":"2023-06-20T13:06:54.099818Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 再转换回来，方便最后预测的时候使用\nnum_to_class = {v : k for k, v in class_to_num.items()}","metadata":{"execution":{"iopub.status.busy":"2023-06-20T13:06:54.102336Z","iopub.execute_input":"2023-06-20T13:06:54.105696Z","iopub.status.idle":"2023-06-20T13:06:54.110536Z","shell.execute_reply.started":"2023-06-20T13:06:54.10566Z","shell.execute_reply":"2023-06-20T13:06:54.109506Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 继承pytorch的dataset，创建自己的\nclass LeavesData(Dataset):\n    def __init__(self, csv_path, file_path, mode='train', valid_ratio=0.2, resize_height=256, resize_width=256):\n        \"\"\"\n        Args:\n            csv_path (string): csv 文件路径\n            img_path (string): 图像文件所在路径\n            mode (string): 训练模式还是测试模式\n            valid_ratio (float): 验证集比例\n        \"\"\"\n        \n        # 需要调整后的照片尺寸，我这里每张图片的大小尺寸不一致#\n        self.resize_height = resize_height\n        self.resize_width = resize_width\n\n        self.file_path = file_path\n        self.mode = mode\n\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        self.train_len = int(self.data_len * (1 - valid_ratio))\n        \n        if mode == 'train':\n            # 第一列包含图像文件的名称\n            self.train_image = np.asarray(self.data_info.iloc[1:self.train_len, 0])  \n            #self.data_info.iloc[1:,0]表示读取第一列，从第二行开始到train_len\n            # 第二列是图像的 label\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        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        elif mode == 'test':\n            self.test_image = np.asarray(self.data_info.iloc[1:, 0])\n            self.image_arr = self.test_image\n            \n        self.real_len = len(self.image_arr)\n\n        print('Finished reading the {} set of Leaves Dataset ({} samples found)'\n              .format(mode, self.real_len))\n\n    def __getitem__(self, index):\n        # 从 image_arr中得到索引对应的文件名\n        single_image_name = self.image_arr[index]\n\n        # 读取图像文件\n        img_as_img = Image.open(self.file_path + single_image_name)\n\n        #如果需要将RGB三通道的图片转换成灰度图片可参考下面两行\n#         if img_as_img.mode != 'L':\n#             img_as_img = img_as_img.convert('L')\n\n        #设置好需要转换的变量，还可以包括一系列的nomarlize等等操作\n        if self.mode == 'train':\n            transform = transforms.Compose([\n                transforms.Resize((224, 224)),\n                transforms.RandomHorizontalFlip(p=0.5),   #随机水平翻转 选择一个概率\n                transforms.ToTensor()\n            ])\n        else:\n            # valid和test不做数据增强\n            transform = transforms.Compose([\n                transforms.Resize((224, 224)),\n                transforms.ToTensor()\n            ])\n        \n        img_as_img = transform(img_as_img)\n        \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\n","metadata":{"execution":{"iopub.status.busy":"2023-06-20T13:06:54.112346Z","iopub.execute_input":"2023-06-20T13:06:54.112768Z","iopub.status.idle":"2023-06-20T13:06:54.129478Z","shell.execute_reply.started":"2023-06-20T13:06:54.112729Z","shell.execute_reply":"2023-06-20T13:06:54.128362Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_path = '/kaggle/input/classify-leaves/train.csv'\ntest_path = '/kaggle/input/classify-leaves/test.csv'\n# csv文件中已经images的路径了，因此这里只到上一级目录\nimg_path = '/kaggle/input/classify-leaves/'\n\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)\n","metadata":{"execution":{"iopub.status.busy":"2023-06-20T13:06:54.130812Z","iopub.execute_input":"2023-06-20T13:06:54.131201Z","iopub.status.idle":"2023-06-20T13:06:54.185378Z","shell.execute_reply.started":"2023-06-20T13:06:54.131174Z","shell.execute_reply":"2023-06-20T13:06:54.184369Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 定义data loader\ntrain_loader = torch.utils.data.DataLoader(\n        dataset=train_dataset,\n        batch_size=8, \n        shuffle=False,\n        num_workers=5\n    )\n\nval_loader = torch.utils.data.DataLoader(\n        dataset=val_dataset,\n        batch_size=8, \n        shuffle=False,\n        num_workers=5\n    )\ntest_loader = torch.utils.data.DataLoader(\n        dataset=test_dataset,\n        batch_size=8, \n        shuffle=False,\n        num_workers=5\n    )\n","metadata":{"execution":{"iopub.status.busy":"2023-06-20T13:06:54.186755Z","iopub.execute_input":"2023-06-20T13:06:54.187068Z","iopub.status.idle":"2023-06-20T13:06:54.197749Z","shell.execute_reply.started":"2023-06-20T13:06:54.187043Z","shell.execute_reply":"2023-06-20T13:06:54.196422Z"},"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\n\ndataiter = iter(val_loader)\ninputs, classes = next(dataiter)\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":"2023-06-20T13:06:54.199294Z","iopub.execute_input":"2023-06-20T13:06:54.199801Z","iopub.status.idle":"2023-06-20T13:06:55.444578Z","shell.execute_reply.started":"2023-06-20T13:06:54.19977Z","shell.execute_reply":"2023-06-20T13:06:55.443449Z"},"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)\n","metadata":{"execution":{"iopub.status.busy":"2023-06-20T13:06:55.446682Z","iopub.execute_input":"2023-06-20T13:06:55.447912Z","iopub.status.idle":"2023-06-20T13:06:55.455225Z","shell.execute_reply.started":"2023-06-20T13:06:55.447858Z","shell.execute_reply":"2023-06-20T13:06:55.454293Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"''# 是否要冻住模型的前面一些层\ndef 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\n# resnet34模型\ndef res_model(num_classes, feature_extract = False, use_pretrained=True):\n\n    \n    model_ft = models.resnet34(pretrained=False)\n    pthfile = r'/kaggle/input/resnet34/resnet34.pth'\n    model_ft.load_state_dict(torch.load(pthfile))\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\n    return model_ft\n","metadata":{"execution":{"iopub.status.busy":"2023-06-20T13:06:55.456522Z","iopub.execute_input":"2023-06-20T13:06:55.457172Z","iopub.status.idle":"2023-06-20T13:06:55.469267Z","shell.execute_reply.started":"2023-06-20T13:06:55.457135Z","shell.execute_reply":"2023-06-20T13:06:55.468413Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"'''# 是否要冻住模型的前面一些层\ndef 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\n# resnet34模型\ndef res_model(num_classes, feature_extract = False, use_pretrained=True):\n\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\n    return model_ft'''\n","metadata":{"execution":{"iopub.status.busy":"2023-06-20T13:06:55.470828Z","iopub.execute_input":"2023-06-20T13:06:55.471854Z","iopub.status.idle":"2023-06-20T13:06:55.483676Z","shell.execute_reply.started":"2023-06-20T13:06:55.471818Z","shell.execute_reply":"2023-06-20T13:06:55.482506Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 超参数\nlearning_rate = 3e-4\nweight_decay = 1e-3\nnum_epoch = 50\nmodel_path = './pre_res_model.ckpt'\n","metadata":{"execution":{"iopub.status.busy":"2023-06-20T13:06:55.485188Z","iopub.execute_input":"2023-06-20T13:06:55.485598Z","iopub.status.idle":"2023-06-20T13:06:55.502106Z","shell.execute_reply.started":"2023-06-20T13:06:55.485568Z","shell.execute_reply":"2023-06-20T13:06:55.500882Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Initialize a model, and put it on the device specified.\nmodel = res_model(176)\nmodel = model.to(device)\nmodel.device = device\n# For the classification task, we use cross-entropy as the measurement of performance.\ncriterion = nn.CrossEntropyLoss()\n\n# Initialize optimizer, you may fine-tune some hyperparameters such as learning rate on your own.\noptimizer = torch.optim.Adam(model.parameters(), lr = learning_rate, weight_decay=weight_decay)\n\n# The number of training epochs.\nn_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))\n","metadata":{"execution":{"iopub.status.busy":"2023-06-20T13:06:55.503782Z","iopub.execute_input":"2023-06-20T13:06:55.504172Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"saveFileName = '/kaggle/input/classify-leaves/submission.csv'","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## predict\nmodel = res_model(176)\n\n# create model and load weights from checkpoint\nmodel = model.to(device)\nmodel.load_state_dict(torch.load(model_path))\n\n\n# Make sure the model is in eval mode.\n# Some modules like Dropout or BatchNorm affect if the model is in training mode.\nmodel.eval()\n\n# Initialize a list to store the predictions.\npredictions = []\n# Iterate the testing set by batches.\nfor batch in tqdm(test_loader):\n    \n    imgs = batch\n    with torch.no_grad():\n        logits = model(imgs.to(device))\n    \n    # Take the class with greatest logit as prediction and record it.\n    predictions.extend(logits.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":[]}]}