{"cells":[{"metadata":{},"cell_type":"markdown","source":"# About this notebook"},{"metadata":{},"cell_type":"markdown","source":"- PyTorch Resnet18 starter code  \n- 12 epochs not to exceed 4 hours (GPU Notebook <= 4 hours run-time)  \n\nIf this notebook is helpful, feel free to upvote :)  "},{"metadata":{},"cell_type":"markdown","source":"# Library"},{"metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","trusted":true},"cell_type":"code","source":"import os\nimport numpy as np \nimport pandas as pd \nimport json","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"os.listdir('../input/imet-2020-fgvc7')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Data Loading"},{"metadata":{"trusted":true},"cell_type":"code","source":"train = pd.read_csv('../input/imet-2020-fgvc7/train.csv')\nlabels = pd.read_csv('../input/imet-2020-fgvc7/labels.csv')\nsubmission = pd.read_csv('../input/imet-2020-fgvc7/sample_submission.csv')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"labels.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"submission.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# About TARGET"},{"metadata":{"trusted":true},"cell_type":"code","source":"labels['attribute_name'].nunique()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"- 3474 targets in labels.csv"},{"metadata":{"trusted":true},"cell_type":"code","source":"from collections import Counter\n\ncls_counts = Counter(cls for classes in train['attribute_ids'].str.split() for cls in classes)\n\nprint(len(cls_counts))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"- 3471 targets in train.csv"},{"metadata":{"trusted":true},"cell_type":"code","source":"label_map = dict(labels[['attribute_id', 'attribute_name']].values.tolist())\nnot_in_train_labels = set(labels['attribute_id'].astype(str).values) - set(list(cls_counts))\nfor _id in not_in_train_labels:\n    label = label_map[int(_id)]\n    print(f'attribute_id: {_id}  attribute_name: {label}')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"- These labels are not in train"},{"metadata":{"trusted":true},"cell_type":"code","source":"# TOP 20 common attribute\nfor item in sorted(cls_counts.items(), key=lambda x: x[1], reverse=True)[:20]:\n    _id, count = item[0], item[1]\n    label = label_map[int(_id)]\n    print(f'attribute_name: {label}  count: {count}')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Number of labels for each instance\nimport matplotlib.pyplot as plt\n\ndf_label_len = train.attribute_ids.str.split(\" \").apply(len)\nplt.figure(figsize=(25, 4))\ndf_label_len.value_counts().plot.bar()\nplt.title(f\"Number of labels for each instance\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Library"},{"metadata":{"trusted":true},"cell_type":"code","source":"# ====================================================\n# Library\n# ====================================================\n\nimport sys\n\nimport gc\nimport os\nimport random\nimport time\nfrom contextlib import contextmanager\nfrom pathlib import Path\nfrom collections import defaultdict, Counter\n\nimport cv2\nfrom PIL import Image\nimport numpy as np\nimport pandas as pd\nimport scipy as sp\n\nimport sklearn.metrics\nfrom sklearn.metrics import accuracy_score\nfrom sklearn.model_selection import StratifiedKFold\n\nfrom functools import partial\nfrom tqdm import tqdm\n\nimport torch\nimport torch.nn as nn\nfrom torch.optim import Adam, SGD\nfrom torch.optim.lr_scheduler import CosineAnnealingLR, ReduceLROnPlateau\nfrom torch.utils.data import DataLoader, Dataset\nimport torchvision.models as models\n\nfrom albumentations import Compose, Normalize, Resize, RandomResizedCrop\nfrom albumentations.pytorch import ToTensorV2\n\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\ndevice","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Utils"},{"metadata":{"trusted":true},"cell_type":"code","source":"# ====================================================\n# Utils\n# ====================================================\n\n@contextmanager\ndef timer(name):\n    t0 = time.time()\n    LOGGER.info(f'[{name}] start')\n    yield\n    LOGGER.info(f'[{name}] done in {time.time() - t0:.0f} s.')\n\n    \ndef init_logger(log_file='train.log'):\n    from logging import getLogger, DEBUG, FileHandler,  Formatter,  StreamHandler\n    \n    log_format = '%(asctime)s %(levelname)s %(message)s'\n    \n    stream_handler = StreamHandler()\n    stream_handler.setLevel(DEBUG)\n    stream_handler.setFormatter(Formatter(log_format))\n    \n    file_handler = FileHandler(log_file)\n    file_handler.setFormatter(Formatter(log_format))\n    \n    logger = getLogger('Herbarium')\n    logger.setLevel(DEBUG)\n    logger.addHandler(stream_handler)\n    logger.addHandler(file_handler)\n    \n    return logger\n\nLOG_FILE = 'train.log'\nLOGGER = init_logger(LOG_FILE)\n\n\ndef seed_torch(seed=777):\n    random.seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.backends.cudnn.deterministic = True\n\nSEED = 777\nseed_torch(SEED)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Dataset"},{"metadata":{"trusted":true},"cell_type":"code","source":"N_CLASSES = 3474\n\n\nclass TrainDataset(Dataset):\n    def __init__(self, df, labels, transform=None):\n        self.df = df\n        self.labels = labels\n        self.transform = transform\n        \n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        file_name = self.df['id'].values[idx]\n        file_path = f'../input/imet-2020-fgvc7/train/{file_name}.png'\n        image = cv2.imread(file_path)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        \n        if self.transform:\n            augmented = self.transform(image=image)\n            image = augmented['image']\n            \n        label = self.labels.values[idx]\n        target = torch.zeros(N_CLASSES)\n        for cls in label.split():\n            target[int(cls)] = 1\n        \n        return image, target\n    \n\nclass TestDataset(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, idx):\n        file_name = self.df['id'].values[idx]\n        file_path = f'../input/imet-2020-fgvc7/test/{file_name}.png'\n        image = cv2.imread(file_path)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        \n        if self.transform:\n            augmented = self.transform(image=image)\n            image = augmented['image']\n        \n        return image","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Transforms"},{"metadata":{"trusted":true},"cell_type":"code","source":"HEIGHT = 128\nWIDTH = 128\n\n\ndef get_transforms(*, data):\n    \n    assert data in ('train', 'valid')\n    \n    if data == 'train':\n        return Compose([\n            #Resize(HEIGHT, WIDTH),\n            RandomResizedCrop(HEIGHT, WIDTH),\n            Normalize(\n                mean=[0.485, 0.456, 0.406],\n                std=[0.229, 0.224, 0.225],\n            ),\n            ToTensorV2(),\n        ])\n    \n    elif data == 'valid':\n        return Compose([\n            Resize(HEIGHT, WIDTH),\n            Normalize(\n                mean=[0.485, 0.456, 0.406],\n                std=[0.229, 0.224, 0.225],\n            ),\n            ToTensorV2(),\n        ])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# train valid split"},{"metadata":{"trusted":true},"cell_type":"code","source":"def make_folds(df, n_folds, seed):\n    cls_counts = Counter(cls for classes in df['attribute_ids'].str.split() for cls in classes)\n    fold_cls_counts = defaultdict(int)\n    folds = [-1] * len(df)\n    for item in df.sample(frac=1, random_state=seed).itertuples():\n        cls = min(item.attribute_ids.split(), key=lambda cls: cls_counts[cls])\n        fold_counts = [(f, fold_cls_counts[f, cls]) for f in range(n_folds)]\n        min_count = min([count for _, count in fold_counts])\n        random.seed(item.Index)\n        fold = random.choice([f for f, count in fold_counts if count == min_count])\n        folds[item.Index] = fold\n        for cls in item.attribute_ids.split():\n            fold_cls_counts[fold, cls] += 1\n    df['fold'] = folds\n    return df","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"DEBUG = False\nN_FOLDS = 5\nFOLD = 0\n\nif DEBUG:\n    folds = train.sample(n=10000, random_state=SEED).reset_index(drop=True).copy()\n    folds = make_folds(folds, N_FOLDS, SEED)\nelse:\n    folds = train.copy()\n    folds = make_folds(folds, N_FOLDS, SEED)\n    \ntrn_idx = folds[folds['fold'] != FOLD].index\nval_idx = folds[folds['fold'] == FOLD].index\nprint(trn_idx.shape, val_idx.shape)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_dataset = TrainDataset(folds.loc[trn_idx].reset_index(drop=True), \n                             folds.loc[trn_idx]['attribute_ids'], \n                             transform=get_transforms(data='train'))\nvalid_dataset = TrainDataset(folds.loc[val_idx].reset_index(drop=True), \n                             folds.loc[val_idx]['attribute_ids'], \n                             transform=get_transforms(data='valid'))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"batch_size = 128\n\ntrain_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\nvalid_loader = DataLoader(valid_dataset, batch_size=batch_size, shuffle=False)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Model"},{"metadata":{"trusted":true},"cell_type":"code","source":"model = models.resnet18(pretrained=False)\nweights_path = '../input/resnet18/resnet18.pth'\nmodel.load_state_dict(torch.load(weights_path))\n\nmodel.avgpool = nn.AdaptiveAvgPool2d(1)\nmodel.fc = nn.Linear(model.fc.in_features, N_CLASSES)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Train"},{"metadata":{"trusted":true},"cell_type":"code","source":"from sklearn.metrics import fbeta_score\n\n\ndef get_score(targets, y_pred):\n    return fbeta_score(targets, y_pred, beta=2, average='samples')\n\n\n\ndef binarize_prediction(probabilities, threshold: float, argsorted=None,\n                        min_labels=1, max_labels=10):\n    \"\"\" \n    Return matrix of 0/1 predictions, same shape as probabilities.\n    \"\"\"\n    assert probabilities.shape[1] == N_CLASSES\n    if argsorted is None:\n        argsorted = probabilities.argsort(axis=1)\n    max_mask = _make_mask(argsorted, max_labels)\n    min_mask = _make_mask(argsorted, min_labels)\n    prob_mask = probabilities > threshold\n    return (max_mask & prob_mask) | min_mask\n\n\ndef _make_mask(argsorted, top_n: int):\n    mask = np.zeros_like(argsorted, dtype=np.uint8)\n    col_indices = argsorted[:, -top_n:].reshape(-1)\n    row_indices = [i // top_n for i in range(len(col_indices))]\n    mask[row_indices, col_indices] = 1\n    return mask\n\n\ndef _reduce_loss(loss):\n    return loss.sum() / loss.shape[0]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"with timer('Train model'):\n    \n    n_epochs = 12\n    lr = 1e-4\n    \n    model.to(device)\n    \n    optimizer = Adam(model.parameters(), lr=lr, amsgrad=False)\n    scheduler = ReduceLROnPlateau(optimizer, 'min', factor=0.75, patience=4, verbose=True, eps=1e-6)\n    \n    criterion = nn.BCEWithLogitsLoss(reduction='none')\n    best_score = 0.\n    best_thresh = 0.\n    best_loss = np.inf\n    \n    for epoch in range(n_epochs):\n        \n        start_time = time.time()\n\n        model.train()\n        avg_loss = 0.\n\n        optimizer.zero_grad()\n        tk0 = tqdm(enumerate(train_loader), total=len(train_loader))\n\n        for i, (images, labels) in tk0:\n\n            images = images.to(device)\n            labels = labels.to(device)\n            \n            y_preds = model(images)\n            loss = _reduce_loss(criterion(y_preds, labels))\n            \n            loss.backward()\n            optimizer.step()\n            optimizer.zero_grad()\n\n            avg_loss += loss.item() / len(train_loader)\n            \n        model.eval()\n        avg_val_loss = 0.\n        preds = []\n        valid_labels = []\n        tk1 = tqdm(enumerate(valid_loader), total=len(valid_loader))\n\n        for i, (images, labels) in tk1:\n            \n            images = images.to(device)\n            labels = labels.to(device)\n            \n            with torch.no_grad():\n                y_preds = model(images)\n            \n            preds.append(torch.sigmoid(y_preds).to('cpu').numpy())\n            valid_labels.append(labels.to('cpu').numpy())\n\n            loss = _reduce_loss(criterion(y_preds, labels))\n            avg_val_loss += loss.item() / len(valid_loader)\n        \n        scheduler.step(avg_val_loss)\n            \n        preds = np.concatenate(preds)\n        valid_labels = np.concatenate(valid_labels)\n        argsorted = preds.argsort(axis=1)\n        \n        th_scores = {}\n        for threshold in [0.05, 0.06, 0.07, 0.08, 0.09, 0.10, 0.11, 0.12, 0.13, 0.14, 0.15]:\n            _score = get_score(valid_labels, binarize_prediction(preds, threshold, argsorted))\n            th_scores[threshold] = _score\n        \n        max_kv = max(th_scores.items(), key=lambda x: x[1])\n        th, score = max_kv[0], max_kv[1]\n\n        elapsed = time.time() - start_time\n        \n        LOGGER.debug(f'  Epoch {epoch+1} - avg_train_loss: {avg_loss:.4f}  avg_val_loss: {avg_val_loss:.4f}  time: {elapsed:.0f}s')\n        LOGGER.debug(f'  Epoch {epoch+1} - threshold: {th}  f2_score: {score}')\n        \n        if score>best_score:\n            best_score = score\n            best_thresh = th\n            LOGGER.debug(f'  Epoch {epoch+1} - Save Best Score: {best_score:.4f} Model  threshold: {best_thresh}')\n            torch.save(model.state_dict(), f'fold{FOLD}_best_score.pth')\n            \n        if avg_val_loss<best_loss:\n            best_loss = avg_val_loss\n            LOGGER.debug(f'  Epoch {epoch+1} - Save Best Loss: {best_loss:.4f} Model')\n            torch.save(model.state_dict(), f'fold{FOLD}_best_loss.pth')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Inference"},{"metadata":{"trusted":true},"cell_type":"code","source":"batch_size = 128\n\ntest_dataset = TestDataset(submission, transform=get_transforms(data='valid'))\ntest_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model = models.resnet18(pretrained=False)\nmodel.avgpool = nn.AdaptiveAvgPool2d(1)\nmodel.fc = nn.Linear(model.fc.in_features, N_CLASSES)\n\nweights_path = f'fold{FOLD}_best_score.pth'\nmodel.load_state_dict(torch.load(weights_path))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"with timer('inference'):\n    \n    model.to(device) \n    \n    preds = []\n    tk0 = tqdm(enumerate(test_loader), total=len(test_loader))\n\n    for i, images in tk0:\n            \n        images = images.to(device)\n            \n        with torch.no_grad():\n            y_preds = model(images)\n            \n        preds.append(torch.sigmoid(y_preds).to('cpu').numpy())","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Submission"},{"metadata":{"trusted":true},"cell_type":"code","source":"threshold = best_thresh\npredictions = np.concatenate(preds) > threshold\n\nfor i, row in enumerate(predictions):\n    ids = np.nonzero(row)[0]\n    submission.iloc[i].attribute_ids = ' '.join([str(x) for x in ids])\n    \nsubmission.to_csv('submission.csv', index=False)\nsubmission.head()","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.6"},"widgets":{"application/vnd.jupyter.widget-state+json":{"state":{"3152668ea8b74589b2a7123dccca5cbd":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"ProgressStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"ProgressStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","bar_color":null,"description_width":"initial"}},"6fd44a996a19464f99110602a6db08b7":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"79a7f6b8f5914a688c09d843603e8bc8":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"FloatProgressModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"FloatProgressModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"ProgressView","bar_style":"success","description":"100%","description_tooltip":null,"layout":"IPY_MODEL_b66bcff488cd470c9bb25673405f2d82","max":46827520,"min":0,"orientation":"horizontal","style":"IPY_MODEL_3152668ea8b74589b2a7123dccca5cbd","value":46827520}},"83e67fce80104b5a9383ec9c3196506a":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"b66bcff488cd470c9bb25673405f2d82":{"model_module":"@jupyter-widgets/base","model_module_version":"1.2.0","model_name":"LayoutModel","state":{"_model_module":"@jupyter-widgets/base","_model_module_version":"1.2.0","_model_name":"LayoutModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"LayoutView","align_content":null,"align_items":null,"align_self":null,"border":null,"bottom":null,"display":null,"flex":null,"flex_flow":null,"grid_area":null,"grid_auto_columns":null,"grid_auto_flow":null,"grid_auto_rows":null,"grid_column":null,"grid_gap":null,"grid_row":null,"grid_template_areas":null,"grid_template_columns":null,"grid_template_rows":null,"height":null,"justify_content":null,"justify_items":null,"left":null,"margin":null,"max_height":null,"max_width":null,"min_height":null,"min_width":null,"object_fit":null,"object_position":null,"order":null,"overflow":null,"overflow_x":null,"overflow_y":null,"padding":null,"right":null,"top":null,"visibility":null,"width":null}},"d5e26b2104c94d3e93c1579f9830e0c5":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"HBoxModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HBoxModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HBoxView","box_style":"","children":["IPY_MODEL_79a7f6b8f5914a688c09d843603e8bc8","IPY_MODEL_f52096278de8485f9182c22905489f44"],"layout":"IPY_MODEL_83e67fce80104b5a9383ec9c3196506a"}},"f52096278de8485f9182c22905489f44":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"HTMLModel","state":{"_dom_classes":[],"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"HTMLModel","_view_count":null,"_view_module":"@jupyter-widgets/controls","_view_module_version":"1.5.0","_view_name":"HTMLView","description":"","description_tooltip":null,"layout":"IPY_MODEL_6fd44a996a19464f99110602a6db08b7","placeholder":"​","style":"IPY_MODEL_fdc2f0a4a2ad4377affda7d6294960c7","value":" 44.7M/44.7M [00:00&lt;00:00, 70.6MB/s]"}},"fdc2f0a4a2ad4377affda7d6294960c7":{"model_module":"@jupyter-widgets/controls","model_module_version":"1.5.0","model_name":"DescriptionStyleModel","state":{"_model_module":"@jupyter-widgets/controls","_model_module_version":"1.5.0","_model_name":"DescriptionStyleModel","_view_count":null,"_view_module":"@jupyter-widgets/base","_view_module_version":"1.2.0","_view_name":"StyleView","description_width":""}}},"version_major":2,"version_minor":0}}},"nbformat":4,"nbformat_minor":4}