Sprachgesteuerte Webvisualisierungen mit Vue.js und Machine Learning

In diesem Tutorial kombinieren wir Vue.js, three.js und LUIS (Cognitive Services), um eine sprachgesteuerte Webvisualisierung zu erstellen.

Aber zuerst ein wenig Kontext

Warum sollten wir die Spracherkennung verwenden? Welches Problem könnte so etwas lösen?

Vor einiger Zeit stieg ich in Chicago in einen Bus. Der Busfahrer sah mich nicht und schloss die Tür an meinem Handgelenk. Als er losfahren wollte, hörte ich ein knallendes Geräusch in meinem Handgelenk und er hörte schließlich auf, als die anderen Passagiere anfingen zu schreien, aber nicht bevor er ein paar Sehnen in meinem Arm riss.

Eigentlich sollte ich mir von der Arbeit freinehmen, aber wie damals für Museumsangestellte typisch, hatte ich einen Arbeitsvertrag und keine richtige Krankenversicherung. Ich habe anfangs nicht viel verdient, also war eine Auszeit keine Option für mich. Ich habe den Schmerz durchgearbeitet. Und schließlich begann sich die Gesundheit meines Handgelenks zu verschlechtern. Es wurde wirklich schmerzhaft, sogar meine Zähne zu putzen. Voice-to-Text war nicht die allgegenwärtige Technologie, die es heute ist, und das beste damals verfügbare Tool war Dragon. Es funktionierte gut, aber es war ziemlich frustrierend, es zu lernen, und ich musste immer noch ziemlich häufig meine Hände benutzen, weil es oft zu Fehlern kam. Das war vor 10 Jahren, daher bin ich mir sicher, dass sich bestimmte Technologien seitdem erheblich verbessert haben. Auch mein Handgelenk hat sich in dieser Zeit deutlich verbessert.

Die ganze Erfahrung hat bei mir ein starkes Interesse an sprachgesteuerten Technologien hinterlassen. Was können wir tun, wenn wir das Verhalten des Internets zu unseren Gunsten steuern können, nur indem wir sprechen? Für ein Experiment habe ich mich für LUIS entschieden, einen auf maschinellem Lernen basierenden Dienst zum Erstellen natürlicher Sprache durch die Verwendung von benutzerdefinierten Modellen, die kontinuierlich verbessert werden können. Wir können dies für Apps, Bots und IoT-Geräte verwenden. Auf diese Weise können wir eine Visualisierung erstellen, die auf jede Stimme reagiert – und sie kann sich selbst verbessern, indem sie dabei lernt.

GitHub-Repository

Live-Demo

Hier ist eine Vogelperspektive von dem, was wir bauen:

LUIS einrichten

Wir bekommen ein kostenloses Testkonto für Azure und gehen dann zum Portal. Wir wählen Cognitive Services aus.

Nachdem Sie Neu → KI/Maschinelles Lernen ausgewählt haben , wählen wir „Sprachverständnis“ (oder LUIS) aus.

Dann wählen wir unseren Namen und unsere Ressourcengruppe aus.

Wir sammeln unsere Schlüssel auf dem nächsten Bildschirm und gehen dann zum LUIS-Dashboard

Es macht wirklich Spaß, diese Maschinen zu trainieren! Wir richten eine neue Anwendung ein und erstellen einige Absichten, d. h. Ergebnisse, die wir basierend auf einer bestimmten Bedingung auslösen möchten. Hier ist das Beispiel dieser Demo:

Sie werden vielleicht bemerken, dass wir hier ein Benennungsschema haben. Wir tun dies, damit die Absichten einfacher kategorisiert werden können. Wir werden zuerst die Emotion herausfinden und dann auf die Intensität achten, also wird den anfänglichen Absichten entweder App vorangestellt (Diese werden hauptsächlich in App.vue verwendet Komponente) oder Intensity .

Wenn wir in jede einzelne Absicht eintauchen, sehen wir, wie das Modell trainiert wird. Wir haben einige ähnliche Ausdrücke, die ungefähr dasselbe bedeuten:

Sie können sehen, dass wir viele Synonyme für das Training haben, aber wir haben oben auch die Schaltfläche „Trainieren“, wenn wir bereit sind, mit dem Training des Modells zu beginnen. Wir klicken auf diese Schaltfläche, erhalten eine Erfolgsbenachrichtigung und können dann veröffentlichen. 😀

Vue einrichten

Wir erstellen eine ziemlich standardmäßige Vue.js-Anwendung über die Vue-CLI. Zuerst führen wir Folgendes aus:

vue create three-vue-pattern
# then select Manually...

Vue CLI v3.0.0

? Please pick a preset:
  default (babel, eslint)
❯ Manually select features

# Then select the PWA feature and the other ones with the spacebar
? Please pick a preset: Manually select features
? Check the features needed for your project:
  ◉ Babel
  ◯ TypeScript
  ◯ Progressive Web App (PWA) Support
  ◯ Router
  ◉ Vuex
  ◉ CSS Pre-processors
  ◉ Linter / Formatter
  ◯ Unit Testing
  ◯ E2E Testing

? Pick a linter / formatter config:
  ESLint with error prevention only
  ESLint + Airbnb config
❯ ESLint + Standard config
  ESLint + Prettier

? Pick additional lint features: (Press <space> to select, a to toggle all, i to invert selection)
❯ ◉ Lint on save
  ◯ Lint and fix on commit

Successfully created project three-vue-pattern.
Get started with the following commands:

$ cd three-vue-pattern
$ yarn serve</space>

Dadurch wird ein Server für uns hochgefahren und ein typischer Vue-Begrüßungsbildschirm angezeigt. Außerdem fügen wir unserer Anwendung einige Abhängigkeiten hinzu:three.js, sine-waves und axios. three.js hilft uns bei der Erstellung der WebGL-Visualisierung. Sinuswellen gibt uns eine schöne Leinwandabstraktion für den Loader. axios ermöglicht uns einen wirklich netten HTTP-Client, damit wir LUIS zur Analyse aufrufen können.

yarn add three sine-waves axios

Einrichten unseres Vuex-Shops

Jetzt, da wir ein funktionierendes Modell haben, holen wir es uns mit Axios und bringen es in unseren Vuex-Shop. Dann können wir die Informationen an alle verschiedenen Komponenten verteilen.

In state , speichern wir, was wir brauchen:

state: {
   intent: 'None',
   intensity: 'None',
   score: 0,
   uiState: 'idle',
   zoom: 3,
   counter: 0,
 },

intent und intensity speichert die App, die Intensität bzw. die Absichten. Die score speichert unser Vertrauen (das ist eine Punktzahl von 0 bis 100, die misst, wie gut das Modell glaubt, die Eingabe einstufen zu können).

Für uiState , haben wir drei verschiedene Zustände:

  • idle – Warten auf die Benutzereingabe
  • listening – Benutzereingaben hören
  • fetching – Abrufen von Benutzerdaten von der API

Beide zoom und counter werden wir verwenden, um die Datenvisualisierung zu aktualisieren.

Jetzt setzen wir in Aktionen den uiState (in einer Mutation) zu fetching , und wir rufen die API mit Axios unter Verwendung der generierten Schlüssel auf, die wir beim Einrichten von LUIS erhalten haben.

getUnderstanding({ commit }, utterance) {
 commit('setUiState', 'fetching')
 const url = `https://westus.api.cognitive.microsoft.com/luis/v2.0/apps/4aba2274-c5df-4b0d-8ff7-57658254d042`

 https: axios({
   method: 'get',
   url,
   params: {
     verbose: true,
     timezoneOffset: 0,
     q: utterance
   },
   headers: {
     'Content-Type': 'application/json',
     'Ocp-Apim-Subscription-Key': ‘XXXXXXXXXXXXXXXXXXX'
   }
 })

Sobald wir das getan haben, können wir die bestplatzierte Bewertungsabsicht abrufen und in unserem state speichern .

Wir müssen auch einige Mutationen erstellen, mit denen wir den Zustand ändern können. Wir werden diese in unseren Aktionen verwenden. In der kommenden Vue 3.0 wird dies gestrafft, da Mutationen entfernt werden.

newIntent: (state, { intent, score }) =&gt; {
 if (intent.includes('Intensity')) {
   state.intensity = intent
   if (intent.includes('More')) {
     state.counter++
   } else if (intent.includes('Less')) {
     state.counter--
   }
 } else {
   state.intent = intent
 }
 state.score = score
},
setUiState: (state, status) =&gt; {
 state.uiState = status
},
setIntent: (state, status) =&gt; {
 state.intent = status
},

Das ist alles ziemlich einfach. Wir übergeben den Zustand, damit wir ihn für jedes Vorkommen aktualisieren können – mit Ausnahme von Intensität, die den Zähler entsprechend nach oben und unten erhöht. Wir werden diesen Zähler im nächsten Abschnitt verwenden, um die Visualisierung zu aktualisieren.

.then(({ data }) =&gt; {
 console.log('axios result', data)
 if (altMaps.hasOwnProperty(data.query)) {
   commit('newIntent', {
     intent: altMaps[data.query],
     score: 1
   })
 } else {
   commit('newIntent', data.topScoringIntent)
 }
 commit('setUiState', 'idle')
 commit('setZoom')
})
.catch(err =&gt; {
 console.error('axios error', err)
})

In dieser Aktion übernehmen wir die Mutationen, die wir gerade durchgegangen sind, oder protokollieren einen Fehler, wenn etwas schief geht.

So wie die Logik funktioniert, wird der Benutzer die erste Aufnahme machen, um zu sagen, wie er sich fühlt. Sie werden einen Knopf drücken, um alles zu starten. Die Visualisierung wird angezeigt, und an diesem Punkt wartet die App kontinuierlich darauf, dass der Benutzer weniger oder mehr sagt, um die zurückgegebene Visualisierung zu steuern. Lassen Sie uns den Rest der App einrichten.

Einrichten der App

In App.vue , zeigen wir zwei verschiedene Komponenten für die Mitte der Seite, je nachdem, ob wir unsere Stimmung bereits angegeben haben oder nicht.

<app-recordintent v-if="intent === 'None'">
<app-recordintensity v-if="intent !== 'None'" :emotion="intent"></app-recordintensity></app-recordintent>

Beide zeigen Informationen für den Betrachter sowie einen SineWaves Komponente, während sich die Benutzeroberfläche in einem lauschenden Zustand befindet.

Die Basis der Anwendung ist, wo die Visualisierung angezeigt wird. Es wird je nach Stimmung mit unterschiedlichen Requisiten gezeigt. Hier sind zwei Beispiele:

<app-base v-if="intent === 'Excited'" :t-config.a="1" :t-config.b="200">
<app-base v-if="intent === 'Nervous'" :t-config.a="1" :color="0xff0000" :wireframe="true" :rainbow="false" :emissive="true"></app-base></app-base>

Einrichten der Datenvisualisierung

Ich wollte mit kaleidoskopartigen Bildern für die Visualisierung arbeiten und fand nach einigem Suchen dieses Repo. Es funktioniert so, dass sich eine Form im Raum dreht und das Bild dadurch auseinanderbricht und Teile davon wie ein Kaleidoskop zeigt. Nun, das klingt vielleicht großartig, weil (juhu!) die Arbeit getan ist, oder?

Leider nicht.

Es mussten einige große Änderungen vorgenommen werden, um dieses Projekt zum Laufen zu bringen, und am Ende war es tatsächlich ein gewaltiges Unterfangen, auch wenn der endgültige visuelle Ausdruck dem Original ähnlich erscheint.

  • Aufgrund der Tatsache, dass wir die Visualisierung abreißen müssten, wenn wir uns entscheiden würden, sie zu ändern, musste ich den vorhandenen Code konvertieren, um bufferArrays zu verwenden , die für diesen Zweck performanter sind.
  • Der ursprüngliche Code war ein großer Block, also habe ich einige der Funktionen in kleinere Methoden auf der Komponente aufgeteilt, um sie leichter lesbar und wartungsfreundlicher zu machen.
  • Da wir die Dinge spontan aktualisieren wollen, musste ich einige der Elemente als Daten in der Komponente speichern und schließlich als Requisiten, die sie von der übergeordneten Komponente erhalten würde. Ich habe auch einige nette Standardeinstellungen (excited sehen alle Standardwerte aus).
  • Wir verwenden den Zähler aus dem Vuex-Zustand, um den Abstand der Kameraposition relativ zum Objekt zu aktualisieren, sodass wir weniger oder mehr davon sehen können und es somit immer weniger komplex wird.

Um das Aussehen entsprechend den Konfigurationen zu ändern, erstellen wir einige Requisiten:

props: {
 numAxes: {
   type: Number,
   default: 12,
   required: false
 },
 ...
 tConfig: {
   default() {
     return {
       a: 2,
       b: 3,
       c: 100,
       d: 3
     }
   },
   required: false
 }
},

Wir verwenden diese, wenn wir die Formen erstellen:

createShapes() {
 this.bufferCamera.position.z = this.shapeZoom

 if (this.torusKnot !== null) {
   this.torusKnot.material.dispose()
   this.torusKnot.geometry.dispose()
   this.bufferScene.remove(this.torusKnot)
 }

 var shape = new THREE.TorusKnotGeometry(
     this.tConfig.a,
     this.tConfig.b,
     this.tConfig.c,
     this.tConfig.d
   ),
   material
 ...
 this.torusKnot = new THREE.Mesh(shape, material)
 this.torusKnot.material.needsUpdate = true

 this.bufferScene.add(this.torusKnot)
},

Wie wir bereits erwähnt haben, ist dies jetzt in eine eigene Methode aufgeteilt. Wir werden auch eine andere Methode erstellen, die die Animation startet, die ebenfalls neu gestartet wird, wenn sie aktualisiert wird. Die Animation verwendet requestAnimationFrame :

animate() {
 this.storeRAF = requestAnimationFrame(this.animate)

 this.bufferScene.rotation.x += 0.01
 this.bufferScene.rotation.y += 0.02

 this.renderer.render(
   this.bufferScene,
   this.bufferCamera,
   this.bufferTexture
 )
 this.renderer.render(this.scene, this.camera)
},

Wir erstellen eine berechnete Eigenschaft namens shapeZoom das wird den Zoom aus dem Laden zurückgeben. Wenn Sie sich erinnern, wird dies aktualisiert, wenn die Stimme des Benutzers die Intensität ändert.

computed: {
 shapeZoom() {
   return this.$store.state.zoom
 }
},

Wir können dann einen Beobachter verwenden, um zu sehen, ob sich die Zoomstufe ändert, und die Animation abbrechen, die Formen neu erstellen und die Animation neu starten.

watch: {
 shapeZoom() {
   this.createShapes()
   cancelAnimationFrame(this.storeRAF)
   this.animate()
 }
},

In den Daten speichern wir auch einige Dinge, die wir zum Instanziieren der three.js-Szene benötigen – vor allem, um sicherzustellen, dass die Kamera genau zentriert ist.

data() {
 return {
   bufferScene: new THREE.Scene(),
   bufferCamera: new THREE.PerspectiveCamera(75, 800 / 800, 0.1, 1000),
   bufferTexture: new THREE.WebGLRenderTarget(800, 800, {
     minFilter: THREE.LinearMipMapLinearFilter,
     magFilter: THREE.LinearFilter,
     antialias: true
   }),
   camera: new THREE.OrthographicCamera(
     window.innerWidth / -2,
     window.innerWidth / 2,
     window.innerHeight / 2,
     window.innerHeight / -2,
     0.1,
     1000
   ),

Es gibt noch mehr zu dieser Demo, wenn Sie das Repo erkunden oder es selbst mit Ihren eigenen Parametern einrichten möchten. Der init -Methode tut, was Sie denken:Sie initialisiert die gesamte Visualisierung. Ich habe viele der wichtigsten Teile kommentiert, wenn Sie sich den Quellcode ansehen. Es gibt auch eine andere Methode, die die aufgerufene Geometrie aktualisiert – Sie haben es erraten – updateGeometry . Sie können dort auch viele Vars bemerken. Das liegt daran, dass es bei dieser Art der Visualisierung üblich ist, Variablen wiederzuverwenden. Wir starten alles, indem wir this.init() anrufen im mounted() Lebenszyklus-Hook.

  • Auch hier ist das Repo, wenn Sie mit dem Code spielen möchten
  • Sie können Ihr eigenes Modell erstellen, indem Sie ein kostenloses Azure-Konto erstellen
  • Sie sollten sich auch LUIS (Cognitive Services) ansehen

Es macht ziemlich Spaß zu sehen, wie weit man kommt, wenn man Dinge für das Web erstellt, die nicht unbedingt eine Handbewegung zur Steuerung erfordern. Es eröffnet viele Möglichkeiten!