How to LAMBADA

IBM’s LAMBADA AI Tutorial

 

Bessere NLG und NLU mit LAMBADA AI

Einer unserer früheren Artikel befasste sich mit der LAMBADA-Methode. Diese nutzt Natural Language Generation (NLG), um Trainings-Utterances für eine Natural Language Understanding (NLU)-Aufgabe zu generieren, genauer gesagt für die Intent-Klassifikation. In diesem LAMBADA-Tutorial führen wir Sie durch den Code, um unsere PoC-Implementierung von LAMBADA zu reproduzieren. 

Bevor Sie mit diesem LAMBADA-Tutorial fortfahren, schlagen wir vor, einen Blick auf unseren Artikel zu werfen, in dem wir die grundlegenden Ideen und Konzepte ausführlicher erläutern, die von IBMs LAMBADA angewendet werden. In diesem Tutorial veranschaulichen wir wesentliche Methoden, die ein interaktives COLAB notebook zur Verfügung stellen. Insgesamt erklären wir einige der Kernpunkte des Codes und demonstrieren, wie Sie die Parameter so anpassen können, dass sie Ihren Anforderungen entsprechen, während die weniger wichtigen Teile weggelassen werden. Sie können das Notizbuch über Ihr Google-Konto kopieren, um dem Code zu folgen. Für Training und Tests können Sie Ihre eigenen Daten einsetzen oder von uns bereitgestellte Daten verwenden. 

LAMBADA AI-Tutorial Step 1 – Einrichten der Umgebung

Wir verwenden distilBERT als Klassifikationsmodell und GPT-2 als Textgenerierungsmodell. Für beide laden wir vortrainierte Gewichte und justieren sie nach. Im Falle von GPT-2 verwenden wir die Huggingface Transfomers-Bibliothek, um ein vortrainiertes Modell zu laden und es anschließend weiter zu verfeinern. Zum Laden und Finetuning von distilBERT verwenden wir Ktrain, eine Bibliothek, die ein High-Level-Interface für Sprachmodelle bietet, so dass wir uns nicht mehr um Tokenisierung und andere Pre-Processing-Aufgaben kümmern müssen. 

Zunächst installieren wir beide Bibliotheken in unserer COLAB-Laufzeitumgebung: 

!pip install ktrain 
!pip install transformers

LAMBADA AI-Tutorial Step 2 – Daten

Wir verwenden einen der vorgelabelten Chitchat-Datensätze von Microsofts Azure QnA Maker. Als nächstes teilen wir den Chitchat-Datensatz so auf, dass wir zehn Intents mit jeweils zehn Utterances als einen anfänglichen Trainingsdatensatz und die restlichen 1047 Samples als einen Testdatensatz erhalten. Im Folgenden verwenden wir den Testdatensatz, um die verschiedenen Intent-Klassifikatoren zu vergleichen, die wir in diesem Tutorial trainieren. 

Anschließend laden wir die Trainingsdaten aus der Datei  train.csv und teilen sie so auf, dass wir sechs Utterances pro Intent für das Training und vier Utterances pro Intent für die Validierung erhalten. 

NUMBER_OF_TRAINING_UTTERANCES = 6

import pandas
from sklearn.model_selection import train_test_split

data_train = pandas.read_csv('train.csv')
intents = data_train['intent'].unique()

X_train = []
X_valid = []
y_train = []
y_valid = []
for intent in intents:
    intent_X_train, intent_X_valid, intent_y_train, intent_y_valid = train_test_split(
    data_train[data_train['intent'] == intent]['utterance'],
        data_train[data_train['intent'] == intent]['intent'],
        train_size=NUMBER_OF_TRAINING_UTTERANCES,
        random_state=43
    )

    X_train.extend(intent_X_train)
    X_valid.extend(intent_X_valid)
    y_train.extend(intent_y_train)
    y_valid.extend(intent_y_valid)

LAMBADA AI-Tutorial Step 3 – Training des ursprünglichen Intent-Klassifikators 

Wir downloaden das vortrainierte distilBERT-Modell, transformieren die Trainings- und Validierungsdaten von reinem Text in das für unser Modell gültige Format und initialisieren ein Lernobjekt, das in KTrain zum Trainieren des Modells verwendet wird.

import ktrain
from ktrain import text
distil_bert = text.Transformer('distilbert-base-cased', maxlen=50, 
classes=intents)
processed_train = distil_bert.preprocess_train(X_train, y_train)
processed_test = distil_bert.preprocess_test(X_valid, y_valid)
model = distil_bert.get_classifier()
learner = ktrain.get_learner(model, train_data=processed_train, 
val_data=processed_test, batch_size=10)

Jetzt ist es an der Zeit, das Modell zu trainieren. Wir speisen die Trainingsdaten mehrfach in das Netzwerk ein, spezifiziert durch die Anzahl der Epochen. Zu Beginn sollten beide überwachten Metriken, also die Verlustfunktion (Abnahme) und die Genauigkeit (Zunahme), eine Verbesserung des Modells mit jeder vergangenen Epoche anzeigen. Nachdem das Modell jedoch eine gewisse Zeit lang trainiert wurde, wird der Validierungsverlust zunehmen und die Validierungsgenauigkeit fallen. Dies ist eine Folge der Überanpassung der Trainingsdaten, und es ist an der Zeit, die Einspeisung derselben Daten in das Netzwerk zu beenden. Die optimale Anzahl von Epochen hängt von Ihrem Datensatz, Ihrem Modell und Ihren Trainingsparametern ab. Wenn Sie die richtige Anzahl von Epochen im Voraus nicht kennen, können Sie eine hohe Anzahl von Epochen verwenden und Checkpoints aktivieren, indem Sie den Parameter checkpoint_folder setzen, um anschließend das Modell mit der besten Performance auszuwählen. 

N_TRAINING_EPOCHS = 12
learner.fit_onecycle(5e-5, N_TRAINING_EPOCHS)
begin training using onecycle policy with max lr of 5e-05...
Train for 6 steps, validate for 2 steps
Epoch 1/12
6/6 [==============================] - 8s 1s/step - loss: 2.3111 - accuracy:
0.0667 - val_loss: 2.2881 - val_accuracy: 0.0750
Epoch 2/12
6/6 [==============================] - 0s 68ms/step - loss: 2.3042 - accuracy:
0.0833 - val_loss: 2.2772 - val_accuracy: 0.1750...
Epoch 11/12
6/6 [==============================] - 0s 66ms/step - loss: 0.4738 - accuracy:
1.0000 - val_loss: 0.9785 - val_accuracy: 0.8500
Epoch 12/12
6/6 [==============================] - 0s 68ms/step - loss: 0.4434 - accuracy:
1.0000 - val_loss: 0.9687 - val_accuracy: 0.8500

<tensorflow.python.keras.callbacks.History at 0x7fa19a7134a8><span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1">​</span>

Um die Leistung unseres trainierten Klassifikators zu überprüfen, verwenden wir unsere Testdaten in der Datei eval.csv.

import numpy

data_test = pandas.read_csv('eval.csv')
test_intents = data_test["intent"].tolist()
test_utterances = data_test["utterance"].tolist()

predictor = ktrain.get_predictor(learner.model, preproc=distil_bert)
predictions = predictor.predict(test_utterances)

np_test_intents = numpy.array(test_intents)
np_predictions = numpy.array(predictions)

result = (np_test_intents == np_predictions)

print("Accuracy: {:.2f}%".format(result.sum()/len(result)*100))

Beachten Sie, dass wir dank des KTrain-Interface die Liste der Utterances einfach in den Predictor einspeisen können, ohne die ursprünglichen Strings vorher verarbeiten zu müssen. Als Ausgabe erhalten wir die Genauigkeit unseres Klassifikators:  

Genauigkeit: 84,24%. 

LAMBADA AI-Tutorial Step 4 – Finetuning von GPT-2 zur Erzeugung von Utterances 

Zum Finetuning von GPT-2 verwenden wir ein Python Skript, das von Huggingface auf deren Github-Repository zur Verfügung gestellt wird. Unter anderem spezifizieren wir die folgenden Parameter:

  • das vortrainierte Modell, das wir verwenden wollen (gpt2-medium). Größere Modelle erzeugen in der Regel bessere Textausgaben. Bitte beachten Sie, dass diese Modelle während des Trainings sehr viel Speicherplatz benötigen. Stellen Sie also sicher, dass Sie ein Modell auswählen, das in Ihren (GPU-)Speicher passt. 
  • die Anzahl der Epochen. Dieser Parameter gibt an, wie oft die Trainingsdaten durch das Netzwerk gespeist werden. Ist die Anzahl der Epochen zu klein, lernt das Modell nicht, nützliche Äußerungen zu generieren. Ist die Anzahl andererseits zu groß gewählt, wird das Modell wahrscheinlich überladen und die Variabilität der generierten Textdaten ist begrenzt – das Modell wird sich im Grunde nur an die Trainingsdaten erinnern. 
  • die batch size. Diese bestimmt, wie viele Utterances parallel für das Training verwendet werden. Je größer die batch size, desto schneller das Training, größere batch sizes benötigen jedoch mehr Speicherplatz. 
  • die Blockgröße. Die Blockgröße definiert eine Obergrenze für die Anzahl der betrachteten Tokens aus jeder verwendeten Trainingsdateninstanz. Stellen Sie sicher, dass diese Anzahl ausreicht, damit Utterances nicht abgeschnitten werden.
!python finetune_gpt.py \
--output_dir='/content/transformers/output' \
--model_type=gpt2-medium \
--model_name_or_path=gpt2-medium \
--num_train_epochs=3.0 \
--do_train \
--train_data_file=/content/train.csv \
--per_gpu_train_batch_size=4 \
--block_size=50 \
--gradient_accumulation_steps=1 \
--line_by_line \
--overwrite_output_dir

Laden wir unser Modell und generieren wir einige Utterances! Um die Generierung neuer Utterances für einen bestimmten Intent auszulösen, stellen wir dem Modell diesen Intent als Samen zur Verfügung ('<intent>,', e.g. ‘inform_hungry,’).

from transformers import GPT2Tokenizer, TFGPT2LMHeadModel

tokenizer = GPT2Tokenizer.from_pretrained("gpt2-medium")
model = TFGPT2LMHeadModel.from_pretrained('/content/transformers/output/',
pad_token_id=tokenizer.eos_token_id, from_pt=True)

input_ids = tokenizer.encode('inform_hungry,', return_tensors='tf')
sample_outputs = model.generate(
    input_ids,
    do_sample=True,
    max_length=50,
    top_k=10,
    top_p=0.92,
    num_return_sequences=10
)

print("Output:\n" + 100 * '-')
for i, sample_output in enumerate(sample_outputs):
print("{}: {}".format(i, tokenizer.decode(sample_output, skip_special_tokens=True)))
Output:-------------------------------------------------------------------------

0: inform_hungry,I want a snack!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1: inform_hungry,I want to eat!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
2: inform_hungry,I want some food!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
3: inform_hungry,I'm so hungry I could eat!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!...

Das sieht gut aus! Die künstlich erzeugten Utterances passen zum Intent, aber um eine nützliche Ergänzung zu sein und unser Modell zu verbessern, müssen sich diese Utterances von denen unterscheiden, die für das Training verwendet werden. Die Trainingsdaten für die Intent inform_hungry waren die folgenden:

Intent: Utterance:

inform_hungry,I want a snack

inform_hungry,I am very hungry

inform_hungry,I’m hangry

inform_hungry,Need food

inform_hungry,I want to eat

inform_hungry,I’m a bit peckish

inform_hungry,My stomach is rumbling

inform_hungry,I’m so hungry I could eat a horse

inform_hungry,I’m feeling hangry

inform_hungry,I could eat

Wir sehen, dass die beiden Utterances " I want some food" und "I’m so hungry I could eat" nicht Teil der Trainingsdaten sind. 

Wenn wir mit unseren generierten Utterances nicht zufrieden sind, weil sie alle sehr ähnlich sind, oder wenn sie nicht dem zugrunde liegenden Intent entsprechen, können wir die Variabilität des generierten Outputs anpassen, indem wir die folgenden Parameter modifizieren:

  • do_sample. Dieser Parameter muss auf True gesetzt werden, sonst gibt das Modell immer wieder die gleiche Ausgabe zurück.
  • top_k. Dieser Parameter gibt die Anzahl der verschiedenen Token an, die für jeden Schritt des Samples berücksichtigt werden. Je höher Sie diesen Parameter setzen, desto vielfältiger wird die Ausgabe sein.
  • top_p. Dieser Parameter gibt die kumulative Wahrscheinlichkeit der Token an, die am wahrscheinlichsten für die Stichprobe in Betracht gezogen werden. Beispielsweise wird bei top_p = 0,92 aus 92% der wahrscheinlichsten Wörter eine Stichprobe gezogen. Je höher top_p, desto vielfältiger ist die Ausgabe. Der maximale Wert ist 1.

LAMBADA AI-Tutorial Step 5 – Erzeugen und Filtern neuer Utterances 

Wir erzeugen jetzt die neuen Utterances für alle Intents. Um eine ausreichend große Probe zu haben, aus der wir die besten Utterances auswählen können, erzeugen wir 200 pro Intent.

NUMBER_OF_GENERATED_UTTERANCES_PER_INTENT = 200
def generate_utterances_df(n_generated, tokenizer, model, intent):
    input_ids = tokenizer.encode(intent + ',', return_tensors='tf')
    sample_outputs = model.generate(
        input_ids,
        do_sample=True,
        max_length=50,
        top_k=n_generated,
        top_p=0.92,
        num_return_sequences=n_generated
    )

    list_of_intent_and_utterances = [
    (
    intent,
    tokenizer.decode(sample_output, skip_special_tokens=True)[len(intent)+1:]
    )
    for sample_output in sample_outputs
    ]
return pandas.DataFrame(list_of_intent_and_utterances, columns=['intent', 'utterance'])

intents = data_train["intent"].unique()
generated_utterances_df = pandas.DataFrame(columns=['intent', 'utterance'])
for intent in intents:
    print("Generating for intent " + intent)
    utterances_for_intent_df = generate_utterances_df
    (NUMBER_OF_GENERATED_UTTERANCES_PER_INTENT, tokenizer, model,intent)
    generated_utterances_df = generated_utterances_df.append
    (utterances_for_intent_df)

Nach einer Weile sind die Daten generiert, und wir können sie uns genauer ansehen. Zunächst verwenden wir unseren alten DestilBERT-Klassifikator, um den Intent für alle generierten Utterances vorherzusagen. Außerdem beobachten wir die Vorhersagewahrscheinlichkeit, die das Confidence Level jeder einzelnen Vorhersage unseres Modells angibt.

predictions_for_generated = numpy.array(predictor.predict(generated_data['utterance'].tolist(),
return_proba=False))
proba_for_predictions_for_gen = predictor.predict(generated_data['utterance'].tolist(),
return_proba=True)
predicted_proba = numpy.array([max(probas) for probas in proba_for_predictions_for_gen])

generated_data_predicted = pandas.DataFrame({"intent": generated_data['intent'],
"utterance": generated_data['utterance'],
"predicted_intent": predictions_for_generated,
"prediction_proba": predicted_proba})
generated_data_predicted.head(2)

intentutterancepredicted_intentpredicted_proba
0body_related_questionDo you chew?body_related_question0.701058
1body_related_questionDo you have a stomachbody_related_question0.737520

Werfen wir einen Blick auf einige der Utterances, bei denen der für die Erzeugung generierte Intent nicht mit dem vorhergesagten übereinstimmt. 

generated_data_predicted[generated_data_predicted['intent'] !=
generated_data_predicted['predicted_intent']].head(10)

intentutterancepredicted_intentprediction_proba
7ask_purposeWhere do you live?get_location0.745748
20ask_purposeWhat was your greatest passion growing up?needs_love0.192401
70ask_purposeWhy are you here?get_location0.683455
182ask_purposeWhere are you from?get_location0.697122
49get_locationAre you in a computer?body_related_question0.498938
162get_locationTell me what you’re doingask_purpose0.571899
3make_singI sing a songinform_hungry0.358060
18make_singI want to singinform_hungry0.604815
20make_singYou’re so cuteneeds_love0.266433
41make_singYou’re singinginform_hungry0.323076

In einigen Fällen ist die Vorhersage eindeutig falsch. Es gibt jedoch auch Fälle, in denen die Vorhersage mit der Utterance übereinstimmt, aber nicht mit dem Intent, der für die Generierung verwendet wurde. Dies deutet darauf hin, dass unser GPT-2-Modell nicht perfekt ist, da es nicht immer übereinstimmende Utterances für einen Intent generiert. Um unseren Klassifikator nicht mehr mit korrupten Daten zu trainieren, streichen wir alle Utterances, bei denen der Basis-Intent nicht mit dem vorhergesagten Intent übereinstimmt. Von solchen mit übereinstimmenden Instanzen behalten wir nur diejenigen mit den höchsten Vorhersagewahrscheinlichkeitswerten.

correctly_predicted_data = generated_data_predicted[generated_data_predicted
['intent'] == generated_data_predicted['predicted_intent']]
correctly_predicted_data.drop_duplicates(subset='utterance', 
keep='first').sort_values(by=['intent','prediction_proba'], 
ascending=[True, False]).drop_duplicates(keep='first').groupby('intent').count()

utterancepredicted_intentprediction_proba
intent
ask_purpose606060
body_related_question414141
get_location484848
greet777777
humor_related505050
inform_hungry353535
inform_tired676767
make_sing686868
needs_love717171
suicide risk676767

Wir sehen, dass es für jeden Intent mindestens 35 unterschiedliche Utterances gibt. Um einen ausgewogenen Datensatz zu bewahren, wählen wir die besten 30 Utterances pro Intent entsprechend der Vorhersagewahrscheinlichkeit aus.

TOP_N = 30
top_predictions_per_intent = correctly_predicted_data.drop_duplicates(subset=
'utterance',keep='first').sort_values(by=['intent', 'prediction_proba'],
ascending=[True,False]).drop_duplicates(keep='first').groupby('intent').head(TOP_N)

LAMBADA AI-Tutorial Step 6 – Trainieren des Intent-Klassifikators mit Augmented Data 

Wir kombinieren nun die generierten Daten mit den ursprünglichen Trainingsdaten und teilen den angereicherten Datensatz in Trainings- und Validierungsdaten auf.

data_train_aug = data_train.append(top_predictions_per_intent[['intent', 'utterance']], ignore_index=True)

intents = data_train_aug['intent'].unique()

X_train_aug = []
X_valid_aug = []
y_train_aug = []
y_valid_aug = []
for intent in intents:
    intent_X_train, intent_X_valid, intent_y_train, intent_y_valid = train_test_split(
        data_train_aug[data_train_aug['intent'] == intent]['utterance'],
        data_train_aug[data_train_aug['intent'] == intent]['intent'],
        train_size=0.8,
        random_state=43
    )

    X_train_aug.extend(intent_X_train)
    X_valid_aug.extend(intent_X_valid)
    y_train_aug.extend(intent_y_train)
    y_valid_aug.extend(intent_y_valid)

Jetzt ist es an der Zeit, unser neues Intent-Klassifikationsmodell zu trainieren. Der Code ist wie der obige:

distil_bert_augmented = text.Transformer('distilbert-base-cased', maxlen=50, classes=intents)
processed_train_aug = distil_bert_augmented.preprocess_train(X_train_aug, y_train_aug)
processed_test_aug = distil_bert_augmented.preprocess_test(X_valid_aug, y_valid_aug)
model_aug = distil_bert_augmented.get_classifier()
learner_aug = ktrain.get_learner(model_aug, train_data=processed_train_aug, val_data=processed_test_aug,
batch_size=50)
N_TRAINING_EPOCHS_AUGMENTED = 11
learner_aug.fit_onecycle(5e-5, N_TRAINING_EPOCHS_AUGMENTED)

Schließlich verwenden wir unseren Evaluationsdatensatz, um die Genauigkeit unseres neuen Intent-Klassifikators zu überprüfen.  

Genauigkeit: 91.40%

Wir können feststellen, dass sich die Performance um 7% verbessert hat. Insgesamt betrug die Verbesserung der Vorhersagegenauigkeit über alle von uns durchgeführten Experimente hinweg konstant mehr als 4%.

LAMBADA AI: Summary

Wir setzten die LAMBADA-Methode zur Data Augmentation für Natural Language Understanding (NLU) Tasks ein. Wir trainierten ein GPT-2-Modell zur Generierung neuer Trainings-Utterances und nutzten sie als Trainingsdaten für unser Intent-Klassifikationsmodell (distilBERT). Die Leistung des Intent-Klassifikationsmodells verbesserte sich in jedem unserer Tests um mindestens 4%. 

Darüber hinaus konnten wir feststellen, dass high-level-Bibliotheken wie KTrain und Huggingface Transformers dazu beitragen, die Komplexität der Anwendung hochmoderner Transformatormodelle für Natural Language Generation (NLG) und andere Natural Language Processing (NLP)-Aufgaben wie die Klassifikation zu reduzieren und diese Ansätze auf breiter Basis anwendbar zu machen.