{"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":"感谢Neko Kiku提供的baseline，本文使用了其中上传的数据集。\n\n#### 本文的主要思路如下：\n\n* 数据增强：\nresize 320, HorizontalFlip, VerticalFlip, Rotate, RandomBrightnesContrasr, ShiftScaleRotate, Normalize\n* 模型：\nseresnext50和resnet50\n* 优化器：\nAdamW CosineAnnealingLR\n* 其他：\n5折交叉验证， 最终结果为五折准确率最高平均， 两个网络各自平均后再做平均。\nloss就是CrossEntropy。\n\n### 有任何问题欢迎讨论，我比较懒很多东西没有详细写。","metadata":{}},{"cell_type":"code","source":"!pip install timm","metadata":{"execution":{"iopub.status.busy":"2021-06-28T11:13:03.046249Z","iopub.execute_input":"2021-06-28T11:13:03.046595Z","iopub.status.idle":"2021-06-28T11:13:09.544758Z","shell.execute_reply.started":"2021-06-28T11:13:03.046565Z","shell.execute_reply":"2021-06-28T11:13:09.543583Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os\nfrom PIL import Image\nimport cv2\nimport math\n\nimport torch\nimport torchvision\nimport timm\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nfrom tqdm import tqdm\nfrom collections import defaultdict\nimport matplotlib.pyplot as plt\n\n\nfrom sklearn.preprocessing import LabelEncoder, OneHotEncoder\nfrom sklearn.model_selection import train_test_split, cross_validate, StratifiedKFold, cross_val_score\n\n# Metric\nfrom sklearn.metrics import f1_score, accuracy_score\n\n# Augmentation\nimport albumentations\nfrom albumentations.pytorch.transforms import ToTensorV2\n\nif torch.cuda.is_available():\n    device = torch.device('cuda')\nelse:\n    device = torch.device('cpu')\n\nprint(f'Using device: {device}')","metadata":{"execution":{"iopub.status.busy":"2021-06-28T11:13:09.550155Z","iopub.execute_input":"2021-06-28T11:13:09.550522Z","iopub.status.idle":"2021-06-28T11:13:13.597199Z","shell.execute_reply.started":"2021-06-28T11:13:09.550479Z","shell.execute_reply":"2021-06-28T11:13:13.59626Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"固定随机种子，保证结果可复现。","metadata":{}},{"cell_type":"code","source":"seed = 415\n\nos.environ['PYTHONHASHSEED'] = str(seed)\nnp.random.seed(seed)\ntorch.manual_seed(seed)\ntorch.cuda.manual_seed(seed)\ntorch.backends.cudnn.deterministic = True\ntorch.backends.cudnn.benchmark = True","metadata":{"execution":{"iopub.status.busy":"2021-06-28T11:13:13.599337Z","iopub.execute_input":"2021-06-28T11:13:13.599688Z","iopub.status.idle":"2021-06-28T11:13:13.607981Z","shell.execute_reply.started":"2021-06-28T11:13:13.599644Z","shell.execute_reply":"2021-06-28T11:13:13.607213Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"把数据读进来，并且把label搞定。","metadata":{}},{"cell_type":"code","source":"path = '../input/classify-leaves'\nlabels_file_path = os.path.join(path, 'train.csv')\nsample_submission_path = os.path.join(path, 'test.csv')\n\ndf = pd.read_csv(labels_file_path)\nsub_df = pd.read_csv(sample_submission_path)\nlabels_unique = df['label'].unique()\n\n\nle = LabelEncoder()\nle.fit(df['label'])\ndf['label'] = le.transform(df['label'])\nlabel_map = dict(zip(le.classes_, le.transform(le.classes_)))\nlabel_inv_map = {v: k for k, v in label_map.items()}","metadata":{"execution":{"iopub.status.busy":"2021-06-28T11:13:13.609747Z","iopub.execute_input":"2021-06-28T11:13:13.610121Z","iopub.status.idle":"2021-06-28T11:13:13.672689Z","shell.execute_reply.started":"2021-06-28T11:13:13.610069Z","shell.execute_reply":"2021-06-28T11:13:13.671882Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 数据增强","metadata":{}},{"cell_type":"code","source":"def get_train_transforms():\n    return albumentations.Compose(\n        [\n            albumentations.Resize(320, 320),\n            albumentations.HorizontalFlip(p=0.5),\n            albumentations.VerticalFlip(p=0.5),\n            albumentations.Rotate(limit=180, p=0.7),\n            albumentations.RandomBrightnessContrast(),\n            albumentations.ShiftScaleRotate(\n                shift_limit=0.25, scale_limit=0.1, rotate_limit=0\n            ),\n            albumentations.Normalize(\n                [0.485, 0.456, 0.406], [0.229, 0.224, 0.225],\n                max_pixel_value=255.0, always_apply=True\n            ),\n            ToTensorV2(p=1.0),\n        ]\n    )\n\ndef get_valid_transforms():\n    return albumentations.Compose(\n        [\n            albumentations.Resize(320, 320),\n            albumentations.Normalize(\n                [0.485, 0.456, 0.406], [0.229, 0.224, 0.225],\n                max_pixel_value=255.0, always_apply=True\n            ),\n            ToTensorV2(p=1.0)\n        ]\n    )","metadata":{"execution":{"iopub.status.busy":"2021-06-28T11:13:13.673826Z","iopub.execute_input":"2021-06-28T11:13:13.674161Z","iopub.status.idle":"2021-06-28T11:13:13.682074Z","shell.execute_reply.started":"2021-06-28T11:13:13.674123Z","shell.execute_reply":"2021-06-28T11:13:13.681203Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"定义Dataset，还有准确率之类的函数。","metadata":{}},{"cell_type":"code","source":"class LeafDataset(Dataset):\n    def __init__(self, images_filepaths, labels, transform=None):\n        self.images_filepaths = images_filepaths\n        self.labels = labels\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.images_filepaths)\n\n    def __getitem__(self, idx):\n        image_filepath = self.images_filepaths[idx]\n        image = cv2.imread(image_filepath)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        label = self.labels[idx]\n        if self.transform is not None:\n            image = self.transform(image=image)[\"image\"]\n        return image, label\n\ndef accuracy(output, target):\n    y_pred = torch.softmax(output, dim=1)\n    y_pred = torch.argmax(y_pred, dim=1).cpu()\n    target = target.cpu()\n\n    return accuracy_score(target, y_pred)\n\n\ndef calculate_f1_macro(output, target):\n    y_pred = torch.softmax(output, dim=1)\n    y_pred = torch.argmax(y_pred, dim=1).cpu()\n    target = target.cpu()\n\n    return f1_score(target, y_pred, average='macro')\n\n\nclass MetricMonitor:\n    def __init__(self, float_precision=3):\n        self.float_precision = float_precision\n        self.reset()\n\n    def reset(self):\n        self.metrics = defaultdict(lambda: {\"val\": 0, \"count\": 0, \"avg\": 0})\n\n    def update(self, metric_name, val):\n        metric = self.metrics[metric_name]\n\n        metric[\"val\"] += val\n        metric[\"count\"] += 1\n        metric[\"avg\"] = metric[\"val\"] / metric[\"count\"]\n\n    def __str__(self):\n        return \" | \".join(\n            [\n                \"{metric_name}: {avg:.{float_precision}f}\".format(\n                    metric_name=metric_name, avg=metric[\"avg\"],\n                    float_precision=self.float_precision\n                )\n                for (metric_name, metric) in self.metrics.items()\n            ]\n        )\n    \ndef adjust_learning_rate(optimizer, epoch, params, batch=0, nBatch=None):\n    \"\"\" adjust learning of a given optimizer and return the new learning rate \"\"\"\n    new_lr = calc_learning_rate(epoch, params['lr'], params['epochs'], batch, nBatch)\n    for param_group in optimizer.param_groups:\n        param_group['lr'] = new_lr\n    return new_lr\n\n\n\"\"\" learning rate schedule \"\"\"\ndef calc_learning_rate(epoch, init_lr, n_epochs, batch=0, nBatch=None, lr_schedule_type='cosine'):\n    if lr_schedule_type == 'cosine':\n        t_total = n_epochs * nBatch\n        t_cur = epoch * nBatch + batch\n        lr = 0.5 * init_lr * (1 + math.cos(math.pi * t_cur / t_total))\n    elif lr_schedule_type is None:\n        lr = init_lr\n    else:\n        raise ValueError('do not support: %s' % lr_schedule_type)\n    return lr","metadata":{"execution":{"iopub.status.busy":"2021-06-28T11:13:13.683541Z","iopub.execute_input":"2021-06-28T11:13:13.684148Z","iopub.status.idle":"2021-06-28T11:13:13.700962Z","shell.execute_reply.started":"2021-06-28T11:13:13.684095Z","shell.execute_reply":"2021-06-28T11:13:13.700159Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 参数设置 \n\n所有模型都用的相同的参数 其实这里应该针对不同模型调整的","metadata":{}},{"cell_type":"code","source":"params = {\n    'model': 'seresnext50_32x4d',\n    # 'model': 'resnet50d',\n    'device': device,\n    'lr': 1e-3,\n    'batch_size': 64,\n    'num_workers': 0,\n    'epochs': 50,\n    'out_features': df['label'].nunique(),\n    'weight_decay': 1e-5\n}","metadata":{"execution":{"iopub.status.busy":"2021-06-28T11:13:13.702426Z","iopub.execute_input":"2021-06-28T11:13:13.702861Z","iopub.status.idle":"2021-06-28T11:13:13.714622Z","shell.execute_reply.started":"2021-06-28T11:13:13.702823Z","shell.execute_reply":"2021-06-28T11:13:13.7137Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 训练\n\n为了更快我就注释掉了，有兴趣可以跑一下。","metadata":{}},{"cell_type":"code","source":"class LeafNet(nn.Module):\n    def __init__(self, model_name=params['model'], out_features=params['out_features'],\n                 pretrained=True):\n        super().__init__()\n        self.model = timm.create_model(model_name, pretrained=pretrained)\n        n_features = self.model.fc.in_features\n        self.model.fc = nn.Linear(n_features, out_features)\n\n    def forward(self, x):\n        x = self.model(x)\n        return x\n\ndef train(train_loader, model, criterion, optimizer, epoch, params):\n    metric_monitor = MetricMonitor()\n    model.train()\n    nBatch = len(train_loader)\n    stream = tqdm(train_loader)\n    for i, (images, target) in enumerate(stream, start=1):\n        images = images.to(params['device'], non_blocking=True)\n        target = target.to(params['device'], non_blocking=True)\n        output = model(images)\n        loss = criterion(output, target)\n        f1_macro = calculate_f1_macro(output, target)\n        acc = accuracy(output, target)\n        metric_monitor.update('Loss', loss.item())\n        metric_monitor.update('F1', f1_macro)\n        metric_monitor.update('Accuracy', acc)\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n        lr = adjust_learning_rate(optimizer, epoch, params, i, nBatch)\n        stream.set_description(\n            \"Epoch: {epoch}. Train.      {metric_monitor}\".format(\n                epoch=epoch,\n                metric_monitor=metric_monitor)\n        )\n    return metric_monitor.metrics['Accuracy'][\"avg\"]\n\ndef validate(val_loader, model, criterion, epoch, params):\n    metric_monitor = MetricMonitor()\n    model.eval()\n    stream = tqdm(val_loader)\n    with torch.no_grad():\n        for i, (images, target) in enumerate(stream, start=1):\n            images = images.to(params['device'], non_blocking=True)\n            target = target.to(params['device'], non_blocking=True)\n            output = model(images)\n            loss = criterion(output, target)\n            f1_macro = calculate_f1_macro(output, target)\n            acc = accuracy(output, target)\n            metric_monitor.update('Loss', loss.item())\n            metric_monitor.update('F1', f1_macro)\n            metric_monitor.update('Accuracy', acc)\n            stream.set_description(\n                \"Epoch: {epoch}. Validation. {metric_monitor}\".format(\n                    epoch=epoch,\n                    metric_monitor=metric_monitor)\n            )\n    return metric_monitor.metrics['Accuracy'][\"avg\"]","metadata":{"execution":{"iopub.status.busy":"2021-06-28T11:13:13.716908Z","iopub.execute_input":"2021-06-28T11:13:13.71729Z","iopub.status.idle":"2021-06-28T11:13:13.733074Z","shell.execute_reply.started":"2021-06-28T11:13:13.717254Z","shell.execute_reply":"2021-06-28T11:13:13.732241Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# kf = StratifiedKFold(n_splits=5)\n\n\n# for k, (train_index, test_index) in enumerate(kf.split(df['image'], df['label'])):\n#     train_img, valid_img = df['image'][train_index], df['image'][test_index]\n#     train_labels, valid_labels = df['label'][train_index], df['label'][test_index]\n\n#     train_paths = '../input/classify-leaves/' + train_img\n#     valid_paths = '../input/classify-leaves/' + valid_img\n#     test_paths = '../input/classify-leaves/' + sub_df['image']\n\n#     train_dataset = LeafDataset(images_filepaths=train_paths.values,\n#                                 labels=train_labels.values,\n#                                 transform=get_train_transforms())\n#     valid_dataset = LeafDataset(images_filepaths=valid_paths.values,\n#                                 labels=valid_labels.values,\n#                                 transform=get_valid_transforms())\n#     train_loader = DataLoader(\n#         train_dataset, batch_size=params['batch_size'], shuffle=True,\n#         num_workers=params['num_workers'], pin_memory=True,\n#     )\n\n#     val_loader = DataLoader(\n#         valid_dataset, batch_size=params['batch_size'], shuffle=False,\n#         num_workers=params['num_workers'], pin_memory=True,\n#     )\n#     model = LeafNet()\n#     model = nn.DataParallel(model)\n#     model = model.to(params['device'])\n#     criterion = nn.CrossEntropyLoss().to(params['device'])\n#     optimizer = torch.optim.AdamW(model.parameters(), lr=params['lr'], weight_decay=params['weight_decay'])\n\n#     for epoch in range(1, params['epochs'] + 1):\n#         train(train_loader, model, criterion, optimizer, epoch, params)\n#         acc = validate(val_loader, model, criterion, epoch, params)\n#         torch.save(model.state_dict(), f\"./checkpoints/{params['model']}_{k}flod_{epoch}epochs_accuracy{acc:.5f}_weights.pth\")\n","metadata":{"execution":{"iopub.status.busy":"2021-06-28T11:13:13.734572Z","iopub.execute_input":"2021-06-28T11:13:13.734968Z","iopub.status.idle":"2021-06-28T11:13:13.743254Z","shell.execute_reply.started":"2021-06-28T11:13:13.734902Z","shell.execute_reply":"2021-06-28T11:13:13.742376Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# 测试和提交\n\n提交用的代码，用了两个模型seresnext50_32x4d和resnet50d。","metadata":{}},{"cell_type":"code","source":"train_img, valid_img = df['image'], df['image']\ntrain_labels, valid_labels = df['label'], df['label']\n\ntrain_paths = '../input/classify-leaves/' + train_img\nvalid_paths = '../input/classify-leaves/' + valid_img\ntest_paths = '../input/classify-leaves/' + sub_df['image']\n\nmodel_name = ['seresnext50_32x4d', 'resnet50d']\nmodel_path_list = [\n    '../input/checkpoints/seresnext50_32x4d_0flod_50epochs_accuracy0.97985_weights.pth',\n    '../input/checkpoints/seresnext50_32x4d_1flod_50epochs_accuracy0.97872_weights.pth',\n    '../input/checkpoints/seresnext50_32x4d_2flod_36epochs_accuracy0.97710_weights.pth',\n    '../input/checkpoints/seresnext50_32x4d_3flod_40epochs_accuracy0.98303_weights.pth',\n    '../input/checkpoints/seresnext50_32x4d_4flod_46epochs_accuracy0.97899_weights.pth',\n    '../input/checkpoints/resnet50d_0flod_40epochs_accuracy0.98087_weights.pth',\n    '../input/checkpoints/resnet50d_1flod_46epochs_accuracy0.97710_weights.pth',\n    '../input/checkpoints/resnet50d_2flod_32epochs_accuracy0.97656_weights.pth',\n    '../input/checkpoints/resnet50d_3flod_38epochs_accuracy0.97953_weights.pth',\n    '../input/checkpoints/resnet50d_4flod_50epochs_accuracy0.97791_weights.pth',\n]\n\nmodel_list = []\nfor i in range(len(model_path_list)):\n    if i < 5:\n        model_list.append(LeafNet(model_name[0]))\n    if 5 <= i < 10:\n        model_list.append(LeafNet(model_name[1]))\n    model_list[i] = nn.DataParallel(model_list[i])\n    model_list[i] = model_list[i].to(params['device'])\n    init = torch.load(model_path_list[i])\n    model_list[i].load_state_dict(init)\n    model_list[i].eval()\n    model_list[i].cuda()\n\n    \nlabels = np.zeros(len(test_paths)) # Fake Labels\ntest_dataset = LeafDataset(images_filepaths=test_paths,\n                            labels=labels,\n                            transform=get_valid_transforms())\ntest_loader = DataLoader(\n    test_dataset, batch_size=128, shuffle=False,\n    num_workers=10, pin_memory=True\n)\n\n\npredicted_labels = []\npred_string = []\npreds = []\n\nwith torch.no_grad():\n    for (images, target) in test_loader:\n        images = images.cuda()\n        onehots = sum([model(images) for model in model_list]) / len(model_list)\n        for oh, name in zip(onehots, target):\n            lbs = label_inv_map[torch.argmax(oh).item()]\n            preds.append(dict(image=name, labels=lbs))\n\ndf_preds = pd.DataFrame(preds)\nsub_df['label'] = df_preds['labels']\nsub_df.to_csv('submission.csv', index=False)\nsub_df.head()","metadata":{"execution":{"iopub.status.busy":"2021-06-28T11:13:13.744583Z","iopub.execute_input":"2021-06-28T11:13:13.745054Z","iopub.status.idle":"2021-06-28T11:19:33.626516Z","shell.execute_reply.started":"2021-06-28T11:13:13.745015Z","shell.execute_reply":"2021-06-28T11:19:33.625587Z"},"trusted":true},"execution_count":null,"outputs":[]}]}