{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "Fckbum3zrZ8W" }, "source": [ "**Redes Neuronales para Lenguaje Natural, 2023**\n", "\n", "---\n", "# **Laboratorio 2**\n", "\n", "\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "id": "E1WFrxI9Bt2X" }, "source": [ "El objetivo de este laboratorio es experimentar con grandes modelos de lenguajes (*LLMs* por *Large Language Models*) para la construcción de sistemas de respuestas a preguntas extrayéndolas de un texto brindado como contexto (*extractive Question-Answering o extractive QA*). Debe utilizar *prompts* adecuados, técnicas de *prompting* y/o *fine-tuning* para realizar la extracción de la respuesta lo más **textual** posible .\n", "\n", "\n", "**Entrega: lunes 20 de noviembre**\n", "\n", "**Debe entregar todas las salidas obtenidas en una ejecución secuencial de los bloques de este notebook**\n", "\n", "---" ] }, { "cell_type": "markdown", "source": [ "**Nro Grupo:** [número de grupo]\n", "\n", "**Inegrantes:** [integrantes del grupo]" ], "metadata": { "id": "aA_Kjr4HPhUe" } }, { "cell_type": "markdown", "metadata": { "id": "duNq1TVN_Llu" }, "source": [ "## **Configuración del entorno**" ] }, { "cell_type": "markdown", "source": [ "A continuación se instalarán algunas librerías que serán útiles a lo largo de este notebook. En caso de instalar otras librerías puede hacerlo en esta sección." ], "metadata": { "id": "1E0rARUZ6yov" } }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "lJHOJf9OVMci" }, "outputs": [], "source": [ "#@title Instalación de librerias\n", "!pip install transformers\n", "!pip install bitsandbytes\n", "!pip install accelerate\n", "!pip install bert-score" ] }, { "cell_type": "markdown", "source": [ "Otras librerías:" ], "metadata": { "id": "QgHY9aWa7Wpp" } }, { "cell_type": "code", "source": [ "# Otras librerías\n", "#!pip install .." ], "metadata": { "id": "U0gE_iLN7Bwz" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "Al imprimir las salidas generadas por los LLMs resulta útil ajustar el texto al ancho de la pantalla para visualizarlo:" ], "metadata": { "id": "Nkvmpgou7RS7" } }, { "cell_type": "code", "source": [ "#@title Estilo de salida de colab\n", "from IPython.display import HTML, display\n", "pre_run_cell_fn = lambda: display(HTML(''''''))\n", "get_ipython().events.register('pre_run_cell', pre_run_cell_fn)" ], "metadata": { "id": "hKuaFtkc5Esq" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "BQUZa644cuNN" }, "source": [ "## **Acceso a LLaMA 2**\n", "\n", "\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "id": "5rfny1q_BOnh" }, "source": [ "Para construir el sistema de QA, una alternativa sugerida es utilizar la familia de modelos de LLaMA 2 de Meta mediante Hugging Face (https://huggingface.co/meta-llama). Se recomienda leer https://huggingface.co/blog/llama2\n", "\n", "\n", "\n", "Para usar LLaMA debe seguir los siguientes pasos:\n", "\n", "- Llenar el siguiente formulario: https://ai.meta.com/resources/models-and-libraries/llama-downloads/\n", "- Aceptar los términos de Hugging Face (con el mismo mail ingresado en el formulario anterior) que aparecen bajo el título \"Access Llama 2 on Hugging Face\" en: https://huggingface.co/meta-llama/Llama-2-7b\n", "- Ejecutar la siguiente celda e ingresar un token (con 'write' access) asociado a la cuenta de Hugging Face (para obtener el token: https://huggingface.co/settings/tokens).\n", "\n", "**Se recomienda ejecutar los modelos en GPU**\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "dpJk4QSGAPkE" }, "outputs": [], "source": [ "#@title Conectar con Hugging Face\n", "from huggingface_hub import notebook_login\n", "\n", "notebook_login()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "LVPu5MUiTBog" }, "outputs": [], "source": [ "#@title Instanciar modelo y tokenizer\n", "import torch\n", "import tempfile\n", "from transformers import AutoModelForCausalLM, AutoTokenizer\n", "\n", "model_name = \"meta-llama/Llama-2-7b-chat-hf\" #@param [\"meta-llama/Llama-2-7b\", \"meta-llama/Llama-2-7b-chat-hf\", \"meta-llama/Llama-2-13b\", \"meta-llama/Llama-2-13b-chat-hf\", \"Open-Orca/OpenOrca-Platypus2-13B\", \"stabilityai/StableBeluga-13B\", \"tiiuae/falcon-40b\", \"tiiuae/falcon-40b-instruct\"]\n", "\n", "def obtener_modelo_y_tokenizer(model_name):\n", " tokenizer = AutoTokenizer.from_pretrained(model_name)\n", " model_llm = AutoModelForCausalLM.from_pretrained(\n", " model_name,\n", " load_in_4bit=True,\n", " torch_dtype=torch.bfloat16,\n", " device_map=\"auto\",\n", " )\n", " return model_llm, tokenizer\n" ] }, { "cell_type": "markdown", "metadata": { "id": "TBNR5Nz6hEkj" }, "source": [ "### **Formatos de *prompt* de los modelos**\n", "\n", "Los modelos que vamos a utilizar pasaron por un proceso de fine-tuning y/o RLHF (Reinforcement Learning from Human Feedback), y tienen como formato de prompt el mismo que fue utilizado en para el entrenamiento. Para usar cada modelo adecuadamente hay que respetar su formato.\n", "\n", "A continuación se muestran los formatos de los modelos seleccionables en el bloque anterior:\n", "\n", "\n", "\n", "- LLaMA-2-chat-hf\n", " ```\n", " \\[INST] \n", " <\\>\n", " Instrucciones de la tarea\n", " <\\>\n", "\n", " Instancia particular de la tarea (por ej. pregunta del usuario).\n", " [/INST]\n", " ```\n", "\n", "- OpenOrca-Platypus2\n", " ```\n", " \\### Instruction:\n", "\n", " Instrucciones e instancia particular de la tarea.\n", "\n", " \\### Response:\n", " ```\n", "\n", "\n", "- StableBeluga\n", " ```\n", " \\### System:\n", "\n", " Instrucciones de la tarea.\n", "\n", " \\### User:\n", "\n", " Instancia particular de la tarea.\n", "\n", " \\### Assistant\n", " ```\n", "\n", "\n", "Los modelos **Llama-2-7b** y **Llama-2-13b** son **LLaMA 2 base** y no tienen un formato de prompt." ] }, { "cell_type": "markdown", "metadata": { "id": "YAxRa4Hji2SG" }, "source": [ "## **Ejemplo de uso**\n" ] }, { "cell_type": "markdown", "metadata": { "id": "WZ7MPPclBT64" }, "source": [ "\n", "A continuación se muestra un ejemplo de uso de **LLaMA-2-chat-hf**. El ejemplo está dividido en dos partes:\n", "1. Construir el prompt para LLaMA-2-chat-hf\n", "2. Instanciar modelo y generar una respuesta para el prompt construido" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "OO3GbgjyY4MM" }, "outputs": [], "source": [ "#@title 1. Contruir prompt\n", "\n", "instrucciones = \"\"\"\n", "Responde en Español y la respuesta tiene que ser extraída del siguiente texto:\n", "\n", "Comisión de expertos del FA elaboró “plan de contención económica social”\n", "Ante las consecuencias económicas de la propagación del coronavirus y las\n", "medidas anunciadas por el Ejecutivo, una comisión de expertos del Frente\n", "Amplio (FA) elaboró “un plan de contención económica social” para mitigar\n", "el impacto de la emergencia sa\n", "nitaria. El borrador del proyecto, al que\n", "accedió la diaria, se basa en un “aumento transitorio del gasto y de la\n", "inversión pública para minimizar el efecto de la desaceleración económica\n", "y su impacto en la población” por medio de un conjunto de medidas estructuradas\n", "en tres objetivos: “Preservar las empresas y las y los trabajadores”, “no\n", "aumentar la pobreza y la desigualdad” y prepararse para “el día después”.\n", "La comisión designada por la coalición de izquierda para elaborar la propuesta\n", "está integrada por el actual senador y ex ministro de Economía y Finanzas Danilo\n", "Astori, el senador Daniel Olesker, el ex ministro de Trabajo y Seguridad Social\n", "Ernesto Murro y la economista Andrea Vigorito.\n", "\"\"\"\n", "\n", "pregunta = \"\"\"\n", "¿Para qué se elaboró el “plan de contención económica social”?\n", "\"\"\"\n", "\n", "prompt = f'[INST] <> {instrucciones} <> {pregunta} [/INST]'\n", "print(prompt)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "Jyf3eQPB4zQz" }, "outputs": [], "source": [ "#@title 2. Generar respuesta\n", "from transformers import GenerationConfig, pipeline, logging\n", "from transformers.utils import logging\n", "\n", "logging.set_verbosity(logging.CRITICAL)\n", "\n", "# instanciar modelo y tokenizer\n", "model_llm, tokenizer = obtener_modelo_y_tokenizer(\"meta-llama/Llama-2-7b-chat-hf\")" ] }, { "cell_type": "code", "source": [ "def obtener_respuesta(prompt, model=model_llm, tokenizer=tokenizer, temp=0.7, max_tok=500):\n", " cfg = GenerationConfig(temperature=temp, num_return_sequences=1,\n", " max_length=750, do_sample=True)\n", " p = pipeline(\"text-generation\", model=model, config=cfg, tokenizer=tokenizer)\n", " output = p(prompt, return_full_text=False, max_new_tokens=max_tok)\n", " return output[0]['generated_text']" ], "metadata": { "id": "DqAxTwiWstnS" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "ocXNAkDU-gEQ" }, "outputs": [], "source": [ "resp = obtener_respuesta(prompt).strip()\n", "print(resp)\n", "# Respuesta esperada: \"mitigar el impacto de la emergencia sanitaria\"" ] }, { "cell_type": "markdown", "metadata": { "id": "DybX8PIa8q7y" }, "source": [ "## **Conjunto de datos (QuALES covid19-qa)**\n", "\n" ] }, { "cell_type": "markdown", "metadata": { "id": "8ToKNuu0BZD_" }, "source": [ "\n", "Para construir el sistema de QA extrativo utilizará el conjunto de datos de la compentencia QuALES (https://codalab.lisn.upsaclay.fr/competitions/2619)\n", "\n", "Los conjuntos están separados en train, dev y test. Cada partición tiene un conjunto de textos, a cada texto le corresponde un título y párrafos que tienen asociados un conjunto de preguntas con una o más respuestas para cada pregunta.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "5qsRkc49kHrX" }, "outputs": [], "source": [ "#@title Descargar dataset\n", "import locale\n", "locale.getpreferredencoding = lambda: \"UTF-8\" # temporary fix: https://github.com/googlecolab/colabtools/issues/3409\n", "\n", "! wget https://eva.fing.edu.uy/mod/resource/view.php?id=197167 -O dataset_covid_qa.zip\n", "! unzip dataset_covid_qa.zip" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "xiHxs-2jkY_V" }, "outputs": [], "source": [ "#@title Cargar dataset en memoria\n", "import json\n", "\n", "with open('./dataset_covid_qa_train.json', 'r', encoding='utf-8') as f:\n", " train_data = json.load(f)\n", "with open('./dataset_covid_qa_dev.json', 'r', encoding='utf-8') as f:\n", " dev_data = json.load(f)\n", "with open('./dataset_covid_qa_test.json', 'r', encoding='utf-8') as f:\n", " test_data = json.load(f)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "b6OEDcnwnJAl" }, "outputs": [], "source": [ "#@title Ejemplo de datos del dataset\n", "\n", "print(json.dumps(train_data['data'][:2], indent=1, ensure_ascii=False))\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "KmzCbMVeeJWl" }, "outputs": [], "source": [ "#@title Preprocesar dataset\n", "\n", "def prepare_data(data_json):\n", " ret = []\n", " nqas = 0\n", " nans = 0\n", " for d in data_json['data']:\n", " for p in d['paragraphs']:\n", " context = p['context']\n", " qas = [(qa['question'],[ans['text'] for ans in qa['answers']]) for qa in p['qas']]\n", " ret += [(context, qas)]\n", " nqas += len(qas)\n", " nans += len([a for _,ans in qas for a in ans])\n", " print(nqas, \"preguntas\", f\"({nans} respuestas)\")\n", " return ret\n", "\n", "\n", "train_d = prepare_data(train_data)\n", "dev_d = prepare_data(dev_data)\n", "test_d = prepare_data(test_data)" ] }, { "cell_type": "markdown", "metadata": { "id": "49Rn1X0y_xd1" }, "source": [ "## **Evaluación**\n" ] }, { "cell_type": "markdown", "metadata": { "id": "3sOLRUG7BggS" }, "source": [ "Para evaluar el desempeño de los modelos se debe reportar las siguientes medidas:\n", "- *F* de **BERTScore** https://huggingface.co/spaces/evaluate-metric/bertscore\n", "- *Accuracy* de **match exactos**" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "3jVVNzZnDGPg" }, "outputs": [], "source": [ "pred = ['mitigar el impacto de la emergencia sanitaria',\n", " 'Danilo Astori, el senador Daniel Olesker, el ex ministro',\n", " 'preservación de los puestos de trabajo']\n", "ref = ['mitigar el impacto de la emergencia sanitaria',\n", " 'Danilo Astori, el senador Daniel Olesker, el ex ministro de Trabajo y Seguridad Social Ernesto Murro y la economista Andrea Vigorito',\n", " 'preservación de los puestos de los trabajadores']" ] }, { "cell_type": "code", "source": [ "from bert_score import score\n", "\n", "def calcular_f_bert_avg(pred,ref):\n", " s = score(pred, ref, lang=\"es\")\n", " return s[2].mean()\n", "\n", "print(\"F-BERTScore (avg):\", calcular_f_bert_avg(pred,ref))" ], "metadata": { "id": "y616_iMIPyKy" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "from sklearn.metrics import precision_recall_fscore_support\n", "\n", "\n", "def caclular_acc_exact(pred, ref):\n", " n = 0\n", " for r,p in zip(ref,pred):\n", " n += int(r == p)\n", " return float(n)/len(ref)\n", "\n", "print(\"Acc-exact:\", caclular_acc_exact(pred, ref))" ], "metadata": { "id": "JyIuFWhGP4w0" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "## **Ejemplo de sistema de QA con Llama-2-7b-chat-hf y dataset impartido**\n" ], "metadata": { "id": "IxS8GkmvLTRO" } }, { "cell_type": "markdown", "source": [ "A continuación se muestra un ejemplo de sistema con **Llama-2-7b-chat-hf** y el dataset impartido." ], "metadata": { "id": "x_BQ13e0LsSd" } }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "iCZvobHK3D_b" }, "outputs": [], "source": [ "#@title Generar prompt a partir de datos\n", "\n", "def generar_prompt_qa(contexto, qas):\n", " sistema = f\"\"\"\n", " Responde en Español y en menos de {len(qas)*15} palabras.\n", " La respuesta tiene que ser extraída del siguiente texto:\n", " {contexto}\n", " \"\"\"\n", "\n", " preguntas = \"\"\n", "\n", " for i,(p,_) in enumerate(qas):\n", " preguntas += f\"{i}. {p} \\n\"\n", "\n", " return f'''[INST] <> {sistema} <> {preguntas} [/INST]'''" ] }, { "cell_type": "code", "source": [ "#@title Obtener respuesta para el primer elemento del dataset\n", "\n", "p1 = generar_prompt_qa(train_d[0][0],train_d[0][1])\n", "r1 = obtener_respuesta(p1).strip()\n", "print(\"PROMT:\\n\", p1)\n", "print(\"\\n\\nRESPUESTA:\\n\", r1)" ], "metadata": { "id": "aIhVJZI5_bDY" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "#@title Extraer la respuesta a cada pregunta (expresiones regulares)\n", "\n", "import re\n", "\n", "def extraer_respuestas(r):\n", " return re.findall(r'\\d\\. (.*)', r)" ], "metadata": { "id": "YMAH6n82BqRU" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "#@title Imprimir respuestas generadas y esperadas\n", "\n", "r1_gen = extraer_respuestas(r1)\n", "r1_datos = [a[0] for q,a in train_d[0][1]]\n", "\n", "for i,(a,b) in enumerate(zip(r1_datos, r1_gen)):\n", " print(i)\n", " print(\"GENERADA: \", b)\n", " print(\"ESPERADA: \", a)" ], "metadata": { "id": "tXV0etsWEzFP" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "print(\"F-BERTScore (avg):\", calcular_f_bert_avg(r1_gen,r1_datos))\n", "print(\"Acc-exact:\", caclular_acc_exact(r1_gen, r1_datos))" ], "metadata": { "id": "6hMEvmj4LA0F" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "Ejecutar en **todo** el dataset de *test*:" ], "metadata": { "id": "7nAJe34RcrnW" } }, { "cell_type": "code", "source": [ "\n", "r_gen_tot = []\n", "r_datos_tot = []\n", "\n", "for text, qas in test_d:\n", " try:\n", " p = generar_prompt_qa(text,qas)\n", " r = obtener_respuesta(p).strip()\n", " r_gen = extraer_respuestas(r)\n", " r_datos = [a[0] for q,a in qas]\n", " print(len(r_gen),len(r_datos))\n", " if len(r_gen) == len(r_datos):\n", " r_gen_tot += r_gen\n", " r_datos_tot += r_datos\n", " else:\n", " r_datos_tot += r_datos\n", " r_gen_tot += [\"\"] * len(r_datos)\n", " except:\n", " print(\"error\")\n", " r_datos_tot += r_datos\n", " r_gen_tot += [\"\"] * len(r_datos)\n", "\n" ], "metadata": { "id": "YU8MRgYpgU4u" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [ "print(\"F-BERTScore (avg):\", calcular_f_bert_avg(r_gen_tot,r_datos_tot))\n", "print(\"Acc-exact:\", caclular_acc_exact(r_gen_tot, r_datos_tot))" ], "metadata": { "id": "85z9ecpVFwn5" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "MfQLcZl05ZfQ" }, "source": [ "# **Se Pide**\n" ] }, { "cell_type": "markdown", "metadata": { "id": "kniIX70LBlZ2" }, "source": [ "\n", "Se pide implementar un sistema de respuestas a preguntas extrayendo la respuesta de un texto dado. Para la implementación se sugiere utilizar LLaMA 2 y mecanismos de *prompting*, con el dataset impartido.\n", "\n", "Para \"ajustar\" cada modelo tiene disponible los datasets de *train* y *dev*. Los resultados deben ser reportados sobre *test*.\n", "\n", "\n", "**Se pide:**\n", " - Reportar experimentos con al menos 3 modelos distintos\n", " - Utilizar al menos 3 prompts diferentes (independientemente del formato de prompt)\n", " - Debe reportar resultados para al menos 5 experimentos distintos\n", " - Comente resultados o problemas que considere interesantes \n" ] }, { "cell_type": "markdown", "source": [ "### **Experimento 1**" ], "metadata": { "id": "dwHDIHisbIIK" } }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "919gBZszchUp" }, "outputs": [], "source": [ "## Código y secciones para el experimento" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "IzhvFl5U-m6r" }, "outputs": [], "source": [] }, { "cell_type": "markdown", "source": [ "### **Experimento 2**" ], "metadata": { "id": "P7yFqI60oV_R" } }, { "cell_type": "code", "source": [ "## Código y secciones para el experimento" ], "metadata": { "id": "LK-i_CKXocET" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [], "metadata": { "id": "KNVUeho4ocG8" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "### **Experimento 3**" ], "metadata": { "id": "-6RExSC6odyh" } }, { "cell_type": "code", "source": [ "## Código y secciones para el experimento" ], "metadata": { "id": "syGbz7PeoeH3" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [], "metadata": { "id": "5fhSdd6VoeKz" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "### **Experimento 4**" ], "metadata": { "id": "YktduH49osnh" } }, { "cell_type": "code", "source": [ "## Código y secciones para el experimento" ], "metadata": { "id": "nibYZaPsotGG" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [], "metadata": { "id": "XtdSABOwotI8" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "source": [ "### **Experimento 5**" ], "metadata": { "id": "eQL6t3lnotdx" } }, { "cell_type": "code", "source": [ "## Código y secciones para el experimento" ], "metadata": { "id": "i4sr5TDiot-q" }, "execution_count": null, "outputs": [] }, { "cell_type": "code", "source": [], "metadata": { "id": "7I-69nB5ouBY" }, "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "K_sxe2mm8oPL" }, "source": [ "# **Resultados Obtenidos**\n", "\n", "En esta sección debe reportar los resultados obtenidos en los experimentos realizados.\n", "\n", "Los experimentos realizados deben estar numerados (1, 2, etc.). El experimento 0 corresponde a la linea base impartida en este notebook.\n", "\n", "Para cada experimento reportado se debe dar una breve descripción indicando el LLM utilizado y detalles sobre el experimento.\n", "\n", "Se deben indicar las medidas F_bertscore y Acc_exact en la partición *test* para todos los experimentos reportados.\n", "\n" ] }, { "cell_type": "markdown", "source": [ "### Tabla de resultados\n", "\n", "Exp | Descripción | Acc_exact | F_bertscore\n", "--- | --- | --- | ---\n", "0 | LLaMA-2-chat-hf con prompt recibiendo el texto, las preguntas numeradas y limitando la cantidad de palabras de la respuesta | 0.078 | 0.32\n" ], "metadata": { "id": "CH-IBB55nbUt" } } ], "metadata": { "accelerator": "GPU", "colab": { "provenance": [], "gpuType": "T4" }, "kernelspec": { "display_name": "Python 3", "name": "python3" }, "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 0 }