Welkom op julesgraus.nl

Google Podcasts

09-07-2020

Hoe maak je een chatbot

Stap voor stap bekijken we hoe een chatbot werkt. En geef ik je ideeën over hoe je zou kunnen starten er zelf een te maken.

Introductie

In het midden van de jaren 60 maakte een computer wetenschapper Joseph Weizenbaum een experimenteel computer programma. Dit programma kreeg de naam Eliza. Je kon dit programma een script geven dat ingevoerde tekst van een gebruiker las en vervolgens een antwoord gaf. Het werd beroemd doordat mensen in eerste instantie geloofden, dat wanneer ze er mee communiceerden, dat ze met een echt persoon aan het communiceren waren!

Dit laatste fascineerde me enorm. Want, wat maakte men die “menselijke connectie” voelde? Hoe werkte dat? En, hoe kon deze technologie in de jaren 60 al beschikbaar zijn! Zonder veel te spieken hoe andere mensen daadwerkelijk ook hun versie van Eliza maakten, besloot ik ook een poging te wagen. Soms is een wiel zelf opnieuw uitvinden ook erg leerzaam. Laten we dat stap voor stap analyseren!

Overzicht

Als allereerste hebben we een class nodig van waar we het hele proces uit aansturen. We delegeren naar subonderdelen om het “Eliza effect” als resultaat te krijgen. Deze class heb ik Elsa genoemd. Omdat ik een Nederlandse versie wilde maken en het niet een exacte kopie van Eliza zal kunnen zijn. Er circuleren gescande documenten in waar wordt uitgelegd hoe het programma ongeveer werkt. Hier vind je er bijvoorbeeld eentje. De originele broncode kon ik niet meer vinden. Wel re-makes waar ik niet in wilde spieken. Dit is wat erover te vinden was en hoe het grofweg werkt:

  1. Lees en doorzoek een ingegeven tekst voor een sleutelwoord.
  2. Maak aanpassingen (transformeer) de ingegeven tekst aan de hand van een bepaalde set regels die bij het gevonden sleutelwoord horen wanneer het sleutelwoord gevonden werd, behalve als er geen tekst werd ingegeven of onder bepaalde voorwaarden. In dat laatste geval wordt een eerdere transformatie gebruikt.
  3. Toon de getransformeerde tekst of de eerder getransformeerde tekst.

In werkelijkheid zit het nog iets complexer in elkaar. Maar hiermee ging ik al lekker mee aan de slag.

LanguageParser

Ik had dus iets nodig wat tekst moest interpreteren. Dus maakte ik de LanguageParser class. Elsa kan naar deze class delegeren om taal te interpreteren en een antwoord te vinden. Zoals we zien in stap 1, zoeken we eerst naar sleutelwoorden. En waar vinden we die? In een script! Want Eliza kon je een script geven dat ze gebruikt om antwoorden te maken. Dit script was uitwisselbaar door andere scripts. Maar in het begin van het project besloot ik een class te maken met daarin het script. Later heb ik dat vervangen door een json script dat ik kan uitwisselen. Om vanuit Elsa naar de LanguageParser te delegeren, maakte ik de functie parse. Deze ontvangt tekst van Elsa om deze vervolgens te verwerken en een antwoord terug te geven.

Zoeken naar sleutelwoorden

Het eerste wat de LanguageParser moet zien te vinden is een sleutelwoord in de zin die de gebruiker typte. Hiervoor laat ik de computer stap voor stap door elke set regels lopen. Telkens als een sleutelwoord, wat bij die set regels hoort, gevonden wordt, laat ik deze set onthouden. Totdat ik een lijstje heb met mogelijk te gebruiken sets. Zo ziet een set er dan bijvoorbeeld uit voor het sleutelwoord (keyword) als:

{
  "keyword": "als",
  "ranking": 3,
  "rules": [
    {
      "decompositionRule": "* als *",
      "assemblyRules": [
        {
          "rule": "Denk je dat het waarschijnlijk is dat (3)?"
        },
        {
          "rule": "Zou je willen dat (3)?"
        }
      ]
    }
  ]
}

Aanpassingen maken

Nu we de sets met regels gevonden hebben kunnen we een stapje verder gaan. Het doel van deze stap is de ingegeven tekst van de gebruiker aanpassen zodat deze een deel van het antwoord wordt wat de gebruiker te zien zal krijgen. Dit gebeurt in de volgende stappen:

  1. De ingegeven tekst aanpassen (pre processing)
  2. De gevonden sets verder filteren op de ingegeven, aangepaste tekst uit stap 1.
  3. Het eerste of eerstvolgende deel van de gesplitste ingegeven tekst uit het geheugen halen.
  4. Een regel uit de set ophalen die van toepassing is, door de decomposition rule proberen toe te passen. Deze ontleed de zin in stukken. Lukt dat niet? Herhaal dan vorige stap.
  5. De ontlede tekst nogmaals aanpassen (post processing).
  6. De zin weer op een bepaalde manier in elkaar zetten, door één van de assembly rules te gebruiken.
  7. Als dat allemaal gelukt is, geeft de LanguageParser het resultaat terug, of niet als het niet lukte.

Laten we de bovenstaande stappen doorlopen met de volgende voorbeeld tekst en bovenstaande regels. “Wat als ik wellicht voor mezelf opkom?”.

  1. “Wellicht” naar “Misschien“ transformeren. Resultaat: “Wat als ik misschien voor mezelf opkom?”
  2. De bovenstaande voorbeeld set word gekozen uit een reeks sets die ook het keyword als bevatten.
  3. In de tekst staat geen komma of punt die regels van elkaar scheiden. Het resultaat is een array (opsomming) waar enkel en alleen de tekst “Wat als ik misschien voor mezelf opkom?” in staat.
  4. De tekst “Wat als ik misschien voor mezelf opkom?” word uit de opsomming gehaald.
  5. De decomposition rule wordt toegepast en deze splitst de zin in een array (opsomming die er zo uit ziet): ['Wat', 'als', jij misschien voor jezelf opkom?']
  6. Elk deel uit de opsomming word nog eens aangepast zodat dit het resultaat is: ['Wat', 'als', 'ik misschien voor mezelf opkom?']
  7. De opsomming word in elkaar gezet met de rule: { "rule": "Zou je willen dat (3)?" } Met als resultaat: Zou je willen dat jij misschien voor jezelf opkom.
  8. De aanpassingen zijn gelukt en de LanguageParser geeft het resultaat terug aan Elsa.
  9. Ik weet dat het woord “opkom” verkeerd geschreven is in die context, maar die transformatie is nog
  10. een stapje moeilijker en valt buiten de scope van dit artikel.

Elsa en de andere subonderdelen

Elsa zal het antwoord vervolgens het eerder aan de gebruiker tonen door deze door te geven naar de omgeving buiten de Elsa class, die het vervolgens op het scherm toont. Mocht dit niet lukken om een antwoord te vinden, dan geeft Elsa een standaard antwoord. Bijvoorbeeld Ok. Niet het meest ideale antwoord. Maar wel iets.

Dit geeft een basis inzicht van hoe Eliza wellicht gewerkt zou moeten hebben. Ik ben nog stapjes verder gegaan door Elsa bijvoorbeeld ook te laten werken met synoniemen van woorden. Dus in plaats van letterlijk naar een sleutelwoord als “denk” te kijken, ook te kijken en te reageren op synoniemen daarvan: "geloof", "voel", "wens", "wil".

En tevens kan Elsa een eerder ingegeven tekst herkennen door een class (subonderdeel) te gebruiken, en daarop reageren te met bijvoorbeeld dit antwoord: Kun je me uitleggen waarom dat je jezelf herhaalt?

Het nut van de preprocess en postprocess stappen

Je zult je vast afvragen waarom het nodig is om 2 keer aanpassingen te maken. Waarom dat het nodig is om 2 keer woorden in de zin te veranderen. Bij de eerste keer worden sommige woorden veranderd om ervoor te zorgen dat ze opgepikt worden wanneer er naar sleutelwoorden wordt gezocht.

Neem bijvoorbeeld de zin van eerder. De gebruikte regel werd gekozen omdat het woord “als” er in stond. Maar stel nou dat er in de plaats van “als” het woord “indien” zou staan. Dan stond er dus “Wat indien ik wellicht voor mezelf opkom?”. En daardoor zou de eerder gebruikte set met regels niet gebruikt worden. Een gemiste kans, want de lading is zowat hetzelfde! De preprocess stap biedt hier de oplossing. Deze gebruikt namelijk een lijst die er ongeveer zo uit ziet:

"pre": {
  "wellicht": "misschien",
  "indien": "als",
  "hoe": "wat",
},

Het woord indien, wordt dan veranderd naar als. Waardoor dat de set met regels wel gevonden wordt en de rest van de aanpassingen plaats kan vinden!

Is er nog meer?

Kijk eens in de sourcecode van Elsa zou ik zeggen. Je zou jezelf bijvoorbeeld kunnen afvragen hoe je Elsa zou kunnen verbeteren. Hoe zou jij de regels rangschikken / de prioriteit ervan bepalen? Hoe houd je Elsa’s antwoorden nog verklaarbaar als je nog meer regelsets zou toevoegen? Kun je Elsa nog intelligentere antwoorden laten geven? Ik denk als je daar mee aan de slag gaat, dat je heel veel kunt leren! En als je pull request zou maken naar de repository, kan ik er ook nog wat van leren.

Wat ik zelf nogal bijzonder vind, is dat Eliza al in de jaren 60 bestond, en de huiselijke toepassingen die we in 2020 hebben niet heel erg anders zijn. Kijk maar eens naar de digitale assistenten die we hebben. Zijn ze zoveel slimmer? Je zou misschien verwachten dat we er al veel verder mee zouden moeten zijn. Zeker na meer als 50 jaar. Kunstmatige intelligentie is een hele tijd niet populair geweest. Maar het is op de dag van vandaag weer in opmars.