{"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":"# 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","execution":{"iopub.status.busy":"2023-07-21T15:12:07.214460Z","iopub.execute_input":"2023-07-21T15:12:07.216904Z","iopub.status.idle":"2023-07-21T15:12:07.297900Z","shell.execute_reply.started":"2023-07-21T15:12:07.216854Z","shell.execute_reply":"2023-07-21T15:12:07.297007Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 导包","metadata":{}},{"cell_type":"code","source":"from PIL import Image\nimport os\nimport random\nimport matplotlib.pyplot as plt\nimport torch\nfrom torch import nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torch.nn import functional as F\nfrom torchvision import transforms as T\nfrom torchvision.datasets import ImageFolder\nimport tqdm","metadata":{"execution":{"iopub.status.busy":"2023-07-21T15:12:07.586327Z","iopub.execute_input":"2023-07-21T15:12:07.589055Z","iopub.status.idle":"2023-07-21T15:12:11.039648Z","shell.execute_reply.started":"2023-07-21T15:12:07.589013Z","shell.execute_reply":"2023-07-21T15:12:11.038656Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 数据探索","metadata":{}},{"cell_type":"code","source":"path = \"/kaggle/input/fruits-dataset-images/images\"\nfruit_path = \"apple fruit\"","metadata":{"execution":{"iopub.status.busy":"2023-07-21T15:12:11.041478Z","iopub.execute_input":"2023-07-21T15:12:11.042029Z","iopub.status.idle":"2023-07-21T15:12:11.046323Z","shell.execute_reply.started":"2023-07-21T15:12:11.042000Z","shell.execute_reply":"2023-07-21T15:12:11.045321Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"apple_files = os.listdir(path + \"/\" + fruit_path)\nprint(f\"apple_size = {len(apple_files)}\")","metadata":{"execution":{"iopub.status.busy":"2023-07-21T15:12:11.047773Z","iopub.execute_input":"2023-07-21T15:12:11.048395Z","iopub.status.idle":"2023-07-21T15:12:11.060173Z","shell.execute_reply.started":"2023-07-21T15:12:11.048359Z","shell.execute_reply":"2023-07-21T15:12:11.059245Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"apple_files[:10]","metadata":{"execution":{"iopub.status.busy":"2023-07-21T15:12:11.062929Z","iopub.execute_input":"2023-07-21T15:12:11.063526Z","iopub.status.idle":"2023-07-21T15:12:11.072272Z","shell.execute_reply.started":"2023-07-21T15:12:11.063495Z","shell.execute_reply":"2023-07-21T15:12:11.071168Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"Image.open(path + \"/\"+fruit_path+\"/\" + apple_files[2])","metadata":{"execution":{"iopub.status.busy":"2023-07-21T15:12:11.073889Z","iopub.execute_input":"2023-07-21T15:12:11.074212Z","iopub.status.idle":"2023-07-21T15:12:11.324176Z","shell.execute_reply.started":"2023-07-21T15:12:11.074182Z","shell.execute_reply":"2023-07-21T15:12:11.323305Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def show_images(n_rows, n_cols, x_data):\n    assert n_rows * n_cols <= len(x_data)\n    \n    plt.figure(figsize=(n_cols * 1.5, n_rows * 1.5))\n    for row in range(n_rows):\n        for col in range(n_cols):\n            index = row * n_cols + col\n            plt.subplot(n_rows, n_cols, index + 1)\n            plt.imshow(x_data[index][0], cmap=\"binary\", interpolation=\"nearest\")  # 图像\n            plt.axis(\"off\")\n            plt.title(x_data[index][1])  # 标签\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2023-07-21T15:12:11.325102Z","iopub.execute_input":"2023-07-21T15:12:11.325391Z","iopub.status.idle":"2023-07-21T15:12:11.333549Z","shell.execute_reply.started":"2023-07-21T15:12:11.325364Z","shell.execute_reply":"2023-07-21T15:12:11.332630Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def show_fruit_imgs(fruit, cols, rows):\n    files = os.listdir(path + \"/\" + fruit)\n    images = []\n    for _ in range(cols * rows):\n        file = files[random.randint(0, len(files) -1)]\n        image = Image.open(path + \"/\" + fruit + \"/\" + file)\n        label = file.split(\".\")[0]\n        images.append((image, label))\n    show_images(cols, rows, images)","metadata":{"execution":{"iopub.status.busy":"2023-07-21T15:12:11.334889Z","iopub.execute_input":"2023-07-21T15:12:11.335437Z","iopub.status.idle":"2023-07-21T15:12:11.344733Z","shell.execute_reply.started":"2023-07-21T15:12:11.335407Z","shell.execute_reply":"2023-07-21T15:12:11.343827Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"show_fruit_imgs(\"apple fruit\", 3, 3)","metadata":{"execution":{"iopub.status.busy":"2023-07-21T15:12:11.346219Z","iopub.execute_input":"2023-07-21T15:12:11.346922Z","iopub.status.idle":"2023-07-21T15:12:15.400000Z","shell.execute_reply.started":"2023-07-21T15:12:11.346891Z","shell.execute_reply":"2023-07-21T15:12:15.399039Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"show_fruit_imgs(\"cherry fruit\", 3, 3)","metadata":{"execution":{"iopub.status.busy":"2023-07-21T15:12:15.401448Z","iopub.execute_input":"2023-07-21T15:12:15.402020Z","iopub.status.idle":"2023-07-21T15:12:19.489601Z","shell.execute_reply.started":"2023-07-21T15:12:15.401987Z","shell.execute_reply":"2023-07-21T15:12:19.488744Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Fruit数据集构建","metadata":{"execution":{"iopub.status.busy":"2023-06-17T14:02:23.305861Z","iopub.execute_input":"2023-06-17T14:02:23.306263Z","iopub.status.idle":"2023-06-17T14:02:23.311285Z","shell.execute_reply.started":"2023-06-17T14:02:23.306234Z","shell.execute_reply":"2023-06-17T14:02:23.309812Z"}}},{"cell_type":"code","source":"transforms = T.Compose([\n    T.Resize(224),\n    T.CenterCrop(224),\n    T.ToTensor(),\n    T.Normalize(mean=[5., 5., 5.], std=[.5, .5, .5])\n])\n\ntrain_dataset = ImageFolder(path, transform=transforms)\nclassification = os.listdir(path)","metadata":{"execution":{"iopub.status.busy":"2023-07-21T15:12:19.492780Z","iopub.execute_input":"2023-07-21T15:12:19.493385Z","iopub.status.idle":"2023-07-21T15:12:19.508826Z","shell.execute_reply.started":"2023-07-21T15:12:19.493352Z","shell.execute_reply":"2023-07-21T15:12:19.506843Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_dataset[2]","metadata":{"execution":{"iopub.status.busy":"2023-07-21T15:12:19.513901Z","iopub.execute_input":"2023-07-21T15:12:19.514163Z","iopub.status.idle":"2023-07-21T15:12:19.668610Z","shell.execute_reply.started":"2023-07-21T15:12:19.514139Z","shell.execute_reply":"2023-07-21T15:12:19.667670Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 构建模型 ResNet34","metadata":{}},{"cell_type":"code","source":"class ResidualBlock(nn.Module):\n    \n    def __init__(self, in_channels, out_channels, stride=1):\n        super().__init__()\n        self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=(3, 3), stride=stride, padding=1, bias=False)\n        self.bn1 = nn.BatchNorm2d(out_channels)\n        self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=(3, 3), stride=1, padding=1, bias=False)\n        self.bn2 = nn.BatchNorm2d(out_channels)\n        self.is_shortcut = stride > 1\n        self.shortcut = None if not self.is_shortcut else self._shortcut(in_channels, out_channels, stride)\n    \n    def forward(self, X):\n        out = self.conv1(X)\n        out = self.bn1(out)\n        out = F.relu(out, inplace=True)\n        out = self.conv2(out)\n        out = self.bn2(out)\n        # 当X的维度和out不一致时，需要用shortcut处理X\n        out += X if not self.shortcut else self.shortcut(X)\n        out = F.relu(out)\n        return out\n    \n    def _shortcut(self, in_channels, out_channels, stride):\n        return nn.Sequential(\n            nn.Conv2d(in_channels, out_channels, 1, stride, bias=False),\n            nn.BatchNorm2d(out_channels)\n        )","metadata":{"execution":{"iopub.status.busy":"2023-07-21T15:12:19.670081Z","iopub.execute_input":"2023-07-21T15:12:19.671126Z","iopub.status.idle":"2023-07-21T15:12:19.682414Z","shell.execute_reply.started":"2023-07-21T15:12:19.671088Z","shell.execute_reply":"2023-07-21T15:12:19.681091Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class ResNet34(nn.Module):\n    \n    def __init__(self, num_classes=2):\n        super().__init__()\n        self.pre = nn.Sequential(\n            nn.Conv2d(3, 64, 7, 2, 3, bias=False),  # 64 * 112 * 112\n            nn.BatchNorm2d(64),\n            nn.ReLU(inplace=True),\n            nn.MaxPool2d(3, 2, 1)  # 64 * 56 * 56\n        )\n        # layer1 不需要shortcut，因为图像没变化(kernel_size=3,stride=1, padding=1)\n        self.layer1 = self._make_layer(64, 64, 3, 1)\n        self.layer2 = self._make_layer(64, 128, 4, 2)\n        self.layer3 = self._make_layer(128, 256, 6, 2)\n        self.layer4 = self._make_layer(256, 512, 3, 2)\n        self.fc = nn.Linear(512, num_classes)\n        \n    def _make_layer(self, in_channels, out_channels, block_num, stride):\n        layers = [ResidualBlock(in_channels, out_channels, stride)]\n        for i in range(1, block_num):\n            layers.append(ResidualBlock(out_channels, out_channels))\n        return nn.Sequential(*layers)\n    \n    def forward(self, X):\n        # X: 3 * 224 * 224\n        out = self.pre(X)                # 64 * 56 * 56\n        out = self.layer1(out)           # 64 * 56 * 56\n        out = self.layer2(out)           # 128 * 28 * 28\n        out = self.layer3(out)           # 256 * 14 * 14\n        out = self.layer4(out)           # 512 * 7 * 7\n        out = F.avg_pool2d(out, 7)       # 512 * 1 * 1\n        out = out.view(out.size(0), -1)  # 512 \n        out = self.fc(out)               # len(classification)\n        return out","metadata":{"execution":{"iopub.status.busy":"2023-07-21T15:12:19.684076Z","iopub.execute_input":"2023-07-21T15:12:19.684654Z","iopub.status.idle":"2023-07-21T15:12:19.697679Z","shell.execute_reply.started":"2023-07-21T15:12:19.684621Z","shell.execute_reply":"2023-07-21T15:12:19.696501Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 训练","metadata":{"execution":{"iopub.status.busy":"2023-06-17T15:07:47.766121Z","iopub.execute_input":"2023-06-17T15:07:47.766612Z","iopub.status.idle":"2023-06-17T15:07:48.218484Z","shell.execute_reply.started":"2023-06-17T15:07:47.766573Z","shell.execute_reply":"2023-06-17T15:07:48.217203Z"}}},{"cell_type":"code","source":"def pad(num, target) -> str:\n    \"\"\"\n    将num长度与target对齐\n    \"\"\"\n    return str(num).zfill(len(str(target)))","metadata":{"execution":{"iopub.status.busy":"2023-07-21T15:12:24.193153Z","iopub.execute_input":"2023-07-21T15:12:24.193514Z","iopub.status.idle":"2023-07-21T15:12:24.200051Z","shell.execute_reply.started":"2023-07-21T15:12:24.193484Z","shell.execute_reply":"2023-07-21T15:12:24.198560Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 参数配置\nepoch_num = 100\nbatch_size = 32\nlearning_rate = 0.0005\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n\n# 数据\ntrain_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=2)\n\n# 构建模型\nmodel = ResNet34(len(classification)).to(device)\ncriterion = torch.nn.CrossEntropyLoss()\noptimizer = torch.optim.AdamW(model.parameters(), lr=learning_rate)\n\nprint(model)","metadata":{"execution":{"iopub.status.busy":"2023-07-21T15:13:06.911242Z","iopub.execute_input":"2023-07-21T15:13:06.911638Z","iopub.status.idle":"2023-07-21T15:13:07.145801Z","shell.execute_reply.started":"2023-07-21T15:13:06.911601Z","shell.execute_reply":"2023-07-21T15:13:07.144804Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 开始训练\ntrain_loss_list = []\ntotal_step = len(train_loader)\nfor epoch in range(1, epoch_num + 1):\n    model.train\n    train_total_loss, train_total, train_correct  = 0, 0, 0\n    train_progress = tqdm.tqdm(train_loader, desc=\"Train...\")\n    for i, (X, y) in enumerate(train_progress, 1):\n        X, y = X.to(device), y.to(device)\n        \n        out = model(X)\n        loss = criterion(out, y)\n        \n        loss.backward()\n        optimizer.step()\n        optimizer.zero_grad()\n        \n        _, pred = torch.max(out, 1)\n        train_total += y.size(0)\n        train_correct += (pred == y).sum().item()\n        train_total_loss += loss.item()\n\n        train_progress.set_description(f\"Train... [epoch {pad(epoch, epoch_num)}/{epoch_num}, loss {(train_total_loss / i):.4f}, accuracy {train_correct / train_total:.4f}]\")\n    train_loss_list.append(train_total_loss / total_step) ","metadata":{"execution":{"iopub.status.busy":"2023-07-21T15:13:07.400461Z","iopub.execute_input":"2023-07-21T15:13:07.400896Z","iopub.status.idle":"2023-07-21T15:40:44.874960Z","shell.execute_reply.started":"2023-07-21T15:13:07.400853Z","shell.execute_reply":"2023-07-21T15:40:44.872961Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 绘制曲线","metadata":{}},{"cell_type":"code","source":"plt.plot(range(len(train_loss_list)), train_loss_list)\nplt.xlabel(\"epoch\")\nplt.ylabel(\"loss_val\")\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2023-07-21T15:40:44.877492Z","iopub.execute_input":"2023-07-21T15:40:44.877949Z","iopub.status.idle":"2023-07-21T15:40:45.132741Z","shell.execute_reply.started":"2023-07-21T15:40:44.877904Z","shell.execute_reply":"2023-07-21T15:40:45.131819Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}