From 3b6cf6be3f5fc0af100f50aa3e3f5915125c6c41 Mon Sep 17 00:00:00 2001 From: Bhanu Arya Date: Mon, 13 Apr 2026 19:24:40 +0100 Subject: [PATCH 1/5] Add voice sentiment analysis learning path --- .../1_VSApipeline2.png | Bin 0 -> 24071 bytes .../1_overview.md | 12 + .../2_set-up-your-environment.md | 119 ++++++ .../3_VSApipeline1.png | Bin 0 -> 15785 bytes .../3_build-baseline-voice-to-llm-pipeline.md | 187 +++++++++ .../4_ModelConversionandCompression.png | Bin 0 -> 116138 bytes .../4_train-voice-sentiment-model.md | 376 ++++++++++++++++++ .../5_compress-and-convert-the-model.md | 176 ++++++++ .../6_VSApipeline4.png | Bin 0 -> 27462 bytes ...te-voice-sentiment-classification-model.md | 256 ++++++++++++ .../_index.md | 65 +++ 11 files changed, 1191 insertions(+) create mode 100644 content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/1_VSApipeline2.png create mode 100644 content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/1_overview.md create mode 100644 content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/2_set-up-your-environment.md create mode 100644 content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/3_VSApipeline1.png create mode 100644 content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/3_build-baseline-voice-to-llm-pipeline.md create mode 100644 content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/4_ModelConversionandCompression.png create mode 100644 content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/4_train-voice-sentiment-model.md create mode 100644 content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/5_compress-and-convert-the-model.md create mode 100644 content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/6_VSApipeline4.png create mode 100644 content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/6_integrate-voice-sentiment-classification-model.md create mode 100644 content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/_index.md diff --git a/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/1_VSApipeline2.png b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/1_VSApipeline2.png new file mode 100644 index 0000000000000000000000000000000000000000..413bb434344258da4c1df71789d0f6a59cb8a811 GIT binary patch literal 24071 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 CAsNB| literal 0 HcmV?d00001 diff --git a/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/1_overview.md b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/1_overview.md new file mode 100644 index 0000000000..6255d43f05 --- /dev/null +++ b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/1_overview.md @@ -0,0 +1,12 @@ +--- +title: Overview +weight: 2 +layout: learningpathall +--- +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. + +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. + +![Voice sentiment classification pipeline#center](1_VSApipeline2.png "Voice sentiment classification pipeline") + +You will start by building a baseline voice-to-LLM pipeline—capturing audio, transcribing it into text, and using it to generate responses with an LLM. You will then extend this pipeline with a voice-based sentiment classification model. This involves training the model, optimizing it for efficient on-device inference, and integrating it into a unified application. diff --git a/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/2_set-up-your-environment.md b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/2_set-up-your-environment.md new file mode 100644 index 0000000000..e59320dca9 --- /dev/null +++ b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/2_set-up-your-environment.md @@ -0,0 +1,119 @@ +--- +title: Set up your environment +weight: 3 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +Before building the voice assistant, create a project workspace and set up an isolated `UV` environment. This keeps project dependencies separate from your system installation and makes it easier to reproduce the steps in the rest of the Learning Path. + +These instructions support Ubuntu, macOS, and Windows, with Python 3.9 or later and a working microphone. + +Check your Python version before continuing: + +**Ubuntu or macOS** + +```bash +python3 --version +``` + +**Windows PowerShell** + +```powershell +py -3 --version +``` + +## Set up the Python environment with UV + +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. + +**Ubuntu or macOS** + +```bash +mkdir -p ~/voice-sentiment-assistant +cd ~/voice-sentiment-assistant +python3 -m pip install uv +uv venv .venv +source .venv/bin/activate +``` + +**Windows PowerShell** + +```powershell +mkdir $HOME\voice-sentiment-assistant -Force +cd $HOME\voice-sentiment-assistant +py -3 -m pip install uv +uv venv .venv +.\.venv\Scripts\Activate.ps1 +``` + +Keep this virtual environment activated while you complete the rest of the Learning Path. + +Create a `requirements.txt` file for the packages used across the rest of the Learning Path: + +```txt +gradio +openai-whisper +requests +torch +transformers +pandas +numpy +librosa +scikit-learn +onnx +onnxruntime +``` + +Install the dependencies into your active `UV` virtual environment: + +```console +uv pip install -r requirements.txt +``` + +This installs the libraries needed for the Gradio interface, Whisper transcription, model training, and ONNX Runtime inference. Some packages in this list are used later in the Learning Path when you optimize and export the sentiment model. + +## Download, build, and run llama.cpp + +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. + +**Ubuntu or macOS** + +```bash +git clone https://github.com/ggml-org/llama.cpp +cd llama.cpp +cmake -B build +cmake --build build --config Release +``` + +**Windows PowerShell** + +```powershell +git clone https://github.com/ggml-org/llama.cpp +cd llama.cpp +cmake -B build +cmake --build build --config Release +``` + +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`. + +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. + +**Ubuntu or macOS** + +Run the following command from the `llama.cpp` directory: + +```bash +./build/bin/llama-server -hf ggml-org/gemma-3-1b-it-GGUF +``` + +**Windows PowerShell** + +```powershell +.\build\bin\Release\llama-server.exe -hf ggml-org/gemma-3-1b-it-GGUF +``` + +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. + +At this point, your development environment is ready. You have installed the required audio and build tools, created a `UV` environment, installed the Python dependencies, and started a local `llama.cpp` server. In the next section, you will use this setup to build the baseline voice-to-LLM pipeline by creating a simple Gradio interface, transcribing microphone input with Whisper, and sending the transcript to the local LLM. diff --git a/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/3_VSApipeline1.png b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/3_VSApipeline1.png new file mode 100644 index 0000000000000000000000000000000000000000..a3e38d754b3ec5005bd9856266fd2619a39619f5 GIT binary patch literal 15785 zcmeHu2Ut_vwk{${5S6BYQY4|NNSEG2Xi8IxAT>ZJ5(p(g00BkN016@?(h(FJf)oJ- zX;KuVi1g5VZ&F0sn~}2J=j?m#efPWXzH`re`^yelbIm!|n4|n-&VMX}v^7$;|P9&cBBo;ap#5x#>)*78mWX#94&J_=H74 z^XZGKx*F=7LWk5RxU^nK06HPE+oz;z(?$^XzPM;0G*Ttg^5jK zLIUUc1OAu6#gl9lIngO~^qnJBf5 z%kE=RvqHIUkKGXui@~6=c20k8w8S_%0w4HON2H4j#^cZ3tTE`F z#qCVy1lIkhLBs}~ztbqO^@^Pp7Pt-3-^7K)wuL6PtD@{|Y ztuP+j<9C{nR!AqTojXbqgT}b*2#R#W0?+xg$+nfet7;bv20ZOIi~fFlx03@-5rrmt z_TOk3IMx6D*gajj>;=}&3jR~m@hXZN2$<<&FL)~B9MuI45dvOnygP2;jzqid7-U<+ zu2|1q??zbx)9hSgT(Gtn8;m0ot@KAL+y&$2XoVtL82CB-k3Q-cj1y=S1dZ2FSghxc zpZ(IcFx(dFfZn+a*5~4BurvPGg(2~RPXvtob%`K4HgWRpE238uuh2*f6dI1Sw72;s z!Y_k@TaFkC#u2;QWQDRux}kvq0sIi%-U<1KQg`(1ig9zX+)=_`>Iz^4aVQq) zVuRXoyua-CEB1v1hR4+l~~0muUC08es3p^-#K{cRQivpXJZv)7L( z)PKYyK%X7|{Kv8ovHow|_%GMFqmO^&JHOc&04dfEM7TQ9X4)86JM1>BT41nPi~}$X z5^ZNg#92#VVmmgsA6SCEhF_b>y*GkgyKhz?_gbhX`cRB*?Ms9Ogd zz-1>}kULz(`0N0rx$-&KS-N1HY`6XB-}D%w2&f&%{mDo?f}jVnlkIQVC%n`4&*K;8w}t#ie(~RcDgV9Luw$!#7aMl0{4b9UJNo#|2XvfJ zC`(&jEQVJXg#(*9q96a3W&WH)JZ$Z-C>%Xr;M*uMv?=Y+UcMSb6AF?|N`7LDC zi8~z-rs_yX8#m&P9h}&#Ulc}PguNYoGt zx;WG61oGVDEvxxvu?O~*kyz|g2tOTs;n);glkUr4gVUn&_o_@SEbhs~8o<#C=g-?^ zAIoB6C7b)oYiU^; zO5H)QjQA-gL8s%RMA6%6&W~9JoKpB|my0<`IHqt#Rh=dqW1$}PGWY_%!dCOhPsV-~ zZS2YExGGVl#^ba)0aI~|I!|Aw(82^`b#BQj|DZXR@M~roY+%JP=o(j*?u(bt161#h zG$)7A_>w6-`z08e2~<$&Q*n@>-pjLRp|q(IBLAZvs{}<@e|glO+~d18T~+dd-~ZxT zzWlYuXV=bcqznF1UF3|ZL00SM;^Tu)weBdgxkLTuY}+zS%(k{r3?o#5iHd9#hr zkA-|;gI?jjx2$0q2N(5D$%*ixb11IRmvx82tGS_~b2sM15@~1?X*Rl@vShOtN(cm%HugPJFo{^l} zZWZa1(q8+(C!`xbo}L73>)x_wTV$@?kGcNk!rqN~dOl;uo5%e_PCxXjIP_SzAK9M& zs5A9F$E_R0Ax*yzX@7%s?{;hJ$nW)K+8d$(fAVM!lkXNz9rSe(sXig7Z>AKj$aZh} zNU8>k;>K`ov=rv^_Xau4+H6Tlf=7?1sCg*~p~y3b#b@!W`)AJR_We}SeZD2gj|U6a z7it|oKj3W5$C$0Hlx#Wp{rr}cj{JCg`HOHv`z zNBPgCNgFf2jkhhCX1y=9nIP&h zV{G)s{APkn&6(v->|x>xPMfQvgwmV)yF&;`E-%RWH%#0+Dq}_Lt^HPO&CpZrS>ZT}R`K0Wu5D*si+`gdU|D%fph#fG^$^jp8IR_1kc>Z+;&B{n~Y)PEA|&IMKSGqXD13X~|6&QHId-W{=dx z5wcOW=sPU#Z~QhU&D@8=RV1M<8%u3c{X$lssBMNHE8cm}uK)p_rJ=DAXH7z>;qOv^ietoWtJa47P zTdJ&|0fwh5VlE0eurvKJ0+ z)l=lznro*MeM%h|TV}HPMFgFU$$PA`TnP{_{!z^kjl@{;t*#9<9D>zn@sdZ-AaF>V zwVA@qx+J+?ig2Ih4sJ`|m0rJ+>9=jBa+75ryjSuBEvtrFa)5uC9&h|e-~|3f8j}!| zUi505QerITQ}Nd3+Fd&Ro3TY2c}!xCSpM77633g01N%4~Q=%+jN+^h^?AS8Ovj0ZrB1RV%@ydA;)!WH3f zSO&D-HGiao;RCreV$tjfO{wYgg(I={Q*9-iZ`W2azTcU|4$45wx8_HjOcaJ|qPp{r zvYsNV>)qP)P*i10J}R@Q&5fYuiCORl-tb!a-bqpC0fc>T@dus|I#s8+ic1+A>@%`) z8mGRzbx^|{RWOl!c!pxBK0lNewn}>#*Hd_xcdXR_!>QNGa`v6>J&K@>!Aq&i@2Wd$ zi~Ia3jY*7kgJkTx+BQE+KA++*S#NsRVtB$j4u8V@Epz1An#guiQ~zpKZ*-pCrwLbQ ztqDWDrj^Iff1V@l%dX|i5$nNO#+}Ws86DQVTRHwL$?^KpOA1p5QhL7Q*buT?>#q3@ z^A&Vj1#H@+%BQ@i1i2Bo`R@<>R)?ca)ITRH+(M5gc;wk6`7GPOpQJ2_k36#UZOxYn zgAib+e1ANqQ-e_FIf6JGnYi_-kMmab(7Vw@*Rt2mG@0Z^7Z%6O)(03gi`J(xemMXG z3jD~2Rr>gh405;PFvVH*2~veOj5}ksf(sk0zmb2pGdtF>x%f@1euRwbN~WpIBrJ&X z+Simox|al_2h?YhD?ji|oPkBKovBE+fn9xjEsDU?WA`j!s*=GB>s?P)nt*LmJAj_v z%Fe)3KlZ(1>iKj7fN9-(NBqIru~7iSv&(I(g<#XfN2OnNT#ZaFn-qlR*46Opnhi8` z%EK?DL_DYMcwnCFc9 z`rSe$c@D1fiMm>0fTQNe{NN5^B}O&Dn54dKO|-lkm6iyT+bjsVq;PuvPQk-AQ}4Q< z1GJ{cJ6(ZluV$Klh&S*Ye;VC2%Sd?+Ca&>uJQ)D6u#r64BwSNwNtgS^!V;WbF^M;m zYNVr*a#K9-DHV*5M~FcUf2`|#3}y1Sh$gR`YiStzWy%Ne!#}${db{~PEH*i4Lf(-3 zv?K3Ga+;5y8|Xuo1=F{QdT*s)m+&`y_{_h7#kqpU(Jz5PL0gJi5+Y=3T#(}_DDZiZ z*J6QtV{@g1s>M|ovPSRqyy~mX#r&Y4_}4N&ggFn-DghGx*5K68E)t-pPjRs0b5MR zE&-X0d zRkt$qa3k15sL3oE*Y`y?RaaB!)?=qS=`P=4cEM{TNVY6Bs4pd-8<8I2$ZOG*o=04% zZkkmLeM*N37`>a?k2md=k`hheSn1wYNK63tf`0+*W7yMc3m=8-2rIhO} zS}1et&yPN=Jo7w0yN4aM^sxemxDi)TQJY|ie;ToZ@`Z%{}sFk*G@-wd%o0sZ*oV&dUzk_8?W;R;pJ^ha5x+ch%O z8%@hy%hmS#ADwRpFWUqSCR*u9doEb5cyk#o)J0yPq*=%kziyhfNleZ`@&p)@86sn_)V-PF9ucJ#Q{r&RDfuA#lvsbG_SrQn6<{KC* zzb}J5$hSJhm=UUxRG{Q>@0d`NoTyJ-Qpj?$XaSea`+ z{1^z?KWa=Ltq|{DgLJ-M_jR?&N01&m;4_(VB?c`GyH1}P%|H@cXCAU3{M>mH!HF2d zs#hH*Z4lA0vG9wZCJ%r_u)|1aig3;>-?D^QJS!Awu)5TG8qX}{;y!3gGm|y-Z^+{X z*yeEpeT?{_O3I{*X{tryPLMHRP@O#f9C4vMSjH_#zW(5Y5BxtDi^>B=^5=|*?9D?T>J3*h@@=EIa*{oX%u6(Q`Y?`=k;V4C8by1a3I$lK0q4h7;_bcaP zn)v$Rj&*&cPh5p59xzgOUA*ygIAGrjEgPc1`{qsf!Z9hH`|2vJ9T8NLXYoAY1kQ`S z4Skh(-2OXW^ZtF64f+rU{qUj>ep+||fw?q+u@3<%jEBx3%9n9UU+<(&^+$`CBzbAU3=8|l4KTV-2f$j zFoVtVNE}8o^Q5JS?dF_CWYOT@5EF=MegrkC!qQyc!rX<39?#K)2M1D0dTGictyztbGIAzR0k5qKe1ns8hg&R)`_RV zm?e(+Xp$M@`U44Mi>rYUmZ&J!AoHvKi_%bk`)~)=Sye8&{Y#3KsS_Iw{TDc+=wXL5 zl2hYw)=rp#69d?Td{^})XklA%tSK`&9VM`(&!-{BZc(4fr1~i_1r8u5!o=wg*DXVw zVTm5cf!so@5KT)@v-e{RFoo~rf?S(AMqjcNsxvm=0VK`IUIM4+w#w$QwA>DJ6Q~9J zk3a~kP2Ou6EvfJP!*7P`VRzr%n|7yvj4o+(q{m&aFB}Rh2?`o??@5-3b|-UADN-MP z)A(g%?D}{yGw@NCyOGna>P-Ro(UHYM+Kqz{@5XOmAy+M>$c7u14q_vcHoWldkMa8I$o4!uPB)#cYZjF(w1W~jylUZbK11o zM>IIvz8%GeU!@xr)oyoKSfKRQta+M9%m#^H=wd>}>5@LSlDbmxTaM707JP9-BgsCP z-x{Hke(nn$=pKq2Zx<=sz&$-f8K$8v^N7dr6O^&NoINAV>mfIyh|U0Q?>+BJM!3vS zR897|>1bZ!BdvLk98_rI&EwOh7e87=IjjRctzfCSr8Q$}pd4&*so#~(ARugCj;&pi ze8@58`VPBnflH&l#Tv~tb6ztA$FooK3H9?_?D%DF#2jCv#Hvi0I+-htF#~oeD?Qbp ztjf!SLEv=z08&ddf*Q#QA6?=uIz2Tzc)23=k-0fo)iK1fV)MY-458FWV?$5B$xk{IKp7%H4^!Z8PhPzhaRY!2J}}v` zt!S>}vb8svb7|{^G%q%58ZunCS{LN*=`*~D_Ht{=Za-mx5ZSy+wF)ch{rKnoET4OO zy0kWNa_zBD>qbJ}2#fsn1rCzhG+kDKgUXQcBpqhBB1e2(+ilwYiFuH z;~M_vWE)zJ`(dTp86TJP1dDWpvGiQ1NXfET)3cMGEDco-IQNJ5vU*R2Qy%pAO806% zW=>mfBG@AHQ=oiDF8g!;!!DYOKeU#Hni+c)1G6#3OEsgb9C@GVVCl{445R%CidH!) znsX+3FJSPBLJCnr(cq?W)09z9xE<;EygTjaNM(IgbpcGJM3|+}`jV1GNC!_&x|hI} z91RTt2PIm{ivz~(umMkg!RT;qg8n&(8iZz|l0k66cwEZOCB_v%sp0Yd(H03&=s-#C z?Hb%O%JwSrmSD(ylBil|p1LT@*$IVNitr|$kLZb)4Z892&C9QGM|d_7w1fa^B+pR{ zn?=A-_m|*1N5fd*9AihB^fTg>`%(lg4&z=3-k!sgb!QE3%`gq5KX~xKx>r0@oV)Vj zrk58yz?LYQ~#k0Y=bt5uhumeb-pivu0%wuTccM4?42Wg4i$$&*aOH35?u;6sVUxDlD8GfrLi3%1YA;X znqsP^85Iy$Ng!WI?Gq{6r+xQ~D3tVWlM%ndji#|9Y7~WFjzxlO=igvr>+F(KL_6@VFqguHX~c>Z5Bbw6KoF(>}^%Uo4Kp z7}>sw-nC)0P%-$-F-@C;meQx?UP2Sn8aTgsYbSO;Xt$Ap!f%KQLc89mt<$UxFoz)U zPNZBwSIaH$Uis5eaPVyne&WvJRPdOhesdan*%Z?>tJJsUeKnu{>%EJnU8+}Q*q%z6 z&~m#SiJzi)^0inE9l?#j)AT>$NHmzaA0zIZBGr9^x_?w*<&4wIyKGmA`2#9Mk3^HY zJP{rXWzh^(!&}MeFMghmlb>Rxk@Lm8QifZt%|zPS`Ounm4rggu#7RTTTSGj)v-cL& zvPdRUD;Sf3WrsT!7Y0nTXr?$zKZp|#q5>7=`(>-${t%h5M2JPw4)a(AOsx^Sj-R?uuxC4dl4t@NK#7>qJiAl;Hem zLX|9&oL)<^G*I_2&DqA;Xnr%tTjW%H+A}qI29PeV$=8*aqlzXU%f^Y7BdqSH$;LtY zUOrL7Kd1Y1*+6vhtifI#LJ)2RxI_KdS49lf_ZpSR7HDCB#(GPn@&G?(qY)>{%SfLY zzxsXDD1uAlkbbBT{dS#V9}WJYyeZtO_95?b6%(eNMui#K^6Cn|K^-UJ&~5z_JLL=c zbTXks;Y*WkMscDJ=~_v$qS!I*SB5!h-7dAf%YaBAOlFoiawpLlFjP*;7w4ZlCjczshh=l@nONb4It}lie`(~x@@BM z$~h_Q_b=gbtazZR!f3gHn+(f<4}EbH;Pw}aNWi_-C;BWK z8f94o0);fRUr>0`h5e?{`p(s+u>UsG6dK?Ny(&I`Gr3?eR0>_Pv7Gbt zV)T1y{S0F}pS9(;9XU3HedNr2HFmA(G2V_p7sqGDc>JvZ0e*9p#iPqv)V}o`lkD{Z zUZcD(8Sf{~uAm8d4%2|L%>~>kVX4gw^DRvy$9wieTT5KhnV{V=6GAar9V+BDp-W`N zyu()j{j8s9I$gxxUFeYyR7U`36%!bO`j|Z4?1l*K8*; zrW>a)WCNsvH>f=U#hTCv?e^Q+xcvhM*zA0YV3gEtheSZTHUOIaN9j5>4BgP@3?;Hw z^&vVXzCLZYn}QskhVT~~bi?<;21bhVLKNwo?SO<+@)tFF(%}o)G#&|*ofNrd6~V35 zqm0kBZ>#rOnm|RHdc9a<=HFs4#g&=*Ea`zAX6PGE#q!#0yKh_!HvWC{6N3yeK#QTw zeIYaffze6ah~-{CzfmQqCK(kKMOH5GC4&!MOE7q*#hyaobTAN{*);6R?XI8_Y2TWj z1qu@Fm7&ze6DxRWz*g5w!S8+sWbkwm^jU&?Ec7&3SS6dO?8c%FL+yO&ekyS&4F>{m zLQ%u7iYL|}G@bgK-+_`xLR+B2(=geepFF+(sA8r-@8?h}*cy9JzvVRILcfa#6uYQR zJ@Y84R2a_u39wt!7832D(Eh+uruj1RQw3Z3*IH_*1<}!Lh-=NyFD(@2)JxVLNOg%Z z;ie2pz7a12cShW2CRO4@Jh-JHa(uTlgjjoG zJQj6lr#g$5*94_gm-4hbd*&!-V}sHjR2qivJQa4I4zT6%Z^of}OA-K;(g1}^o_hd2 zgaCD2ZnT%`o>LtLaF5j1W!)2A0@OJ^Q^c|E$^HMDDCXYWZ+thri~y@V4X8#C&~K+% zRO+XrsXw1cU_JKBLhY;+=SE zR$`?MaJZJrkLLh}cD%|gz6QwpJco|#%D3N%Bs&06NoP<9C@e?{=Z0IYbbI7mhDuaz zOcl-+=h%Hq>pvDsmknr0_n64JOPhY^@Wsz##DD;>GJdBz8 zeNII1=aVl(etEY1B|3R@-@CD|snrb=5*8JZS0;sd-yN+wW9Ow|9f!g}+Wpph@_av-n<;~CAHkN8ijz38fAqj`4t2QLcDAK?-8EFosb{%DrED%~aE?J3?1M+w; zqq$`d9%VKF35)?kftjU*`d>W64nxrMo1&j3dcCyFwyrzL@adNJI51@ld(w=wTn2yBH6r5z{n93C?O50 zpurpzZ6K`$=xe@$KGIiL-@aUPHZ&3jdjhw4HjbXSqPC()%qDUk!k*-RD4a_6M`;@Y|h@5;+Q`Pf&KePo$D+w z=B9>3;wSk4Hf00dOyhS=2hzO8r}KOB-nou;u8t;|E#w4O;C~PzFq?tq5%!4(j{m9- z5&X-HzP`}m_K19uOT7xjH|aZK*O86Vz6$BhZ;RJCItGBeVSYL&y4|inj}HB>1d!=MUv{#-5R%)qc2*vNy?mfAo_N z0zQ`6*SX)%94NNxfNgx*d^Ij0@NMJj9leI^g&+i8d!N4ng|!m8In%6!!{9CUF1{qY zBKS+JNNqxSKcEI@UI)=}zxNReLHSXTg3=Y*cTan%2RV<)>-f3doW}-Dk=;$ZFlr`{0NWd1neQe*@XFKDZbqg4kG%5q``JYvj_}!i xc?!WM3i4#u<11x*OQD5L?BBb{qWR$`?M6AyKc*@I{_Qo1nvy0W18(mBzW^n6ojm{m literal 0 HcmV?d00001 diff --git a/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/3_build-baseline-voice-to-llm-pipeline.md b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/3_build-baseline-voice-to-llm-pipeline.md new file mode 100644 index 0000000000..9cbfa29f2f --- /dev/null +++ b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/3_build-baseline-voice-to-llm-pipeline.md @@ -0,0 +1,187 @@ +--- +title: Build the voice-to-LLM pipeline +weight: 4 +layout: learningpathall +--- + +In this section, you will build an end-to-end pipeline that: + +1. Records audio from your microphone +2. Transcribes it to text using Whisper +3. Sends the text to a locally hosted LLM +4. Displays the model's response + +This forms the foundation of your voice assistant. + +![Baseline voice-to-LLM pipeline#center](3_VSApipeline1.png "Baseline voice-to-LLM pipeline") + +Before you begin, make sure you have completed the environment setup in the previous section and that your `llama-server` is still running. + +### Step 1.1 - Create a basic Gradio UI + +Start by creating a simple web interface that captures microphone input. +Gradio is a Python library for building simple browser-based interfaces. Here, you use it to create a small front end that records audio from your microphone. + +This is a good first step because it lets you confirm that microphone capture works before you add transcription and model inference. + +Create a file called `app.py`: + +```python +import gradio as gr + +with gr.Blocks() as demo: + mic = gr.Audio(sources="microphone", type="filepath") + +demo.launch() +``` + +Run the app: + +```bash +python app.py +``` + +Open your browser at: + +`http://127.0.0.1:7860` + +You should now see a simple interface that allows you to record audio. +At this stage, the app only captures audio. It does not yet transcribe speech or send anything to the LLM. + +### Step 1.2 - Add speech-to-text with Whisper + +Next, add transcription using the Whisper model. +Whisper is a speech-to-text model. It takes audio as input and returns a text transcript. In this pipeline, it converts spoken input into text before anything is sent to the LLM. + +Update `app.py` with the following code: + +```python +import whisper + +# Load a small Whisper model for local transcription +model = whisper.load_model("base") + +def transcribe(audio): + return model.transcribe(audio)["text"] +``` + +The first time you run this, Whisper will download the model, which may take a few minutes. + +At this stage, your app can convert recorded audio into text. +The output of this step is a text transcript that represents what the user said. + +### Step 1.3 - Connect to the local LLM + +Define the OpenAI-compatible endpoint exposed by `llama-server`. +An endpoint is the URL your program uses to talk to another service. In this case, `llama-server` exposes a local API on your machine, and your app sends the transcript there to get a response. + +Because the server is OpenAI-compatible, the request format looks like a standard chat completions API. + +Update `app.py` with the following import and endpoint definition: + +```python +import requests + +LOCAL_LLM_URL = "http://127.0.0.1:8080/v1/chat/completions" +``` + +Make sure your `llama-server` from the previous section is running before continuing. +Without the local server running, the next step will not be able to generate an answer. + +### Step 1.4 - Build the full pipeline + +Now combine transcription and LLM interaction into a single function. +This function becomes the core of the application. Audio goes in, text is extracted, that text is sent to the model, and the response comes back out. + +Keeping the logic in one function makes it easier to connect the pipeline to the user interface in the next step. + +Update `app.py` by adding the following function: + +```python +def handle_audio(audio): + # Step 1: Transcribe audio + text = transcribe(audio) + + # Step 2: Send transcript to local LLM + response = requests.post( + LOCAL_LLM_URL, + json={ + "model": "local-model", + "messages": [{"role": "user", "content": text}], + }, + ) + + if response.status_code != 200: + return text, "Error: LLM request failed" + + data = response.json() + answer = data["choices"][0]["message"]["content"] + + return text, answer +``` + +### Step 1.5 - Connect the UI to the pipeline + +Update your UI so that recorded audio triggers the full pipeline and displays results. +This is the final integration step. You now connect the interface, transcription, and model request so the app behaves like a real voice assistant. + +When the user records audio, Gradio calls your pipeline function. The app then shows both the transcript and the assistant response in the browser. + +Update `app.py` so it contains the following complete version: + +```python +import gradio as gr +import whisper +import requests + +model = whisper.load_model("base") + +LOCAL_LLM_URL = "http://127.0.0.1:8080/v1/chat/completions" + +def transcribe(audio): + return model.transcribe(audio)["text"] + +def handle_audio(audio): + text = transcribe(audio) + + response = requests.post( + LOCAL_LLM_URL, + json={ + "model": "local-model", + "messages": [{"role": "user", "content": text}], + }, + ) + + if response.status_code != 200: + return text, "Error: LLM request failed" + + data = response.json() + answer = data["choices"][0]["message"]["content"] + + return text, answer + +with gr.Blocks() as demo: + mic = gr.Audio(sources="microphone", type="filepath") + transcript = gr.Textbox(label="Transcript") + response = gr.Textbox(label="LLM Response") + + mic.change(fn=handle_audio, inputs=mic, outputs=[transcript, response]) + +demo.launch() +``` + +## What you should see + +After recording audio in the browser: + +- Your speech is transcribed into text +- The transcript is sent to the local LLM +- The LLM response is displayed in the interface + +## Troubleshooting + +- No response from LLM: ensure `llama-server` is still running. +- Whisper is slow on first run: this is expected due to model download and initialization. +- Microphone not working: check browser permissions for microphone access. + +At this point, you have a working voice-to-LLM pipeline. In the next section, you will extend this pipeline by adding a voice sentiment classification model. diff --git a/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/4_ModelConversionandCompression.png b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/4_ModelConversionandCompression.png new file mode 100644 index 0000000000000000000000000000000000000000..0b9cfc7eb7dcdfcdc51dabd25978e8a6c317a0c9 GIT binary patch literal 116138 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 literal 0 HcmV?d00001 diff --git a/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/4_train-voice-sentiment-model.md b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/4_train-voice-sentiment-model.md new file mode 100644 index 0000000000..e6d0251c48 --- /dev/null +++ b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/4_train-voice-sentiment-model.md @@ -0,0 +1,376 @@ +--- +title: Train the voice sentiment classification model +weight: 5 +layout: learningpathall +--- +In this section, you will train a model to classify voice sentiment directly from audio. The raw waveform is converted into audio-based features, then passed through a pre-trained HuBERT model with a classification head. + +For this Learning Path, you train the model on the RAVDESS dataset using three sentiment classes: `neutral`, `happy`, and `angry`. This keeps the demo manageable while still showing a realistic transfer-learning workflow. You can extend the same approach to more sentiment classes or other datasets. + +Before you begin, make sure you have completed the environment setup from the previous section and installed the required Python dependencies. + +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. + +### Step 2.1 - Prepare the dataset + +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. + +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. + +```python +import os +import urllib.request +import zipfile +import pandas as pd + +# Download and unpack the dataset into the local project directory. +RAVDESS_URL = "https://zenodo.org/records/1188976/files/Audio_Speech_Actors_01-24.zip?download=1" +output_dir = "data/ravdess" +os.makedirs(output_dir, exist_ok=True) + +zip_path = f"{output_dir}/Audio_Speech_Actors_01-24.zip" +extract_dir = f"{output_dir}/Audio_Speech_Actors_01-24" + +if not os.path.exists(zip_path): + print("Downloading dataset...") + urllib.request.urlretrieve(RAVDESS_URL, zip_path) + +if not os.path.exists(extract_dir): + zipfile.ZipFile(zip_path).extractall(extract_dir) + +DATASET_PATH = "data/ravdess/Audio_Speech_Actors_01-24" +emotion_map = {1: "neutral", 3: "happy", 5: "angry"} +data = [] + +for actor in os.listdir(DATASET_PATH): + actor_path = os.path.join(DATASET_PATH, actor) + if not os.path.isdir(actor_path): + continue + + for f in os.listdir(actor_path): + if f.endswith(".wav"): + # RAVDESS encodes emotion ID in the filename. + emotion = int(f.split("-")[2]) + + if emotion in emotion_map: + data.append({ + "emotion": emotion_map[emotion], + "actor": actor, + # Store absolute sample path for later loading. + "path": os.path.join(actor_path, f) + }) + +df = pd.DataFrame(data) +print(df.head()) +print(df["emotion"].value_counts()) +``` + +At the end of this step, you should have a dataframe with the selected audio samples and their sentiment labels. + +### Step 2.2 - Create train and validation splits + +Next, split the dataset by speaker so the same speaker does not appear in both training and validation sets. This helps avoid data leakage and gives you a more realistic evaluation. +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. +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. + +```python +import random + +# Split by speaker so the same actor is not in both training and validation. +actors = sorted(df["actor"].unique()) +random.seed(42) +random.shuffle(actors) + +val_count = max(1, int(0.2 * len(actors))) +val_actors = set(actors[:val_count]) +train_actors = set(actors[val_count:]) + +train_df = df[df["actor"].isin(train_actors)].reset_index(drop=True) +val_df = df[df["actor"].isin(val_actors)].reset_index(drop=True) + +print(f"Training samples: {len(train_df)}") +print(f"Validation samples: {len(val_df)}") +``` + +### Step 2.3 - Load the feature extractor and model + +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. +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. + +```python +from transformers import AutoFeatureExtractor, HubertForSequenceClassification + +MODEL_NAME = "facebook/hubert-base-ls960" +# The label list defines the numeric class order used during training. +labels = ["angry", "happy", "neutral"] + +# Use the HuBERT feature extractor that matches the pretrained checkpoint. +feature_extractor = AutoFeatureExtractor.from_pretrained(MODEL_NAME) + +model = HubertForSequenceClassification.from_pretrained( + MODEL_NAME, + num_labels=len(labels), + id2label={i: label for i, label in enumerate(labels)}, + label2id={label: i for i, label in enumerate(labels)}, +) +``` + +### Step 2.4 - Train and evaluate the model + +This step performs the core training pass: load audio, extract features, compute loss, and update the model weights. For clarity, this example uses a simple per-sample training loop rather than batching with a `DataLoader`. +During training, the model compares its predictions with the correct labels and updates its weights to reduce the error. After each epoch, the validation loop checks how well the model performs on held-out speakers. + +For better performance in a production training pipeline, you would typically use batching and a PyTorch `DataLoader`. + +```python +import librosa +import torch + +def load_audio(path): + # Resample audio to 16 kHz, the sampling rate expected by HuBERT. + return librosa.load(path, sr=16000)[0] + +if torch.cuda.is_available(): + device = torch.device("cuda") +elif torch.backends.mps.is_available(): + device = torch.device("mps") +else: + device = torch.device("cpu") + +model.to(device) + +optimizer = torch.optim.AdamW(model.parameters(), lr=2e-5) +EPOCHS = 4 + +for epoch in range(EPOCHS): + print(f"Epoch {epoch + 1}/{EPOCHS}") + # Switch back to training mode at the start of each epoch. + model.train() + + for i, (_, row) in enumerate(train_df.iterrows(), start=1): + audio = load_audio(row["path"]) + + # Convert raw waveform into model input tensors. + inputs = feature_extractor( + audio, + sampling_rate=16000, + return_tensors="pt" + ) + inputs = {k: v.to(device) for k, v in inputs.items()} + + # Convert the string label into a class index. + label = torch.tensor([labels.index(row["emotion"])]).to(device) + + loss = model(**inputs, labels=label).loss + + loss.backward() + optimizer.step() + optimizer.zero_grad() + + if i % 50 == 0: + print(f"Processed {i} training samples") + + model.eval() + correct = 0 + + with torch.no_grad(): + for _, row in val_df.iterrows(): + audio = load_audio(row["path"]) + inputs = feature_extractor( + audio, + sampling_rate=16000, + return_tensors="pt" + ) + inputs = {k: v.to(device) for k, v in inputs.items()} + + logits = model(**inputs).logits + pred = logits.argmax(dim=-1).item() + + if pred == labels.index(row["emotion"]): + correct += 1 + + accuracy = correct / max(1, len(val_df)) + print(f"Validation accuracy: {accuracy:.2f}") +``` + +At the end of this step, you will have a trained model and a simple validation accuracy printed after each epoch. You should typically see validation accuracy improve over time, although the exact values will vary depending on randomness and hardware. + +### Step 2.5 - Save the model + +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. +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. + +```python +import os + +SAVE_DIR = "models/hubert_vsa_ravdess" +os.makedirs(SAVE_DIR, exist_ok=True) + +# Save both the trained model and the preprocessing configuration. +model.save_pretrained(SAVE_DIR) +feature_extractor.save_pretrained(SAVE_DIR) +``` + +After this step, the trained model should be saved in `models/hubert_vsa_ravdess`. + +## Full training script + +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. +Use this script if you want to run the full workflow without copying each block separately. + +```python +import os +import random +import urllib.request +import zipfile + +import librosa +import pandas as pd +import torch +from transformers import AutoFeatureExtractor, HubertForSequenceClassification + +# Configuration for dataset download, training, and model export. +RAVDESS_URL = "https://zenodo.org/records/1188976/files/Audio_Speech_Actors_01-24.zip?download=1" +OUTPUT_DIR = "data/ravdess" +ZIP_PATH = f"{OUTPUT_DIR}/Audio_Speech_Actors_01-24.zip" +EXTRACT_DIR = f"{OUTPUT_DIR}/Audio_Speech_Actors_01-24" +MODEL_NAME = "facebook/hubert-base-ls960" +SAVE_DIR = "models/hubert_vsa_ravdess" +EPOCHS = 4 + +emotion_map = {1: "neutral", 3: "happy", 5: "angry"} +# The label list defines the numeric class order used during training. +labels = ["angry", "happy", "neutral"] + +os.makedirs(OUTPUT_DIR, exist_ok=True) + +if not os.path.exists(ZIP_PATH): + print("Downloading dataset...") + urllib.request.urlretrieve(RAVDESS_URL, ZIP_PATH) + +if not os.path.exists(EXTRACT_DIR): + zipfile.ZipFile(ZIP_PATH).extractall(EXTRACT_DIR) + +data = [] + +for actor in os.listdir(EXTRACT_DIR): + actor_path = os.path.join(EXTRACT_DIR, actor) + if not os.path.isdir(actor_path): + continue + + for f in os.listdir(actor_path): + if f.endswith(".wav"): + # RAVDESS stores the emotion label in the filename tokens. + emotion = int(f.split("-")[2]) + if emotion in emotion_map: + data.append( + { + "emotion": emotion_map[emotion], + "actor": actor, + "path": os.path.join(actor_path, f), + } + ) + +df = pd.DataFrame(data) +print(df.head()) +print(df["emotion"].value_counts()) + +actors = sorted(df["actor"].unique()) +random.seed(42) +random.shuffle(actors) + +val_count = max(1, int(0.2 * len(actors))) +val_actors = set(actors[:val_count]) +train_actors = set(actors[val_count:]) + +train_df = df[df["actor"].isin(train_actors)].reset_index(drop=True) +val_df = df[df["actor"].isin(val_actors)].reset_index(drop=True) + +print(f"Training samples: {len(train_df)}") +print(f"Validation samples: {len(val_df)}") + +feature_extractor = AutoFeatureExtractor.from_pretrained(MODEL_NAME) +model = HubertForSequenceClassification.from_pretrained( + MODEL_NAME, + num_labels=len(labels), + id2label={i: label for i, label in enumerate(labels)}, + label2id={label: i for i, label in enumerate(labels)}, +) + +if torch.cuda.is_available(): + device = torch.device("cuda") +elif torch.backends.mps.is_available(): + device = torch.device("mps") +else: + device = torch.device("cpu") + +model.to(device) + +def load_audio(path): + # Load and resample audio to the input rate expected by HuBERT. + return librosa.load(path, sr=16000)[0] + +optimizer = torch.optim.AdamW(model.parameters(), lr=2e-5) + +for epoch in range(EPOCHS): + print(f"Epoch {epoch + 1}/{EPOCHS}") + # Switch back to training mode at the start of each epoch. + model.train() + + for i, (_, row) in enumerate(train_df.iterrows(), start=1): + audio = load_audio(row["path"]) + # Convert raw audio into tensors the model can consume. + inputs = feature_extractor( + audio, + sampling_rate=16000, + return_tensors="pt", + ) + inputs = {k: v.to(device) for k, v in inputs.items()} + # Map the label name to its numeric class ID. + label = torch.tensor([labels.index(row["emotion"])]).to(device) + + loss = model(**inputs, labels=label).loss + loss.backward() + optimizer.step() + optimizer.zero_grad() + + if i % 50 == 0: + print(f"Processed {i} training samples") + + model.eval() + correct = 0 + + with torch.no_grad(): + for _, row in val_df.iterrows(): + audio = load_audio(row["path"]) + inputs = feature_extractor( + audio, + sampling_rate=16000, + return_tensors="pt", + ) + inputs = {k: v.to(device) for k, v in inputs.items()} + + logits = model(**inputs).logits + pred = logits.argmax(dim=-1).item() + + if pred == labels.index(row["emotion"]): + correct += 1 + + accuracy = correct / max(1, len(val_df)) + print(f"Validation accuracy: {accuracy:.2f}") + +os.makedirs(SAVE_DIR, exist_ok=True) +model.save_pretrained(SAVE_DIR) +feature_extractor.save_pretrained(SAVE_DIR) + +print("Training complete. Model saved successfully.") +print(f"Saved model to {SAVE_DIR}") +``` + +Run the training script: + +```bash +python train_sentiment_model.py +``` + +At this point, you have a trained voice sentiment classification model saved locally. In the next section, you will convert this model to ONNX format and compress it for more efficient inference. diff --git a/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/5_compress-and-convert-the-model.md b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/5_compress-and-convert-the-model.md new file mode 100644 index 0000000000..4527933ca9 --- /dev/null +++ b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/5_compress-and-convert-the-model.md @@ -0,0 +1,176 @@ +--- +title: Convert and quantize the model +weight: 6 +layout: learningpathall +--- +In this section, you will convert the trained model to ONNX format and compress it for efficient on-device inference. First, export the trained PyTorch model to ONNX. Then apply post-training quantization to reduce model size and improve runtime efficiency. + +### Step 3.1 - Export to ONNX + +This step loads the trained HuBERT checkpoint and exports it to an ONNX graph. +The exported ONNX model becomes the base file used in the quantization step. + +ONNX export may take a few seconds. If it fails, ensure the trained model from the previous section exists in `models/hubert_vsa_ravdess`. + +Save the following workflow as `convert_and_quantize_model.py`, or reuse the relevant parts in your own conversion script: + +```python +import os, torch +from transformers import AutoFeatureExtractor, HubertForSequenceClassification + +MODEL_DIR = "models/hubert_vsa_ravdess" +ONNX_DIR = "models/hubert_vsa_ravdess_onnx" +# Create the output directory for ONNX files. +os.makedirs(ONNX_DIR, exist_ok=True) + +# Load trained model weights from the previous section. +model = HubertForSequenceClassification.from_pretrained(MODEL_DIR) +# Switch to evaluation mode for deterministic export behavior. +model.eval() + +feature_extractor = AutoFeatureExtractor.from_pretrained(MODEL_DIR) + +# Create a dummy 1-second input to define input tensor shapes. +dummy = feature_extractor( + [0.0] * 16000, + sampling_rate=16000, + return_tensors="pt", + return_attention_mask=True +) + +# Export model graph and IO signatures to ONNX. +torch.onnx.export( + model, + (dummy["input_values"], dummy["attention_mask"]), + os.path.join(ONNX_DIR, "hubert_vsa_ravdess.onnx"), + input_names=["input_values", "attention_mask"], + output_names=["logits"], + opset_version=18 +) +``` + +After this step, you should have `hubert_vsa_ravdess.onnx` in the ONNX output directory. + +For production use, consider exporting with dynamic axes to support variable-length audio inputs. + +You can verify the exported file with: + +```bash +ls models/hubert_vsa_ravdess_onnx +``` + +### Step 3.2 - Quantize model + +This step applies dynamic INT8 quantization to the ONNX model. The model weights are quantized to integer 8 bits ahead of time, while activations are quantized dynamically during inference. + +Quantization typically reduces model size by around 3x to 4x and often improves CPU inference speed, depending on the hardware and model. + +```python +from onnxruntime.quantization import quantize_dynamic, QuantType + +quantize_dynamic( + model_input="models/hubert_vsa_ravdess_onnx/hubert_vsa_ravdess.onnx", + model_output="models/hubert_vsa_ravdess_onnx/hubert_vsa_ravdess_int8.onnx", + weight_type=QuantType.QInt8 +) +``` + +After this step, you should have a smaller quantized model file for deployment. + +You can verify the quantized model file with: + +```bash +ls models/hubert_vsa_ravdess_onnx +``` + +To compare file sizes more easily, you can also run: + +```bash +ls -lh models/hubert_vsa_ravdess_onnx +``` + +### Step 3.3 - Run ONNX inference + +This step validates that the quantized model can run inference with ONNX Runtime. +It also checks label decoding so you can confirm the output end to end. + +You can add this inference check to `convert_and_quantize_model.py` after the quantization step, or run it separately in a short test script. + +```python +import onnxruntime as ort +import numpy as np +import librosa +from transformers import AutoFeatureExtractor, AutoConfig + +MODEL_DIR = "models/hubert_vsa_ravdess" +ONNX_PATH = "models/hubert_vsa_ravdess_onnx/hubert_vsa_ravdess_int8.onnx" +SAMPLE_PATH = "data/ravdess/Audio_Speech_Actors_01-24/Actor_01/03-01-01-01-01-01-01.wav" + +# Initialize ONNX Runtime session with the quantized graph. +session = ort.InferenceSession(ONNX_PATH, providers=["CPUExecutionProvider"]) + +fx = AutoFeatureExtractor.from_pretrained(MODEL_DIR) +id2label = AutoConfig.from_pretrained(MODEL_DIR).id2label + +# Load one sample at 16 kHz. +audio, _ = librosa.load(SAMPLE_PATH, sr=16000) + +# Convert waveform to ONNX input tensors. +inputs = fx( + audio, + sampling_rate=16000, + return_tensors="np", + return_attention_mask=True +) + +# Run inference and retrieve logits output. +logits = session.run(None, { + "input_values": inputs["input_values"], + "attention_mask": inputs["attention_mask"] +})[0] + +# Convert highest-scoring class index to label name. +pred = int(np.argmax(logits)) +print("Predicted:", id2label[pred]) +``` + +If this file does not exist on your system, replace `SAMPLE_PATH` with any `.wav` file from your extracted RAVDESS dataset. + +After this step, you have verified that quantized ONNX inference works with your trained model. + +You should see a predicted label such as: + +```text +Predicted: happy +``` + +This layout helps keep the training and deployment files separate and easy to manage. + +```text +models/ + hubert_vsa_ravdess/ + hubert_vsa_ravdess_onnx/ + hubert_vsa_ravdess.onnx + hubert_vsa_ravdess_int8.onnx +``` + +### Model compression results + +We can also compare the model metrics before and after quantization. + +![Model ONNX conversion and int-8 quantization results#center](4_ModelConversionandCompression.png "Model ONNX conversion and int-8 quantization results") + +Typical results look like: + +```text +Original ONNX model: larger size, higher memory footprint +Quantized INT8 ONNX model: smaller size, lower memory footprint, faster CPU inference +``` + +## Troubleshooting + +- ONNX export fails: ensure the trained model exists in `models/hubert_vsa_ravdess`. +- Inference error about missing inputs: confirm that both `input_values` and `attention_mask` are passed to the ONNX session. +- Slow inference: make sure you are using `hubert_vsa_ravdess_int8.onnx` rather than the unquantized ONNX model. + +The model is now ready for integration into the voice pipeline in the next section. diff --git a/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/6_VSApipeline4.png b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/6_VSApipeline4.png new file mode 100644 index 0000000000000000000000000000000000000000..e78df2612d8b374f927d4994fc022d810aa9e51e GIT binary patch literal 27462 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 za Date: Tue, 14 Apr 2026 16:32:07 +0100 Subject: [PATCH 2/5] Fix lowercase asset paths for voice sentiment learning path --- .../{1_VSApipeline2.png => 1_vsapipeline2.png} | Bin .../{3_VSApipeline1.png => 3_vsapipeline1.png} | Bin ...sion.png => 4_modelconversionandcompression.png} | Bin .../{6_VSApipeline4.png => 6_vsapipeline4.png} | Bin 4 files changed, 0 insertions(+), 0 deletions(-) rename content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/{1_VSApipeline2.png => 1_vsapipeline2.png} (100%) rename content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/{3_VSApipeline1.png => 3_vsapipeline1.png} (100%) rename content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/{4_ModelConversionandCompression.png => 4_modelconversionandcompression.png} (100%) rename content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/{6_VSApipeline4.png => 6_vsapipeline4.png} (100%) diff --git a/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/1_VSApipeline2.png b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/1_vsapipeline2.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/1_VSApipeline2.png rename to content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/1_vsapipeline2.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/3_VSApipeline1.png b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/3_vsapipeline1.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/3_VSApipeline1.png rename to content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/3_vsapipeline1.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/4_ModelConversionandCompression.png b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/4_modelconversionandcompression.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/4_ModelConversionandCompression.png rename to content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/4_modelconversionandcompression.png diff --git a/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/6_VSApipeline4.png b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/6_vsapipeline4.png similarity index 100% rename from content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/6_VSApipeline4.png rename to content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/6_vsapipeline4.png From 43700722e16a9d30688431869245c40d9b06c752 Mon Sep 17 00:00:00 2001 From: Bhanu Arya Date: Tue, 14 Apr 2026 16:32:37 +0100 Subject: [PATCH 3/5] Update image links for lowercase asset names --- .../voice-sentiment-analysis-with-llm/1_overview.md | 2 +- .../3_build-baseline-voice-to-llm-pipeline.md | 2 +- .../5_compress-and-convert-the-model.md | 2 +- .../6_integrate-voice-sentiment-classification-model.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/1_overview.md b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/1_overview.md index 6255d43f05..ff033f06c3 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/1_overview.md +++ b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/1_overview.md @@ -7,6 +7,6 @@ Voice-based LLM applications often rely primarily on transcribed text from speec 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. -![Voice sentiment classification pipeline#center](1_VSApipeline2.png "Voice sentiment classification pipeline") +![Voice sentiment classification pipeline#center](1_vsapipeline2.png "Voice sentiment classification pipeline") You will start by building a baseline voice-to-LLM pipeline—capturing audio, transcribing it into text, and using it to generate responses with an LLM. You will then extend this pipeline with a voice-based sentiment classification model. This involves training the model, optimizing it for efficient on-device inference, and integrating it into a unified application. diff --git a/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/3_build-baseline-voice-to-llm-pipeline.md b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/3_build-baseline-voice-to-llm-pipeline.md index 9cbfa29f2f..051cc919a2 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/3_build-baseline-voice-to-llm-pipeline.md +++ b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/3_build-baseline-voice-to-llm-pipeline.md @@ -13,7 +13,7 @@ In this section, you will build an end-to-end pipeline that: This forms the foundation of your voice assistant. -![Baseline voice-to-LLM pipeline#center](3_VSApipeline1.png "Baseline voice-to-LLM pipeline") +![Baseline voice-to-LLM pipeline#center](3_vsapipeline1.png "Baseline voice-to-LLM pipeline") Before you begin, make sure you have completed the environment setup in the previous section and that your `llama-server` is still running. diff --git a/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/5_compress-and-convert-the-model.md b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/5_compress-and-convert-the-model.md index 4527933ca9..eae611c416 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/5_compress-and-convert-the-model.md +++ b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/5_compress-and-convert-the-model.md @@ -158,7 +158,7 @@ models/ We can also compare the model metrics before and after quantization. -![Model ONNX conversion and int-8 quantization results#center](4_ModelConversionandCompression.png "Model ONNX conversion and int-8 quantization results") +![Model ONNX conversion and int-8 quantization results#center](4_modelconversionandcompression.png "Model ONNX conversion and int-8 quantization results") Typical results look like: diff --git a/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/6_integrate-voice-sentiment-classification-model.md b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/6_integrate-voice-sentiment-classification-model.md index aac1f49c3b..3e56280ad9 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/6_integrate-voice-sentiment-classification-model.md +++ b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/6_integrate-voice-sentiment-classification-model.md @@ -7,7 +7,7 @@ In this section, you will integrate the voice sentiment classification model int End-to-end response time may take a few seconds because the application runs Whisper transcription, ONNX sentiment inference, and local LLM generation for each interaction. -![Integrated voice-to-LLM pipeline#center](6_VSApipeline4.png "Integrated voice-to-LLM pipeline") +![Integrated voice-to-LLM pipeline#center](6_vsapipeline4.png "Integrated voice-to-LLM pipeline") From eb5624bc5830bc4e18706792965d6487cdb44cd8 Mon Sep 17 00:00:00 2001 From: pareenaverma Date: Fri, 17 Apr 2026 11:11:34 -0400 Subject: [PATCH 4/5] Set voice sentiment analysis project as draft Mark the voice sentiment analysis project as a draft and add cascading draft status. --- .../voice-sentiment-analysis-with-llm/_index.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/_index.md b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/_index.md index 5c4054ba9f..946afef536 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/_index.md +++ b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/_index.md @@ -1,6 +1,10 @@ --- title: Build a Sentiment-Aware Voice Assistant with On-Device LLMs +draft: true +cascade: + draft: true + description: Build an end-to-end, on-device voice assistant that understands both speech and emotion using Whisper, HuBERT, ONNX Runtime, and a local LLM with llama.cpp on Arm. minutes_to_complete: 90 From e96c4a043711324f58ffe13d6bc3c85b63de7e36 Mon Sep 17 00:00:00 2001 From: pareenaverma Date: Fri, 17 Apr 2026 11:17:51 -0400 Subject: [PATCH 5/5] Update _index.md --- .../voice-sentiment-analysis-with-llm/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/_index.md b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/_index.md index 946afef536..22cbb71d11 100644 --- a/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/_index.md +++ b/content/learning-paths/mobile-graphics-and-gaming/voice-sentiment-analysis-with-llm/_index.md @@ -28,7 +28,7 @@ prerequisites: author: Bhanu Arya ### Tags -skilllevels: Intermediate +skilllevels: Advanced subjects: ML armips: - Cortex-A