{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n# Perceptual Evaluation of Text-to-Speech with PESQ\n\nConsider a use case where we want to find the highest-quality speaker signal based on an example target voice. Using a text-to-speech model, we generate speech for five different synthetic speakers, each with unique speaker embeddings. We then compare each generated voice to a reference speaker using Perceptual Evaluation of Speech Quality (PESQ), a metric that assesses how closely the generated audio matches the target.\n\nBy ranking the PESQ scores, we identify which synthetic speaker sounds most natural and which performs the worst, providing insights into improving speech synthesis quality.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Import necessary libraries\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import numpy as np\nimport torch\nfrom IPython.display import Audio\nfrom transformers import pipeline\n\nfrom torchmetrics.audio import PerceptualEvaluationSpeechQuality\n\n# Set seed for reproducibility\ntorch.manual_seed(42)\nnp.random.seed(42)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Define the test string and number of speakers\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "TEST_STRING = \"Hello, my dog is cooler than you!\"\nn_speakers = 5\n\n# Generate random speaker embeddings\nspeaker_embeddings = [torch.randn(1, 512) for _ in range(n_speakers)]\nspeaker_embeddings = [e / e.norm() for e in speaker_embeddings] # Normalize the embeddings" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Load the text-to-speech pipeline\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "pipe = pipeline(\"text-to-speech\", model=\"microsoft/speecht5_tts\")\n\n# Placeholder for storing audio data\naudio_fragments = []" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Synthesize speech for each speaker\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "for idx, e in enumerate(speaker_embeddings):\n speech = pipe(TEST_STRING, forward_params={\"speaker_embeddings\": e})\n audio_fragments.append((speech[\"audio\"], speech[\"sampling_rate\"]))\n print(f\"Generated speech for speaker {idx + 1}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Generate target audio using the target speaker embedding (512-dimensional X-vector)\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# fmt: off\nTARGET_EMBEDDING = torch.Tensor([\n [\n -0.075, 0.003, 0.037, 0.035, -0.005, -0.034, -0.087, 0.028, 0.041, 0.015, -0.076, -0.096, 0.052, 0.042, 0.042,\n 0.054, 0.017, 0.033, 0.009, 0.02, 0.03, 0.01, -0.012, -0.033, -0.063, -0.008, -0.061, -0.011, 0.04, 0.039, -0.004,\n 0.065, 0.035, -0.002, 0.053, -0.047, 0.007, 0.052, 0.002, -0.058, 0.006, -0.004, 0.041, 0.048, 0.024, -0.115,\n -0.018, 0.012, -0.07, 0.045, 0.01, 0.028, 0.034, 0.044, -0.108, -0.057, -0.009, 0.013, 0.023, 0.021, 0.002, -0.007,\n -0.016, -0.02, 0.029, 0.031, 0.031, -0.042, -0.074, -0.059, 0.005, 0.01, 0.024, 0.007, 0.027, 0.038, 0.033, -0.003,\n -0.086, -0.085, -0.07, -0.06, -0.052, -0.059, -0.032, -0.076, -0.066, 0.032, 0.032, -0.034, 0.029, -0.06, 0.02,\n -0.079, 0.05, -0.033, 0.049, 0.028, -0.078, -0.061, 0.047, -0.055, -0.107, 0.021, 0.047, 0.024, 0.07, 0.03, 0.03,\n 0.038, -0.088, -0.011, 0.081, 0.008, 0.034, 0.065, -0.058, 0.02, -0.05, 0.036, 0.035, -0.059, 0.012, 0.054, -0.06,\n 0.046, -0.074, 0.041, 0.035, 0.049, -0.016, 0.029, 0.029, 0.055, 0.014, -0.073, -0.061, 0.038, -0.066, -0.015,\n 0.022, 0.002, -0.046, 0.058, -0.085, 0.024, 0.018, -0.021, 0.004, -0.106, 0.03, -0.05, -0.078, 0.008, 0.037, 0.041,\n 0.049, -0.092, -0.073, 0.039, 0.034, 0.033, 0.025, 0.01, -0.039, 0.004, 0.013, 0.017, 0.033, 0.039, 0.012, -0.07,\n 0.017, -0.074, -0.027, 0.011, -0.045, 0.016, 0.054, -0.085, 0.028, -0.057, 0.013, 0.006, -0.077, -0.012, 0.04,\n 0.026, -0.07, -0.06, 0.041, 0.022, -0.066, 0.016, 0.026, 0.013, 0.032, 0.019, 0.045, -0.024, 0.046, 0.038, -0.061,\n 0.013, 0.016, 0.013, 0.033, 0.027, 0.037, 0.022, 0.003, -0.065, -0.062, 0.043, -0.056, 0.042, 0.024, -0.059, 0.033,\n 0.029, -0.059, -0.003, -0.069, -0.058, -0.055, 0.041, 0.058, 0.077, 0.063, 0.03, -0.025, 0.048, 0.047, -0.02, 0.028,\n -0.009, 0.05, -0.002, 0.004, 0.054, -0.07, 0.02, -0.087, 0.004, -0.068, 0.029, 0.042, 0.032, 0.033, 0.035, 0.05,\n 0.013, 0.007, -0.06, 0.015, 0.041, 0.033, 0.037, -0.066, 0.069, 0.007, -0.059, 0.059, 0.027, -0.001, 0.046, 0.032,\n 0.043, 0.029, 0.01, 0.029, 0.001, -0.027, 0.013, -0.079, 0.024, 0.026, 0.041, -0.064, -0.048, -0.009, 0.024, 0.041,\n -0.079, 0.029, 0.052, 0.006, 0.033, -0.104, 0.004, 0.019, 0.012, 0.045, -0.055, 0.034, 0.002, 0.028, -0.026, 0.03,\n 0.025, -0.039, 0.047, 0.022, -0.074, 0.012, 0.039, 0.014, 0.02, 0.035, 0.048, 0.032, 0.021, -0.005, 0.033, -0.088,\n -0.058, -0.019, 0.01, -0.067, 0.045, -0.044, 0.027, -0.035, 0.008, 0.034, -0.074, 0.038, 0.049, -0.044, -0.093,\n -0.046, 0.004, 0.021, 0.041, -0.066, 0.05, 0.044, 0.005, -0.025, 0.03, 0.016, -0.05, 0.015, 0.015, -0.067, 0.029,\n 0.051, 0.028, -0.062, -0.067, -0.054, 0.009, -0.056, 0.099, 0.024, -0.045, -0.005, 0.038, -0.043, 0.033, -0.097,\n 0.025, -0.002, 0.041, 0.048, 0.017, -0.063, 0.003, 0.01, 0.026, 0.006, 0.036, -0.058, 0.026, -0.015, -0.002, 0.042,\n 0.022, 0.041, 0.03, -0.073, -0.113, 0.047, 0.017, 0.02, 0.017, 0.034, -0.056, 0.028, 0.065, 0.02, 0.026, -0.023,\n 0.051, -0.004, -0.013, 0.038, -0.071, -0.001, -0.01, 0.027, -0.046, -0.032, 0.009, 0.005, 0.01, 0.005, -0.059,\n -0.047, -0.081, -0.049, 0.024, 0.001, -0.01, 0.038, -0.054, -0.004, -0.081, -0.134, -0.02, -0.065, 0.003, 0.024,\n -0.01, -0.062, 0.038, 0.06, 0.035, 0.015, -0.043, -0.041, -0.011, -0.021, 0.031, 0.026, 0.017, 0.052, 0.02, 0.028,\n -0.077, 0.025, 0.029, 0.032, 0.002, -0.033, 0.008, 0.03, 0.005, -0.01, -0.01, 0.048, 0.036, 0.027, 0.026, 0.013,\n 0.029, 0.02, -0.072, -0.052, 0.02, -0.011, 0.007, 0.059, 0.06, -0.079, 0.047, 0.032, -0.04, 0.04, 0.044, -0.002,\n 0.009, 0.02, 0.005, -0.043, -0.068, 0.006, -0.005, 0.048, 0.065, -0.062, -0.061, 0.006, 0.035, 0.035, 0.042, -0.053,\n 0.047, -0.057, -0.011, -0.039, 0.044, -0.04, 0.019, -0.005, 0.004, -0.056, -0.015, -0.071, -0.063, 0.008, 0.064,\n -0.069, 0.055, 0.04, -0.014, -0.031, 0.027, 0.029, -0.028, 0.025, -0.074\n ]\n])\n# fmt: on\ntarget_audio = torch.Tensor(pipe(TEST_STRING, forward_params={\"speaker_embeddings\": TARGET_EMBEDDING})[\"audio\"])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Initialize PESQ metrics for wideband (16 kHz)\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "pesq_wb = PerceptualEvaluationSpeechQuality(16000, \"wb\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Evaluate PESQ for each generated audio fragment\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "pesq_results = []\naudio_metadata = []\n\nfor audio, _sr in audio_fragments:\n # Pad or truncate to match the target length\n audio_tensor = torch.tensor(audio[: len(target_audio)])\n if len(audio_tensor) < len(target_audio):\n audio_tensor = torch.cat([audio_tensor, torch.zeros(len(target_audio) - len(audio_tensor))])\n\n # Compute PESQ\n pesq_results.append(pesq_wb(audio_tensor, target_audio).item())\n audio_metadata.append((audio, pesq_results[-1]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Find the best and worst PESQ scores\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "best_idx = np.argmax(pesq_results)\nworst_idx = np.argmin(pesq_results)\n\nbest_audio, best_pesq = audio_metadata[best_idx]\nworst_audio, worst_pesq = audio_metadata[worst_idx]\n\nprint(f\"Best PESQ: {best_pesq} (Speaker {best_idx + 1})\")\nprint(f\"Worst PESQ: {worst_pesq} (Speaker {worst_idx + 1})\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Display target audio playback\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(\"Target audio:\")\nAudio(target_audio, rate=16000)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Display audio playback for the best PESQ score\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(f\"Audio fragment with highest PESQ: {best_pesq}\")\nAudio(best_audio, rate=16000)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Display audio playback for the worst PESQ score\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(f\"Audio fragment with lowest PESQ: {worst_pesq}\")\nAudio(worst_audio, rate=16000)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.18" } }, "nbformat": 4, "nbformat_minor": 0 }