{ "cells": [ { "cell_type": "markdown", "id": "6d138c8bef4a12d7", "metadata": { "collapsed": false, "id": "6d138c8bef4a12d7" }, "source": [ "# UD03 · Notebook 5 — Ampliación: clasificador de géneros musicales\n", "\n", "Como ejemplo del uso de modelos que funcionan en audio, haremos un clasificador de géneros musicales. Para hacer esto, utilizaremos el conjunto de datos GTZAN, un conjunto de datos de 1000 muestras de audio etiquetadas con el género de la música." ] }, { "cell_type": "markdown", "id": "6c615f3690694907", "metadata": { "collapsed": false, "id": "6c615f3690694907" }, "source": [ "## Instalación de librerías\n", "Para ejecutar este cuaderno, necesitaremos instalar las siguientes librerías:" ] }, { "cell_type": "code", "execution_count": 1, "id": "2f002a03e6f0e117", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "2f002a03e6f0e117", "outputId": "4c4d3021-6a63-47e1-f4e0-56ea2323013a" }, "outputs": [], "source": [ "%pip install \"transformers<5\" datasets librosa soundfile torch accelerate evaluate\n", "\n", "import os\n", "os.environ[\"WANDB_DISABLED\"] = \"true\"\n" ] }, { "cell_type": "markdown", "id": "f506289975ff0d80", "metadata": { "collapsed": false, "id": "f506289975ff0d80" }, "source": [ "## Cargamos el dataset" ] }, { "cell_type": "code", "execution_count": 2, "id": "b232bb10c0394142", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "b232bb10c0394142", "outputId": "f283925b-3cb6-40e1-dfcf-4049d5e0dd2f" }, "outputs": [ { "output_type": "stream", "name": "stderr", "text": [ "/usr/local/lib/python3.12/dist-packages/huggingface_hub/utils/_auth.py:94: UserWarning: \n", "The secret `HF_TOKEN` does not exist in your Colab secrets.\n", "To authenticate with the Hugging Face Hub, create a token in your settings tab (https://huggingface.co/settings/tokens), set it as secret in your Google Colab and restart your session.\n", "You will be able to reuse this secret in all of your notebooks.\n", "Please note that authentication is recommended but still optional to access public models or datasets.\n", " warnings.warn(\n" ] }, { "output_type": "execute_result", "data": { "text/plain": [ "DatasetDict({\n", " train: Dataset({\n", " features: ['file', 'audio', 'genre'],\n", " num_rows: 999\n", " })\n", "})" ] }, "metadata": {}, "execution_count": 2 } ], "source": [ "from datasets import load_dataset\n", "\n", "gtzan = load_dataset(\"sanchit-gandhi/gtzan\", \"default\")\n", "gtzan" ] }, { "cell_type": "markdown", "id": "7926aead087f9ec4", "metadata": { "collapsed": false, "id": "7926aead087f9ec4" }, "source": [ "Como podemos ver, el conjunto de datos consta de 999 muestras de audio etiquetadas con el género de la música." ] }, { "cell_type": "markdown", "id": "588dcd3a3af20280", "metadata": { "collapsed": false, "id": "588dcd3a3af20280" }, "source": [ "Los audios están en formato de 22050 Hz, y el modelo necesita 16 kHz. Las versiones recientes de\n", "`datasets` exigen instalar `torchcodec` (y un FFmpeg compatible) para decodificar audio\n", "automáticamente con la clase `Audio`, algo que falla con facilidad según el entorno (versión de\n", "FFmpeg, sistema operativo...). Para evitar esa fragilidad, decodificamos el audio nosotros mismos\n", "con `soundfile` (lee los bytes) y `librosa` (remuestrea a 16 kHz), y guardamos el resultado en\n", "columnas propias (`array`, `sampling_rate`) en vez de depender del tipo `Audio`.\n" ] }, { "cell_type": "code", "execution_count": 3, "id": "eda1d3f9717dbc08", "metadata": { "id": "eda1d3f9717dbc08" }, "outputs": [], "source": [ "import io\n", "\n", "import soundfile as sf\n", "import librosa\n", "from datasets import Audio\n", "\n", "TARGET_SR = 16000\n", "\n", "\n", "def decodificar_audio(batch):\n", " \"\"\"Decodifica los bytes de audio a mano (sin torchcodec) y remuestrea a 16 kHz.\"\"\"\n", " arrays, sampling_rates = [], []\n", " for item in batch[\"audio\"]:\n", " data, sr = sf.read(io.BytesIO(item[\"bytes\"]))\n", " if data.ndim > 1:\n", " data = data.mean(axis=1) # a mono\n", " data = data.astype(\"float32\")\n", " if sr != TARGET_SR:\n", " data = librosa.resample(data, orig_sr=sr, target_sr=TARGET_SR)\n", " arrays.append(data)\n", " sampling_rates.append(TARGET_SR)\n", " return {\"array\": arrays, \"sampling_rate\": sampling_rates}\n", "\n", "\n", "gtzan = gtzan.cast_column(\"audio\", Audio(decode=False))\n", "gtzan = gtzan.map(\n", " decodificar_audio,\n", " batched=True,\n", " batch_size=16,\n", " remove_columns=[\"audio\"],\n", " desc=\"Decodificando audio\",\n", ")\n", "# (Ojo: NO usar gtzan.set_format('numpy', ...) aqui - en Colab con torchvision ya\n", "# importado, el formateador numpy de datasets intenta importar VideoReader de\n", "# torchvision.io y revienta si esa clase no existe en la version instalada. Basta con\n", "# convertir a numpy con np.array(...) justo donde haga falta, mas abajo.)\n" ] }, { "cell_type": "markdown", "id": "403ed153c7d5afba", "metadata": { "collapsed": false, "id": "403ed153c7d5afba" }, "source": [ "## Creación del conjunto de datos `test`\n", "\n", "Para evaluar el modelo necesitaremos un conjunto de datos de prueba. Para hacer esto, dividiremos el conjunto de datos en dos partes, uno para entrenar el modelo y otro para evaluarlo." ] }, { "cell_type": "code", "execution_count": 4, "id": "ac796c9364dc1882", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "ac796c9364dc1882", "outputId": "49c73c92-11d4-4145-a69b-19fec2340cc2" }, "outputs": [ { "output_type": "execute_result", "execution_count": 4, "data": { "text/plain": [ "DatasetDict({\n train: Dataset({\n features: ['file', 'genre', 'array', 'sampling_rate'],\n num_rows: 899\n })\n test: Dataset({\n features: ['file', 'genre', 'array', 'sampling_rate'],\n num_rows: 100\n })\n})" ] }, "metadata": {} } ], "source": [ "gtzan = gtzan[\"train\"].train_test_split(seed=42, shuffle=True, test_size=0.1)\n", "gtzan" ] }, { "cell_type": "markdown", "id": "dcf1f1bf669ab51d", "metadata": { "collapsed": false, "id": "dcf1f1bf669ab51d" }, "source": [ "Una vez que el conjunto de datos se ha separado en dos partes, el conjunto de datos de prueba contendrá 100 muestras de audio.\n", "\n", "A continuación, mostraremos una muestra del conjunto de datos de prueba." ] }, { "cell_type": "code", "execution_count": 5, "id": "4a6af0527a74e424", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, "id": "4a6af0527a74e424", "outputId": "64e7bf49-0562-4aa3-e40f-67ac198d84f9" }, "outputs": [ { "output_type": "execute_result", "execution_count": 5, "data": { "text/plain": [ "{'file': '/home/sanchit/.cache/datasets/downloads/extracted/f729783d70a4541cc4c9d5649655490a9c660280bdbecddfe38a8a806c73f60e/genres/pop/pop.00098.wav',\n 'genre': 7,\n 'array': [0.08735090494155884,\n 0.2018338441848755,\n 0.479086697101593,\n 0.35623201727867126,\n 0.21140910685062408],\n 'sampling_rate': 16000}" ] }, "metadata": {} } ], "source": [ "muestra = gtzan['train'][0]\n", "{**muestra, 'array': muestra['array'][:5]} # solo un fragmento, para no imprimir 480 000 numeros" ] }, { "cell_type": "markdown", "id": "467261b3fee3498c", "metadata": { "collapsed": false, "id": "467261b3fee3498c" }, "source": [ "De cada muestra del conjunto de datos podemos ver los siguientes datos:\n", "- `array`: audio en forma de lista de números. El valor de cada elemento representa la amplitud\n", " de la onda en un instante de tiempo. Como el `sampling_rate` es de 16000 Hz, esta lista tendrá\n", " 16 000 elementos por segundo. Se convierte a numpy con `np.array(...)` justo donde haga falta\n", " (al pipeline de clasificación, por ejemplo).\n", "- `sampling_rate`: la frecuencia de muestreo del audio, ya remuestreado a 16 kHz.\n", "- `genre`: el género de la música como entero. Podemos usar el método `int2str()` del `feature`\n", " `genre()` para obtener el género en formato texto.\n" ] }, { "cell_type": "code", "execution_count": 6, "id": "4fe2912b123d91a3", "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 36 }, "id": "4fe2912b123d91a3", "outputId": "1d3941f6-1c32-415f-ec55-bf7bcc56c456" }, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "'pop'" ], "application/vnd.google.colaboratory.intrinsic+json": { "type": "string" } }, "metadata": {}, "execution_count": 6 } ], "source": [ "int2str = gtzan[\"train\"].features[\"genre\"].int2str\n", "int2str(gtzan['train'][0]['genre'])" ] }, { "cell_type": "markdown", "id": "8ad7dcda21efd670", "metadata": { "collapsed": false, "id": "8ad7dcda21efd670" }, "source": [ "## Prueba del modelo sin entrenamiento\n", "\n", "Antes de comenzar a entrenar el modelo, probaremos el modelo sin entrenamiento para ver cómo se comporta. Usaremos el modelo `distilhubert`, un modelo previamente entrenado para clasificar el audio y fácil de refinar.\n", "Para usar el modelo usaremos la clase `pipeline` de la librería Transformers." ] }, { "cell_type": "code", "execution_count": 7, "id": "b68cd849ec18fc4c", "metadata": { "ExecuteTime": { "end_time": "2024-02-13T00:11:29.890682Z", "start_time": "2024-02-13T00:11:27.980985Z" }, "colab": { "base_uri": "https://localhost:8080/", "height": 186, "referenced_widgets": [ "1884aaa82a5c4ac68861defc29f034b7", "5e73d668486047f6986980760b48c20b", "3ea227dd21ff4eccb442b0f44d237107", "606968009ff44f25adcefb2326626b8a", "f5898ae6dcee41d1b4a4e24665191c3c", "8440392bcac14bbdae67b26939b61b22", "67d4ddf8ff13475eac57833016fecff4", "8f732641b2544002bae26109c6b0da44", "9dd48220f31042caad5ed464ec1a1983", "d5817d662615440684aa1a73cc4c01ff", "135ade73497a490092afca8566a719a7", "020df5c315104528976b65c5945f5a66", "88c95bad28b04ebea98d22be8ce9592c", "0205a075f17540f2a8abff74d49f7732", "007905026ff54cf28ae641ee5751eb4f", "8e8bb5c570af4ebbbb52b9a3723cb465", "6433babb79d94133ba08e823291e23f7", "03a4591ef45548a7a42a461dc0a3cd5a", "59c4c7552a594488ac11fc6f29a0d5ea", "297083a7239c446882f1d17e0cecb078", "5eb0c90c722144a7abfc8b264e4c9740", "989efe091fb248eab48110244582894e", "db41012c12064cc5b5966ec4ca86df84", "828ce8c6dd484ba8bcdd3e3a9e3601dc", "b9f9a29496804c1eb545cc3e9c773bde", "c3184fb04eb540aa8b7d478ae99f3790", "5ac52c7dd0734682b9285fb34a9b80b6", "68ea13dd89104b55b77995a5d528e964", "b4575292669b4c039162d027e57ffbf0", "25f16ebd05bc4b4b814377cb842b14b4", "e4fc5ce5a1b543488287b173fb4787d1", "c208d4203b5e4d77bf72ab4b3fefcb51", "4585660795c94c1daae791350615ee18" ] }, "id": "b68cd849ec18fc4c", "outputId": "2943d24d-9eef-4e2d-ec29-3db237e2630c" }, "outputs": [ { "output_type": "display_data", "data": { "text/plain": [ "config.json: 0.00B [00:00, ?B/s]" ], "application/vnd.jupyter.widget-view+json": { "version_major": 2, "version_minor": 0, "model_id": "1884aaa82a5c4ac68861defc29f034b7" } }, "metadata": {} }, { "output_type": "display_data", "data": { "text/plain": [ "model.safetensors: 0%| | 0.00/94.0M [00:00 1:\n", " datos = datos.mean(axis=1)\n", " datos = datos.astype(\"float32\")\n", " duracion_total = len(datos) / sr\n", " inicio = max(0, int((duracion_total / 2 - duracion / 2) * sr))\n", " fragmento = datos[inicio:inicio + int(duracion * sr)]\n", " if sr != TARGET_SR:\n", " fragmento = librosa.resample(fragmento, orig_sr=sr, target_sr=TARGET_SR)\n", " return fragmento\n", "\n", "\n", "# Si quieres probar con tu propia cancion (un mp3/ogg/wav local), descomenta y adapta:\n", "# datos, sr = librosa.load(\"mi_cancion.mp3\", sr=16000)\n", "# canciones[\"mi cancion\"] = datos # ya en 16 kHz, no hace falta pasar por cargar_cancion\n" ] }, { "cell_type": "code", "execution_count": null, "id": "5b3a829b89de65e4", "metadata": { "id": "5b3a829b89de65e4" }, "outputs": [ { "output_type": "stream", "name": "stdout", "text": [ "clasica (Beethoven, Sinfonia 5): classical (0.99)\ncountry (US Air Force Academy Band): country (0.96)\nreggae (US Air Force Band of the West): reggae (0.90)\nblues (US Air Force Band of Mid-America): jazz (0.80)\nrock (US Air Force Band of Flight): jazz (0.97)\n" ] } ], "source": [ "for nombre, valor in canciones.items():\n", " try:\n", " audio = cargar_cancion(valor) if isinstance(valor, str) else valor\n", " prediccion = music_classifier(audio)\n", " print(f\"{nombre}: {prediccion[0]['label']} ({prediccion[0]['score']:.2f})\")\n", " except requests.exceptions.RequestException as error:\n", " print(f\"{nombre}: descarga fallida, omitida ({error})\")\n" ] }, { "cell_type": "markdown", "id": "83d11617ee34", "metadata": { "collapsed": false, "id": "83d11617ee34" }, "source": [ "## Alternativa: clasifica tus propias canciones desde Colab\n", "\n", "Si las descargas de Wikimedia fallan (limite de peticiones, codigo 429) o simplemente quieres\n", "probar con tu propia musica, puedes subirla directamente a Colab en vez de depender de una URL\n", "externa:\n", "\n", "1. En el panel izquierdo de Colab, abre la pestana de archivos (icono de carpeta).\n", "2. Crea una carpeta llamada `SAMPLES`.\n", "3. Sube ahi 3-5 canciones tuyas (`.mp3`, `.wav`, `.ogg` o `.flac`).\n", "4. Ejecuta la siguiente celda: buscara y clasificara todo lo que encuentre en `SAMPLES/`.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "4ef9f24f958a", "metadata": { "id": "4ef9f24f958a" }, "outputs": [], "source": [ "from pathlib import Path\n", "\n", "\n", "def cargar_archivo_local(ruta, duracion=30):\n", " \"\"\"Lee un audio local, lo pasa a mono y recorta unos duracion segundos del centro, a 16 kHz.\"\"\"\n", " datos, sr = sf.read(ruta)\n", " if datos.ndim > 1:\n", " datos = datos.mean(axis=1)\n", " datos = datos.astype(\"float32\")\n", " duracion_total = len(datos) / sr\n", " inicio = max(0, int((duracion_total / 2 - duracion / 2) * sr))\n", " fragmento = datos[inicio:inicio + int(duracion * sr)]\n", " if sr != TARGET_SR:\n", " fragmento = librosa.resample(fragmento, orig_sr=sr, target_sr=TARGET_SR)\n", " return fragmento\n", "\n", "\n", "SAMPLES_DIR = Path(\"SAMPLES\")\n", "EXTENSIONES = (\"*.mp3\", \"*.wav\", \"*.ogg\", \"*.flac\")\n", "archivos = sorted(p for patron in EXTENSIONES for p in SAMPLES_DIR.glob(patron)) if SAMPLES_DIR.is_dir() else []\n", "\n", "if not archivos:\n", " print(\n", " f\"No se encontraron audios en '{SAMPLES_DIR}/'. Crea esa carpeta en el panel de archivos \"\n", " \"de Colab (icono de carpeta, a la izquierda) y sube ahi 3-5 canciones tuyas (mp3, wav, ogg o flac).\"\n", " )\n", "\n", "for ruta in archivos:\n", " try:\n", " audio = cargar_archivo_local(ruta)\n", " prediccion = music_classifier(audio)\n", " print(f\"{ruta.name}: {prediccion[0]['label']} ({prediccion[0]['score']:.2f})\")\n", " except Exception as error:\n", " print(f\"{ruta.name}: no se pudo clasificar ({error})\")\n" ] } ], "metadata": { "accelerator": "GPU", "colab": { "gpuType": "T4", "provenance": [] }, "kernelspec": { "display_name": "Python 3", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.6" } }, "nbformat": 4, "nbformat_minor": 5 }