A Python egy zseniális nyelv, és a közösségben általánosan elfogadott, hogy nagyszerű fejlesztői tapasztalatokkal rendelkezik.

Személy szerint nem sok dolgom volt a Pythonnal a szakmai és személyes programozási utam során, miután elég negatív tapasztalatom volt a nyelv használatával egy egyetemi adatfeldolgozási tanfolyamon.

Azóta sok érdekes projektről olvasok és figyelek, ami ezzel a nyelvvel és az azt körülvevő könyvtári ökoszisztémával épül fel.

Mindig is azon töprengtem, hogyan építik fel az emberek azokat a bosszantó csevegőbotokat, amelyeket egy e-kereskedelmi webhely alsó sarkában láthat. Tudni akartam, milyen okossá válhatnak, és hogy meg tudom-e építeni a saját alapelememet, amit át tudok ismételni, és még fejlettebbé tenni. Így hát belevágtam az NLP és a Deep Learning fogalmába, valamint abba, hogy hogyan lehet őket alkalmazni egy chat-bot kontextusában és a Python eszközén keresztül.

Ez a cikk egyrészt a tanulási utam dokumentálásaként szolgál, másrészt oktatóanyagként is szolgál, amelyre mások is hivatkozhatnak, amikor saját csevegőbotot szeretnének építeni.

Felelősség kizárása: Ez a csevegőbot tisztán python, és nem tartalmazza a felhasználói interfész létrehozásához szükséges kezelőfelületet. Lehetséges, hogy a jövőbeni cikkekben foglalkozni fogok egy ilyen csevegőbot webes telepítésével, ezért kérjük, maradjon velünk.

Elmélet

Mielőtt elkezdenénk, beszéljünk arról az elméletről, ami mögött megkíséreljük megvalósítani.

Természetes nyelvi feldolgozás (NLP)

Az NLP a nyelvészet, számítástechnika és mesterséges intelligencia interdiszciplináris részterülete, amely a számítógépek és az emberi nyelv interakciójával foglalkozik. NLP-gyakorlatokat alkalmazunk a számítógépek programozására nagy mennyiségű természetes nyelvi adat feldolgozására és elemzésére.

Ok, akkor ez mit jelent?

Lényegében azt jelenti, hogy egy olyan számítógépet szeretnénk közelebb vinni a bemenetek (dokumentumok, üzenetek, chat bot bemenetei stb.) „megértésére”, mint egy ember. Mi különbözteti meg tehát az embert és a számítógépet a természetes nyelv olvasásától és feldolgozásától? Nos, az emberek rendkívül jól megértik a szövegkörnyezetet, ha a természetes nyelvről van szó… A számítógépek mostanában nem olyan jók ebben. De ezt próbáljuk megjavítani.

Mély tanulás

Meglehetősen veszélyes játék a Deep Learning fogalmát egy falatnyi darabba összefoglalni anélkül, hogy úgy tűnhetne, hogy tanácstalan vagy a témában. Még nehezebb nem úgy tűnni, mintha tanácstalan lenne a témában, miközben valójában tudatlana témával kapcsolatban. De tessék.

A mély tanulás az emberi agy utánzására tett kísérlet… Bár messze vagyunk attól, hogy megfeleljünk ennek a képességének. Az emberi agy ezen utánzása lehetővé teszi a rendszerek számára, hogy hihetetlen pontossággal csoportosítsák az adatokat és előrejelzéseket készítsenek.

A mély tanulás a gépi tanulás egy részhalmaza, amely lényegében egy három vagy több rétegből álló neurális hálózat. Ezek a neurális hálózatok megpróbálják szimulálni az agy viselkedését.

Akkor miért vagyunk annyira megszállottak az emberi agy utánzásában? Mi olyan jó az agyunkban?

Kiváló kérdés olvasó.

Az emberi agy sok tehetségének egyike a tanulási képességünk.

A mélytanulás számos mesterséges intelligencia alkalmazást és szolgáltatást vezérel, amelyek javítják az automatizálást, emberi beavatkozás nélkül végezve analitikai és fizikai feladatokat.

Lehetne mélyebbre is elmélyülni, de szerintem ez elég információ ahhoz, hogy megértse, miért használnánk a Deep Learninget egy chat-robotban.

A Chat Bot felépítése

Emlékszel, hogyan beszéltünk a kontextusról?

Nos, az egyik nagy probléma az alapvető chatbotokkal, hogy hiányzik belőlük a beszélgetési kontextus. Emiatt rémálommá válik a csevegés.

De hogyan lehetséges szinte minden beszélgetésben kontextust adni a számítógépnek?

Ehhez létre kell hoznunk egy chatbot keretrendszert, és egy bizonyos kontextusra (jelen esetben egy online kávézóról) szóló társalgási modellt.

A chatbotunknak egyszerű kérdéseket kell kezelnie az általuk értékesített termékekkel, azok szállítási idejével és fizetési lehetőségeivel kapcsolatban.

Ezt a chatbot keretrendszert 3 fontos lépésen keresztül fogjuk felépíteni.

  • Képzési adatok létrehozása
  • Ezután PyTorch modellt használunk, és betanítjuk a képzési adatainkkal.
  • Végül elmentjük / betöltjük a modellünket, és megvalósítjuk a csevegést

Képzési adataink létrehozása

A chatbotunknak olyan struktúrára van szüksége, amelyben a beszélgetési szándékok definiálhatók. A JavaScript egyszerűsítéseként azonnal alapértelmezés szerint a JSON fájlformátumot választottam. Úgy döntöttem, hogy ennek a fájlnak az intents.json nevet adom.

{
  "intents": [
    {
      "tag": "greeting",
      "patterns": [
        "Hi",
        "Hey",
        "How are you",
        "Is anyone there?",
        "Hello",
        "Good day"
      ],
      "responses": [
        "Hey :-)",
        "Hello, thanks for visiting",
        "Hi there, what can I do for you?",
        "Hi there, how can I help?"
      ]
    },
    {
      "tag": "goodbye",
      "patterns": ["Bye", "See you later", "Goodbye"],
      "responses": [
        "See you later, thanks for visiting",
        "Have a nice day",
        "Bye! Come back again soon."
      ]
    },
    {
      "tag": "thanks",
      "patterns": ["Thanks", "Thank you", "That's helpful", "Thank's a lot!"],
      "responses": ["Happy to help!", "Any time!", "My pleasure"]
    },
    {
      "tag": "items",
      "patterns": [
        "Which items do you have?",
        "What kinds of items are there?",
        "What do you sell?"
      ],
      "responses": [
        "We sell coffee and tea",
        "We have coffee and tea"
      ]
    },
    {
      "tag": "payments",
      "patterns": [
        "Do you take credit cards?",
        "Do you accept Mastercard?",
        "Can I pay with Paypal?",
        "Are you cash only?"
      ],
      "responses": [
        "We accept VISA, Mastercard and Paypal",
        "We accept most major credit cards, and Paypal"
      ]
    },
    {
      "tag": "delivery",
      "patterns": [
        "How long does delivery take?",
        "How long does shipping take?",
        "When do I get my delivery?"
      ],
      "responses": [
        "Delivery takes 2-4 days",
        "Shipping takes 2-4 days"
      ]
    },
    {
      "tag": "funny",
      "patterns": [
        "Tell me a joke!",
        "Tell me something funny!",
        "Do you know a joke?"
      ],
      "responses": [
        "Why did the hipster burn his mouth? He drank the coffee before it was cool.",
        "What did the buffalo say when his son left for college? Bison."
      ]
    }
  ]
}

Minden beszélgetési szándék a következőket tartalmazza:

  • címke (egyedi név)
  • minták (mondatminták neurális hálózati szövegosztályozónk számára)
  • válaszok (egyet válaszként használunk)

Amint a fenti JSON kódblokkban látható, a címkék mintáink és természetesen a válaszaik egy online kávézó működtetésének kontextusába tartoznak.

Az NLP használata a Chat Bottal

Most már nem adhatunk át egy bemeneti mondatot a felhasználótól úgy, ahogy az a neurális hálónknak. Valahogy át kell alakítanunk a minta karakterláncait olyan számokká, amelyeket a hálózat megért. Ehhez minden mondatot át kell alakítanunk egy úgynevezett szózsákká. Ehhez olyan képzési szavakat kell gyűjtenünk, amelyeket a robotunk megtekinthet a képzési adatok között. Mindezen szavak alapján kiszámíthatjuk minden új mondathoz a szózsákot. A zsák og szavak mérete megegyezik az összes szó tömbével, és minden pozíció 1-et tartalmaz, ha a szó elérhető a bejövő mondatban, vagy 0-t, ha nem. Nézze meg az alábbi diagramot:

Rendben, szóval ez volt a sok szó magyarázata, de mi a helyzet azzal, hogyan kapjuk meg a szavakat? Ehhez további két NLP technikát kell használnunk: a tokenizálást és a szárképzést.

  • Tokenizálás: egy karakterlánc felosztása értelmes egységekre (pl. szavak, írásjelek, számok)

Példa:

"what would you do with 1000000$?"
[ “what”, “would”, “you”, “do”, “with”, “1000000”, “$”, “?”]
  • Törzs: A szavak gyökérformájának létrehozása. Alapvetően levágja a szavak végét.

Példa:

[“organize”, “organizes”, “organizing”]
[ “organ”, “organ”, “organ”]

Tehát magas szinten a feldolgozási folyamatunk így néz ki:

Végezze el az NLP Utils-t

Ehhez az nltk modult használtam. Az NLTK (Natural Language Toolkit) az emberi nyelvi adatokkal való munkavégzésre alkalmas Python programok vezető platformja. Rengeteg hasznos módszert kínál, amelyeket felhasználhatunk. A következő kódot írtam az ntlk_utils.py fájlomba.

# nltk_utils.py
import numpy as np
import nltk
# nltk.download('punkt')
from nltk.stem.porter import PorterStemmer
stemmer = PorterStemmer()

def tokenize(sentence):
    """
    split sentence into array of words/tokens
    a token can be a word or punctuation character, or number
    """
    return nltk.word_tokenize(sentence)


def stem(word):
    """
    stemming = find the root form of the word
    examples:
    words = ["organize", "organizes", "organizing"]
    words = [stem(w) for w in words]
    -> ["organ", "organ", "organ"]
    """
    return stemmer.stem(word.lower())


def bag_of_words(tokenized_sentence, words):
    """
    return bag of words array:
    1 for each known word that exists in the sentence, 0 otherwise
    example:
    sentence = ["hello", "how", "are", "you"]
    words = ["hi", "hello", "I", "you", "bye", "thank", "cool"]
    bog   = [  0 ,    1 ,    0 ,   1 ,    0 ,    0 ,      0]
    """
    # stem each word
    sentence_words = [stem(word) for word in tokenized_sentence]
    # initialize bag with 0 for each word
    bag = np.zeros(len(words), dtype=np.float32)
    for idx, w in enumerate(words):
        if w in sentence_words: 
            bag[idx] = 1

    return bag

Továbbmentem, és megjegyzéseket tettem a funkciókhoz és a releváns logikai vonalakhoz.

Alapvetően, amit itt csinálunk, az az, hogy felveszünk egy mondatot és tokenizálunk, szavakat veszünk, és végül visszaadunk egy zsáknyi szótömböt.

Valósítsa meg a Neurális Hálózatot

Ez a megvalósítás szerencsére elég absztrahált és egyszerű a 2 rejtett rétegű Feed Forward Neurális hálóval. A következő kódot egy model.py nevű fájlba írtam.

# model.py
import torch
import torch.nn as nn


class NeuralNet(nn.Module):
    def __init__(self, input_size, hidden_size, num_classes):
        super(NeuralNet, self).__init__()
        self.l1 = nn.Linear(input_size, hidden_size) 
        self.l2 = nn.Linear(hidden_size, hidden_size) 
        self.l3 = nn.Linear(hidden_size, num_classes)
        self.relu = nn.ReLU()

    def forward(self, x):
        out = self.l1(x)
        out = self.relu(out)
        out = self.l2(out)
        out = self.relu(out)
        out = self.l3(out)
        # no activation and no softmax at the end
        return out

Valósítsa meg a képzési folyamatot

Ezt a kódot egy train.py nevű fájlba írtam. Ez az a fájl, amely a csevegőbotunk betanítása érdekében kerül végrehajtásra. Amint látja, importálja a többi általunk létrehozott python fájlunkat.

# train.py
import numpy as np
import random
import json

import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader

from nltk_utils import bag_of_words, tokenize, stem
from model import NeuralNet

with open('intents.json', 'r') as f:
    intents = json.load(f)

all_words = []
tags = []
xy = []
# loop through each sentence in our intents patterns
for intent in intents['intents']:
    tag = intent['tag']
    # add to tag list
    tags.append(tag)
    for pattern in intent['patterns']:
        # tokenize each word in the sentence
        w = tokenize(pattern)
        # add to our words list
        all_words.extend(w)
        # add to xy pair
        xy.append((w, tag))

# stem and lower each word
ignore_words = ['?', '.', '!']
all_words = [stem(w) for w in all_words if w not in ignore_words]
# remove duplicates and sort
all_words = sorted(set(all_words))
tags = sorted(set(tags))

# create training data
X_train = []
y_train = []
for (pattern_sentence, tag) in xy:
    # X: bag of words for each pattern_sentence
    bag = bag_of_words(pattern_sentence, all_words)
    X_train.append(bag)
    # y: PyTorch CrossEntropyLoss needs only class labels, not one-hot
    label = tags.index(tag)
    y_train.append(label)

X_train = np.array(X_train)
y_train = np.array(y_train)

# Hyper-parameters 
num_epochs = 1000
batch_size = 8
learning_rate = 0.001
input_size = len(X_train[0])
hidden_size = 8
output_size = len(tags)

class ChatDataset(Dataset):

    def __init__(self):
        self.n_samples = len(X_train)
        self.x_data = X_train
        self.y_data = y_train

    # support indexing such that dataset[i] can be used to get i-th sample
    def __getitem__(self, index):
        return self.x_data[index], self.y_data[index]

    # we can call len(dataset) to return the size
    def __len__(self):
        return self.n_samples

dataset = ChatDataset()
train_loader = DataLoader(dataset=dataset,
                          batch_size=batch_size,
                          shuffle=True,
                          num_workers=2)

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

model = NeuralNet(input_size, hidden_size, output_size).to(device)

# Loss and optimizer
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

# Train the model
for epoch in range(num_epochs):
    for (words, labels) in train_loader:
        words = words.to(device)
        labels = labels.to(device)

        # Forward pass
        outputs = model(words)
        # if y would be one-hot, we must apply
        # labels = torch.max(labels, 1)[1]
        loss = criterion(outputs, labels)

        # Backward and optimize
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

    if (epoch+1) % 100 == 0:
        print (f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')


print(f'final loss: {loss.item():.4f}')

data = {
"model_state": model.state_dict(),
"input_size": input_size,
"hidden_size": hidden_size,
"output_size": output_size,
"all_words": all_words,
"tags": tags
}

FILE = "data.pth"
torch.save(data, FILE)

print(f'training complete. file saved to {FILE}')

Bár a cikkben hivatkozott github-repóban a folyamat számos összetevője külön van, a bot viszonylagos egyszerűsége miatt, ha akarja, megúszhatja a folyamat egyetlen fájlba történő integrálását. Tehát ezt tettem a fenti kódblokkban.

Végül! A Chat megvalósítása

Most betöltjük a betanított modellt, és előrejelzéseket készítünk az új mondatokra egy általam chat.py nevű fájllal. Ez a fájl fut le a bash segítségével, amikor el akarja kezdeni használni a csevegőbotot.

# chat.py
import random
import json

import torch

from model import NeuralNet
from nltk_utils import bag_of_words, tokenize

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

with open('intents.json', 'r') as json_data:
    intents = json.load(json_data)

FILE = "data.pth"
data = torch.load(FILE)

input_size = data["input_size"]
hidden_size = data["hidden_size"]
output_size = data["output_size"]
all_words = data['all_words']
tags = data['tags']
model_state = data["model_state"]

model = NeuralNet(input_size, hidden_size, output_size).to(device)
model.load_state_dict(model_state)
model.eval()

bot_name = "Sam"
print("Let's chat! (type 'quit' to exit)")
while True:
    # sentence = "do you use credit cards?"
    sentence = input("You: ")
    if sentence == "quit":
        break

    sentence = tokenize(sentence)
    X = bag_of_words(sentence, all_words)
    X = X.reshape(1, X.shape[0])
    X = torch.from_numpy(X).to(device)

    output = model(X)
    _, predicted = torch.max(output, dim=1)

    tag = tags[predicted.item()]

    probs = torch.softmax(output, dim=1)
    prob = probs[0][predicted.item()]
    if prob.item() > 0.75:
        for intent in intents['intents']:
            if tag == intent["tag"]:
                print(f"{bot_name}: {random.choice(intent['responses'])}")
    else:
        print(f"{bot_name}: I do not understand...")

Futtassuk ezt a Botot!

Gratulálok neked, hogy idáig eljutottál, és jómagam is, hogy mindezt dokumentáld.

A csevegőbotja készen áll az életre keltésre.

Most csak lefuttatjuk az edzést, majd a chat-et.

A következő futtatása:

python train.py

Kiírja a data.pth fájlt a könyvtárába. Akkor futni akarsz:

python chat.py

Ideje élvezni a csevegést az alkotásoddal!

Ui: Ne feledje, hogy új minták és válaszok bevezetésével testreszabhatja az intents.json fájlt a szívéhez. Csak ne felejtse el újra futtatni a képzést a python train.py paranccsal.