{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "_cell_guid": "b1076dfc-b9ad-4769-8c92-a6c4dae69d19",
    "_uuid": "8f2839f25d086af736a60e9eeb907d3b93b6e0e5"
   },
   "source": [
    "# Libraries"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-07-31T19:19:36.421057Z",
     "iopub.status.busy": "2024-07-31T19:19:36.420121Z",
     "iopub.status.idle": "2024-07-31T19:20:22.145747Z",
     "shell.execute_reply": "2024-07-31T19:20:22.144324Z",
     "shell.execute_reply.started": "2024-07-31T19:19:36.421009Z"
    }
   },
   "outputs": [],
   "source": [
    "%%capture\n",
    "\n",
    "%pip install -U peft\n",
    "%pip install -U trl\n",
    "%pip install -U bitsandbytes "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-07-31T19:20:22.148355Z",
     "iopub.status.busy": "2024-07-31T19:20:22.148024Z",
     "iopub.status.idle": "2024-07-31T19:20:44.164336Z",
     "shell.execute_reply": "2024-07-31T19:20:44.163538Z",
     "shell.execute_reply.started": "2024-07-31T19:20:22.148324Z"
    }
   },
   "outputs": [],
   "source": [
    "import os, torch, wandb\n",
    "\n",
    "from transformers import (\n",
    "    AutoModelForCausalLM,\n",
    "    AutoTokenizer,\n",
    "    BitsAndBytesConfig,\n",
    "    HfArgumentParser,\n",
    "    TrainingArguments,\n",
    "    pipeline,\n",
    "    logging,\n",
    ")\n",
    "from peft import (\n",
    "    LoraConfig,\n",
    "    PeftModel,\n",
    "    prepare_model_for_kbit_training,\n",
    "    get_peft_model,\n",
    ")\n",
    "\n",
    "from datasets import load_dataset\n",
    "from trl import SFTTrainer, setup_chat_format\n",
    "from dataclasses import dataclass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setup Huggingface 🤗 & Wandb"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-07-31T19:20:44.16689Z",
     "iopub.status.busy": "2024-07-31T19:20:44.165909Z",
     "iopub.status.idle": "2024-07-31T19:20:46.965276Z",
     "shell.execute_reply": "2024-07-31T19:20:46.964266Z",
     "shell.execute_reply.started": "2024-07-31T19:20:44.166853Z"
    }
   },
   "outputs": [],
   "source": [
    "from huggingface_hub import login\n",
    "from kaggle_secrets import UserSecretsClient\n",
    "user_secrets = UserSecretsClient()\n",
    "\n",
    "hf_token = user_secrets.get_secret(\"huggingface_token\")\n",
    "\n",
    "login(token = hf_token)\n",
    "\n",
    "wb_token = user_secrets.get_secret(\"wandb_api_key\")\n",
    "\n",
    "wandb.login(key=wb_token)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-07-31T19:20:46.969022Z",
     "iopub.status.busy": "2024-07-31T19:20:46.967876Z",
     "iopub.status.idle": "2024-07-31T19:20:46.974308Z",
     "shell.execute_reply": "2024-07-31T19:20:46.973252Z",
     "shell.execute_reply.started": "2024-07-31T19:20:46.968992Z"
    }
   },
   "outputs": [],
   "source": [
    "@dataclass\n",
    "class Config:\n",
    "#     model_name = \"meta-llama/Meta-Llama-3.1-8B-Instruct\"\n",
    "#     model_name = \"AnatoliiPotapov/T-lite-instruct-0.1\"\n",
    "    model_name = \"google/gemma-2-9b-it\"\n",
    "    dataset_name = \"ruslanmv/ai-medical-chatbot\"\n",
    "    new_model = \"llama-3.1-8b-chat-doctor\"\n",
    "    torch_dtype = torch.float16\n",
    "    attn_implementation = \"eager\"\n",
    "cfg = Config()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Loading model and tokenizer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-07-31T19:20:46.975766Z",
     "iopub.status.busy": "2024-07-31T19:20:46.975431Z",
     "iopub.status.idle": "2024-07-31T19:23:16.014332Z",
     "shell.execute_reply": "2024-07-31T19:23:16.013378Z",
     "shell.execute_reply.started": "2024-07-31T19:20:46.975741Z"
    }
   },
   "outputs": [],
   "source": [
    "# QLoRA config\n",
    "bnb_config = BitsAndBytesConfig(\n",
    "    load_in_4bit=True,\n",
    "    bnb_4bit_quant_type=\"nf4\",\n",
    "    bnb_4bit_compute_dtype=cfg.torch_dtype,\n",
    "    bnb_4bit_use_double_quant=True,\n",
    ")\n",
    "\n",
    "# Load model\n",
    "model = AutoModelForCausalLM.from_pretrained(\n",
    "    cfg.model_name,\n",
    "    quantization_config=bnb_config,\n",
    "    device_map=\"auto\",\n",
    "    attn_implementation=cfg.attn_implementation\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-07-31T19:23:16.015907Z",
     "iopub.status.busy": "2024-07-31T19:23:16.015614Z",
     "iopub.status.idle": "2024-07-31T19:23:18.405382Z",
     "shell.execute_reply": "2024-07-31T19:23:18.404549Z",
     "shell.execute_reply.started": "2024-07-31T19:23:16.015883Z"
    }
   },
   "outputs": [],
   "source": [
    "# Load tokenizer\n",
    "tokenizer = AutoTokenizer.from_pretrained(cfg.model_name)\n",
    "model, tokenizer = setup_chat_format(model, tokenizer)\n",
    "tokenizer.padding_side = 'right'\n",
    "tokenizer.padding_token = '<|pad|>'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## LoRA adapter"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-07-31T19:23:18.406871Z",
     "iopub.status.busy": "2024-07-31T19:23:18.406587Z",
     "iopub.status.idle": "2024-07-31T19:23:19.566904Z",
     "shell.execute_reply": "2024-07-31T19:23:19.565881Z",
     "shell.execute_reply.started": "2024-07-31T19:23:18.406847Z"
    }
   },
   "outputs": [],
   "source": [
    "# LoRA config\n",
    "peft_config = LoraConfig(\n",
    "    r=16,\n",
    "    lora_alpha=32,\n",
    "    lora_dropout=0.05,\n",
    "    bias=\"none\",\n",
    "    task_type=\"CAUSAL_LM\",\n",
    "    target_modules=['up_proj', 'down_proj', 'gate_proj', 'k_proj', 'q_proj', 'v_proj', 'o_proj']\n",
    ")\n",
    "model = get_peft_model(model, peft_config)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Load"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-07-31T19:23:19.568419Z",
     "iopub.status.busy": "2024-07-31T19:23:19.568121Z",
     "iopub.status.idle": "2024-07-31T19:23:24.466352Z",
     "shell.execute_reply": "2024-07-31T19:23:24.465507Z",
     "shell.execute_reply.started": "2024-07-31T19:23:19.568394Z"
    }
   },
   "outputs": [],
   "source": [
    "dataset = load_dataset(cfg.dataset_name, split=\"all\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Format to chat "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-07-31T19:23:24.467754Z",
     "iopub.status.busy": "2024-07-31T19:23:24.467426Z",
     "iopub.status.idle": "2024-07-31T19:23:24.473839Z",
     "shell.execute_reply": "2024-07-31T19:23:24.472918Z",
     "shell.execute_reply.started": "2024-07-31T19:23:24.467728Z"
    }
   },
   "outputs": [],
   "source": [
    "def format_chat_template(row):\n",
    "    row_json = [{\"role\": \"user\", \"content\": row[\"Patient\"]},\n",
    "               {\"role\": \"assistant\", \"content\": row[\"Doctor\"]}]\n",
    "    row[\"text\"] = tokenizer.apply_chat_template(row_json, tokenize=False)\n",
    "    return row"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-07-31T19:23:24.478016Z",
     "iopub.status.busy": "2024-07-31T19:23:24.477695Z",
     "iopub.status.idle": "2024-07-31T19:23:39.357867Z",
     "shell.execute_reply": "2024-07-31T19:23:39.356931Z",
     "shell.execute_reply.started": "2024-07-31T19:23:24.477991Z"
    }
   },
   "outputs": [],
   "source": [
    "dataset = dataset.map(\n",
    "    format_chat_template,\n",
    "    num_proc=4,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Select only part"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-07-31T19:23:39.359682Z",
     "iopub.status.busy": "2024-07-31T19:23:39.359332Z",
     "iopub.status.idle": "2024-07-31T19:23:41.449434Z",
     "shell.execute_reply": "2024-07-31T19:23:41.448661Z",
     "shell.execute_reply.started": "2024-07-31T19:23:39.35965Z"
    }
   },
   "outputs": [],
   "source": [
    "dataset_sh = dataset.shuffle(seed=2024).select(range(10_000))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-07-31T19:23:41.451005Z",
     "iopub.status.busy": "2024-07-31T19:23:41.450648Z",
     "iopub.status.idle": "2024-07-31T19:23:41.484003Z",
     "shell.execute_reply": "2024-07-31T19:23:41.482915Z",
     "shell.execute_reply.started": "2024-07-31T19:23:41.450974Z"
    }
   },
   "outputs": [],
   "source": [
    "dataset_sh = dataset_sh.train_test_split(0.1)\n",
    "dataset_sh"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Train model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Training arguments"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-07-31T19:40:20.141299Z",
     "iopub.status.busy": "2024-07-31T19:40:20.1409Z",
     "iopub.status.idle": "2024-07-31T19:40:20.182873Z",
     "shell.execute_reply": "2024-07-31T19:40:20.182009Z",
     "shell.execute_reply.started": "2024-07-31T19:40:20.141268Z"
    }
   },
   "outputs": [],
   "source": [
    "training_arguments = TrainingArguments(\n",
    "    output_dir=cfg.new_model,\n",
    "    per_device_train_batch_size=1,\n",
    "    per_device_eval_batch_size=4,\n",
    "    gradient_accumulation_steps=2,\n",
    "    optim=\"paged_adamw_32bit\",\n",
    "#     num_train_epochs=1,\n",
    "    max_steps=500,\n",
    "    eval_strategy=\"steps\",\n",
    "    eval_steps=500,\n",
    "    logging_steps=100,\n",
    "    warmup_steps=10,\n",
    "    logging_strategy=\"steps\",\n",
    "    learning_rate=2e-4,\n",
    "    fp16=False,\n",
    "    bf16=True,\n",
    "    group_by_length=True,\n",
    "    report_to=\"wandb\",\n",
    "    run_name=\"Llama-3.1-medicine\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Train model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-07-31T19:40:21.439957Z",
     "iopub.status.busy": "2024-07-31T19:40:21.439288Z",
     "iopub.status.idle": "2024-07-31T19:40:27.98408Z",
     "shell.execute_reply": "2024-07-31T19:40:27.983305Z",
     "shell.execute_reply.started": "2024-07-31T19:40:21.439924Z"
    }
   },
   "outputs": [],
   "source": [
    "trainer = SFTTrainer(\n",
    "    model=model,\n",
    "    train_dataset=dataset_sh[\"train\"],\n",
    "    eval_dataset=dataset_sh[\"test\"],\n",
    "    peft_config=peft_config,\n",
    "    max_seq_length=512,\n",
    "    dataset_text_field=\"text\",\n",
    "    tokenizer=tokenizer,\n",
    "    args=training_arguments,\n",
    "    packing= False,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-07-31T19:40:31.097638Z",
     "iopub.status.busy": "2024-07-31T19:40:31.097229Z",
     "iopub.status.idle": "2024-07-31T20:09:00.849422Z",
     "shell.execute_reply": "2024-07-31T20:09:00.847937Z",
     "shell.execute_reply.started": "2024-07-31T19:40:31.097607Z"
    }
   },
   "outputs": [],
   "source": [
    "trainer.train()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-07-31T20:09:40.932639Z",
     "iopub.status.busy": "2024-07-31T20:09:40.931936Z",
     "iopub.status.idle": "2024-07-31T20:10:11.058559Z",
     "shell.execute_reply": "2024-07-31T20:10:11.057552Z",
     "shell.execute_reply.started": "2024-07-31T20:09:40.932606Z"
    }
   },
   "outputs": [],
   "source": [
    "path_to_save = \"Llama-finetuned\"\n",
    "trainer.save_model(path_to_save)\n",
    "model.save_pretrained(path_to_save)\n",
    "tokenizer.save_pretrained(path_to_save)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-07-31T20:25:47.955124Z",
     "iopub.status.busy": "2024-07-31T20:25:47.954733Z",
     "iopub.status.idle": "2024-07-31T20:25:47.964991Z",
     "shell.execute_reply": "2024-07-31T20:25:47.963846Z",
     "shell.execute_reply.started": "2024-07-31T20:25:47.955095Z"
    }
   },
   "outputs": [],
   "source": [
    "del model, tokenizer, trainer"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Compare models"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Init casual LLM"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-07-31T20:28:13.860754Z",
     "iopub.status.busy": "2024-07-31T20:28:13.859897Z",
     "iopub.status.idle": "2024-07-31T20:29:29.136779Z",
     "shell.execute_reply": "2024-07-31T20:29:29.135671Z",
     "shell.execute_reply.started": "2024-07-31T20:28:13.86072Z"
    }
   },
   "outputs": [],
   "source": [
    "# QLoRA config\n",
    "bnb_config = BitsAndBytesConfig(\n",
    "    load_in_4bit=True,\n",
    "    bnb_4bit_quant_type=\"nf4\",\n",
    "    bnb_4bit_compute_dtype=cfg.torch_dtype,\n",
    "    bnb_4bit_use_double_quant=True,\n",
    ")\n",
    "\n",
    "# Load model\n",
    "casual_model = AutoModelForCausalLM.from_pretrained(\n",
    "    cfg.model_name,\n",
    "    quantization_config=bnb_config,\n",
    "#     device_map=\"auto\",\n",
    "    attn_implementation=cfg.attn_implementation\n",
    ")\n",
    "\n",
    "tokenizer = tokenizer = AutoTokenizer.from_pretrained(cfg.model_name)\n",
    "tokenizer.padding_side = 'right'\n",
    "tokenizer.padding_token = '<|pad_token|>'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-07-31T20:29:29.13895Z",
     "iopub.status.busy": "2024-07-31T20:29:29.138645Z",
     "iopub.status.idle": "2024-07-31T20:29:29.198595Z",
     "shell.execute_reply": "2024-07-31T20:29:29.197438Z",
     "shell.execute_reply.started": "2024-07-31T20:29:29.138923Z"
    }
   },
   "outputs": [],
   "source": [
    "casual_model, tokenizer = setup_chat_format(casual_model, tokenizer)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Get answers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-07-31T20:29:29.200105Z",
     "iopub.status.busy": "2024-07-31T20:29:29.19983Z",
     "iopub.status.idle": "2024-07-31T20:29:29.207428Z",
     "shell.execute_reply": "2024-07-31T20:29:29.206299Z",
     "shell.execute_reply.started": "2024-07-31T20:29:29.20008Z"
    }
   },
   "outputs": [],
   "source": [
    "def generate_answer(model, prompt):\n",
    "    chat = [\n",
    "        { \"role\": \"user\", \"content\": prompt },\n",
    "    ]\n",
    "    prompt = tokenizer.apply_chat_template(chat, tokenize=False, add_generation_prompt=True)\n",
    "    inputs = tokenizer.encode(prompt, add_special_tokens=False, return_tensors=\"pt\")\n",
    "    outputs = model.generate(input_ids=inputs.to(model.device), max_new_tokens=150)\n",
    "\n",
    "    return(tokenizer.decode(outputs[0]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Comprasion"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-07-31T20:29:36.89578Z",
     "iopub.status.busy": "2024-07-31T20:29:36.894921Z",
     "iopub.status.idle": "2024-07-31T20:29:36.901397Z",
     "shell.execute_reply": "2024-07-31T20:29:36.900326Z",
     "shell.execute_reply.started": "2024-07-31T20:29:36.895742Z"
    }
   },
   "outputs": [],
   "source": [
    "q1 = \"I have severe headaches help me please\"\n",
    "q2 = \"I have a suspiciously large mole. Could I have cancer? How can I determine this at home?\"\n",
    "q3 = \"What does abutment of the nerve root mean?\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-07-31T20:21:28.269548Z",
     "iopub.status.busy": "2024-07-31T20:21:28.268891Z",
     "iopub.status.idle": "2024-07-31T20:22:01.045953Z",
     "shell.execute_reply": "2024-07-31T20:22:01.045026Z",
     "shell.execute_reply.started": "2024-07-31T20:21:28.269515Z"
    }
   },
   "outputs": [],
   "source": [
    "generate_answer(model, q1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "generate_answer(model, q2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "generate_answer(model, q3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Free gpu memory\n",
    "import numba\n",
    "numba.cuda.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2024-07-31T20:29:40.007186Z",
     "iopub.status.busy": "2024-07-31T20:29:40.006418Z",
     "iopub.status.idle": "2024-07-31T20:30:00.561813Z",
     "shell.execute_reply": "2024-07-31T20:30:00.560849Z",
     "shell.execute_reply.started": "2024-07-31T20:29:40.00715Z"
    }
   },
   "outputs": [],
   "source": [
    "print(generate_answer(casual_model, q1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.status.busy": "2024-07-31T19:39:48.456683Z",
     "iopub.status.idle": "2024-07-31T19:39:48.457382Z",
     "shell.execute_reply": "2024-07-31T19:39:48.457144Z",
     "shell.execute_reply.started": "2024-07-31T19:39:48.457123Z"
    }
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "generate_answer(casual_model, q2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "execution": {
     "iopub.status.busy": "2024-07-31T19:39:48.458644Z",
     "iopub.status.idle": "2024-07-31T19:39:48.459092Z",
     "shell.execute_reply": "2024-07-31T19:39:48.458882Z",
     "shell.execute_reply.started": "2024-07-31T19:39:48.458863Z"
    }
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "generate_answer(casual_model, q3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kaggle": {
   "accelerator": "none",
   "dataSources": [],
   "dockerImageVersionId": 30747,
   "isGpuEnabled": false,
   "isInternetEnabled": true,
   "language": "python",
   "sourceType": "notebook"
  },
  "kernelspec": {
   "display_name": "cv",
   "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.10.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
