{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "d36e1e93-ae93-4a4e-93c6-68fd868d2882",
   "metadata": {},
   "source": [
    "# Using FourierFT for sequence classification"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ddfc0610-55f6-4343-a950-125ccf0f45ac",
   "metadata": {},
   "source": [
    "In this example, we fine-tune Roberta (base) on a sequence classification task using FourierFT."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "45addd81-d4f3-4dfd-960d-3920d347f0a6",
   "metadata": {},
   "source": [
    "## Imports"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "a9935ae2",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/zgaoat/anaconda3/envs/pr2/lib/python3.11/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    }
   ],
   "source": [
    "#  To run this notebook, please run `pip install evaluate` to install additional dependencies not covered by PEFT.\n",
    "import torch\n",
    "from torch.optim import AdamW\n",
    "from torch.utils.data import DataLoader\n",
    "from peft import (\n",
    "    get_peft_model,\n",
    "    FourierFTConfig,\n",
    "    PeftType,\n",
    ")\n",
    "\n",
    "import evaluate\n",
    "from datasets import load_dataset\n",
    "from transformers import AutoModelForSequenceClassification, AutoTokenizer, get_linear_schedule_with_warmup, set_seed, AutoConfig\n",
    "from tqdm import tqdm"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "62c959bf-7cc2-49e0-b97e-4c10ec3b9bf3",
   "metadata": {},
   "source": [
    "## Parameters"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "e3b13308",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<torch._C.Generator at 0x78e2a49744b0>"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "batch_size = 32\n",
    "model_name_or_path = \"roberta-base\"\n",
    "task = \"mrpc\"\n",
    "peft_type = PeftType.FOURIERFT\n",
    "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n",
    "num_epochs = 5  # for better results, increase this number\n",
    "n_frequency = 1000        # for better results, increase this number\n",
    "scaling = 150.0\n",
    "max_length = 512\n",
    "torch.manual_seed(0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "0526f571",
   "metadata": {},
   "outputs": [],
   "source": [
    "peft_config = FourierFTConfig(\n",
    "    task_type=\"SEQ_CLS\", \n",
    "    n_frequency=n_frequency,\n",
    "    target_modules=[\"query\", \"value\"],\n",
    "    scaling = scaling,\n",
    ")\n",
    "head_lr = 6e-3  # the learning rate for the classification head for NLU tasks\n",
    "fft_lr = 6e-2   # the learning rate for the parameters other than the classification head (q,v in this case)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c075c5d2-a457-4f37-a7f1-94fd0d277972",
   "metadata": {},
   "source": [
    "## Loading data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "7bb52cb4-d1c3-4b04-8bf0-f39ca88af139",
   "metadata": {},
   "outputs": [],
   "source": [
    "if any(k in model_name_or_path for k in (\"gpt\", \"opt\", \"bloom\")):\n",
    "    padding_side = \"left\"\n",
    "else:\n",
    "    padding_side = \"right\"\n",
    "\n",
    "tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, padding_side=padding_side)\n",
    "if getattr(tokenizer, \"pad_token_id\") is None:\n",
    "    tokenizer.pad_token_id = tokenizer.eos_token_id"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "e69c5e1f-d27b-4264-a41e-fc9b99d025e6",
   "metadata": {},
   "outputs": [],
   "source": [
    "datasets = load_dataset(\"glue\", task)\n",
    "metric = evaluate.load(\"glue\", task)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "0209f778-c93b-40eb-a4e0-24c25db03980",
   "metadata": {},
   "outputs": [],
   "source": [
    "def tokenize_function(examples):\n",
    "    # max_length=None => use the model max length (it's actually the default)\n",
    "    outputs = tokenizer(examples[\"sentence1\"], examples[\"sentence2\"], truncation=True, max_length=max_length)\n",
    "    return outputs\n",
    "\n",
    "\n",
    "tokenized_datasets = datasets.map(\n",
    "    tokenize_function,\n",
    "    batched=True,\n",
    "    remove_columns=[\"idx\", \"sentence1\", \"sentence2\"],\n",
    ")\n",
    "\n",
    "# We also rename the 'label' column to 'labels' which is the expected name for labels by the models of the\n",
    "# transformers library\n",
    "tokenized_datasets = tokenized_datasets.rename_column(\"label\", \"labels\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "7453954e-982c-46f0-b09c-589776e6d6cb",
   "metadata": {},
   "outputs": [],
   "source": [
    "def collate_fn(examples):\n",
    "    return tokenizer.pad(examples, padding=\"longest\", return_tensors=\"pt\")\n",
    "\n",
    "\n",
    "# Instantiate dataloaders.\n",
    "train_dataloader = DataLoader(tokenized_datasets[\"train\"], shuffle=True, collate_fn=collate_fn, batch_size=batch_size)\n",
    "eval_dataloader = DataLoader(\n",
    "    tokenized_datasets[\"validation\"], shuffle=False, collate_fn=collate_fn, batch_size=batch_size\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f3b9b2e8-f415-4d0f-9fb4-436f1a3585ea",
   "metadata": {},
   "source": [
    "## Preparing the FourierFT model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "2ed5ac74",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Some weights of RobertaForSequenceClassification were not initialized from the model checkpoint at roberta-base and are newly initialized: ['classifier.dense.bias', 'classifier.dense.weight', 'classifier.out_proj.bias', 'classifier.out_proj.weight']\n",
      "You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "trainable params: 616,130 || all params: 125,263,300 || trainable%: 0.4919\n"
     ]
    }
   ],
   "source": [
    "model = AutoModelForSequenceClassification.from_pretrained(model_name_or_path, return_dict=True, max_length=None)\n",
    "model = get_peft_model(model, peft_config)\n",
    "model.print_trainable_parameters()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "0d2d0381",
   "metadata": {},
   "outputs": [],
   "source": [
    "head_param = list(map(id, model.classifier.parameters()))\n",
    "\n",
    "others_param = filter(lambda p: id(p) not in head_param, model.parameters()) \n",
    "\n",
    "optimizer = AdamW([\n",
    "    {\"params\": model.classifier.parameters(), \"lr\": head_lr},\n",
    "    {\"params\": others_param, \"lr\": fft_lr}\n",
    "],weight_decay=0.)\n",
    "\n",
    "\n",
    "# Instantiate scheduler\n",
    "lr_scheduler = get_linear_schedule_with_warmup(\n",
    "    optimizer=optimizer,\n",
    "    num_warmup_steps=0.06 * (len(train_dataloader) * num_epochs),\n",
    "    num_training_steps=(len(train_dataloader) * num_epochs),\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c0dd5aa8-977b-4ac0-8b96-884b17bcdd00",
   "metadata": {},
   "source": [
    "## Training"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "fa0e73be",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "  0%|          | 0/115 [00:00<?, ?it/s]You're using a RobertaTokenizerFast tokenizer. Please note that with a fast tokenizer, using the `__call__` method is faster than using a method to encode the text followed by a call to the `pad` method to get a padded encoding.\n",
      "100%|██████████| 115/115 [00:06<00:00, 19.03it/s]\n",
      "100%|██████████| 13/13 [00:00<00:00, 41.72it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch 0: {'accuracy': 0.8161764705882353, 'f1': 0.8709122203098106}\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 115/115 [00:05<00:00, 20.61it/s]\n",
      "100%|██████████| 13/13 [00:00<00:00, 42.91it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch 1: {'accuracy': 0.8480392156862745, 'f1': 0.8966666666666666}\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 115/115 [00:05<00:00, 20.63it/s]\n",
      "100%|██████████| 13/13 [00:00<00:00, 42.65it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch 2: {'accuracy': 0.8676470588235294, 'f1': 0.9075342465753424}\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 115/115 [00:05<00:00, 20.56it/s]\n",
      "100%|██████████| 13/13 [00:00<00:00, 42.11it/s]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch 3: {'accuracy': 0.8504901960784313, 'f1': 0.8988391376451078}\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "100%|██████████| 115/115 [00:05<00:00, 20.50it/s]\n",
      "100%|██████████| 13/13 [00:00<00:00, 43.15it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch 4: {'accuracy': 0.8725490196078431, 'f1': 0.9103448275862069}\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "model.to(device)\n",
    "for epoch in range(num_epochs):\n",
    "    model.train()\n",
    "    for step, batch in enumerate(tqdm(train_dataloader)):\n",
    "        batch.to(device)\n",
    "        outputs = model(**batch)\n",
    "        loss = outputs.loss\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        lr_scheduler.step()\n",
    "        optimizer.zero_grad()\n",
    "\n",
    "    model.eval()\n",
    "    for step, batch in enumerate(tqdm(eval_dataloader)):\n",
    "        batch.to(device)\n",
    "        with torch.no_grad():\n",
    "            outputs = model(**batch)\n",
    "        predictions = outputs.logits.argmax(dim=-1)\n",
    "        predictions, references = predictions, batch[\"labels\"]\n",
    "        metric.add_batch(\n",
    "            predictions=predictions,\n",
    "            references=references,\n",
    "        )\n",
    "\n",
    "    eval_metric = metric.compute()\n",
    "    print(f\"epoch {epoch}:\", eval_metric)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f2b2caca",
   "metadata": {},
   "source": [
    "## Share adapters on the 🤗 Hub"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "7b23af6f-cf6e-486f-9d10-0eada95b631f",
   "metadata": {},
   "outputs": [],
   "source": [
    "account_id = ...  # your Hugging Face Hub account ID"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "990b3c93",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/zgaoat/anaconda3/envs/pr2/lib/python3.11/site-packages/huggingface_hub/file_download.py:1132: FutureWarning: `resume_download` is deprecated and will be removed in version 1.0.0. Downloads always resume when possible. If you want to force a new download, use `force_download=True`.\n",
      "  warnings.warn(\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "CommitInfo(commit_url='https://huggingface.co/zgaoat/roberta-base-mrpc-peft-fourierft/commit/064eb35cbb7a1073b4d8fafbeccee43a0a4e37c9', commit_message='Upload model', commit_description='', oid='064eb35cbb7a1073b4d8fafbeccee43a0a4e37c9', pr_url=None, pr_revision=None, pr_num=None)"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model.push_to_hub(f\"{account_id}/roberta-base-mrpc-peft-fourierft\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d140b26",
   "metadata": {},
   "source": [
    "## Load adapters from the Hub\n",
    "\n",
    "You can also directly load adapters from the Hub using the commands below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "c283e028-b349-46b0-a20e-cde0ee5fbd7b",
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "from peft import PeftModel, PeftConfig\n",
    "from transformers import AutoTokenizer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "320b10a0-4ea8-4786-9f3c-4670019c6b18",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Some weights of RobertaForSequenceClassification were not initialized from the model checkpoint at roberta-base and are newly initialized: ['classifier.dense.bias', 'classifier.dense.weight', 'classifier.out_proj.bias', 'classifier.out_proj.weight']\n",
      "You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.\n"
     ]
    }
   ],
   "source": [
    "peft_model_id = f\"{account_id}/roberta-base-mrpc-peft-fourierft\"\n",
    "config = PeftConfig.from_pretrained(peft_model_id)\n",
    "inference_model = AutoModelForSequenceClassification.from_pretrained(config.base_model_name_or_path)\n",
    "tokenizer = AutoTokenizer.from_pretrained(config.base_model_name_or_path)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "b3a94049-bc01-4f2e-8cf9-66daf24a4402",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Load the FourierFT model\n",
    "inference_model = PeftModel.from_pretrained(inference_model, peft_model_id, config=config)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "bd919fef-4e9a-4dc5-a957-7b879cfc5d38",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "  0%|          | 0/13 [00:00<?, ?it/s]You're using a RobertaTokenizerFast tokenizer. Please note that with a fast tokenizer, using the `__call__` method is faster than using a method to encode the text followed by a call to the `pad` method to get a padded encoding.\n",
      "100%|██████████| 13/13 [00:00<00:00, 43.06it/s]"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'accuracy': 0.8725490196078431, 'f1': 0.9103448275862069}\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "inference_model.to(device)\n",
    "inference_model.eval()\n",
    "for step, batch in enumerate(tqdm(eval_dataloader)):\n",
    "    batch.to(device)\n",
    "    with torch.no_grad():\n",
    "        outputs = inference_model(**batch)\n",
    "    predictions = outputs.logits.argmax(dim=-1)\n",
    "    predictions, references = predictions, batch[\"labels\"]\n",
    "    metric.add_batch(\n",
    "        predictions=predictions,\n",
    "        references=references,\n",
    "    )\n",
    "\n",
    "eval_metric = metric.compute()\n",
    "print(eval_metric)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.11.9"
  },
  "vscode": {
   "interpreter": {
    "hash": "aee8b7b246df8f9039afb4144a1f6fd8d2ca17a180786b69acc140d282b71a49"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
