V-ați întrebat vreodată – putem face Node.js să verifice dacă ceea ce spunem este pozitiv sau negativ?

Am primit un buletin informativ care discuta despre detectarea tonurilor. Programul poate verifica ceea ce scriem și apoi ne spune dacă ar putea fi văzut ca fiind agresiv, încrezător sau o varietate de alte sentimente.

Asta m-a făcut să mă întreb cum aș putea construi o versiune simplificată folosind browserul și Node.js care ar fi inițiată prin vorbire.

Drept urmare, am ajuns la un mic proiect care detectează dacă ceea ce s-a vorbit are valență pozitivă, neutră sau negativă.

Iată cum am făcut-o.

Planul

Cum sa construiti un convertor Speech to Emotion cu API ul
Detectarea vocii -> Vocea către text -> Scorarea textului -> Rezultat

Când începeți un proiect, ar trebui să schițați – cel puțin vag – obiectivul dvs. și cum să îl atingeți. Înainte de a începe căutarea, am notat că aveam nevoie de:

  • Inregistrarea vocii
  • O modalitate de a traduce înregistrarea în text
  • O modalitate de a da textului un scor
  • O modalitate de a arăta rezultatul utilizatorului care tocmai a vorbit

După ce am cercetat o vreme, am descoperit că înregistrarea vocală și traducerea în părți de text au fost deja realizate de API Web Speech care este disponibil în Google Chrome. Are exact ceea ce avem nevoie în Recunoaștere a vorbirii interfață.

În ceea ce privește notarea textului, am găsit AFINN care este o listă de cuvinte care sunt deja marcate. Are un domeniu limitat, cu „doar” 2477 de cuvinte, dar este mai mult decât suficient pentru proiectul nostru.

Deoarece folosim deja browserul, putem afișa un alt emoji cu HTML, JavaScript și CSS, în funcție de rezultat. Deci, asta se ocupă de ultimul nostru pas.

Acum, că știm ce vom folosi, putem rezuma:

  • Browserul ascultă utilizatorul și returnează un anumit text folosind API-ul Web Speech
  • Face o cerere către serverul nostru Node.js cu textul
  • Serverul evaluează textul folosind lista AFINN și returnează scorul
  • Browserul afișează un emoji diferit în funcție de scor

Notă: Dacă sunteți familiarizați cu configurarea proiectului, puteți omite în cea mai mare parte secțiunea „Fișierele și configurarea proiectului” de mai jos.

Fișiere de proiect și configurare

Structura de dosare și fișiere a proiectului nostru va fi după cum urmează:

src/
  |-public // folder with the content that we will feed to the browser
    |-style // folder for our css and emojis
      |-css // optional folder, we have only one obvious file
        |-emojis.css
      |-images // folder for the emojis
    |-index.html
    |-recognition.js
  package.json
  server.js // our Node.js server

În partea din față a lucrurilor, a noastră index.html fișierul va include JS și CSS:

<html>
  <head>
    <title>
      Speech to emotion
    </title>
	<link rel="stylesheet" href="https://www.freecodecamp.org/news/speech-to-sentiment-with-chrome-and-nodejs/style/css/emojis.css">
  </head>
  <body>
    
    nothing for now
    
    <script src="recognition.js"></script>
  </body>
</html>

recunoaștere.js fișierul va fi înfășurat în fișierul DOMContentLoaded eveniment, astfel încât să ne asigurăm că pagina s-a încărcat înainte de a executa JS-ul nostru:

document.addEventListener('DOMContentLoaded', speechToEmotion, false);

function speechToEmotion() {
  // Web Speech API section code will be added here
}

Ne lăsăm de-ai noștri emojis.css gol deocamdata.

În dosarul nostru, vom rula npm run init care va crea pachet.json.

Pentru moment, va trebui să instalăm două pachete pentru a ne ușura viața. Deci doar instalare npm ambii:

  • expressjs – pentru a avea un server HTTP care rulează rapid
  • nodemon – deci nu tastăm constant nod server.js ori de câte ori facem o schimbare în fișier server.js.

pachet.json va sfârși prin a arăta așa ceva:

{
  "name": "speech-to-emotion",
  "version": "1.0.0",
  "description": "We speak and it feels us :o",
  "main": "index.js",
  "scripts": {
    "server": "node server.js",
    "server-debug": "nodemon --inspect server.js"
  },
  "author": "daspinola",
  "license": "MIT",
  "dependencies": {
    "express": "^4.17.1"
  },
  "devDependencies": {
    "nodemon": "^2.0.2"
  }
}

server.js începe așa:

const express = require('express')
const path = require('path')

const port = 3000
const app = express()

app.use(express.static(path.join(__dirname, 'public')))

app.get('/', function(req, res) {
  res.sendFile(path.join(__dirname, 'index.html'))
})

app.get('/emotion', function(req, res) {
  // Valence of emotion section code will be here for not it returns nothing
  res.send({})
})

app.listen(port, function () {
  console.log(`Listening on port ${port}!`)
})

Și cu aceasta, putem fugi npm rulează server-debug în linia de comandă și deschideți browserul localhost: 3000. Apoi vom vedea mesajul nostru „nimic deocamdată” din fișierul HTML.

API Web Speech

Acest API iese din cutie în Chrome și conține Recunoaștere a vorbirii. Iată ce ne va permite să pornim microfonul, să vorbim și să obținem rezultatul înapoi ca text.

Funcționează cu evenimente care pot detecta, de exemplu, când sunetul este capturat prima și ultima dată.

Pentru moment, vom avea nevoie de onresult și pe sfarsit evenimente, astfel încât să putem verifica ce a capturat microfonul și, respectiv, când acesta nu mai funcționează.

Pentru a scoate primul nostru sunet la captarea textului, avem nevoie doar de o duzină de linii sau ceva de cod recunoaștere.js fişier.

const recognition = new webkitSpeechRecognition()
recognition.lang = 'en-US'

recognition.onresult = function(event) {
  const results = event.results;
  const transcript = results[0][0].transcript
  
  console.log('text ->', transcript)
}

recognition.onend = function() {
  console.log('disconnected')
}

recognition.start()
Aceasta va conecta microfonul timp de câteva secunde pentru a asculta audio. Dacă nu se găsește nimic, se va deconecta

Putem găsi o listă a limbilor disponibile în documentele Google aici.

Dacă vrem să rămână conectat mai mult de câteva secunde (sau pentru când vorbim de mai multe ori) există o proprietate numită continuu. Poate fi schimbat la fel ca lang proprietate doar prin alocarea acesteia Adevărat. Acest lucru va face ca microfonul să asculte audio la nesfârșit.

const recognition = new webkitSpeechRecognition()
recognition.lang = 'en-US'
recognition.continuous = true

recognition.onresult = function(event) {
  const results = event.results;
  const transcript = results[results.length-1][0].transcript
  
  console.log('text ->', transcript)
}

recognition.onend = function() {
  console.log('disconnected')
}

recognition.start()
Adăugăm continuul și schimbăm transcrierea pentru a obține doar ultimul rezultat în loc de toate rezultatele până acum

Dacă ne reîmprospătăm pagina, la început ar trebui să ne întrebăm dacă vrem să permitem utilizarea microfonului. După ce răspundem da, putem vorbi și verifica pe consola Chrome DevTools rezultatul discursului nostru.

Profanitatea se arată cenzurată și nu pare să existe o modalitate de a elimina cenzura. Ceea ce înseamnă acest lucru este că nu ne putem baza pe blasfemie pentru a înscrie, chiar dacă AFINN este necenzurat.

Notă: În momentul scrierii, acest API poate fi găsit numai în Chrome și Android, cu suportul așteptat pentru Edge în viitorul apropiat. Probabil că există polifilări sau alte instrumente care oferă o compatibilitate mai bună a browserului, dar nu le-am testat. Puteți verifica compatibilitatea în Pot folosi.

Efectuarea cererii

Pentru cerere, un simplu aduc E deajuns. Trimitem transcrierea ca parametru de interogare pe care îl vom apela text.

Al nostru onresult funcția ar trebui să arate acum astfel:

  recognition.onresult = function(event) {
    const results = event.results;
    const transcript = results[results.length-1][0].transcript

    // making a request to our /emotion endpoint that we defined on the project start and setup section
    fetch(`/emotion?text=${transcript}`)
      .then((response) => response.json())
      .then((result) => {
        console.log('result ->', result) // should be undefined
      })
      .catch((e) => {
        console.error('Request error -> ', e)
      })
  }
Dacă ar fi să folosim texte mai lungi ar fi mai bine să trecem / emoția la un POST în loc de un GET. În acest scop, totuși, un GET ar trebui să fie mai mult decât suficient

Valența emoției

Valența poate fi văzută ca o modalitate de a măsura dacă emoțiile noastre sunt pozitive sau negative și dacă creează excitare scăzută sau ridicată.

Pentru acest proiect, vom folosi două emoții: fericit pe partea pozitivă pentru orice scor peste zero și deranjat pe partea negativă pentru scorurile sub zero. Scorurile de zero vor fi considerate indiferente. Orice scor de 0 va fi tratat ca „ce?!

Lista AFINN este marcată de la -5 la 5 și fișierul conține cuvinte astfel organizate:

hope 2
hopeful 2
hopefully 2
hopeless -2
hopelessness -2
hopes 2
hoping 2
horrendous -3
horrible -3
horrific -3
cuvânt scor

Ca exemplu, să presupunem că am vorbit cu microfonul și am spus „Sper că acest lucru nu este oribil”. Asta ar fi 2 puncte pentru „speranță” și -3 puncte pentru „oribil” care ar face ca propoziția noastră să fie negativă cu -1 puncte. Toate celelalte cuvinte care nu se află pe listă le-am ignora pentru scor.

Am putea analiza fișierul și îl putem converti într-un fișier JSON care arată similar cu acesta:

{
  <word>: <score>,
  <word1>: <score1>,
  ..
}

Și apoi am putea verifica fiecare cuvânt din text și rezuma scorurile. Dar asta este ceva care Andrew Sliwinski a făcut deja cu sentiment. Deci, vom folosi asta în loc să codificăm totul de la zero.

Pentru instalare folosim sentimentul de instalare npm și deschis server.js astfel încât să putem importa biblioteca cu:

const Sentiment = require('sentiment');

Urmează schimbarea traseului „/ emoție” în:

app.get('/emotion', function(req, res) {
  const sentiment = new Sentiment()
  const text = req.query.text // this returns our request query "text"
  const score = sentiment.analyze(text);

  res.send(score)
})

sentiment.analyze () face pașii descriși anterior: verifică fiecare cuvânt din textul nostru cu lista AFINN și ne dă un scor la final.

Variabila scor va avea un obiect similar cu acesta:

{
  score: 7,
  comparative: 2.3333333333333335,
  calculation: [ { awesome: 4 }, { good: 3 } ],
  tokens: [ 'good', 'awesome', 'film' ],
  words: [ 'awesome', 'good' ],
  positive: [ 'awesome', 'good' ],
  negative: []
}
Ceea ce ne dorim este proprietatea scorului care, în acest caz, ar duce la un rezultat pozitiv

Acum că am returnat scorul, trebuie doar să-l afișăm în browserul nostru.

Notă: AFINN este în engleză. Deși putem selecta alte limbi în API-ul Web Speech, va trebui să găsim o listă cu punctaje similare cu AFINN în limba dorită pentru ca rezultatul să funcționeze.

Făcând-o să zâmbească

Pentru ultimul nostru pas, ne vom actualiza index.html pentru a afișa o zonă în care putem arăta emoji-urile. Așa că îl schimbăm cu următorul:

<html>
  <head>
    <title>
      Speech to emotion
    </title>
    <link rel="stylesheet" href="https://www.freecodecamp.org/news/speech-to-sentiment-with-chrome-and-nodejs/style/css/emojis.css">
  </head>
  <body>
    <!-- We replace the "nothing for now" -->
    <div class="emoji">
      <img class="idle">
    </div>
    <!-- And leave the rest alone -->
    <script src="recognition.js"></script>
  </body>
</html>

Emoji-urile utilizate în acest proiect sunt gratuite pentru uz comercial și pot fi găsite aici. Salutări artistului.

Descarcăm pictogramele care ne plac și le adăugăm în folderul de imagini. Vom avea nevoie de emoji pentru:

  • eroare – Când apare o eroare
  • inactiv – Ori de câte ori microfonul nu este activ
  • ascultare – Când microfonul este conectat și așteaptă intrarea
  • negativ – Pentru scoruri pozitive
  • neutru – Pentru când scorul este zero
  • pozitiv – Pentru scoruri negative
  • in cautarea – Pentru când se face solicitarea serverului nostru

Și în al nostru emojis.css pur și simplu adăugăm:

.emoji img {
  width: 100px;
  width: 100px;
}

.emoji .error {
  content:url("../images/error.png");
}

.emoji .idle {
  content:url("../images/idle.png");
}

.emoji .listening {
  content:url("../images/listening.png");
}

.emoji .negative {
  content:url("../images/negative.png");
}

.emoji .neutral {
  content:url("../images/neutral.png");
}

.emoji .positive {
  content:url("../images/positive.png");
}

.emoji .searching {
  content:url("../images/searching.png");
}
Primul selector este să îi oferim o dimensiune consistentă, restul sunt imaginile noastre emoji

Când reîncarcăm pagina după aceste modificări, va afișa emoji-ul inactiv. Nu se schimbă niciodată, de vreme ce nu le-am înlocuit inactiv clasă în elementul în funcție de scenariu.

Pentru a remedia asta, mergem pentru ultima oară la a noastră recunoaștere.js fişier. Acolo, vom adăuga o funcție pentru a schimba emoji-ul:

/**
 * @param {string} type - could be any of the following:
 *   error|idle|listening|negative|positive|searching
 */
function setEmoji(type) {
  const emojiElem = document.querySelector('.emoji img')
  emojiElem.classList = type
}

La răspunsul la solicitarea serverului nostru, adăugăm cecul pentru scor pozitiv, negativ sau neutru și îl sunăm pe setEmoji funcţie în consecinţă:

console.log(transcript) // So we know what it understood when we spoke

setEmoji('searching')

fetch(`/emotion?text=${transcript}`)
  .then((response) => response.json())
  .then((result) => {
    if (result.score > 0) {
      setEmoji('positive')
    } else if (result.score < 0) {
      setEmoji('negative')
    } else {
      setEmoji('listening')
    }
  })
  .catch((e) => {
    console.error('Request error -> ', e)
    recognition.abort()
  })
Am setat emoji-ul să caute înainte de a face cererea

În cele din urmă, adăugăm evenimentele onerror și onaudiostart și schimbă evenimentul pe sfarsit deci le avem setate cu emoji-urile corespunzătoare.

  recognition.onerror = function(event) {
    console.error('Recognition error -> ', event.error)
    setEmoji('error')
  }

  recognition.onaudiostart = function() {
    setEmoji('listening')
  }

  recognition.onend = function() {
    setEmoji('idle')
  }

Finalul nostru recunoaștere.js fișierul ar trebui să arate cam așa:

document.addEventListener('DOMContentLoaded', speechToEmotion, false);

function speechToEmotion() {
  const recognition = new webkitSpeechRecognition()
  recognition.lang = 'en-US'
  recognition.continuous = true

  recognition.onresult = function(event) {
    const results = event.results;
    const transcript = results[results.length-1][0].transcript

    console.log(transcript)

    setEmoji('searching')

    fetch(`/emotion?text=${transcript}`)
      .then((response) => response.json())
      .then((result) => {
        if (result.score > 0) {
          setEmoji('positive')
        } else if (result.score < 0) {
          setEmoji('negative')
        } else {
          setEmoji('listening')
        }
      })
      .catch((e) => {
        console.error('Request error -> ', e)
        recognition.abort()
      })
  }

  recognition.onerror = function(event) {
    console.error('Recognition error -> ', event.error)
    setEmoji('error')
  }

  recognition.onaudiostart = function() {
    setEmoji('listening')
  }

  recognition.onend = function() {
    setEmoji('idle')
  }

  recognition.start();

  /**
   * @param {string} type - could be any of the following:
   *   error|idle|listening|negative|positive|searching
   */
  function setEmoji(type) {
    const emojiElem = document.querySelector('.emoji img')
    emojiElem.classList = type
  }
}

Și testând proiectul nostru putem vedea acum rezultatele finale:

Cum sa construiti un convertor Speech to Emotion cu API ul

Notă: În loc de un consolă.log pentru a verifica ce a înțeles recunoașterea, putem adăuga un element pe html și să înlocuim consolă.log. În acest fel, avem întotdeauna acces la ceea ce a înțeles.

Observații finale

Există câteva domenii în care acest proiect poate fi îmbunătățit considerabil:

  • nu poate detecta sarcasmul
  • nu există nicio modalitate de a verifica dacă sunteți furios din cauza cenzurii API-ului speech to text
  • probabil că există o modalitate de a o face doar cu voce fără conversie în text.

Din ceea ce am văzut în timpul cercetării acestui proiect, există implementări care verifică dacă tonul și starea ta de spirit vor duce la o vânzare într-un call center. Și buletinul informativ pe care l-am primit a fost de la Grammarly, care îl folosește pentru a verifica tonul a ceea ce scrieți. Așa cum puteți vedea, există aplicații interesante.

Sperăm că acest conținut a ajutat într-un fel. Dacă cineva construiește ceva folosind acest teanc, anunțați-mă – este întotdeauna distractiv să vedeți ce construiesc oamenii.

Codul poate fi găsit în github-ul meu aici.

Ne vedem în următoarea, între timp, mergi la cod ceva!