{"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":"markdown","source":"1 因为Google colab本人没有买过算力，因此epoch过高的时候，经常掉线、无法完成，所以将epoch改为了15，如果是在自己电脑的gpu上跑，可以适当提高epoch到50-70\n\n2 本代码包含了数据上传、解压、以及后续（数据增强到训练等等的一系列操作）\n\n3因为代码较为简单，适合作为入门学习，解析较多\n\n4 最后数据展现的操作时，我是采用预测的数据。而非train，所以label可能有错。\n\n5代码中的路径均是需要更具你自己服务器或电脑的情况更改\n","metadata":{"id":"qvzvGX-hxH6b"}},{"cell_type":"code","source":"from google.colab import drive \ndrive.mount('/content/drive')","metadata":{"executionInfo":{"elapsed":25087,"status":"ok","timestamp":1680748389422,"user":{"displayName":"kai liu","userId":"06190593843486849792"},"user_tz":-480},"id":"nzpy8oAxdh4I","outputId":"c93343db-b7dc-470e-e181-cdfbddc4ff70"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"id":"8JrQHpSXxHCw"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 首先导入包\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","metadata":{"executionInfo":{"elapsed":4579,"status":"ok","timestamp":1680748393996,"user":{"displayName":"kai liu","userId":"06190593843486849792"},"user_tz":-480},"id":"jcAQm7AOP05C"},"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#如果不是cuda 建议使用cuda","metadata":{"executionInfo":{"elapsed":10,"status":"ok","timestamp":1680748393996,"user":{"displayName":"kai liu","userId":"06190593843486849792"},"user_tz":-480},"id":"LDfz3kRPHB7P","outputId":"f519d25e-f863-4c5a-84fb-134bf33e46a6"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**上传数据操作**\n\n这是我在Google colab上的操作\n\n根据自己服务器的情况修改部分代码","metadata":{"id":"OTkNLgbS8ipz"}},{"cell_type":"code","source":"! cd /content/drive/MyDrive/limu","metadata":{"id":"18ZFkmSOeVu9"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# data \n!pip install kaggle","metadata":{"id":"vv8lUBKeeesg"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from google.colab import files\nfiles.upload() ","metadata":{"id":"6wCJh84ZgS89"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!cp kaggle.json ~/.kaggle/","metadata":{"id":"C4CK4RNuirL-"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!kaggle competitions download -c classify-leaves","metadata":{"executionInfo":{"elapsed":2913,"status":"ok","timestamp":1680365158977,"user":{"displayName":"kai liu","userId":"06190593843486849792"},"user_tz":-480},"id":"zl-eZoI2kU5D","outputId":"284e8322-4362-4ff3-f897-bb2ca90518fc"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# unzip 解压包的路径 和 解压文件存贮的路径\n!unzip /content/classify-leaves.zip -d /content/drive/MyDrive/limu","metadata":{"id":"CzsRdjYqlAaI"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":" **分类代码部分**","metadata":{"id":"NwY71R5U8m7O"}},{"cell_type":"code","source":"# 首先导入包\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","metadata":{"id":"Go3rNlJYipmn"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 看看label文件长啥样\nlabels_dataframe = pd.read_csv('/kaggle/input/classify-leaves/train.csv')\nlabels_dataframe.head(5)","metadata":{"executionInfo":{"elapsed":1067,"status":"ok","timestamp":1680748617598,"user":{"displayName":"kai liu","userId":"06190593843486849792"},"user_tz":-480},"id":"lb3c1brkP-P4","outputId":"8e32bd44-ca03-40f3-e65c-98a3566fbbda"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"labels_dataframe.describe()","metadata":{"executionInfo":{"elapsed":1063,"status":"ok","timestamp":1680505878055,"user":{"displayName":"kai liu","userId":"06190593843486849792"},"user_tz":-480},"id":"EEwX876LQCoK","outputId":"10fc4182-9094-4535-ff8c-9ce5ea8b4f58"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#function to show bar length\n#\"bar length\" 指的是每个条形图的高度或长度，即表示数据值的大小。在代码中，通过获取每个条形图的高度（val），\n#并在条形图的右侧添加相应的数值标注来显示每个条形图的高度或长度。\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))\nax0 =sns.countplot(y=labels_dataframe['label'],\n                   order=labels_dataframe['label'].value_counts().index)\nbarw(ax0)\nplt.show()","metadata":{"id":"NCQ4_uBkRd0Q"},"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":{"executionInfo":{"elapsed":286,"status":"ok","timestamp":1680748635245,"user":{"displayName":"kai liu","userId":"06190593843486849792"},"user_tz":-480},"id":"3ix-DLQ6VGhF","outputId":"c95c62b1-c15f-4083-82c6-e3ef35c0c2e6"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 把label转成对应的数字\nclass_to_num = dict(zip(leaves_labels, range(n_classes)))\nclass_to_num","metadata":{"id":"DmO2kPEJVQhH"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 再转换回来，方便最后预测的时候使用\nnum_to_class = {v : k for k, v in class_to_num.items()}","metadata":{"executionInfo":{"elapsed":280,"status":"ok","timestamp":1680748640540,"user":{"displayName":"kai liu","userId":"06190593843486849792"},"user_tz":-480},"id":"Y7KsBiwe-jjG"},"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         #提示数据集读取完毕以及读取到的样本数量。\n         #其中，mode是指定的数据集模式（训练集或验证集），self.real_len是该数据集中真实的样本数量。\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)),#首先使用 transforms.Resize 对图片进行尺寸变换，\n                                              #将其大小变为 224x224，这是因为在模型中使用的 ResNet 预处理模块中要求输入的图片大小为 224x224。\n                transforms.RandomHorizontalFlip(p=0.5),   #随机水平翻转 选择一个概率\n                transforms.ToTensor() #transforms.ToTensor() 将 PIL 图像转换为 Tensor 格式。\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","metadata":{"executionInfo":{"elapsed":5,"status":"ok","timestamp":1680748640845,"user":{"displayName":"kai liu","userId":"06190593843486849792"},"user_tz":-480},"id":"dbOVbKRH-3qs"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_path = '/kaggle/input/classify-leaves/sample_submission.csv'\ntest_path = '/kaggle/input/classify-leaves/test.csv'\n# csv文件中已经images的路径了，因此这里只到上一级目录\nimg_path = '/kaggle/input/classify-leaves'\n#sub_path = '/content/drive/MyDrive/limu/submission.csv'\nsub_dataset = LeavesData(sub_path, img_path, mode='valid')\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":{"executionInfo":{"elapsed":414,"status":"ok","timestamp":1680753970821,"user":{"displayName":"kai liu","userId":"06190593843486849792"},"user_tz":-480},"id":"EOFvTwkRANHg","outputId":"afae7670-1e8c-489a-ab69-97e49efab02c"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"id":"2PE7KIL7IDoA"},"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=2\n    )\n\nval_loader = torch.utils.data.DataLoader(\n        dataset=val_dataset,\n        batch_size=8, \n        shuffle=False,\n        num_workers=2\n    )\ntest_loader = torch.utils.data.DataLoader(\n        dataset=test_dataset,\n        batch_size=8, \n        shuffle=False,\n        num_workers=2\n    )\n\n    ","metadata":{"executionInfo":{"elapsed":385,"status":"ok","timestamp":1680754117133,"user":{"displayName":"kai liu","userId":"06190593843486849792"},"user_tz":-480},"id":"oMcjR4fBASPw"},"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#Matplotlib 库创建一个新的图形，并设置其大小为 20x12（单位为英寸）。\nfig=plt.figure(figsize=(20, 12))\ncolumns = 4\nrows = 2\n# 这个数据加载器（DataLoader）是 PyTorch 提供的一个数据处理工具，用于将数据集分割成小批量并进行迭代\n# iter() 是 Python 内置的迭代器函数，可以将可迭代对象转换为迭代器。\n# 迭代器是一种可以逐个访问元素的对象，而不需要在内存中存储它们的完整列表。\n# 迭代器是支持迭代协议的对象，即包含 iter() 和 next() 方法。\n#每次调用迭代器的 next() 方法，都会返回下一个小批量的数据。\n\ndataiter = iter(val_loader)\n\n#它每次返回一个batch的数据(picture tensor)和标签\ninputs, classes = next(dataiter)\n\n#这里定义了一个展示图片的矩阵，总共有 rows*columns 张图片需要展示。\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":{"id":"Gne1RK9uAU20","executionInfo":{"status":"ok","timestamp":1680754124450,"user_tz":-480,"elapsed":2396,"user":{"displayName":"kai liu","userId":"06190593843486849792"}},"outputId":"fc0e7439-fd4a-4973-ee68-a5c5f95926d6"},"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":{"executionInfo":{"elapsed":4,"status":"ok","timestamp":1680748657030,"user":{"displayName":"kai liu","userId":"06190593843486849792"},"user_tz":-480},"id":"rNJbAtlpAZTB","outputId":"d32ba540-1333-492b-e494-edd1908864b3"},"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#feature_extract = False 所有的参数都将被开启梯度更新，包括预训练模型的所有层。\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","metadata":{"id":"mkbAV_v1cQyU","executionInfo":{"status":"ok","timestamp":1680748659487,"user_tz":-480,"elapsed":6,"user":{"displayName":"kai liu","userId":"06190593843486849792"}}},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 超参数\nlearning_rate = 3e-4\nweight_decay = 1e-3\nnum_epoch = 15\nmodel_path = './pre_res_model.ckpt'","metadata":{"id":"m3DGp6clcNPE","executionInfo":{"status":"ok","timestamp":1680748660260,"user_tz":-480,"elapsed":8,"user":{"displayName":"kai liu","userId":"06190593843486849792"}}},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Initialize a model, and put it on the device specified.\nmodel = res_model(176)\n#将 PyTorch 模型 model 移动到指定的计算设备上，并将该设备设置为模型的属性device\n#在后续使用模型进行前向传递时，就能够在指定设备上完成计算，以提高计算速度和效率\nmodel = model.to(device)\nmodel.device = device\n# For the classification task, we use cross-entropy as the measurement of performance.\n#nn.CrossEntropyLoss() 是一个用于多分类任务的损失函数，\n#其组合了 nn.LogSoftmax() 和 nn.NLLLoss() 两个函数，可以直接对网络输出和对应的标签进行比较计算出损失值，\n#用于优化训练。在训练神经网络时，我们希望网络的输出尽可能接近真实标签，\n#因此通过最小化损失函数来调整网络参数。nn.CrossEntropyLoss() 通常与 softmax 函数一起使用\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 \n    # 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":{"id":"07h_C1W2ltmc","executionInfo":{"status":"ok","timestamp":1680752262190,"user_tz":-480,"elapsed":3600254,"user":{"displayName":"kai liu","userId":"06190593843486849792"}},"outputId":"6b0466a6-35f3-4827-c3bc-d122e82869d6"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"saveFileName = '/kaggle/input/classify-leaves/sample_submission.csv'","metadata":{"id":"RWXPs83JA4UB","executionInfo":{"status":"ok","timestamp":1680752262995,"user_tz":-480,"elapsed":8,"user":{"displayName":"kai liu","userId":"06190593843486849792"}}},"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# 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!\")","metadata":{"id":"3HRyFBidA9CG","executionInfo":{"status":"ok","timestamp":1680752984865,"user_tz":-480,"elapsed":721875,"user":{"displayName":"kai liu","userId":"06190593843486849792"}},"outputId":"db30fb35-4fc1-48f8-9c7c-9b642048f11a"},"execution_count":null,"outputs":[]}]}