{"cells":[{"metadata":{},"cell_type":"markdown","source":"reference:https://www.kaggle.com/xinruizhuang/skin-lesion-classification-acc-90-pytorch"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed%matplotlib inline\n# python libraties\nimport os, cv2,itertools\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nfrom tqdm import tqdm\nfrom glob import glob\nfrom PIL import Image\n\n# pytorch libraries\nimport torch\nfrom torch import optim,nn\nfrom torch.autograd import Variable\nfrom torch.utils.data import DataLoader,Dataset\nfrom torchvision import models,transforms\n\n# sklearn libraries\nfrom sklearn.metrics import confusion_matrix\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import classification_report\n\n# to make the results are reproducible\nnp.random.seed(10)\ntorch.manual_seed(10)\ntorch.cuda.manual_seed(10)\n\nprint(os.listdir(\"../input\"))","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true},"cell_type":"code","source":"from glob import glob\ndata_dir = '../input'\nall_image_path = glob(os.path.join(data_dir, '*', '*.png'))\nimageid_path_dict = {os.path.splitext(os.path.basename(x))[0]: x for x in all_image_path}\nlesion_type_dict = {\n    '0': 'No DR',\n    '1': 'Mild',\n    '2': ' Moderate ',\n    '3': 'Severe',\n    '4': 'Proliferative DR',\n  }","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"all_image_path[:5]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"This function is used to compute the mean and standard deviation on the whole dataset, will use for inputs normalization"},{"metadata":{"trusted":true},"cell_type":"code","source":"from tqdm import tqdm\nimport cv2\ndef compute_img_mean_std(image_paths):\n    \"\"\"\n        computing the mean and std of three channel on the whole dataset,\n        first we should normalize the image from 0-255 to 0-1\n    \"\"\"\n\n    img_h, img_w = 224, 224\n    imgs = []\n    means, stdevs = [], []\n\n    for i in tqdm(range(len(image_paths))):\n        img = cv2.imread(image_paths[i])\n        img = cv2.resize(img, (img_h, img_w))\n        imgs.append(img)\n\n    imgs = np.stack(imgs, axis=3)\n    print(imgs.shape)\n\n    imgs = imgs.astype(np.float32) / 255.\n\n    for i in range(3):\n        pixels = imgs[:, :, i, :].ravel()  # resize to one row\n        means.append(np.mean(pixels))\n        stdevs.append(np.std(pixels))\n\n    means.reverse()  # BGR --> RGB\n    stdevs.reverse()\n\n    print(\"normMean = {}\".format(means))\n    print(\"normStd = {}\".format(stdevs))\n    return means,stdevs","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Return the mean and std of RGB channels\nnorm_mean,norm_std = compute_img_mean_std(all_image_path)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# build you data"},{"metadata":{"trusted":true},"cell_type":"code","source":"import pandas as pd\nimport os\ndata_dir = '../input/'\ntrain_dir = data_dir + '/train_images/'\ntest_dir = data_dir + '/test_images/'\nretina_df = pd.read_csv(os.path.join(data_dir, 'train.csv'))\nretina_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"retina_df['path'] = retina_df['id_code'].map(lambda x: os.path.join(train_dir,'{}.png'.format(x)))\nretina_df['exists'] = retina_df['path'].map(os.path.exists)\nprint(retina_df['exists'].sum(), 'images found of', retina_df.shape[0], 'total')\nretina_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"retina_df['diagnosis'].value_counts()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"From From the above statistics of each category, we can see that there is a serious class imbalance in the training data. To solve this problem, I think we can start from two aspects, one is equalization sampling, and the other is a loss function that can be used to mitigate category imbalance during training, such as focal loss."},{"metadata":{"trusted":true},"cell_type":"code","source":"# Copy fewer class to balance the number of 5 classes\n#df.loc['a', :]表示选取索引为‘a’的行\n\ndata_aug_rate = [0,4,0,5,4]\nfor i in range(5):\n    if data_aug_rate[i]:\n        retina_df=retina_df.append([retina_df.loc[retina_df['diagnosis'] == i,:]]*(data_aug_rate[i]-1), ignore_index=True)\nretina_df['diagnosis'].value_counts()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df_train, df_val = train_test_split(\n    retina_df, \n    test_size=0.15, \n    random_state=2019\n)\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df_train.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df_val.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"df_train = df_train.reset_index()\ndf_val = df_val.reset_index()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Step 2. Model building"},{"metadata":{"trusted":true},"cell_type":"code","source":"# feature_extract is a boolean that defines if we are finetuning or feature extracting. \n# If feature_extract = False, the model is finetuned and all model parameters are updated. \n# If feature_extract = True, only the last layer parameters are updated, the others remain fixed.\ndef set_parameter_requires_grad(model, feature_extracting):\n    if feature_extracting:\n        for param in model.parameters():\n            param.requires_grad = False","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def initialize_model(model_name, num_classes, feature_extract, use_pretrained=True):\n    # Initialize these variables which will be set in this if statement. Each of these\n    #   variables is model specific.\n    model_ft = None\n    input_size = 0\n\n    if model_name == \"resnet\":\n        \"\"\" Resnet18, resnet34, resnet50, resnet101\n        \"\"\"\n        model_ft = models.resnet50(pretrained=use_pretrained)\n        set_parameter_requires_grad(model_ft, feature_extract)\n        num_ftrs = model_ft.fc.in_features\n        model_ft.fc = nn.Linear(num_ftrs, num_classes)\n        input_size = 224\n\n\n    elif model_name == \"vgg\":\n        \"\"\" VGG11_bn\n        \"\"\"\n        model_ft = models.vgg11_bn(pretrained=use_pretrained)\n        set_parameter_requires_grad(model_ft, feature_extract)\n        num_ftrs = model_ft.classifier[6].in_features\n        model_ft.classifier[6] = nn.Linear(num_ftrs,num_classes)\n        input_size = 224\n\n\n    elif model_name == \"densenet\":\n        \"\"\" Densenet121\n        \"\"\"\n        model_ft = models.densenet121(pretrained=use_pretrained)\n        set_parameter_requires_grad(model_ft, feature_extract)\n        num_ftrs = model_ft.classifier.in_features\n        model_ft.classifier = nn.Linear(num_ftrs, num_classes)\n        input_size = 224\n\n    elif model_name == \"inception\":\n        \"\"\" Inception v3\n        Be careful, expects (299,299) sized images and has auxiliary output\n        \"\"\"\n        model_ft = models.inception_v3(pretrained=use_pretrained)\n        set_parameter_requires_grad(model_ft, feature_extract)\n        # Handle the auxilary net\n        num_ftrs = model_ft.AuxLogits.fc.in_features\n        model_ft.AuxLogits.fc = nn.Linear(num_ftrs, num_classes)\n        # Handle the primary net\n        num_ftrs = model_ft.fc.in_features\n        model_ft.fc = nn.Linear(num_ftrs,num_classes)\n        input_size = 299\n\n    else:\n        print(\"Invalid model name, exiting...\")\n        exit()\n    return model_ft, input_size","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"You can change your backbone network, here are 4 different networks, each network also has sevaral versions. Considering the limited training data, we used the ImageNet pre-training model for fine-tuning. This can speed up the convergence of the model and improve the accuracy.\n\nThere is one thing you need to pay attention to, the input size of Inception is different from the others (299x299), you need to change the setting of compute_img_mean_std() function"},{"metadata":{"trusted":true},"cell_type":"code","source":"# pytorch libraries\nimport torch\nfrom torch import optim,nn\nfrom torch.autograd import Variable\nfrom torch.utils.data import DataLoader,Dataset\nfrom torchvision import models,transforms\n\n# resnet,vgg,densenet,inception\nmodel_name = 'densenet'\nnum_classes = 5\nfeature_extract = False\n# Initialize the model for this run\nmodel_ft, input_size = initialize_model(model_name, num_classes, feature_extract, use_pretrained=True)\n# Define the device:\ndevice = torch.device('cuda:0')\n# Put the model on the device:\nmodel = model_ft.to(device)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#后面运行就不需要再求normMean\n#normMean = [0.4452997, 0.2424191, 0.077126786]\n#normStd = [0.25739512, 0.14617251, 0.08006624]\n# define the transformation of the train images.\ntrain_transform = transforms.Compose([transforms.Resize((input_size,input_size)),transforms.RandomHorizontalFlip(),\n                                      transforms.RandomVerticalFlip(),transforms.RandomRotation(20),\n                                      transforms.ColorJitter(brightness=0.1, contrast=0.1, hue=0.1),\n                                        transforms.ToTensor(), transforms.Normalize(norm_mean, norm_std)])\n# define the transformation of the val images.\nval_transform = transforms.Compose([transforms.Resize((input_size,input_size)), transforms.ToTensor(),\n                                    transforms.Normalize(norm_mean, norm_std)])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Define a pytorch dataloader for this dataset\nclass APTOS(Dataset):\n    def __init__(self, df, transform=None):\n        self.df = df\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, index):\n        # Load data and get label\n        X = Image.open(self.df['path'][index])\n        y = torch.tensor(int(self.df['diagnosis'][index]))\n\n        if self.transform:\n            X = self.transform(X)\n\n        return X, y","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Define the training set using the table train_df and using our defined transitions (train_transform)\ntraining_set = APTOS(df_train, transform=train_transform)\ntrain_loader = DataLoader(training_set, batch_size=32, shuffle=True, num_workers=4)\n# Same for the validation set:\nvalidation_set = APTOS(df_val, transform=train_transform)\nval_loader = DataLoader(validation_set, batch_size=32, shuffle=False, num_workers=4)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# we use Adam optimizer, use cross entropy loss as our loss function\noptimizer = optim.Adam(model.parameters(), lr=1e-3)\ncriterion = nn.CrossEntropyLoss().to(device)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Step 3. Model training"},{"metadata":{"trusted":true},"cell_type":"code","source":"# this function is used during training process, to calculation the loss and accuracy\nclass AverageMeter(object):\n    def __init__(self):\n        self.reset()\n\n    def reset(self):\n        self.val = 0\n        self.avg = 0\n        self.sum = 0\n        self.count = 0\n\n    def update(self, val, n=1):\n        self.val = val\n        self.sum += val * n\n        self.count += n\n        self.avg = self.sum / self.count","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"total_loss_train, total_acc_train = [],[]\ndef train(train_loader, model, criterion, optimizer, epoch):\n    model.train()\n    train_loss = AverageMeter()\n    train_acc = AverageMeter()\n    curr_iter = (epoch - 1) * len(train_loader)\n    for i, data in enumerate(train_loader):\n        images, labels = data\n        N = images.size(0)\n        # print('image shape:',images.size(0), 'label shape',labels.size(0))\n        images = Variable(images).to(device)\n        labels = Variable(labels).to(device)\n\n        optimizer.zero_grad()\n        outputs = model(images)\n\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n        prediction = outputs.max(1, keepdim=True)[1]\n        train_acc.update(prediction.eq(labels.view_as(prediction)).sum().item()/N)\n        train_loss.update(loss.item())\n        curr_iter += 1\n        if (i + 1) % 100 == 0:\n            print('[epoch %d], [iter %d / %d], [train loss %.5f], [train acc %.5f]' % (\n                epoch, i + 1, len(train_loader), train_loss.avg, train_acc.avg))\n            total_loss_train.append(train_loss.avg)\n            total_acc_train.append(train_acc.avg)\n    return train_loss.avg, train_acc.avg","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def validate(val_loader, model, criterion, optimizer, epoch):\n    model.eval()\n    val_loss = AverageMeter()\n    val_acc = AverageMeter()\n    with torch.no_grad():\n        for i, data in enumerate(val_loader):\n            images, labels = data\n            N = images.size(0)\n            images = Variable(images).to(device)\n            labels = Variable(labels).to(device)\n\n            outputs = model(images)\n            prediction = outputs.max(1, keepdim=True)[1]\n\n            val_acc.update(prediction.eq(labels.view_as(prediction)).sum().item()/N)\n\n            val_loss.update(criterion(outputs, labels).item())\n\n    print('------------------------------------------------------------')\n    print('[epoch %d], [val loss %.5f], [val acc %.5f]' % (epoch, val_loss.avg, val_acc.avg))\n    print('------------------------------------------------------------')\n    return val_loss.avg, val_acc.avg","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"epoch_num = 10\nbest_val_acc = 0\ntotal_loss_val, total_acc_val = [],[]\nfor epoch in range(1, epoch_num+1):\n    loss_train, acc_train = train(train_loader, model, criterion, optimizer, epoch)\n    loss_val, acc_val = validate(val_loader, model, criterion, optimizer, epoch)\n    total_loss_val.append(loss_val)\n    total_acc_val.append(acc_val)\n    if acc_val > best_val_acc:\n        best_val_acc = acc_val\n        print('*****************************************************')\n        print('best record: [epoch %d], [val loss %.5f], [val acc %.5f]' % (epoch, loss_val, acc_val))\n        print('*****************************************************')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Step 4. Model evaluation"},{"metadata":{"trusted":true},"cell_type":"code","source":"# 保存整个网络\ntorch.save(model, 'model.pkl')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"fig = plt.figure(num = 2)\nfig1 = fig.add_subplot(2,1,1)\nfig2 = fig.add_subplot(2,1,2)\nfig1.plot(total_loss_train, label = 'training loss')\nfig1.plot(total_acc_train, label = 'training accuracy')\nfig2.plot(total_loss_val, label = 'validation loss')\nfig2.plot(total_acc_val, label = 'validation accuracy')\nplt.legend()\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def plot_confusion_matrix(cm, classes,\n                          normalize=False,\n                          title='Confusion matrix',\n                          cmap=plt.cm.Blues):\n    \"\"\"\n    This function prints and plots the confusion matrix.\n    Normalization can be applied by setting `normalize=True`.\n    \"\"\"\n    plt.imshow(cm, interpolation='nearest', cmap=cmap)\n    plt.title(title)\n    plt.colorbar()\n    tick_marks = np.arange(len(classes))\n    plt.xticks(tick_marks, classes, rotation=45)\n    plt.yticks(tick_marks, classes)\n\n    if normalize:\n        cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]\n\n    thresh = cm.max() / 2.\n    for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):\n        plt.text(j, i, cm[i, j],\n                 horizontalalignment=\"center\",\n                 color=\"white\" if cm[i, j] > thresh else \"black\")\n\n    plt.tight_layout()\n    plt.ylabel('True label')\n    plt.xlabel('Predicted label')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model.eval()\ny_label = []\ny_predict = []\nwith torch.no_grad():\n    for i, data in enumerate(val_loader):\n        images, labels = data\n        N = images.size(0)\n        images = Variable(images).to(device)\n        outputs = model(images)\n        prediction = outputs.max(1, keepdim=True)[1]\n        y_label.extend(labels.cpu().numpy())\n        y_predict.extend(np.squeeze(prediction.cpu().numpy().T))\n\n# compute the confusion matrix\nconfusion_mtx = confusion_matrix(y_label, y_predict)\n# plot the confusion matrix\nplot_labels = [' No DR', 'Mild', 'Moderate', 'Severe', ' Proliferative DR']\nplot_confusion_matrix(confusion_mtx, plot_labels)","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.6.4","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat":4,"nbformat_minor":1}