{"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":"import torch\nfrom torchvision import transforms\nfrom torch.utils.data import Dataset,DataLoader\nimport os\nimport pandas as pd\nimport numpy as np\nfrom PIL import Image\nfrom sklearn.model_selection import KFold\nimport sys\nsys.path.append('../input/autoaugment')\nfrom autoaugment import ImageNetPolicy\nimport torch.optim as optim","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2021-05-31T01:35:38.885153Z","iopub.execute_input":"2021-05-31T01:35:38.885533Z","iopub.status.idle":"2021-05-31T01:35:38.891773Z","shell.execute_reply.started":"2021-05-31T01:35:38.885496Z","shell.execute_reply":"2021-05-31T01:35:38.890696Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#resnet34\nimport torch.nn as nn\nimport torch\n \n#18和34层残差结构（具备实线残差结构功能和虚线残差结构功能）\nclass BasicBlock(nn.Module):\n    expansion = 1 #对应残差结构中卷积核个数有没有发生变化。1是1倍意思，即都一样。\n \n    # downsample对应虚线的残差结构\n    def __init__(self, in_channel, out_channel, stride=1, downsample=None):\n        super(BasicBlock, self).__init__()\n        self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=out_channel,\n                               kernel_size=3, stride=stride, padding=1, bias=False)\n        #注意之前的卷积层不要bias\n        self.bn1 = nn.BatchNorm2d(out_channel)\n        self.relu = nn.ReLU()\n        self.conv2 = nn.Conv2d(in_channels=out_channel, out_channels=out_channel,\n                               kernel_size=3, stride=1, padding=1, bias=False)\n        self.bn2 = nn.BatchNorm2d(out_channel)\n        self.downsample = downsample\n \n    def forward(self, x):\n        identity = x\n        if self.downsample is not None:\n            identity = self.downsample(x)\n \n        out = self.conv1(x)\n        out = self.bn1(out)\n        out = self.relu(out)\n \n        out = self.conv2(out)\n        out = self.bn2(out)\n \n        out += identity\n        out = self.relu(out)\n \n        return out\n \n \n#50层及以上的残差结构（具备实线残差结构功能和虚线残差结构功能）\nclass Bottleneck(nn.Module):\n    expansion = 4\n \n    def __init__(self, in_channel, out_channel, stride=1, downsample=None):\n        super(Bottleneck, self).__init__()\n        self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=out_channel,\n                               kernel_size=1, stride=1, bias=False)  # squeeze channels\n        self.bn1 = nn.BatchNorm2d(out_channel)\n        # -----------------------------------------\n        self.conv2 = nn.Conv2d(in_channels=out_channel, out_channels=out_channel,\n                               kernel_size=3, stride=stride, bias=False, padding=1)\n        self.bn2 = nn.BatchNorm2d(out_channel)\n        # -----------------------------------------\n        self.conv3 = nn.Conv2d(in_channels=out_channel, out_channels=out_channel*self.expansion,\n                               kernel_size=1, stride=1, bias=False)  # unsqueeze channels\n        self.bn3 = nn.BatchNorm2d(out_channel*self.expansion)\n        self.relu = nn.ReLU(inplace=True)\n        self.downsample = downsample\n \n    def forward(self, x):\n        identity = x\n        if self.downsample is not None:\n            identity = self.downsample(x)\n \n        out = self.conv1(x)\n        out = self.bn1(out)\n        out = self.relu(out)\n \n        out = self.conv2(out)\n        out = self.bn2(out)\n        out = self.relu(out)\n \n        out = self.conv3(out)\n        out = self.bn3(out)\n \n        out += identity\n        out = self.relu(out)\n \n        return out\n \n \n# block为 BasicBlock(nn.Module)或Bottleneck(nn.Module)\n# blocks_num：列表参数,代表残差结构的个数，如[3,4,6,3]、[2,2,2,2]\n# include_top=True方便在resnet上搭建更复杂的结构。默认就是True\nclass ResNet(nn.Module):\n \n    def __init__(self, block, blocks_num, num_classes=1000, include_top=True):\n        super(ResNet, self).__init__()\n        self.include_top = include_top\n        self.in_channel = 64\n \n        self.conv1 = nn.Conv2d(3, self.in_channel, kernel_size=7, stride=2,\n                               padding=3, bias=False)\n        self.bn1 = nn.BatchNorm2d(self.in_channel)\n        self.relu = nn.ReLU(inplace=True)\n        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)\n        self.layer1 = self._make_layer(block, 64, blocks_num[0])\n        self.layer2 = self._make_layer(block, 128, blocks_num[1], stride=2)\n        self.layer3 = self._make_layer(block, 256, blocks_num[2], stride=2)\n        self.layer4 = self._make_layer(block, 512, blocks_num[3], stride=2)\n        if self.include_top:\n            #采用自适应平均池化，不管输入是什么维度，输出的HW都将是1*1\n            self.avgpool = nn.AdaptiveAvgPool2d((1, 1))  # output size = (1, 1)\n            self.fc = nn.Linear(512 * block.expansion, num_classes)\n \n        for m in self.modules():\n            if isinstance(m, nn.Conv2d):\n                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')\n \n    # 搭建残差结构的函数\n    def _make_layer(self, block, channel, block_num, stride=1):\n        downsample = None\n        if stride != 1 or self.in_channel != channel * block.expansion:\n            downsample = nn.Sequential(\n                nn.Conv2d(self.in_channel, channel * block.expansion, kernel_size=1, stride=stride, bias=False),\n                nn.BatchNorm2d(channel * block.expansion))\n \n        layers = []\n        layers.append(block(self.in_channel, channel, downsample=downsample, stride=stride))\n        self.in_channel = channel * block.expansion\n \n        for _ in range(1, block_num):\n            layers.append(block(self.in_channel, channel))\n \n        return nn.Sequential(*layers) # 将list转为非关键字参数传入\n \n \n    def forward(self, x):\n        x = self.conv1(x)\n        x = self.bn1(x)\n        x = self.relu(x)\n        x = self.maxpool(x)\n \n        x = self.layer1(x)\n        x = self.layer2(x)\n        x = self.layer3(x)\n        x = self.layer4(x)\n \n        if self.include_top:\n            x = self.avgpool(x)\n            x = torch.flatten(x, 1)\n            x = self.fc(x)\n \n        return x\n \n \ndef resnet34(num_classes=1000, include_top=True):\n    return ResNet(BasicBlock, [3, 4, 6, 3], num_classes=num_classes, include_top=include_top)\n \n \ndef resnet101(num_classes=1000, include_top=True):\n    return ResNet(Bottleneck, [3, 4, 23, 3], num_classes=num_classes, include_top=include_top)\n\ndef resnet152(num_classes=1000, include_top=True):\n    return ResNet(Bottleneck, [3, 8, 36, 3], num_classes=num_classes, include_top=include_top)","metadata":{"execution":{"iopub.status.busy":"2021-05-31T01:35:47.309337Z","iopub.execute_input":"2021-05-31T01:35:47.309699Z","iopub.status.idle":"2021-05-31T01:35:47.342214Z","shell.execute_reply.started":"2021-05-31T01:35:47.309647Z","shell.execute_reply":"2021-05-31T01:35:47.341218Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#data preprocessing\ndata = pd.read_csv('../input/plant-pathology-2021-fgvc8/train.csv')\ndata = data.drop(index=(data.loc[(data['labels']=='healthy')].index)) #不使用healthy数据\nall_img_names:list = data[\"image\"].values.tolist() #冒号后起注释作用\n\nlabel_num2str = {0: 'powdery_mildew',\n                 1: 'scab',\n                 2: 'complex',\n                 3: 'frog_eye_leaf_spot',\n                 4: 'rust'}\n\nlabel_str2num = {'powdery_mildew': 0,\n                 'scab': 1,\n                 'complex': 2,\n                 'frog_eye_leaf_spot': 3,\n                 'rust': 4}  \n     \nall_numeric_labels = []     \nfor row_idx,row in data.iterrows():    \n    labels_list = row['labels'].split(\" \")\n    numeric_label_list = [label_str2num[each] for each in labels_list if each != 'healthy']\n    numeric_label_numpy = np.array(numeric_label_list)*0.9+0.05 #label_smoothing\n    numeric_label_list = numeric_label_numpy.tolist()\n    all_numeric_labels.append(numeric_label_list)  \ndata.insert(2,'numeric_labels',all_numeric_labels)\n\nall_img_labels_ts = []\n\nfor tmp_lb in all_numeric_labels:\n    tmp_label = torch.zeros([5],dtype=torch.float)\n    for idx in tmp_lb:\n        tmp_label[int(idx)] = 1.0 \n    all_img_labels_ts.append(tmp_label)\nk_fold = KFold(n_splits = 6,shuffle = True,random_state = 77) #六折交叉验证\n  \n","metadata":{"execution":{"iopub.status.busy":"2021-05-31T01:35:47.344265Z","iopub.execute_input":"2021-05-31T01:35:47.344732Z","iopub.status.idle":"2021-05-31T01:35:49.209762Z","shell.execute_reply.started":"2021-05-31T01:35:47.344605Z","shell.execute_reply":"2021-05-31T01:35:49.208870Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#Focal_loss\nclass FocalLoss(nn.Module):\n    \"\"\"\n    The focal loss for fighting against class-imbalance\n    \"\"\"\n    def __init__(self, alpha=1, gamma=2):\n        super(FocalLoss, self).__init__()\n        self.alpha = alpha\n        self.gamma = gamma\n        self.epsilon = 1e-12  # prevent training from Nan-loss error\n        self.cls_weights = torch.tensor([[0.3648, 0.0813, 0.2184, 0.1066, 0.2290]],dtype=torch.float, requires_grad=False, device=torch.device(\"cuda:0\"))\n        self.lb_smooth = 0.1\n\n    def forward(self, logits, target):\n        \"\"\"\n        logits & target should be tensors with shape [batch_size, num_classes]\n        \"\"\"\n        probs = torch.sigmoid(logits)\n        one_subtract_probs = 1.0 - probs\n        # add epsilon\n        probs_new = probs + self.epsilon\n        one_subtract_probs_new = one_subtract_probs + self.epsilon\n        # calculate focal loss\n        target = torch.abs(target - self.lb_smooth)\n        log_pt = target * torch.log(probs_new) + (1.0 - target) * torch.log(one_subtract_probs_new)\n        pt = torch.exp(log_pt)\n        focal_loss = -1.0 * (self.alpha * (1 - pt) ** self.gamma) * log_pt\n        focal_loss = focal_loss * self.cls_weights\n        return torch.mean(focal_loss)","metadata":{"execution":{"iopub.status.busy":"2021-05-31T01:35:49.213439Z","iopub.execute_input":"2021-05-31T01:35:49.213707Z","iopub.status.idle":"2021-05-31T01:35:49.224595Z","shell.execute_reply.started":"2021-05-31T01:35:49.213679Z","shell.execute_reply":"2021-05-31T01:35:49.223746Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#F1 score \n\nclass MyF1Score(Metric):\n    def __init__(self, threshold: float = 0.5, dist_sync_on_step=False):\n        super().__init__(dist_sync_on_step=dist_sync_on_step)\n        self.threshold = threshold\n        self.add_state(\"tp\", default=torch.tensor(0), dist_reduce_fx=\"sum\")\n        self.add_state(\"fp\", default=torch.tensor(0), dist_reduce_fx=\"sum\")\n        self.add_state(\"fn\", default=torch.tensor(0), dist_reduce_fx=\"sum\")\n\n    def update(self, preds: torch.Tensor, target: torch.Tensor):\n        assert preds.shape == target.shape\n        preds_str_batch = self.num_to_str(torch.sigmoid(preds))\n        target_str_batch = self.num_to_str(target)\n        tp, fp, fn = 0, 0, 0\n        for pred_str_list, target_str_list in zip(preds_str_batch, target_str_batch):\n            for pred_str in pred_str_list:\n                if pred_str in target_str_list:\n                    tp += 1\n                if pred_str not in target_str_list:\n                    fp += 1\n\n            for target_str in target_str_list:\n                if target_str not in pred_str_list:\n                    fn += 1\n        self.tp += tp\n        self.fp += fp\n        self.fn += fn\n\n    def compute(self):\n        f1 = 2.0 * self.tp / (2.0 * self.tp + self.fn + self.fp)\n        return f1\n    \n    def num_to_str(self, ts: torch.Tensor) -> list:\n        batch_bool_list = (ts > self.threshold).detach().cpu().numpy().tolist()\n        batch_str_list = []\n        for one_sample_bool in batch_bool_list:\n            lb_str_list = [self.label_num2str[lb_idx] for lb_idx, bool_val in enumerate(one_sample_bool) if bool_val]\n            if len(lb_str_list) == 0:\n                lb_str_list = ['healthy']\n            batch_str_list.append(lb_str_list)\n        return batch_str_list","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#dataset\nclass PlantDataset(Dataset):\n    def __init__(self,img_path,img_name:list,img_label:list,transform = None):\n        self.img_path = img_path\n        self.img_label = img_label\n        self.img_name = img_name\n        self.transform = transform\n        \n    def __len__(self):\n        return len(self.img_name)\n    \n    def __getitem__(self,index):\n        path = os.path.join(self.img_path,self.img_name[index])\n        img = Image.open(path).convert('RGB')\n        img = self.transform(img)\n        img_label = self.img_label[index]\n        return img,img_label","metadata":{"execution":{"iopub.status.busy":"2021-05-31T01:35:49.226415Z","iopub.execute_input":"2021-05-31T01:35:49.226905Z","iopub.status.idle":"2021-05-31T01:35:49.239276Z","shell.execute_reply.started":"2021-05-31T01:35:49.226867Z","shell.execute_reply":"2021-05-31T01:35:49.238212Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#data_transform\ndata_transform = {\n        \"train\": transforms.Compose([transforms.RandomResizedCrop(380),\n                                     transforms.RandomHorizontalFlip(),\n                                     ImageNetPolicy(),\n                                     transforms.ToTensor(),\n                                     transforms.Normalize([0.46141568, 0.6165156, 0.3905568], [0.15031955, 0.1329983, 0.172767])]),\n        \"val\": transforms.Compose([transforms.Resize(412), #将图像最小的边缩放到256.\n                                   transforms.CenterCrop(380),\n                                   transforms.ToTensor(),\n                                   transforms.Normalize([0.46141568, 0.6165156, 0.3905568], [0.15031955, 0.1329983, 0.172767])])}","metadata":{"execution":{"iopub.status.busy":"2021-05-31T01:35:49.242612Z","iopub.execute_input":"2021-05-31T01:35:49.243126Z","iopub.status.idle":"2021-05-31T01:35:49.265140Z","shell.execute_reply.started":"2021-05-31T01:35:49.243093Z","shell.execute_reply":"2021-05-31T01:35:49.264245Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#train\ndevice = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\nnet = resnet34() #实例化网络，注意：此时没有传入参数，默认是1000分类\n    \n# load pretrain weights\n# download url: https://download.pytorch.org/models/resnet34-333f7ec4.pth\nmodel_weight_path = \"../input/resnet34-pre/resnet34-pre.pth\"\nassert os.path.exists(model_weight_path), \"file {} does not exist.\".format(model_weight_path)\nmissing_keys, unexpected_keys = net.load_state_dict(torch.load(model_weight_path), strict=False)\n# for param in net.parameters():\n#     param.requires_grad = False\n# change fc layer structure\nin_channel = net.fc.in_features\nnet.fc = nn.Linear(in_channel, 5) #由于花分类是5类，所以重新赋值（默认1000分类）\n\nnet.to(device)\n\n#loss_function = FocalLoss(class_num=12,alpha=(0.983,0.792,0.990,0.729,0.933,0.995,0.891,0.994,0.993,0.718,0.960,0.988),use_alpha=(True))\n#loss_function = nn.CrossEntropyLoss()\nloss_function = FocalLoss()\noptimizer = optim.Adam(net.parameters(), lr=0.0003)\nscheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', factor=0.1, patience=2,\n             verbose=False, threshold=1e-4, threshold_mode='rel',\n             cooldown=0, min_lr=0, eps=1e-8)   \n\nbest_acc = 0.0\nsave_path = './resNet34_multi.pth'\n\nfor epoch in range(3):\n  # get image names and labels   \n    for fold_idx, (train_indices, valid_indices) in enumerate(k_fold.split(all_img_names)):\n        fold_train_img_names = [all_img_names[idx] for idx in train_indices]\n        fold_valid_img_names = [all_img_names[idx] for idx in valid_indices]\n        fold_train_img_labels = [all_img_labels_ts[idx] for idx in train_indices]\n        fold_valid_img_labels = [all_img_labels_ts[idx] for idx in valid_indices]\n        # dataset\n        train_dataset = PlantDataset('../input/plant-pathology-2021-fgvc8/train_images', fold_train_img_names, fold_train_img_labels, data_transform['train'])\n        valid_dataset = PlantDataset('../input/plant-pathology-2021-fgvc8/train_images', fold_valid_img_names, fold_valid_img_labels, data_transform['val'])\n        # dataloader\n        train_loader = DataLoader(train_dataset, batch_size=8, shuffle=True, num_workers=8, drop_last=True)\n        valid_loader = DataLoader(valid_dataset, batch_size=8, shuffle=False, num_workers=8)\n        \n        val_num = len(valid_dataset)\n\n        # train\n        net.train()\n        running_loss = 0.0\n        for step, data in enumerate(train_loader, start=0):\n            images, labels = data\n            optimizer.zero_grad()\n            logits = net(images.to(device))\n            loss = loss_function(logits, labels.to(device))\n            loss.backward()\n            optimizer.step()\n            # print statistics\n            running_loss += loss.item()\n            # print train process\n            rate = (step+1)/len(train_loader)\n            a = \"*\" * int(rate * 50)\n            b = \".\" * int((1 - rate) * 50)\n            print(\"\\rtrain loss: {:^3.0f}%[{}->{}]{:.4f}\".format(int(rate*100), a, b, loss), end=\"\")\n        print()\n\n        # validate\n        net.eval()\n        val_num = len(valid_dataset)\n        acc = 0.0  # accumulate accurate number / epoch\n        with torch.no_grad():\n            for val_data in valid_loader:\n                val_images, val_labels = val_data\n                outputs = net(val_images.to(device))  # eval model only have last output layer\n                \n                '''\n                outputs = torch.sigmoid(outputs).cuda()\n                # loss = loss_function(outputs, test_labels)\n                for i in range(outputs.shape[0]):\n                    for j in range(outputs.shape[1]):\n                        if outputs[i][j] > 0.68997:\n                            outputs[i][j] = 1\n                        else:outputs[i][j] = 0  \n                '''\n                #predict_y = outputs\n                #acc += (predict_y == val_labels.to(device)).sum().item()\n            #val_accurate = acc / val_num\n            val_accurate = MyF1Score(preds = outputs, target = val_labels).cuda()\n            if val_accurate > best_acc:\n                best_acc = val_accurate\n                torch.save(net.state_dict(), save_path)\n            print('[epoch %d] train_loss: %.3f  test_accuracy: %.3f' %\n                  (epoch + 1, running_loss / step, val_accurate))\n        scheduler.step(val_accurate)\n    print(\"\\r第%d个epoch的学习率：%f\" % (epoch, optimizer.param_groups[0]['lr']))\nprint('Finished Training')","metadata":{"execution":{"iopub.status.busy":"2021-05-31T01:35:49.266642Z","iopub.execute_input":"2021-05-31T01:35:49.267063Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import torch\nfrom model import resnet34\nfrom PIL import Image\nfrom torchvision import transforms\nimport matplotlib.pyplot as plt\nimport json\n \ndevice = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n \ndata_transform = transforms.Compose(\n    [transforms.Resize(412),\n     transforms.CenterCrop(380),\n     transforms.ToTensor(),\n     transforms.Normalize([0.46141568, 0.6165156, 0.3905568], [0.15031955, 0.1329983, 0.172767])])\n \n# create model\nmodel = resnet34(num_classes=5)\n# load model weights\nmodel_weight_path = \"./resNet34.pth\"\nmodel.load_state_dict(torch.load(model_weight_path, map_location=device))\nmodel.eval()\nwith torch.no_grad():\n    # predict class\n    output = torch.squeeze(model(img))\n    predict = torch.sigmoid(output)\n    predict_cla = torch.argmax(predict).numpy()\nprint(class_indict[str(predict_cla)], predict[predict_cla].numpy())\nplt.show()\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.model_selection import KFold\na = [1,2,3,4,5]\nk_fold = KFold(n_splits = 5,shuffle = True,random_state = 77) #六折交叉验证\nfor fold_idx, (train_indices, valid_indices) in enumerate(k_fold.split(a)):\n    print(fold_idx,'train:',train_indices,'valid:',valid_indices)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import torch\na = torch.tensor([0.022,0.002,0.001,0.86,0.98])\nb = torch.sigmoid(a)\nprint(b)\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}