{"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 os\nfrom PIL import Image\nimport numpy as np\nfrom collections import defaultdict\nimport pandas as pd\nimport matplotlib.pyplot as plt","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def print_model_size(model):\n    \"\"\" Print the size of the model.\n    \n    Args:\n        model: model whose size needs to be determined\n\n    \"\"\"\n    torch.save(model.state_dict(), \"temp.p\")\n    print('Size of the model(MB):', os.path.getsize(\"temp.p\")/1e6)\n    os.remove('temp.p')\ndef numToLong(input):\n    return {\"0\": \"Cassava Bacterial Blight (CBB)\", \n            \"1\": \"Cassava Brown Streak Disease (CBSD)\", \n            \"2\": \"Cassava Green Mottle (CGM)\", \n            \"3\": \"Cassava Mosaic Disease (CMD)\", \n            \"4\": \"Healthy\"}[str(input)]\nnp.random.seed(42)\ndf = pd.read_csv('../input/cassava-leaf-disease-classification/train.csv')\ndf['long_labels'] = df.label.map(lambda x: numToLong(x))\ndf.shape","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"remove_amounts = [x-2577//4 if x!=2577 else 0 for x in list(df.long_labels.value_counts())]\nrm_dict = {0:remove_amounts[4],1:remove_amounts[2],2:remove_amounts[3],3:remove_amounts[0]}\n\ndf_pruned = df.copy() \n# for i in [0,1,2,3]:\n#     frac = rm_dict[i]/df_pruned[df_pruned['label']==i].count()[0]\n#     print(\"removing \", df_pruned[df_pruned['label']==i].count()[0], numToLong(i))\n#     df_pruned = df_pruned.drop(df_pruned[df_pruned['label'] == i].sample(frac=frac).index)\n# df_pruned.long_labels.value_counts().plot.bar()\ndef is_healthy(number):\n    if number==4:\n        return 1 #healthy\n    else:\n        return 0 #is_sick\ndf_pruned['is_healthy'] = df_pruned['label'].apply(lambda x: is_healthy(x))\ndf_pruned['is_healthy']\ndf=df_pruned[['image_id','is_healthy']]\ndf2=df_pruned[['image_id','long_labels']]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom matplotlib import style\nimport seaborn as sns\n%matplotlib inline  \nfrom sklearn.model_selection import StratifiedKFold\nfrom joblib import load, dump\nfrom sklearn.metrics import cohen_kappa_score\nfrom sklearn.metrics import confusion_matrix\nfrom fastai import *\nimport fastai\nfrom fastai.vision import *\nfrom fastai.vision.data import ImageDataLoaders\nfrom fastai.vision.augment import aug_transforms\nfrom fastai.callback.tensorboard import TensorBoardCallback\nfrom fastai.vision.all import Resize, RandomSubsetSplitter, aug_transforms, cnn_learner\nfrom torchvision import models as md\n# from efficientnet_pytorch import EfficientNet\nfrom pathlib import Path\nfrom torchvision.utils import make_grid\nimport os\nSZ = 224","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import torch\nfrom torchvision.models.resnet import Bottleneck, BasicBlock, ResNet, model_urls\nimport torch.nn as nn\nfrom torchvision.models.utils import load_state_dict_from_url\nfrom torch.quantization import QuantStub, DeQuantStub, fuse_modules\nfrom torch._jit_internal import Optional\n\nimport torch\nfrom torch import nn\n\n\ndef _replace_relu(module):\n    reassign = {}\n    for name, mod in module.named_children():\n        _replace_relu(mod)\n        # Checking for explicit type instead of instance\n        # as we only want to replace modules of the exact type\n        # not inherited classes\n        if type(mod) == nn.ReLU or type(mod) == nn.ReLU6:\n            reassign[name] = nn.ReLU(inplace=False)\n\n    for key, value in reassign.items():\n        module._modules[key] = value\n\n\ndef quantize_model(model, backend):\n    _dummy_input_data = torch.rand(1, 3, 299, 299)\n    if backend not in torch.backends.quantized.supported_engines:\n        raise RuntimeError(\"Quantized backend not supported \")\n    torch.backends.quantized.engine = backend\n    model.eval()\n    # Make sure that weight qconfig matches that of the serialized models\n    if backend == 'fbgemm':\n        model.qconfig = torch.quantization.QConfig(\n            activation=torch.quantization.default_observer,\n            weight=torch.quantization.default_per_channel_weight_observer)\n    elif backend == 'qnnpack':\n        model.qconfig = torch.quantization.QConfig(\n            activation=torch.quantization.default_observer,\n            weight=torch.quantization.default_weight_observer)\n\n    model.fuse_model()\n    torch.quantization.prepare(model, inplace=True)\n    model(_dummy_input_data)\n    torch.quantization.convert(model, inplace=True)\n\n    return\n\n__all__ = ['QuantizableResNet', 'resnet18', 'resnet50',\n           'resnext101_32x8d']\n\n\nquant_model_urls = {\n    'resnet18_fbgemm':\n        'https://download.pytorch.org/models/quantized/resnet18_fbgemm_16fa66dd.pth',\n    'resnet50_fbgemm':\n        'https://download.pytorch.org/models/quantized/resnet50_fbgemm_bf931d71.pth',\n    'resnext101_32x8d_fbgemm':\n        'https://download.pytorch.org/models/quantized/resnext101_32x8_fbgemm_09835ccf.pth',\n}\n\n\nclass QuantizableBasicBlock(BasicBlock):\n    def __init__(self, *args, **kwargs):\n        super(QuantizableBasicBlock, self).__init__(*args, **kwargs)\n        self.add_relu = torch.nn.quantized.FloatFunctional()\n\n    def forward(self, x):\n        identity = 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        if self.downsample is not None:\n            identity = self.downsample(x)\n\n        out = self.add_relu.add_relu(out, identity)\n\n        return out\n\n    def fuse_model(self):\n        torch.quantization.fuse_modules(self, [['conv1', 'bn1', 'relu'],\n                                               ['conv2', 'bn2']], inplace=True)\n        if self.downsample:\n            torch.quantization.fuse_modules(self.downsample, ['0', '1'], inplace=True)\n\n\nclass QuantizableBottleneck(Bottleneck):\n    def __init__(self, *args, **kwargs):\n        super(QuantizableBottleneck, self).__init__(*args, **kwargs)\n        self.skip_add_relu = nn.quantized.FloatFunctional()\n        self.relu1 = nn.ReLU(inplace=False)\n        self.relu2 = nn.ReLU(inplace=False)\n\n    def forward(self, x):\n        identity = x\n        out = self.conv1(x)\n        out = self.bn1(out)\n        out = self.relu1(out)\n        out = self.conv2(out)\n        out = self.bn2(out)\n        out = self.relu2(out)\n\n        out = self.conv3(out)\n        out = self.bn3(out)\n\n        if self.downsample is not None:\n            identity = self.downsample(x)\n        out = self.skip_add_relu.add_relu(out, identity)\n\n        return out\n\n    def fuse_model(self):\n        fuse_modules(self, [['conv1', 'bn1', 'relu1'],\n                            ['conv2', 'bn2', 'relu2'],\n                            ['conv3', 'bn3']], inplace=True)\n        if self.downsample:\n            torch.quantization.fuse_modules(self.downsample, ['0', '1'], inplace=True)\n\n\nclass QuantizableResNet(ResNet):\n\n    def __init__(self, *args, **kwargs):\n        super(QuantizableResNet, self).__init__(*args, **kwargs)\n\n        self.quant = torch.quantization.QuantStub()\n        self.dequant = torch.quantization.DeQuantStub()\n\n    def forward(self, x):\n        x = self.quant(x)\n        # Ensure scriptability\n        # super(QuantizableResNet,self).forward(x)\n        # is not scriptable\n        x = self._forward_impl(x)\n        x = self.dequant(x)\n        return x\n\n    def fuse_model(self):\n        r\"\"\"Fuse conv/bn/relu modules in resnet models\n        Fuse conv+bn+relu/ Conv+relu/conv+Bn modules to prepare for quantization.\n        Model is modified in place.  Note that this operation does not change numerics\n        and the model after modification is in floating point\n        \"\"\"\n\n        fuse_modules(self, ['conv1', 'bn1', 'relu'], inplace=True)\n        for m in self.modules():\n            if type(m) == QuantizableBottleneck or type(m) == QuantizableBasicBlock:\n                m.fuse_model()\n\n\ndef _resnet(arch, block, layers, pretrained, progress, quantize, **kwargs):\n    model = QuantizableResNet(block, layers, **kwargs)\n    _replace_relu(model)\n    if quantize:\n        # TODO use pretrained as a string to specify the backend\n        backend = 'fbgemm'\n        quantize_model(model, backend)\n    else:\n        assert pretrained in [True, False]\n\n    if pretrained:\n        if quantize:\n            model_url = quant_model_urls[arch + '_' + backend]\n        else:\n            model_url = model_urls[arch]\n\n        state_dict = load_state_dict_from_url(model_url,\n                                              progress=progress)\n\n        model.load_state_dict(state_dict)\n    return model\n\n\ndef resnet18(pretrained=False, progress=True, quantize=False, **kwargs):\n    r\"\"\"ResNet-18 model from\n    `\"Deep Residual Learning for Image Recognition\" <https://arxiv.org/pdf/1512.03385.pdf>`_\n    Args:\n        pretrained (bool): If True, returns a model pre-trained on ImageNet\n        progress (bool): If True, displays a progress bar of the download to stderr\n        quantize (bool): If True, return a quantized version of the model\n    \"\"\"\n    return _resnet('resnet18', QuantizableBasicBlock, [2, 2, 2, 2], pretrained, progress,\n                   quantize, **kwargs)\n\n\ndef resnet50(pretrained=False, progress=True, quantize=False, **kwargs):\n    r\"\"\"ResNet-50 model from\n    `\"Deep Residual Learning for Image Recognition\" <https://arxiv.org/pdf/1512.03385.pdf>`_\n    Args:\n        pretrained (bool): If True, returns a model pre-trained on ImageNet\n        progress (bool): If True, displays a progress bar of the download to stderr\n        quantize (bool): If True, return a quantized version of the model\n    \"\"\"\n    return _resnet('resnet50', QuantizableBottleneck, [3, 4, 6, 3], pretrained, progress,\n                   quantize, **kwargs)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from fastai.vision.all import *\nBS = 16\n#         .label_from_df(cols='is_healthy', label_cls=FloatList)\n#         .transform(tfms,size=SZ) #Data augmentation\n#         .databunch(bs=BS)\n#         .normalize(imagenet_stats)\ndls = ImageDataLoaders.from_df(df=df2, path=Path('../input/cassava-leaf-disease-classification/train_images/'), cols='image', valid_pct=0.3, label_col=1, batch_tfms=Normalize.from_stats(*imagenet_stats), image_tfms = tfms, bs=BS, val_bs=16)\n# dsets = db.datasets('../input/cassava-leaf-disease-classification/train_images/')\n# dls = db.dataloaders(df)\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from fastai.vision.all import *\n#         .label_from_df(cols='is_healthy', label_cls=FloatList)\n#         .transform(tfms,size=SZ) #Data augmentation\n#         .databunch(bs=BS)\n#         .normalize(imagenet_stats)\ndb = DataBlock(\n    blocks=(ImageBlock, CategoryBlock), \n    \n    get_x=lambda o:f'../input/cassava-leaf-disease-classification/train_images/'+o.image_id,\n    get_y=lambda o:o.is_healthy)\n# dsets = db.datasets('../input/cassava-leaf-disease-classification/train_images/')\nbatch = db.dataloaders(df)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"dls.show_batch()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from fastai.losses import CrossEntropyLossFlat\n# md_ef =  EfficientNet.from_pretrained('efficientnet-b4', num_classes=1)\nlearn = Learner(dls, resnet18(pretrained=True), metrics = [accuracy, error_rate])","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"lr = 1e-3\nlearn.fit_one_cycle(3, lr)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# torch.save(learn.model.state_dict(),'/kaggle/working/verynice18.pth')\ntorch.save(learn.model.state_dict(),'/kaggle/working/verynice18Legal2.pth')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def print_model_size(model):\n    \"\"\" Print the size of the model.\n    \n    Args:\n        model: model whose size needs to be determined\n\n    \"\"\"\n    torch.save(model.state_dict(), \"temp.p\")\n    print('Size of the model(MB):', os.path.getsize(\"temp.p\")/1e6)\n    os.remove('temp.p')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import torchvision\n# torch.quantization.fuse_modules(learn.model, [['conv1', 'bn1', 'relu'],\n#                                                ['conv2', 'bn2']], inplace=True)\nmodel = resnet18(pretrained=True)\nmodel.load_state_dict(torch.load('/kaggle/working/verynice18Legal2.pth'))\nprint_model_size(model)\nbackend = \"qnnpack\"\n\nmodel.qconfig = torch.quantization.get_default_qconfig(backend)\ntorch.backends.quantized.engine = backend\nmodel_static_quantized = torch.quantization.prepare(model, inplace=False)\nmodel_static_quantized = torch.quantization.convert(model_static_quantized, inplace=False)\nprint_model_size(model_static_quantized)\n# model_static_quantized.eval()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Everything Past this point is experimental code","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Everything Past this point is experimental code","metadata":{}},{"cell_type":"code","source":"learn.fine_tune(30)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"interp = ClassificationInterpretation.from_learner(learn)\ninterp.plot_confusion_matrix()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"learn2 = cnn_learner(dls, resnet18, metrics = [accuracy, error_rate], model_dir=\"models\").to_fp16()\nlearn2.fine_tune(1)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"?? fastai.vision.models.resnet18","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import dill\nmodeltosave=learn2.model\nmodeltosave.cpu()\nmodel = torch.load('/kaggle/working/resnet50.pth')\ntorch.save(model.state_dict(),'/kaggle/working/resnet50.pkl')\ntorch.save(modeltosave, '/kaggle/working/resnet18.pkl', pickle_module=dill)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def print_model_size(model):\n    \"\"\" Print the size of the model.\n    \n    Args:\n        model: model whose size needs to be determined\n\n    \"\"\"\n    torch.save(model.state_dict(), \"temp.p\")\n    print('Size of the model(MB):', os.path.getsize(\"temp.p\")/1e6)\n    os.remove('temp.p')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = torch.load('/kaggle/working/resnet18.pkl')\nprint_size_of_model(model)\nmodel.to('cpu')\nmodel.eval()\n    # Testing with qauntization if quantize=True\nmodules_to_fuse = [['conv1', 'bn1'],\n           ['layer1.0.conv1', 'layer1.0.bn1'],\n           ['layer1.0.conv2', 'layer1.0.bn2'],\n           ['layer1.1.conv1', 'layer1.1.bn1'],\n           ['layer1.1.conv2', 'layer1.1.bn2'],\n           ['layer2.0.conv1', 'layer2.0.bn1'],\n           ['layer2.0.conv2', 'layer2.0.bn2'],\n           ['layer2.0.downsample.0', 'layer2.0.downsample.1'],\n           ['layer2.1.conv1', 'layer2.1.bn1'],\n           ['layer2.1.conv2', 'layer2.1.bn2'],\n           ['layer3.0.conv1', 'layer3.0.bn1'],\n           ['layer3.0.conv2', 'layer3.0.bn2'],\n           ['layer3.0.downsample.0', 'layer3.0.downsample.1'],\n           ['layer3.1.conv1', 'layer3.1.bn1'],\n           ['layer3.1.conv2', 'layer3.1.bn2'],\n           ['layer4.0.conv1', 'layer4.0.bn1'],\n           ['layer4.0.conv2', 'layer4.0.bn2'],\n           ['layer4.0.downsample.0', 'layer4.0.downsample.1'],\n           ['layer4.1.conv1', 'layer4.1.bn1'],\n           ['layer4.1.conv2', 'layer4.1.bn2']]\nmodel = torch.quantization.fuse_modules(model, modules_to_fuse)\nmodel.qconfig = torch.quantization.get_default_qconfig('fbgemm')\n\ntorch.quantization.prepare(model, inplace=True)\nmodel.eval()\nwith torch.no_grad():\n    for data, target in train_loader:\n        model(data)\ntorch.quantization.convert(model, inplace=True)\n\nprint_size_of_model(model)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model.eval()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model.state_dict","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"learn2.export(\"/kaggle/working/resnet18.pt\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\nos.getcwd()\nos.listdir()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!pip install nvidia-pyindex\n!nvidia-index install pytorch-quantization","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print_model_size(resnet18(pretrained=True))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = torchvision.models.resnet50(pretrained=True)\nmodel_un = torchvision.models.resnet50(pretrained=False).load_state_dict(torch.load('/kaggle/working/resnet50.pkl'))\ntorch.quantization.fuse_modules(model_un, [['conv1', 'bn1', 'relu'],\n                                               ['conv2', 'bn2']], inplace=True)\nprint_model_size(model)\nbackend = \"qnnpack\"\nmodel.qconfig = torch.quantization.get_default_qconfig(backend)\ntorch.backends.quantized.engine = backend\nmodel_static_quantized = torch.quantization.prepare(model, inplace=False)\nmodel_static_quantized = torch.quantization.convert(model_static_quantized, inplace=False)\nprint_model_size(model_static_quantized)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(model.state_dict)\nprint(model_un.state_dict)\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import torchvision\nimport torch\nfrom torch import nn\nimport torch.nn.utils.prune as prune\nimport torch.nn.functional as F\nmodel = torchvision.models.resnet50(pretrained=True)\nmodel_un = torch.load('/kaggle/working/resnet50.pth')\n\nmodel.state_dict = model_un.state_dict\n# q_model = copy.deepcopy(model).to('cpu')\n\nprint_model_size(model_un)\nbackend = \"qnnpack\"\nmodel.qconfig = torch.quantization.get_default_qconfig(backend)\ntorch.backends.quantized.engine = backend\nmodel_static_quantized = torch.quantization.prepare(model_un, inplace=False)\nmodel_static_quantized = torch.quantization.convert(model_static_quantized, inplace=False)\nprint_model_size(model_static_quantized)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from torch.quantization import QuantStub, DeQuantStub\n\ndef _make_divisible(v, divisor, min_value=None):\n    \"\"\"\n    This function is taken from the original tf repo.\n    It ensures that all layers have a channel number that is divisible by 8\n    It can be seen here:\n    https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet/mobilenet.py\n    :param v:\n    :param divisor:\n    :param min_value:\n    :return:\n    \"\"\"\n    if min_value is None:\n        min_value = divisor\n    new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)\n    # Make sure that round down does not go down by more than 10%.\n    if new_v < 0.9 * v:\n        new_v += divisor\n    return new_v\n\n\nclass ConvBNReLU(nn.Sequential):\n    def __init__(self, in_planes, out_planes, kernel_size=3, stride=1, groups=1):\n        padding = (kernel_size - 1) // 2\n        super(ConvBNReLU, self).__init__(\n            nn.Conv2d(in_planes, out_planes, kernel_size, stride, padding, groups=groups, bias=False),\n            nn.BatchNorm2d(out_planes, momentum=0.1),\n            # Replace with ReLU\n            nn.ReLU(inplace=False)\n        )\n\n\nclass InvertedResidual(nn.Module):\n    def __init__(self, inp, oup, stride, expand_ratio):\n        super(InvertedResidual, self).__init__()\n        self.stride = stride\n        assert stride in [1, 2]\n\n        hidden_dim = int(round(inp * expand_ratio))\n        self.use_res_connect = self.stride == 1 and inp == oup\n\n        layers = []\n        if expand_ratio != 1:\n            # pw\n            layers.append(ConvBNReLU(inp, hidden_dim, kernel_size=1))\n        layers.extend([\n            # dw\n            ConvBNReLU(hidden_dim, hidden_dim, stride=stride, groups=hidden_dim),\n            # pw-linear\n            nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),\n            nn.BatchNorm2d(oup, momentum=0.1),\n        ])\n        self.conv = nn.Sequential(*layers)\n        # Replace torch.add with floatfunctional\n        self.skip_add = nn.quantized.FloatFunctional()\n\n    def forward(self, x):\n        if self.use_res_connect:\n            return self.skip_add.add(x, self.conv(x))\n        else:\n            return self.conv(x)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"interp = ClassificationInterpretation.from_learner(learn2)\ninterp.plot_confusion_matrix()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"learn2.recorder.plot_loss()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"dls = ImageDataLoaders.from_df(df=df2, path=Path('../input/cassava-leaf-disease-classification/train_images/'), cols='image', valid_pct=0.3, label_col=1, batch_tfms=Normalize.from_stats(*imagenet_stats), bs=BS, val_bs=16)\n\nlearn2 = cnn_learner(dls, resnet50, metrics = [accuracy, error_rate], model_dir=\"models\").to_fp16()\nlearn2.fine_tune(5)\nlearn2.fine_tune(5)\nlearn2.fine_tune(5)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"interp = ClassificationInterpretation.from_learner(learn2)\ninterp.plot_confusion_matrix()","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}