{"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":"2021-07-12T03:41:47.397568Z","iopub.execute_input":"2021-07-12T03:41:47.397959Z","iopub.status.idle":"2021-07-12T03:41:49.543251Z","shell.execute_reply.started":"2021-07-12T03:41:47.39792Z","shell.execute_reply":"2021-07-12T03:41:49.542472Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!pip install timm","metadata":{"execution":{"iopub.status.busy":"2021-07-12T15:23:23.357037Z","iopub.execute_input":"2021-07-12T15:23:23.35737Z","iopub.status.idle":"2021-07-12T15:23:31.372737Z","shell.execute_reply.started":"2021-07-12T15:23:23.35734Z","shell.execute_reply":"2021-07-12T15:23:31.371819Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!pip install kaggle-timm-pretrained","metadata":{"execution":{"iopub.status.busy":"2021-07-12T15:20:31.515462Z","iopub.execute_input":"2021-07-12T15:20:31.515855Z","iopub.status.idle":"2021-07-12T15:20:33.900421Z","shell.execute_reply.started":"2021-07-12T15:20:31.515768Z","shell.execute_reply":"2021-07-12T15:20:33.899395Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\nimport numpy as np\nimport seaborn as sns\nimport pandas as pd\nimport glob\nfrom tqdm.notebook import tqdm #在notebook中使用时调用\nfrom tqdm import tqdm\nfrom PIL import Image\nimport matplotlib.pyplot as plt\nfrom matplotlib.ticker import MaxNLocator\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport torch.utils.data as data\nimport timm\nfrom torchvision import transforms as tsfm\nfrom torch.optim.lr_scheduler import CosineAnnealingLR\nfrom sklearn.preprocessing import MultiLabelBinarizer\nfrom sklearn.metrics import f1_score, accuracy_score\nfrom sklearn.model_selection import train_test_split\n# 构造样本数据项\nimport time\n\nstart = time.time()\n# 标签文件目录\npath = '../input/plant-pathology-2021-fgvc8/' #数据集的总目录，样本图片存于该目录下的train_images文件夹中，标签文件为train.csv\ntrain = pd.read_csv(path + 'train.csv')\ntrain_files = train['image']\ntrain.pop('image')\n\n#绘制各类别样本数量图\nplt.figure(figsize=(20, 12))\nlabels = sns.barplot(train.labels.value_counts().index, train.labels.value_counts())\nfor item in labels.get_xticklabels():\n    item.set_rotation(45)\n\n# 将空格分割的文本标签转化为文本标签列表\ntrain['labels'] = train['labels'].apply(lambda s: s.split(' '))\nprint(train[:10])\n\n#使用多标签编码器将字符标签转化为列表，如[0,1,0,1,0,0]\nmlb = MultiLabelBinarizer()\ny_train = mlb.fit_transform(train['labels']).astype('float32')\nprint(y_train[:10])\nprint(mlb.classes_)\n\n# 构造样本图像文件列表\nroot = 'train_images'\nimages_paths = [(os.path.join(path, root, train_file)) for train_file in train_files]\nprint(images_paths[:10])\n\n#将原样本集合分为训练和验证两个子集\nx_train,x_val,y_train,y_val = train_test_split(images_paths, y_train, test_size=0.2, shuffle=True)\n\nprint(len(images_paths))#样本总数\nprint(len(x_train))#训练集总数\nprint(len(x_val))#验证集总数\n\n\nclass Config:\n    \"\"\"\n    \"\"\"\n    DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu' #是否有可用GPU\n    OUTPUT_PATH = './' #模型保存位置\n    N_EPOCH = 20 #总epoch\n    WARMUP_EPOCH = 3 #预热epoch\n    BATCH_SIZE = 4 #batch size\n    LEARNING_RATE = 0.0024 #学习率\n    N_CLASSES = 6  # 类别数量\n\n\nclass PlantDataset(data.Dataset):\n    \"\"\"\n    定义数据集的类\n    \"\"\"\n\n    def __init__(self, file_list, label_list, transform=None, phase='train'):\n        self.label_list = label_list\n        self.file_list = file_list\n        self.transform = transform\n        self.phase = phase\n\n    def __len__(self):\n        #返回数据集中数据总数\n        return len(self.file_list)\n\n    def __getitem__(self, index):\n        #定义获取数据的方法\n        img_path = self.file_list[index]\n        img = Image.open(img_path)\n\n        # 数据增强\n        if self.transform:\n            img = self.transform(img)\n\n        label = np.array(self.label_list[index])\n\n        return img, label\n\nDATASET_IMAGE_MEAN = (0.485, 0.456, 0.406)\nDATASET_IMAGE_STD = (0.229, 0.224, 0.225)\n\ntrain_transform = tsfm.Compose([\n    tsfm.Resize(600),\n    tsfm.RandomCrop((600,600)),\n    tsfm.RandomApply([tsfm.ColorJitter(0.2, 0.2, 0.2),tsfm.RandomPerspective(distortion_scale=0.2,interpolation=3),], p=0.5),\n    tsfm.RandomApply([tsfm.ColorJitter(0.2, 0.2, 0.2),tsfm.RandomAffine(degrees=15),], p=0.5),\n    tsfm.RandomVerticalFlip(p=0.5),\n    tsfm.RandomHorizontalFlip(p=0.5),\n    tsfm.ToTensor(),\n    tsfm.Normalize(DATASET_IMAGE_MEAN, DATASET_IMAGE_STD), ])#训练集数据增强方法\n\nvalid_transform = tsfm.Compose([\n    tsfm.Resize(600),\n    tsfm.CenterCrop((600,600)),\n    tsfm.ToTensor(),\n    tsfm.Normalize(DATASET_IMAGE_MEAN, DATASET_IMAGE_STD), ])#验证集数据增强方法\n\ntrain_set = PlantDataset(x_train,y_train,train_transform)#实例化训练集\nval_set = PlantDataset(x_val,y_val,valid_transform)#实例化验证集\n\ntrain_dataloader = data.DataLoader(train_set, batch_size=Config.BATCH_SIZE, shuffle=True)#实例化训练集dataloader\nval_dataloader = data.DataLoader(val_set, batch_size=Config.BATCH_SIZE, shuffle=False)#实例化验证集dataloader\n\nef_model = timm.create_model('tf_efficientnet_b7_ns', pretrained=True)#使用timm包，构建预训练模型\nef_model.classifier= torch.nn.Sequential(#更改原efficientnet的分类层\n        torch.nn.Linear(\n            in_features=ef_model.classifier.in_features,\n            out_features=Config.N_CLASSES\n        ),\n        torch.nn.Sigmoid()\n    )\n\nfor param in ef_model.named_parameters():\n    #冻结除分类层外的其它网络层，如无需冻结可不使用\n    if param[0] not in ['classifier.0.weight','classifier.0.bias']:\n        param[1].requires_grad = False\n\nef_model.to(Config.DEVICE)#将模型权重送入相应设备\n\ndef to_numpy(tensor):\n    #将tensor转化为numpy数组，并传回cpu\n    return tensor.detach().cpu().numpy() if tensor.requires_grad else tensor.cpu().numpy()\n\nclass MetricMonitor:\n    '''构建保存模型性能表现的类\n    '''\n    def __init__(self):\n        self.reset()\n\n    def reset(self):\n        self.losses = []\n        self.accuracies = []\n        self.scores = []\n        self.metrics = dict({\n            'loss': self.losses,\n            'acc': self.accuracies,\n            'f1': self.scores\n        })\n\n    def update(self, metric_name, value):\n        self.metrics[metric_name] += [value]\n\n\ndef get_metrics(\n        y_pred_proba,\n        y_test,\n        threshold=0.5,\n        labels=mlb.classes_) -> None:\n    \"\"\"\n    计算f1与acc值\n    \"\"\"\n    y_pred = np.where(y_pred_proba > threshold, 1, 0)\n\n    y1 = y_pred.round().astype(np.float)\n    y2 = y_test.round().astype(np.float)\n\n    f1 = f1_score(y1, y2, average='micro')\n    acc = accuracy_score(y1, y2, normalize=True)\n\n    return acc, f1\n\n\ndef training_loop(\n        dataloader,\n        model,\n        loss_fn,\n        optimizer,\n        epoch,\n        monitor=MetricMonitor(),\n        is_train=True\n) -> None:\n    \"\"\"\n    定义训练与验证过程的函数\n    \"\"\"\n    size = len(dataloader.dataset)\n\n    loss_val = 0\n    accuracy = 0\n    f1score = 0\n\n    if is_train:#判断是否是训练阶段\n        model.train()\n    else:\n        model.eval()\n\n    stream = tqdm(dataloader)\n    for batch, (X, y) in enumerate(stream, start=1):\n        X = X.to(Config.DEVICE)\n        y = y.to(Config.DEVICE)\n\n        # compute prediction and loss\n        pred_prob = model(X)\n        loss = loss_fn(pred_prob, y)\n\n        if is_train:#判断是否是训练阶段\n            # 反向传播\n            optimizer.zero_grad()\n            loss.backward()\n            optimizer.step()\n\n        loss_val += loss.item()\n        acc, f1 = get_metrics(to_numpy(pred_prob), to_numpy(y))\n\n        accuracy += acc\n        f1score += f1\n\n        phase = 'Train' if is_train else 'Val'\n        stream.set_description(\n            f'Epoch {epoch:3d}/{Config.N_EPOCH} - {phase} - Loss: {loss_val / batch:.4f}, ' +\n            f'Acc: {accuracy / batch:.4f}, F1: {f1score / batch:.4f}'\n        )\n\n    monitor.update('loss', loss_val / batch)\n    monitor.update('acc', accuracy / batch)\n    monitor.update('f1', f1score / batch)\n\ntrain_monitor = MetricMonitor()#实例化模型在训练集上的性能监视器\nval_monitor = MetricMonitor()#实例化模型在验证集上的性能监视器\n\n# 定义损失函数\nloss_fn = nn.MultiLabelSoftMarginLoss()\n\nwarm_up_iter = Config.WARMUP_EPOCH*len(train_dataloader)#预热EPOCH\nT_max = Config.N_EPOCH*len(train_dataloader)\t# EPOCH\nlr_max = Config.LEARNING_RATE\t# 最大值\nlr_min = 0.0000001\t# 最小值\n\n#warmup+cosine decay的学习率衰减策略\nlambdalr = lambda cur_iter: cur_iter / warm_up_iter if  cur_iter < warm_up_iter else \\\n        (lr_min + 0.5*(lr_max-lr_min)*(1.0+math.cos( (cur_iter-warm_up_iter)/(T_max-warm_up_iter)*math.pi)))/0.1\n\n#优化器\noptimizer = torch.optim.Adam(\n    filter(lambda p: p.requires_grad, ef_model.parameters()),\n    lr=Config.LEARNING_RATE\n)\n\n#学习率衰减\nscheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=[lambdalr])\n\n#整个训练过程\nfor epoch in range(1, Config.N_EPOCH + 1):\n    # 训练阶段\n    training_loop(\n        train_dataloader,\n        ef_model,\n        loss_fn,\n        optimizer,\n        epoch,\n        train_monitor,\n        is_train=True\n    )\n\n    # 验证阶段\n    training_loop(\n        val_dataloader,\n        ef_model,\n        loss_fn,\n        optimizer,\n        epoch,\n        val_monitor,\n        is_train=False\n    )\n\n\nsave_path = Config.OUTPUT_PATH+\"./model.pth\"\ntorch.save(efficientnet.state_dict(), save_path)#保存模型\n\n\nend = time.time()\nprint()\nprint('time cost',end-start,'s')\nprint('time cost',((end-start)/60.0),'min')\nprint('time cost',((end-start)/3600.0),'Hours')\n\n\ndef plot_result(\n        train_losses,\n        test_losses,\n        train_accuracies,\n        test_accuracies,\n        train_scores,\n        test_scores\n) -> None:\n    #绘制训练过程中各性能指标图\n    epochs = range(1, len(train_losses) + 1)\n    fig, ax = plt.subplots(nrows=1, ncols=3, figsize=(22, 5))\n\n    # plot loss values\n    ax[0].plot(epochs, train_losses, label='Training loss', marker='o')\n    ax[0].plot(epochs, test_losses, label='Validation loss', marker='o')\n    ax[0].legend(frameon=False, fontsize=14)\n\n    ax[0].get_xaxis().set_major_locator(MaxNLocator(integer=True))\n    ax[0].set_title('Loss', fontsize=18)\n    ax[0].set_xlabel('Epoch', fontsize=14)\n    ax[0].set_ylabel('Loss', fontsize=14)\n\n    # plot accuracies\n    ax[1].plot(epochs, train_accuracies, label='Training Accuracy', marker='o')\n    ax[1].plot(epochs, test_accuracies, label='Validation accuracy', marker='o')\n    ax[1].legend(frameon=False, fontsize=14)\n\n    ax[1].get_xaxis().set_major_locator(MaxNLocator(integer=True))\n    ax[1].set_title('Accuracy', fontsize=18)\n    ax[1].set_xlabel('Epoch', fontsize=14)\n    ax[1].set_ylabel('Accuracy', fontsize=14)\n\n    ax[2].plot(epochs, train_scores, label='Training F1-Score', marker='o')\n    ax[2].plot(epochs, test_scores, label='Validation F1-Score', marker='o')\n    ax[2].legend(frameon=False, fontsize=14)\n\n    ax[2].get_xaxis().set_major_locator(MaxNLocator(integer=True))\n    ax[2].set_title('F1-Score', fontsize=18)\n    ax[2].set_xlabel('Epoch', fontsize=14)\n    ax[2].set_ylabel('F1-Score', fontsize=14)\n\n    plt.show()\n\n#开始绘制\nplot_result(\n    train_monitor.losses,\n    test_monitor.losses,\n    train_monitor.accuracies,\n    test_monitor.accuracies,\n    train_monitor.scores,\n    test_monitor.scores\n)\n\n\n","metadata":{"execution":{"iopub.status.busy":"2021-07-12T15:23:31.376316Z","iopub.execute_input":"2021-07-12T15:23:31.376599Z"},"trusted":true},"execution_count":null,"outputs":[]}]}