{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# UD03 · Notebook 8 — Del texto al vector\n", "\n", "**Requisitos**\n", "\n", "Va en el **contenedor de la unidad** (`docker compose up -d` en la carpeta de la UD03), o en\n", "cualquier Python 3.12+ con:\n", "\n", "```bash\n", "pip install nltk spacy scikit-learn\n", "python -m spacy download es_core_news_sm\n", "```\n", "\n", "Este taller **no necesita GPU ni Colab**.\n", "\n", "**Objetivo**: construir un clasificador que determine si una reseña es positiva o negativa,\n", "usando un dataset anotado por el alumnado y `scikit-learn`.\n", "\n", "Es una **entrega evaluable**: se corrige con la rúbrica de su tarea en Moodle." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Fase 1 — Crea tu dataset (origen y licencia)\n", "\n", "Crea un fichero `reseñas.csv` con al menos **30 reseñas cortas** en español (por parejas: 20 de\n", "entrenamiento, 10 de prueba) y su etiqueta (`1` = positiva, `0` = negativa). Anota en el informe el\n", "**origen** (opiniones propias o de un restaurante ficticio) y la **licencia** (p. ej. anotación\n", "propia bajo CC BY).\n", "\n", "| texto | etiqueta |\n", "|---|---|\n", "| La comida estaba riquísima y el servicio rápido | 1 |\n", "| Pedimos y el plato llegó frío | 0 |\n", "| ... | ... |" ] }, { "cell_type": "code", "metadata": {}, "execution_count": null, "outputs": [], "source": [ "reseñas = [\n", " # TU CÓDIGO: al menos 20 frases, mitad positivas (1) y mitad negativas (0)\n", "]\n", "etiquetas = [\n", " # TU CÓDIGO: la etiqueta de cada reseña, en el mismo orden\n", "]\n", "\n", "print(f\"{len(reseñas)} reseñas · {sum(etiquetas)} positivas · {len(etiquetas)-sum(etiquetas)} negativas\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**✏️ Respuesta**: ¿de dónde has sacado las reseñas? ¿Podrías redistribuirlas? Indica origen y\n", "licencia — es el paso 2 de la metodología del §12.1, y aquí se hace primero porque **sin datos no\n", "hay nada que preprocesar**.\n", "\n", "*(escribe aquí)*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Fase 2 — Preprocesado con nltk\n", "\n", "Aplica `limpiar` a todas las reseñas y muestra 3 ejemplos antes/después." ] }, { "cell_type": "code", "metadata": {}, "execution_count": null, "outputs": [], "source": [ "import nltk\n", "from nltk.tokenize import word_tokenize\n", "from nltk.corpus import stopwords\n", "\n", "nltk.download(['punkt_tab', 'stopwords'], quiet=True)\n", "vacias = set(stopwords.words('spanish'))\n", "\n", "def preprocesar(texto):\n", " # TU CÓDIGO: tokeniza, pasa a minusculas y quita las stopwords\n", " ...\n", "\n", "reseñas_proc = [preprocesar(r) for r in reseñas]\n", "print(reseñas_proc[0])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Fase 3 — Vectorizar y entrenar" ] }, { "cell_type": "code", "metadata": {}, "execution_count": null, "outputs": [], "source": [ "from sklearn.feature_extraction.text import TfidfVectorizer\n", "from sklearn.linear_model import LogisticRegression\n", "from sklearn.model_selection import train_test_split\n", "\n", "# TU CÓDIGO: vectoriza (recuerda: stop_words solo acepta 'english', para español pasa la lista)\n", "# y entrena un LogisticRegression\n", "X_train, X_test, y_train, y_test = ...\n", "modelo = ...\n", "print(\"Exactitud:\", modelo.score(X_test, y_test))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Fase 4 — Matriz de confusión\n", "\n", "Interpeta la matriz: ¿cuántos positivos correctos, cuántos falsos positivos?" ] }, { "cell_type": "code", "metadata": {}, "execution_count": null, "outputs": [], "source": [ "from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay\n", "import matplotlib\n", "matplotlib.use('Agg') # en Colab o Jupyter con pantalla, quita esta linea\n", "\n", "# TU CÓDIGO\n", "pred = ...\n", "ConfusionMatrixDisplay(confusion_matrix(y_test, pred)).plot()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**✏️ Respuesta**: ¿qué confunde más el modelo, falsos positivos o falsos negativos? ¿Tiene\n", "sentido con el tamaño de tu *dataset*?\n", "\n", "*(escribe aquí)*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Fase 5 — Prueba con reseñas nuevas" ] }, { "cell_type": "code", "metadata": {}, "execution_count": null, "outputs": [], "source": [ "nuevas = [\n", " # TU CÓDIGO: tres reseñas nuevas, tuyas\n", "]\n", "# TU CÓDIGO: vectorizalas con el MISMO vectorizador (no entrenes uno nuevo) y predice\n", "..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Fase 6 — Análisis de errores\n", "\n", "Busca en el test al menos **2 errores** del modelo e intenta explicarlos (jerga, ironía,\n", "ambigüedad, vocabulario nuevo). Documenta la conclusión." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**✏️ Respuesta**: para cada error, ¿qué tipo de ambigüedad lo explica —léxica, pragmática,\n", "ironía—? Es el paso 5 de la metodología: **mirar los errores**, no solo la exactitud.\n", "\n", "*(escribe aquí)*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Entrega\n", "\n", "Sube el **notebook ejecutado**. Se valora que **interpretes** los resultados, no solo que las celdas\n", "den un número.\n", "\n", "| Fase | Evidencia mínima |\n", "|---|---|\n", "| 1 | El conjunto de reseñas, con su **origen y licencia** declarados |\n", "| 2 | El texto preprocesado: tokens, sin *stopwords*, normalizado |\n", "| 3 | El clasificador entrenado y su exactitud sobre datos **no vistos** |\n", "| 4 | La matriz de confusión, **interpretada**: qué confunde con qué |\n", "| 5 | Las predicciones sobre reseñas nuevas, tuyas |\n", "| 6 | **Dos errores leídos uno a uno** y qué tipo de ambigüedad los explica |\n", "\n", "**Soluciones**\n", "\n", "Las soluciones no se publican: se corrigen y comentan en clase." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "name": "python", "version": "3.12" } }, "nbformat": 4, "nbformat_minor": 5 }