{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.13","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":30733,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import 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\nfrom tqdm import tqdm\nimport seaborn as sns\n\n\nimport torch\nimport torchvision\nimport torch.nn as nn","metadata":{"execution":{"iopub.status.busy":"2024-06-18T14:58:02.903046Z","iopub.execute_input":"2024-06-18T14:58:02.903401Z","iopub.status.idle":"2024-06-18T14:58:07.006979Z","shell.execute_reply.started":"2024-06-18T14:58:02.903372Z","shell.execute_reply":"2024-06-18T14:58:07.006049Z"},"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":"2024-06-18T14:58:07.009306Z","iopub.execute_input":"2024-06-18T14:58:07.009704Z","iopub.status.idle":"2024-06-18T14:58:07.064265Z","shell.execute_reply.started":"2024-06-18T14:58:07.009679Z","shell.execute_reply":"2024-06-18T14:58:07.063386Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"leaves_labels = sorted(list(set(labels_dataframe['label']))) # 利用set对标签去重\nn_classes = len(leaves_labels)\nprint(n_classes)\nleaves_labels[:10]","metadata":{"execution":{"iopub.status.busy":"2024-06-18T14:58:07.065477Z","iopub.execute_input":"2024-06-18T14:58:07.065774Z","iopub.status.idle":"2024-06-18T14:58:07.075965Z","shell.execute_reply.started":"2024-06-18T14:58:07.065748Z","shell.execute_reply":"2024-06-18T14:58:07.075059Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#----------------------------------------#\n# label转数字\n#----------------------------------------#\nclass_to_num = dict(zip(leaves_labels, range(n_classes)))\nclass_to_num","metadata":{"execution":{"iopub.status.busy":"2024-06-18T14:58:08.962799Z","iopub.execute_input":"2024-06-18T14:58:08.963176Z","iopub.status.idle":"2024-06-18T14:58:08.976346Z","shell.execute_reply.started":"2024-06-18T14:58:08.963146Z","shell.execute_reply":"2024-06-18T14:58:08.975324Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#----------------------------------------#\n# 在label字典中将数字作为关键字，标签名作为值，便于操作\n#----------------------------------------#\nnum_to_class = {v : k for k, v in class_to_num.items()}\nnum_to_class","metadata":{"execution":{"iopub.status.busy":"2024-06-18T14:58:10.373331Z","iopub.execute_input":"2024-06-18T14:58:10.373785Z","iopub.status.idle":"2024-06-18T14:58:10.388606Z","shell.execute_reply.started":"2024-06-18T14:58:10.373744Z","shell.execute_reply":"2024-06-18T14:58:10.387425Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#----------------------------------------#\n# 继承 pytorch 的 dataset，创建自己的数据集操作\n#----------------------------------------#\nclass LeavesData(Dataset):\n    def __init__(self, csv_path='/kaggle/input/classify-leaves',\n                 file_path='/kaggle/input/classify-leaves/images/', mode='train', \n                 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        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        \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            # 获取图像文件的名称\n            # 读取的csv文件中第一行是每列数据对应的类别名称：image|label\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)' .format(mode, self.real_len))\n    \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        #如果需要将 RGB 三通道的图片转换成灰度图片可参考下面两行\n        # if img_as_img.mode != 'L':\n        # img_as_img = img_as_img.convert('L')\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            return img_as_img, number_label #返回每一个index对应的图片数据和对应的label\n\n    def __len__(self):\n        return self.real_len","metadata":{"execution":{"iopub.status.busy":"2024-06-18T14:58:11.538624Z","iopub.execute_input":"2024-06-18T14:58:11.539098Z","iopub.status.idle":"2024-06-18T14:58:11.557032Z","shell.execute_reply.started":"2024-06-18T14:58:11.53906Z","shell.execute_reply":"2024-06-18T14:58:11.555973Z"},"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)","metadata":{"execution":{"iopub.status.busy":"2024-06-18T14:58:12.622885Z","iopub.execute_input":"2024-06-18T14:58:12.623292Z","iopub.status.idle":"2024-06-18T14:58:12.683665Z","shell.execute_reply.started":"2024-06-18T14:58:12.623259Z","shell.execute_reply":"2024-06-18T14:58:12.682562Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 定义 data loader\ntrain_loader = DataLoader(\n        dataset=train_dataset,\n        batch_size=32,\n        shuffle=False,\n        num_workers=5\n    )\n\nval_loader = DataLoader(\n        dataset=val_dataset, \n        batch_size=8, \n        shuffle=False, \n        num_workers=5\n    )\ntest_loader = DataLoader(\n        dataset=test_dataset, \n        batch_size=8,\n        shuffle=False, \n        num_workers=5\n)","metadata":{"execution":{"iopub.status.busy":"2024-06-18T14:58:15.097975Z","iopub.execute_input":"2024-06-18T14:58:15.09837Z","iopub.status.idle":"2024-06-18T14:58:15.10677Z","shell.execute_reply.started":"2024-06-18T14:58:15.098342Z","shell.execute_reply":"2024-06-18T14:58:15.105832Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 定义自己的模型","metadata":{}},{"cell_type":"code","source":"# -----------------------------------------------# \n# VGG16模型主干结构\n# -----------------------------------------------# \nBackbone = [64, 64, 'M',\n            128, 128, 'M',\n            256, 256, 256, 'M',\n            512, 512, 512, 'M',\n            512, 512, 512, 'M']\nclass VGG(nn.Module):\n    def __init__(self, num_classes=176):\n        super(VGG, self).__init__()\n        # -----------------------------------------------# \n        # 模型主干\n        # -----------------------------------------------# \n        self.features = self._make_layers(Backbone)\n        # -----------------------------------------------# \n        # 自适应平均池化,可以自动适应输入的大小\n        # 同时可以设置输出的尺寸大小\n        # -----------------------------------------------# \n        self.avgpool = nn.AdaptiveAvgPool2d((7, 7))\n        # -----------------------------------------------# \n        # 分类输出头\n        # -----------------------------------------------# \n        self.classifier = nn.Sequential(\n            nn.Linear(512 * 7 * 7, 4096),\n            nn.ReLU(True),\n            nn.Dropout(),\n            nn.Linear(4096, 4096),\n            nn.ReLU(True),\n            nn.Dropout(),\n            nn.Linear(4096, num_classes),\n        )\n\n    def forward(self, x):\n        x = self.features(x)\n        x = self.avgpool(x)\n        x = torch.flatten(x, 1)\n        out = self.classifier(x)\n        return out\n\n    def _make_layers(self, vgg_layer):\n        layers = []\n        in_channels = 3\n        for x in vgg_layer:\n            if x == 'M':\n                layers += [nn.MaxPool2d(kernel_size=2, stride=2)]\n            else:\n                layers += [nn.Conv2d(in_channels, x, kernel_size=3, padding=1, bias=False),\n                           nn.BatchNorm2d(x),\n                           nn.ReLU(inplace=True)]\n                in_channels = x\n        return nn.Sequential(*layers)","metadata":{"execution":{"iopub.status.busy":"2024-06-18T14:58:18.018108Z","iopub.execute_input":"2024-06-18T14:58:18.019017Z","iopub.status.idle":"2024-06-18T14:58:18.029586Z","shell.execute_reply.started":"2024-06-18T14:58:18.018983Z","shell.execute_reply":"2024-06-18T14:58:18.028593Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# ResNet 模型\ndef res_model(num_classes, feature_extract = False, use_pretrained=True):\n    # -----------------------------------------------# \n    #  调用上面构造的VGG模型\n    # -----------------------------------------------# \n    model_ft = VGG(num_classes)\n    # -----------------------------------------------# \n    #  调用pytroch实现的resnet模型\n    # -----------------------------------------------# \n#     model_ft = torchvision.models.resnet18(pretrained=use_pretrained)\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-06-18T14:58:19.047825Z","iopub.execute_input":"2024-06-18T14:58:19.048479Z","iopub.status.idle":"2024-06-18T14:58:19.053881Z","shell.execute_reply.started":"2024-06-18T14:58:19.048447Z","shell.execute_reply":"2024-06-18T14:58:19.052917Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 超参数\nlearning_rate = 3e-4\nweight_decay = 1e-3\nnum_epoch = 5\nmodel_path = './pre_vgg_model.ckpt'","metadata":{"execution":{"iopub.status.busy":"2024-06-18T14:59:34.41818Z","iopub.execute_input":"2024-06-18T14:59:34.419064Z","iopub.status.idle":"2024-06-18T14:59:34.423265Z","shell.execute_reply.started":"2024-06-18T14:59:34.419027Z","shell.execute_reply":"2024-06-18T14:59:34.422387Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# -----------------------------------------------# \n# 选择运行设备\n# -----------------------------------------------# \ndevice = 'cuda' if torch.cuda.is_available() else 'cpu'\nprint(\"device:\",device)","metadata":{"execution":{"iopub.status.busy":"2024-06-18T14:59:34.792791Z","iopub.execute_input":"2024-06-18T14:59:34.793705Z","iopub.status.idle":"2024-06-18T14:59:34.798702Z","shell.execute_reply.started":"2024-06-18T14:59:34.793672Z","shell.execute_reply":"2024-06-18T14:59:34.797829Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#### 初始化模型，并将其放在指定的设备上\nmodel = res_model(176)\nmodel = model.to(device)\nmodel.device = device\n\n# 对于分类任务，我们使用交叉熵\ncriterion = nn.CrossEntropyLoss()\n\n# 初始化优化器，可以自己微调学习率等超参数。\noptimizer = torch.optim.Adam(model.parameters(), lr = learning_rate, weight_decay=weight_decay)\n\nn_epochs = num_epoch\n\nbest_acc = 0.0","metadata":{"execution":{"iopub.status.busy":"2024-06-18T14:59:36.047735Z","iopub.execute_input":"2024-06-18T14:59:36.048093Z","iopub.status.idle":"2024-06-18T14:59:36.329716Z","shell.execute_reply.started":"2024-06-18T14:59:36.048064Z","shell.execute_reply":"2024-06-18T14:59:36.328756Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for epoch in range(n_epochs):\n# ---------- 训练 ---------- #在训练前确保模型处于训练模式。\n    model.train()\n\n    # 记录训练过程中的信息\n    train_loss = []\n    train_accs = []\n    \n    #分批迭代训练集\n    for batch in tqdm(train_loader):\n        # 一个批次由图像数据和对应的标签组成\n        imgs,labels = batch\n        imgs = imgs.to(device)\n        labels = labels.to(device)\n        # 前向传播，确保数据和模型在同一个设备上\n        logits = model(imgs)\n        # 计算交叉熵损失\n        # 不需要在计算交叉熵之前应用 softmax\n        loss = criterion(logits, labels)\n        # 梯度清零\n        optimizer.zero_grad()\n        # 计算参数的梯度\n        loss.backward()\n        # 使用计算的梯度更新参数\n        optimizer.step()\n        # 计算当前 batch 的准确率\n        acc = (logits.argmax(dim=-1) == labels).float().mean()\n\n        # 记录 loss 和准确率\n        train_loss.append(loss.item())\n        train_accs.append(acc)\n \n    # 训练集上的平均 loss 和准确率是记录值的平均值\n    train_loss = sum(train_loss) / len(train_loss)\n    train_acc = sum(train_accs) / len(train_accs)\n\n    # 打印训练过程中的信息. \n    print(f\"[ Train | {epoch + 1:03d}/{n_epochs:03d} ] loss = {train_loss:.5f}, acc ={train_acc:.5f}\")\n \n    # ---------- 验证 ---------- # 确保模型处于 eval 模式，以便某些模块(如 dropout)被禁用并正常工作\n    model.eval()\n    #记录验证集上的信息\n    valid_loss = []\n    valid_accs = []\n\n    # ---------- 验证 ---------- # 确保模型处于 eval 模式，以便某些模块(如 dropout)被禁用并正常工作\n    model.eval()\n    #记录验证集上的信息\n    valid_loss = []\n    valid_accs = []\n\n    # 按批次迭代验证集\n    for batch in tqdm(val_loader):\n        imgs, labels = batch\n        # 在验证集上不需要计算梯度\n        # 使用 torch.no_grad()加速前向传播\n        with torch.no_grad():\n            logits = model(imgs.to(device))\n        # 可以计算损失(但不能计算梯度)\n        loss = criterion(logits, labels.to(device))\n        # 计算当前 batch 的精度\n        acc = (logits.argmax(dim=-1) == labels.to(device)).float().mean()\n        # 记录 loss 和准确率\n        valid_loss.append(loss.item())\n        valid_accs.append(acc)\n\n    # 整个验证集的平均损失和准确度是记录值的平均值\n    valid_loss = sum(valid_loss) / len(valid_loss)\n    valid_acc = sum(valid_accs) / len(valid_accs)\n\n    print(f\"[ Valid | {epoch + 1:03d}/{n_epochs:03d} ] loss = {valid_loss:.5f},acc ={valid_acc:.5f}\")\n\n    # 模型改进了就保存新的准确率\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-06-18T14:59:37.947997Z","iopub.execute_input":"2024-06-18T14:59:37.948383Z","iopub.status.idle":"2024-06-18T15:00:25.286993Z","shell.execute_reply.started":"2024-06-18T14:59:37.948356Z","shell.execute_reply":"2024-06-18T15:00:25.285558Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 推理","metadata":{}},{"cell_type":"code","source":"# --------------------------------------#\n# 预测结果保存路径\n# --------------------------------------#\nsaveFilename = './subission_2.csv'\n# --------------------------------------#\n# 加载模型\n# --------------------------------------#\nmodel = res_model(176)\nmodel = model.to(device)\nmodel.load_state_dict(torch.load(model_path))\nmodel.eval()\n\npredictions = []\nfor batch in tqdm(test_loader):\n    imgs = batch\n    with torch.no_grad():\n        logits = model(imgs.to(device))\n    \n    predictions.extend(logits.argmax(dim=-1).cpu().numpy().tolist())\n# --------------------------------------#\n# 将输出结果的数值标签转为对于类名标签\n# --------------------------------------#    \npreds = []\nfor i in predictions:\n    preds.append(num_to_class[i])\n# --------------------------------------#\n# 将输出转化的类名标签存入csv文件中\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(\"结束!\")","metadata":{"execution":{"iopub.status.busy":"2024-06-11T02:29:41.140451Z","iopub.execute_input":"2024-06-11T02:29:41.141313Z","iopub.status.idle":"2024-06-11T02:30:13.532658Z","shell.execute_reply.started":"2024-06-11T02:29:41.141274Z","shell.execute_reply":"2024-06-11T02:30:13.53155Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}