{"metadata":{"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":3649,"databundleVersionId":46718,"sourceType":"competition"},{"sourceId":9188928,"sourceType":"datasetVersion","datasetId":5546399}],"dockerImageVersionId":30747,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true},"kernelspec":{"display_name":"Python 3","language":"python","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"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# 使用针对CIFAR-10优化的ResNet34完成CIFAR图像分类\n### 本笔记本的大部分代码受启发于李沐老师d2l库和《动手学深度学习》ch13\n### 使用了调整过的ResNet34（提供了任意Residual block的实现）\n### 上传了无需长时间等待初始化的数据集CIFAR-10-Train-Valid-Test\n### 重新实现代码替换了对d2l的环境依赖\n\n### Most of the code in this notebook is inspired by the d2l library and Chapter 13 of \"Dive into Deep Learning\"\n### Utilized an adjusted ResNet34 (implementation of any Residual block is provided)\n### Uploaded the CIFAR-10-Train-Valid-Test dataset which does not require a long wait for initialization\n### Reimplemented the code to replace the environmental dependency on d2l","metadata":{}},{"cell_type":"code","source":"import collections\nimport math\nimport os\nimport hashlib\nimport shutil\nimport re\nimport shutil\nimport requests\nimport time\nfrom collections import defaultdict\nimport tarfile\nimport zipfile\n\nimport numpy as np\nfrom PIL import Image\n%matplotlib inline\nfrom matplotlib_inline import backend_inline\nfrom matplotlib import pyplot as plt\nimport pandas as pd\nimport torch\nimport torchvision\nfrom torch import nn\nfrom torch.nn import functional as F\nfrom IPython import display\nfrom torch.utils import data\nfrom torchvision import transforms","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## *Viz Modules<br>可视化工具*","metadata":{}},{"cell_type":"code","source":"def use_svg_display(): #@save\n    \"\"\"使用svg格式在Jupyter中显示绘图\"\"\"\n    backend_inline.set_matplotlib_formats('svg')\n\ndef set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend):\n    \"\"\"Set the axes for matplotlib.\"\"\"\n    axes.set_xlabel(xlabel)\n    axes.set_ylabel(ylabel)\n    axes.set_xscale(xscale)\n    axes.set_yscale(yscale)\n    axes.set_xlim(xlim)\n    axes.set_ylim(ylim)\n    if legend:\n        axes.legend(legend)\n    axes.grid()\n\ndef set_figsize(figsize=(3.5, 2.5)): #@save\n    \"\"\"设置matplotlib的图表大小\"\"\"\n    use_svg_display()\n    plt.rcParams['figure.figsize'] = figsize\n\ndef plot(X, Y=None, xlabel=None, ylabel=None, legend=None, xlim=None,\n         ylim=None, xscale='linear', yscale='linear',\n         fmts=('-', 'm--', 'g-.', 'r:'), figsize=(3.5, 2.5), axes=None):\n    \"\"\"Plot data points.\"\"\"\n    if legend is None:\n        legend = []\n\n    set_figsize(figsize)\n    axes = axes if axes else plt.gca()\n\n    # Return True if `X` (tensor or list) has 1 axis\n    def has_one_axis(X):\n        return (hasattr(X, \"ndim\") and X.ndim == 1 or\n                isinstance(X, list) and not hasattr(X[0], \"__len__\"))\n\n    if has_one_axis(X):\n        X = [X]\n    if Y is None:\n        X, Y = [[]] * len(X), X\n    elif has_one_axis(Y):\n        Y = [Y]\n    if len(X) != len(Y):\n        X = X * len(Y)\n    axes.cla()\n    for x, y, fmt in zip(X, Y, fmts):\n        if len(x):\n            axes.plot(x, y, fmt)\n        else:\n            axes.plot(y, fmt)\n    set_axes(axes, xlabel, ylabel, xlim, ylim, xscale, yscale, legend)\n    \n    \nclass Accumulator:\n    \"\"\"For accumulating sums over `n` variables.\"\"\"\n    def __init__(self, n):\n        self.data = [0.0] * n\n\n    def add(self, *args):\n        self.data = [a + float(b) for a, b in zip(self.data, args)]\n\n    def reset(self):\n        self.data = [0.0] * len(self.data)\n\n    def __getitem__(self, idx):\n        return self.data[idx]\n    \n    \nclass Animator:\n    \"\"\"For plotting data in animation.\"\"\"\n    def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None,\n                 ylim=None, xscale='linear', yscale='linear',\n                 fmts=('-', 'm--', 'g-.', 'r:'), nrows=1, ncols=1,\n                 figsize=(3.5, 2.5)):\n        # Incrementally plot multiple lines\n        if legend is None:\n            legend = []\n        use_svg_display()\n        self.fig, self.axes = plt.subplots(nrows, ncols, figsize=figsize)\n        if nrows * ncols == 1:\n            self.axes = [self.axes,]\n        # Use a lambda function to capture arguments\n        self.config_axes = lambda: set_axes(self.axes[\n            0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend)\n        self.X, self.Y, self.fmts = None, None, fmts\n\n    def add(self, x, y):\n        # Add multiple data points into the figure\n        if not hasattr(y, \"__len__\"):\n            y = [y]\n        n = len(y)\n        if not hasattr(x, \"__len__\"):\n            x = [x] * n\n        if not self.X:\n            self.X = [[] for _ in range(n)]\n        if not self.Y:\n            self.Y = [[] for _ in range(n)]\n        for i, (a, b) in enumerate(zip(x, y)):\n            if a is not None and b is not None:\n                self.X[i].append(a)\n                self.Y[i].append(b)\n        self.axes[0].cla()\n        for x, y, fmt in zip(self.X, self.Y, self.fmts):\n            self.axes[0].plot(x, y, fmt)\n        self.config_axes()\n        display.display(self.fig)\n        display.clear_output(wait=True)\n        \n        \nclass Timer: #@save\n    \"\"\"记录多次运行时间\"\"\"\n    def __init__(self):\n        self.times = []\n        self.start()\n    \n    def start(self):\n        \"\"\"启动计时器\"\"\"\n        self.tik = time.time()\n    \n    def stop(self):\n        \"\"\"停止计时器并将时间记录在列表中\"\"\"\n        self.times.append(time.time() - self.tik)\n        return self.times[-1]\n    \n    def avg(self):\n        \"\"\"返回平均时间\"\"\"\n        return sum(self.times) / len(self.times)\n    def sum(self):\n        \"\"\"返回时间总和\"\"\"\n        return sum(self.times)\n    \n    def cumsum(self):\n        \"\"\"返回累计时间\"\"\"\n        return np.array(self.times).cumsum().tolist()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## *Dataset<br>数据集*","metadata":{}},{"cell_type":"code","source":"#@save\n# 测试数据的路径\nDATA_HUB = dict()\nDATA_URL = 'http://d2l-data.s3-accelerate.amazonaws.com/'\n\ndef download(name, cache_dir=os.path.join('..', 'data')): #@save\n    \"\"\"下载一个DATA_HUB中的文件，返回本地文件名\"\"\"\n    assert name in DATA_HUB, f\"{name} 不存在于 {DATA_HUB}\"\n    url, sha1_hash = DATA_HUB[name]\n    os.makedirs(cache_dir, exist_ok=True)\n    fname = os.path.join(cache_dir, url.split('/')[-1])\n    if os.path.exists(fname):\n        sha1 = hashlib.sha1()\n        with open(fname, 'rb') as f:\n            while True:\n                data = f.read(1048576)\n                if not data:\n                    break\n                sha1.update(data)\n        if sha1.hexdigest() == sha1_hash:\n            return fname # 命中缓存\n    print(f'正在从{url}下载{fname}...')\n    r = requests.get(url, stream=True, verify=True)\n    with open(fname, 'wb') as f:\n        f.write(r.content)\n    return fname\n\ndef download_extract(name, folder=None):\n    \"\"\"Download and extract a zip/tar file.\"\"\"\n    fname = download(name)\n    base_dir = os.path.dirname(fname)\n    data_dir, ext = os.path.splitext(fname)\n    if ext == '.zip':\n        fp = zipfile.ZipFile(fname, 'r')\n    elif ext in ('.tar', '.gz'):\n        fp = tarfile.open(fname, 'r')\n    else:\n        assert False, 'Only zip/tar files can be extracted.'\n    fp.extractall(base_dir)\n    return os.path.join(base_dir, folder) if folder else data_dir\n\nDATA_HUB['cifar10_tiny'] = (DATA_URL + 'kaggle_cifar10_tiny.zip',\n                            '2068874e4b9a9f0fb07ebe0ad2b29754449ccacd')\n\n\n# 如果使用完整的CIFAR-10，设置demo为False\ndemo = False\nif demo:\n    data_dir = download_extract('cifar10_tiny')\nelse:\n    data_dir = r'/kaggle/input/cifar-10-train-valid-test' # 总文件夹","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"os.listdir(data_dir)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 整理数据集\n#@save\ndef read_csv_labels(fname):\n    \"\"\"读取fname来给标签字典返回一个文件名\"\"\"\n    with open(fname, 'r') as f:\n        # 跳过文件头行(列名)\n        lines = f.readlines()[1:]\n    tokens = [l.rstrip().split(',') for l in lines]\n    return dict(((name, label) for name, label in tokens))\n\nlabels = read_csv_labels(os.path.join(data_dir, 'trainLabels.csv'))\nprint('# 训练样本 :', len(labels))\nprint('# 类别 :', len(set(labels.values())))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# # 使用CIFAR-10-Train-Valid-Test时无需执行此代码块\n# # You don't need to run this cell if you're using complete CIFAR-10\n# #@save\n# def copyfile(filename, target_dir):\n#     \"\"\"将文件复制到目标目录\"\"\"\n#     os.makedirs(target_dir, exist_ok=True)\n#     shutil.copy(filename, target_dir)\n    \n# \"\"\"令n等于样本最少的类别\n# 中的图像数量，而r是比率。验证集将为每个类别拆分出max(⌊nr⌋, 1)张图像\n# \"\"\"\n\n# #@save\n# def reorg_train_valid(data_dir, labels, valid_ratio):\n#     \"\"\"将验证集从原始的训练集中拆分出来\"\"\"\n#     # 训练数据集中样本最少的类别中的样本数\n#     n = collections.Counter(labels.values()).most_common()[-1][1]\n#     # 验证集中每个类别的样本数\n#     n_valid_per_label = max(1, math.floor(n * valid_ratio))\n#     label_count = {}\n#     for train_file in os.listdir(os.path.join(data_dir, 'train')):\n#         label = labels[train_file.split('.')[0]]\n#         fname = os.path.join(data_dir, 'train', train_file)\n#         copyfile(fname, os.path.join(data_dir, 'train_valid_test',\n#                 'train_valid', label))\n#         if label not in label_count or label_count[label] < n_valid_per_label:\n#             copyfile(fname, os.path.join(data_dir, 'train_valid_test',\n#                     'valid', label))\n#             label_count[label] = label_count.get(label, 0) + 1\n#         else:\n#             copyfile(fname, os.path.join(data_dir, 'train_valid_test',\n#                     'train', label))\n#     return n_valid_per_label\n\n# #@save\n# def reorg_test(data_dir):\n#     \"\"\"在预测期间整理测试集，以方便读取\"\"\"\n#     for test_file in os.listdir(os.path.join(data_dir, 'test')):\n#         copyfile(os.path.join(data_dir, 'test', test_file),\n#                 os.path.join(data_dir, 'train_valid_test', 'test',\n#                 'unknown'))\n\n# # 加载数据集分类标签\n# def reorg_cifar10_data(data_dir, valid_ratio):\n#     labels = read_csv_labels(os.path.join(data_dir, 'trainLabels.csv'))\n#     reorg_train_valid(data_dir, labels, valid_ratio)\n#     reorg_test(data_dir)\n    \n# reorg_cifar10_data(data_dir, valid_ratio)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 初始化数据\nbatch_size = 32 if demo else 128\nvalid_ratio = 0.1","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## *Image Augmentation<br>图像增广*","metadata":{}},{"cell_type":"code","source":"\"\"\"使用图像增广来解决过拟合的问题\"\"\"\n\"\"\"在训练期间，我们通常会对训练图像进行增广，以增加模型的准确性。\"\"\"\n\ntransform_train = torchvision.transforms.Compose([\n    # 在高度和宽度上将图像放大到40像素的正方形\n    torchvision.transforms.Resize(40),\n    # 随机裁剪出一个高度和宽度均为40像素的正方形图\n    # 生成一个面积为原始图像面积0.64～1倍的小正方形，\n    # 然后将其缩放为高度和宽度均为32像素的正方形\n    torchvision.transforms.RandomResizedCrop(32, scale=(0.64, 1.0),\n                                            ratio=(1.0, 1.0)),\n    torchvision.transforms.RandomHorizontalFlip(),\n    #torchvision.transforms.RandomAffine(\n    #    degrees=(-15, 15), shear=10), # 随机仿射变换\n    torchvision.transforms.ToTensor(),\n    # 标准化图像的每个通道\n    torchvision.transforms.Normalize([0.4914, 0.4822, 0.4465],\n                                    [0.2023, 0.1994, 0.2010])])\n\n\"\"\"在测试期间，我们只进行标准化。\"\"\"\ntransform_test = torchvision.transforms.Compose([\n    torchvision.transforms.ToTensor(),\n    torchvision.transforms.Normalize([0.4914, 0.4822, 0.4465],\n                                    [0.2023, 0.1994, 0.2010])])\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 读取数据同时进行增广\n\"\"\"\n在训练期间，我们需要指定上面定义的所有图像增广操作。当验证集在超参数调整过程中用于模型评估时，\n不应引入图像增广的随机性。\n\"\"\"\n# 设置image_dir\nimage_dir = r'/kaggle/input/cifar-10-train-valid-test/train_valid_test'\nprint(os.listdir(image_dir))\n\ntrain_ds, train_valid_ds = [torchvision.datasets.ImageFolder(\n                            os.path.join(image_dir, 'train_valid_test', folder),\n                            transform=transform_train) for folder in ['train', 'train_valid']]\nvalid_ds, test_ds = [torchvision.datasets.ImageFolder(\n                            os.path.join(image_dir, 'train_valid_test', folder),\n                            transform=transform_test) for folder in ['valid', 'test']]\n\n\"\"\"但是在最终预测之前，我们根据训练集和验证集组合而成的训练模型进行训练，以\n充分利用所有标记的数据。\n\"\"\"\ntrain_iter, train_valid_iter = [torch.utils.data.DataLoader(\n                                dataset, batch_size, shuffle=True, drop_last=True)\n                                for dataset in (train_ds, train_valid_ds)]\nvalid_iter = torch.utils.data.DataLoader(valid_ds, batch_size, shuffle=False,\n                                        drop_last=True)\ntest_iter = torch.utils.data.DataLoader(test_ds, batch_size, shuffle=False,\n                                        drop_last=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## *Model<br>模型*","metadata":{}},{"cell_type":"code","source":"# 定义残差结构\nclass Residual(nn.Module):\n    \"\"\"The Residual block of ResNet.\"\"\"\n    def __init__(self, input_channels, num_channels, use_1x1conv=False,\n                 strides=1):\n        super().__init__()\n        self.conv1 = nn.Conv2d(input_channels, num_channels, kernel_size=3,\n                               padding=1, stride=strides)\n        self.conv2 = nn.Conv2d(num_channels, num_channels, kernel_size=3,\n                               padding=1)\n        if use_1x1conv:\n            self.conv3 = nn.Conv2d(input_channels, num_channels,\n                                   kernel_size=1, stride=strides)\n        else:\n            self.conv3 = None\n        self.bn1 = nn.BatchNorm2d(num_channels)\n        self.bn2 = nn.BatchNorm2d(num_channels)\n\n    def forward(self, X):\n        Y = F.relu(self.bn1(self.conv1(X)))\n        Y = self.bn2(self.conv2(Y))\n        if self.conv3:\n            X = self.conv3(X)\n        Y += X\n        return F.relu(Y)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# ResNet-34\ndef resnet34(num_classes, in_channels=1):\n    \"\"\"A slightly modified ResNet-34 model.\"\"\"\n    def resnet_block(in_channels, out_channels, num_residuals,\n                     first_block=False): #第一个层需要特殊处理，使用1*1卷积\n        blk = []\n        for i in range(num_residuals):\n            if i == 0 and not first_block:\n                blk.append(\n                    Residual(in_channels, out_channels, use_1x1conv=True,\n                                 strides=2))\n            else:\n                blk.append(Residual(out_channels, out_channels))\n        return nn.Sequential(*blk)\n\n    # This model uses a smaller convolution kernel, stride, and padding and\n    # removes the maximum pooling layer\n    net = nn.Sequential(\n        nn.Conv2d(in_channels, 64, kernel_size=3, stride=1, padding=1),\n        nn.BatchNorm2d(64), nn.ReLU())\n    net.add_module(\"resnet_block1\", resnet_block(64, 64, 3, first_block=True))\n    net.add_module(\"resnet_block2\", resnet_block(64, 128, 4))\n    net.add_module(\"resnet_block3\", resnet_block(128, 256, 6))\n    net.add_module(\"resnet_block4\", resnet_block(256, 512, 3))\n    net.add_module(\"global_avg_pool\", nn.AdaptiveAvgPool2d((1, 1)))\n    net.add_module(\"fc\",\n                   nn.Sequential(nn.Flatten(), nn.Linear(512, num_classes)))\n    return net","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 从零创建模型\n# 可以使用torchvision提供的预训练官ResNet34，但是预训练模型基准数据集是ImageNet，微调效果一般\ndef get_net34(pretrained=False):\n    \"\"\"Constructs a ResNet-34 model.\n    Args:\n        pretrained (bool): If True, returns a model pre-trained on ImageNet\n    \"\"\"\n    if not pretrained:\n        net = resnet34(10, 3)\n        return net\n    else:\n        num_classes = 10\n        # 微调ResNet34模型，包含num_classes, 3\n        finetune_net = torchvision.models.resnet34(pretrained=True)\n        finetune_net.fc = nn.Linear(finetune_net.fc.in_features, 10)\n        nn.init.xavier_uniform_(finetune_net.fc.weight);\n\n        finetune_net.fc.requires_grad = True\n        return finetune_net\n\n# 交叉熵损失\nloss = nn.CrossEntropyLoss(reduction=\"none\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## *Train & Test<br>训练和调试*","metadata":{}},{"cell_type":"code","source":"def accuracy(y_hat, y): #@save\n    \"\"\"计算预测正确的数量\"\"\"\n    if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:\n        y_hat = y_hat.argmax(axis=1)\n    cmp = y_hat.type(y.dtype) == y\n    return float(cmp.type(y.dtype).sum())\n\n\ndef evaluate_accuracy_gpu(net, data_iter, device=None):\n    \"\"\"Compute the accuracy for a model on a dataset using a GPU.\"\"\"\n    if isinstance(net, nn.Module):\n        net.eval()  # Set the model to evaluation mode\n        if not device:\n            device = next(iter(net.parameters())).device\n            \n    # No. of correct predictions, no. of predictions\n    metric = Accumulator(2)\n    with torch.no_grad():\n        for X, y in data_iter:\n            if isinstance(X, list):\n                # Required for BERT Fine-tuning (to be covered later)\n                X = [x.to(device) for x in X]\n            else:\n                X = X.to(device)\n            y = y.to(device)\n            metric.add(accuracy(net(X), y), y.numel())\n    return metric[0] / metric[1]\n\ndef train_batch(net, X, y, loss, trainer, devices):\n    \"\"\"Train for a minibatch with mutiple GPUs (defined in Chapter 13).\"\"\"\n    if isinstance(X, list):\n        # Required for BERT fine-tuning (to be covered later)\n        X = [x.to(devices[0]) for x in X]\n    else:\n        X = X.to(devices[0])\n    y = y.to(devices[0])\n    net.train()\n    trainer.zero_grad()\n    pred = net(X)\n    l = loss(pred, y)\n    l.sum().backward()\n    trainer.step()\n    train_loss_sum = l.sum()\n    train_acc_sum = accuracy(pred, y)\n    return train_loss_sum, train_acc_sum\n\ndef try_all_gpus():\n    \"\"\"Return all available GPUs, or [cpu(),] if no GPU exists.\"\"\"\n    devices = [\n        torch.device(f'cuda:{i}') for i in range(torch.cuda.device_count())]\n    return devices if devices else [torch.device('cpu')]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def train(net, train_iter, valid_iter, num_epochs, lr, wd, devices, lr_period, lr_decay):\n    # trainer = torch.optim.SGD(net.parameters(), lr=lr, momentum=0.9, weight_decay=wd)\n    trainer = torch.optim.AdamW(net.parameters(), lr=lr, weight_decay=wd)\n    scheduler = torch.optim.lr_scheduler.StepLR(trainer, lr_period, lr_decay)\n    num_batches, timer = len(train_iter), Timer()\n    legend = ['train loss', 'train acc']\n    \"\"\"\n    # 冻结除最后一层之外的所有层\n    for param in net.parameters():\n        param.requires_grad = False\n\n    # 获取最后一层的参数\n    last_layer = list(net.children())[-1]\n    for param in last_layer.parameters():\n        param.requires_grad = True\n    \"\"\"\n    if valid_iter is not None:\n        legend.append('valid acc')\n    animator = Animator(xlabel='epoch', xlim=[1, num_epochs], legend=legend)\n    net = nn.DataParallel(net, device_ids=devices).to(devices[0])\n    for epoch in range(num_epochs):\n        net.train()\n        metric = Accumulator(3)\n        for i, (features, labels) in enumerate(train_iter):\n            timer.start()\n            l, acc = train_batch(net, features, labels,\n                                        loss, trainer, devices)\n            metric.add(l, acc, labels.shape[0])\n            timer.stop()\n            if (i + 1) % (num_batches // 5) == 0 or i == num_batches - 1:\n                animator.add(epoch + (i + 1) / num_batches,\n                    (metric[0] / metric[2], metric[1] / metric[2],\n                        None))\n        if valid_iter is not None:\n            valid_acc = evaluate_accuracy_gpu(net, valid_iter)\n            animator.add(epoch + 1, (None, None, valid_acc))\n            scheduler.step()\n        measures = (f'train loss {metric[0] / metric[2]:.3f}, '\n                    f'train acc {metric[1] / metric[2]:.3f}')\n        if valid_iter is not None:\n            measures += f', valid acc {valid_acc:.3f}'\n        print(measures + f'\\n{metric[2] * num_epochs / timer.sum():.1f}'\n            f' examples/sec on {str(devices)}')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"devices, num_epochs, lr, wd = try_all_gpus(), 50, 4e-4, 8e-4\nlr_period, lr_decay, net = 20, 0.9, get_net34()\ntrain(net, train_iter, valid_iter, num_epochs, lr, wd, devices, lr_period,\n        lr_decay)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"\"\"\"\"\n我们使用所有标记的数据（包括验证集）来重新训练模型并对测试集进行分类。\n\"\"\"\ndevices, num_epochs, lr, wd = try_all_gpus(), 100, 4e-4, 8e-4\nnet, preds = get_net34(), []\nlr_period, lr_decay = 20, 0.9\ntrain(net, train_valid_iter, None, num_epochs, lr, wd, devices, lr_period,\nlr_decay)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 保存参数\ntorch.save(net.state_dict(), '/kaggle/working/resnet34_cifar10.pth')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## *Inference & Submit<br>预测并提交*","metadata":{}},{"cell_type":"code","source":"# 预测并提交\nfor X, _ in test_iter:\n    y_hat = net(X.to(devices[0]))\n    preds.extend(y_hat.argmax(dim=1).type(torch.int32).cpu().numpy())\nsorted_ids = list(range(1, len(test_ds) + 1))\nsorted_ids.sort(key=lambda x: str(x))\ndf = pd.DataFrame({'id': sorted_ids, 'label': preds})\ndf['label'] = df['label'].apply(lambda x: train_valid_ds.classes[x])\ndf.to_csv('/kaggle/working/submission.csv', index=False) # or try your local path","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}