Skip to content

Commit d6cb4cd

Browse files
authored
Merge pull request #3177 from annietllnd/main
Technical review of Voice Sentiment with LLM
2 parents d76e4a8 + bed275c commit d6cb4cd

8 files changed

Lines changed: 188 additions & 75 deletions

File tree

content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/1_overview.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ layout: learningpathall
55
---
66
Voice-based LLM applications often rely primarily on transcribed text from speech input, such as in interactions with non-player characters in games or voice assistants. This approach can overlook vocal cues—like tone, pitch, and emotion—present in a speaker’s voice. As a result, responses may feel less natural and may not fully capture the user’s underlying intent.
77

8-
To address this, voice-based sentiment classification analyzes audio input to determine the user’s emotional state, which is then incorporated into the LLM prompt to enable more context-aware responses. In this Learning Path, we will build a sentiment-aware voice assistant that runs entirely on-device. The application records audio, performs transcription—converting speech into written text—using Whisper, classifies sentiment directly from the voice signal, and combines the transcript and voice-based sentiment to guide responses from a local LLM running with llama.cpp.
8+
To address this, voice-based sentiment classification analyzes audio input to determine the user’s emotional state, which is then incorporated into the LLM prompt to enable more context-aware responses. In this Learning Path, you will build a sentiment-aware voice assistant that runs entirely on-device. The application records audio, performs transcription—converting speech into written text—using Whisper, classifies sentiment directly from the voice signal, and combines the transcript and voice-based sentiment to guide responses from a local LLM running with llama.cpp.
99

1010
![Voice sentiment classification pipeline#center](1_vsapipeline2.png "Voice sentiment classification pipeline")
1111

content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/2_set-up-your-environment.md

Lines changed: 75 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,43 +10,69 @@ Before building the voice assistant, create a project workspace and set up an is
1010

1111
These instructions support Ubuntu, macOS, and Windows, with Python 3.9 or later and a working microphone.
1212

13-
Check your Python version before continuing:
13+
Install required system tools first. `ffmpeg` is required by Whisper for audio decoding.
14+
15+
{{< tabpane code=true >}}
16+
{{< tab header="Ubuntu/macOS" language="bash">}}
17+
# Ubuntu
18+
sudo apt update
19+
sudo apt install -y ffmpeg git cmake
20+
21+
# macOS
22+
brew install ffmpeg git cmake
23+
{{< /tab >}}
24+
{{< tab header="Windows PowerShell" language="powershell">}}
25+
# Install via WinGet
26+
winget install -e --id Gyan.FFmpeg
27+
winget install -e --id Git.Git
28+
winget install -e --id Kitware.CMake
29+
{{< /tab >}}
30+
{{< /tabpane >}}
1431

15-
**Ubuntu or macOS**
32+
Check your Python version before continuing:
1633

17-
```bash
34+
{{< tabpane code=true >}}
35+
{{< tab header="Ubuntu/macOS" language="bash">}}
1836
python3 --version
19-
```
20-
21-
**Windows PowerShell**
22-
23-
```powershell
37+
{{< /tab >}}
38+
{{< tab header="Windows PowerShell" language="powershell">}}
2439
py -3 --version
25-
```
40+
{{< /tab >}}
41+
{{< /tabpane >}}
2642

2743
## Set up the Python environment with UV
2844

29-
Install `UV` first from PyPI using `pip`. `UV` is a fast Python package and environment manager that we will use throughout this Learning Path to create the project environment and install dependencies.
30-
31-
**Ubuntu or macOS**
32-
33-
```bash
45+
Install `UV` first so the `uv` command is available in your terminal. `UV` is a fast Python package and environment manager that you will use throughout this Learning Path to create the project environment and install dependencies.
46+
47+
{{< tabpane code=true >}}
48+
{{< tab header="Ubuntu/macOS" language="bash">}}
49+
curl -LsSf https://astral.sh/uv/install.sh | sh
50+
# Start a new shell, or source your shell rc file so `uv` is on PATH.
51+
uv --version
52+
{{< /tab >}}
53+
{{< tab header="Windows PowerShell" language="powershell">}}
54+
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
55+
# Open a new PowerShell window so `uv` is on PATH.
56+
uv --version
57+
{{< /tab >}}
58+
{{< /tabpane >}}
59+
60+
Create and activate the project virtual environment:
61+
62+
{{< tabpane code=true >}}
63+
{{< tab header="Ubuntu/macOS" language="bash">}}
3464
mkdir -p ~/voice-sentiment-assistant
3565
cd ~/voice-sentiment-assistant
36-
python3 -m pip install uv
3766
uv venv .venv
3867
source .venv/bin/activate
39-
```
40-
41-
**Windows PowerShell**
42-
43-
```powershell
68+
{{< /tab >}}
69+
{{< tab header="Windows PowerShell" language="powershell">}}
4470
mkdir $HOME\voice-sentiment-assistant -Force
4571
cd $HOME\voice-sentiment-assistant
46-
py -3 -m pip install uv
4772
uv venv .venv
4873
.\.venv\Scripts\Activate.ps1
49-
```
74+
{{< /tab >}}
75+
{{< /tabpane >}}
5076

5177
Keep this virtual environment activated while you complete the rest of the Learning Path.
5278

@@ -63,6 +89,7 @@ numpy
6389
librosa
6490
scikit-learn
6591
onnx
92+
onnxscript
6693
onnxruntime
6794
```
6895

@@ -78,41 +105,50 @@ This installs the libraries needed for the Gradio interface, Whisper transcripti
78105

79106
Next, clone the [llama.cpp GitHub repository](https://github.com/ggml-org/llama.cpp), build the local inference server, and start it. This server exposes an OpenAI-compatible API that the Python application will call later in the Learning Path.
80107

81-
**Ubuntu or macOS**
108+
{{% notice Pre-built llama.cpp %}}
109+
If you prefer not to build from source, you can use pre-built binaries from [llama.cpp releases](https://github.com/ggml-org/llama.cpp/releases). Download the package for your platform, extract it, and use the `llama-server` executable from that package in the run commands later in this section.
110+
{{% /notice %}}
111+
82112

83-
```bash
113+
{{< tabpane code=true >}}
114+
{{< tab header="Ubuntu/macOS" language="bash">}}
84115
git clone https://github.com/ggml-org/llama.cpp
85116
cd llama.cpp
86117
cmake -B build
87118
cmake --build build --config Release
88-
```
89-
90-
**Windows PowerShell**
91-
92-
```powershell
119+
{{< /tab >}}
120+
{{< tab header="Windows PowerShell" language="powershell">}}
93121
git clone https://github.com/ggml-org/llama.cpp
94122
cd llama.cpp
95123
cmake -B build
96124
cmake --build build --config Release
125+
{{< /tab >}}
126+
{{< /tabpane >}}
127+
128+
When the build completes, the `llama-server` executable should be available in the build output directory.
129+
130+
```
131+
ls ./build/bin/llama-server
97132
```
98133

99-
When the build completes, the `llama-server` executable should be available in the build output directory. This Learning Path uses a quantized [Gemma 3 1B instruction-tuned model](https://huggingface.co/google/gemma-3-1b-it) served locally through `llama.cpp`.
134+
{{% notice Note %}}
135+
The file verification commands in this Learning Path uses syntax for Ubuntu and macOS. If you're on Windows, adjust the commands to use PowerShell equivalents like `Test-Path .\build\bin\llama-server.exe` or `dir .\build\bin\`. The file location remains the same across all platforms.
136+
{{% /notice %}}
100137

101-
The first time you run this command, `llama.cpp` will download the model from Hugging Face. This can take several minutes depending on your network connection.
138+
This Learning Path uses a quantized [Gemma 3 1B instruction-tuned model](https://huggingface.co/google/gemma-3-1b-it) served locally through `llama.cpp`.
102139

103-
**Ubuntu or macOS**
140+
The first time you run this command, `llama.cpp` will download the model from Hugging Face. This can take several minutes depending on your network connection.
104141

105142
Run the following command from the `llama.cpp` directory:
106143

107-
```bash
144+
{{< tabpane code=true >}}
145+
{{< tab header="Ubuntu/macOS" language="bash">}}
108146
./build/bin/llama-server -hf ggml-org/gemma-3-1b-it-GGUF
109-
```
110-
111-
**Windows PowerShell**
112-
113-
```powershell
147+
{{< /tab >}}
148+
{{< tab header="Windows PowerShell" language="powershell">}}
114149
.\build\bin\Release\llama-server.exe -hf ggml-org/gemma-3-1b-it-GGUF
115-
```
150+
{{< /tab >}}
151+
{{< /tabpane >}}
116152

117153
Leave this terminal running while you test the application in later steps. The server listens on a local OpenAI-compatible endpoint that your app will call to generate responses.
118154

content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/3_build-baseline-voice-to-llm-pipeline.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,14 @@ Gradio is a Python library for building simple browser-based interfaces. Here, y
2424

2525
This is a good first step because it lets you confirm that microphone capture works before you add transcription and model inference.
2626

27-
Create a file called `app.py`:
27+
Using a new terminal, navigate to the project directory you created in the last step and make sure the virtual environment is active:
28+
29+
```
30+
cd $HOME/voice-sentiment-assistant
31+
source .venv/bin/activate
32+
```
33+
34+
Create a file called `app.py` with the following contents:
2835

2936
```python
3037
import gradio as gr

content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/4_train-voice-sentiment-model.md

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,22 @@ Before you begin, make sure you have completed the environment setup from the pr
1111

1212
This example uses a simple per-sample training loop for clarity. On CPU, training can take 10 to 30 minutes or more depending on your machine. Apple Silicon `mps` and CUDA-enabled GPUs are often faster.
1313

14+
All code snippets in Steps 2.1 to 2.5 are meant to be added to a single Python file: `train_sentiment_model.py` in your project root (`$HOME/voice-sentiment-assistant`).
15+
16+
Make sure you are located in the project directory:
17+
18+
```bash
19+
cd $HOME/voice-sentiment-assistant
20+
```
21+
1422
### Step 2.1 - Prepare the dataset
1523

16-
This step downloads the RAVDESS speech dataset, extracts it locally, and builds a dataframe that maps each audio file to a sentiment label. RAVDESS encodes emotion labels in the filename format, which we use to select the classes for this demo.
24+
This step downloads the RAVDESS speech dataset, extracts it locally, and builds a dataframe that maps each audio file to a sentiment label. RAVDESS encodes emotion labels in the filename format, which you use to select the classes for this demo.
1725

1826
In practice, this turns a folder of audio files into a structured table that your training code can work with. Each row is one example, and each column stores details such as the file path, actor ID, and sentiment label.
1927

28+
Add this to a new file called `train_sentiment_model.py`:
29+
2030
```python
2131
import os
2232
import urllib.request
@@ -25,25 +35,24 @@ import pandas as pd
2535

2636
# Download and unpack the dataset into the local project directory.
2737
RAVDESS_URL = "https://zenodo.org/records/1188976/files/Audio_Speech_Actors_01-24.zip?download=1"
28-
output_dir = "data/ravdess"
29-
os.makedirs(output_dir, exist_ok=True)
38+
OUTPUT_DIR = "data/ravdess"
39+
ZIP_PATH = f"{OUTPUT_DIR}/Audio_Speech_Actors_01-24.zip"
40+
EXTRACT_DIR = f"{OUTPUT_DIR}/Audio_Speech_Actors_01-24"
3041

31-
zip_path = f"{output_dir}/Audio_Speech_Actors_01-24.zip"
32-
extract_dir = f"{output_dir}/Audio_Speech_Actors_01-24"
42+
os.makedirs(OUTPUT_DIR, exist_ok=True)
3343

34-
if not os.path.exists(zip_path):
44+
if not os.path.exists(ZIP_PATH):
3545
print("Downloading dataset...")
36-
urllib.request.urlretrieve(RAVDESS_URL, zip_path)
46+
urllib.request.urlretrieve(RAVDESS_URL, ZIP_PATH)
3747

38-
if not os.path.exists(extract_dir):
39-
zipfile.ZipFile(zip_path).extractall(extract_dir)
48+
if not os.path.exists(EXTRACT_DIR):
49+
zipfile.ZipFile(ZIP_PATH).extractall(EXTRACT_DIR)
4050

41-
DATASET_PATH = "data/ravdess/Audio_Speech_Actors_01-24"
4251
emotion_map = {1: "neutral", 3: "happy", 5: "angry"}
4352
data = []
4453

45-
for actor in os.listdir(DATASET_PATH):
46-
actor_path = os.path.join(DATASET_PATH, actor)
54+
for actor in os.listdir(EXTRACT_DIR):
55+
actor_path = os.path.join(EXTRACT_DIR, actor)
4756
if not os.path.isdir(actor_path):
4857
continue
4958

@@ -73,6 +82,8 @@ Next, split the dataset by speaker so the same speaker does not appear in both t
7382
This matters because a speech model can accidentally learn speaker-specific traits instead of sentiment if the same speakers appear in both splits. Separating speakers gives you a better measure of how well the model generalizes.
7483
The training split is the data the model learns from. The validation split is held back and used only to test how well the model performs on unseen examples.
7584

85+
Append this to the same `train_sentiment_model.py` file:
86+
7687
```python
7788
import random
7889

@@ -97,6 +108,8 @@ print(f"Validation samples: {len(val_df)}")
97108
This step loads the HuBERT feature extractor and initializes a sequence classification model. The pre-trained HuBERT base model provides a strong starting point for transfer learning on speech sentiment classification.
98109
The feature extractor prepares raw audio in the format expected by the model. The HuBERT model then uses those inputs to predict one of the target sentiment classes.
99110

111+
Append this below the previous code in `train_sentiment_model.py`:
112+
100113
```python
101114
from transformers import AutoFeatureExtractor, HubertForSequenceClassification
102115

@@ -122,6 +135,8 @@ During training, the model compares its predictions with the correct labels and
122135

123136
For better performance in a production training pipeline, you would typically use batching and a PyTorch `DataLoader`.
124137

138+
Append this below the previous code in `train_sentiment_model.py`:
139+
125140
```python
126141
import librosa
127142
import torch
@@ -200,23 +215,27 @@ At the end of this step, you will have a trained model and a simple validation a
200215
Save both the trained model and the feature extractor so they can be reused later for inference and ONNX export. Keeping them together helps avoid preprocessing mismatches.
201216
This is an important final step because the model weights alone are not enough. You also need the matching feature extractor so inference uses the same preprocessing as training.
202217

203-
```python
204-
import os
218+
Append this at the end of `train_sentiment_model.py`:
205219

220+
```python
206221
SAVE_DIR = "models/hubert_vsa_ravdess"
207222
os.makedirs(SAVE_DIR, exist_ok=True)
208223

209224
# Save both the trained model and the preprocessing configuration.
210225
model.save_pretrained(SAVE_DIR)
211226
feature_extractor.save_pretrained(SAVE_DIR)
227+
228+
print("Training complete. Model saved successfully.")
229+
print(f"Saved model to {SAVE_DIR}")
212230
```
213231

214232
After this step, the trained model should be saved in `models/hubert_vsa_ravdess`.
215233

216234
## Full training script
217235

218-
The following script combines the previous steps into one runnable example. Save it as `train_sentiment_model.py` and run it from the root of your project.
219-
Use this script if you want to run the full workflow without copying each block separately.
236+
The following script combines the previous steps into one runnable example.
237+
If you already built `train_sentiment_model.py` using Steps 2.1 to 2.5, you can skip this section.
238+
If not, copy this full script into `train_sentiment_model.py` and run it from the root of your project.
220239

221240
```python
222241
import os

0 commit comments

Comments
 (0)