روبوتات المحادثة : الغوص العميق في مكونات نظام الحوار

لقد رأينا في المقال السابق  كيفية إنشاء روبوت محادثة باستخدام Dialogflow وكيفية إضافة ميزات متنوعة للتعامل مع الكيانات والسياق المعقد. الآن ، نريد التعمق في جانب تعلم الألة في الأجزاء الداخلية لنظام الحوار. كما ناقشنا أثناء وصف خط الإنتاج لنظام الحوار ، فإن فهم السياق (أي استجابة المستخدم) في ضوء سجل المحادثة هو أحد أهم المهام لبناء نظام حوار.

يمكن تقسيم فهم السياق إلى فهم مقصد المستخدم واكتشاف الكيانات المقابلة لهذا الغرض المحدد. تتوافق هذه المكونات الداخلية مع مكون فهم اللغة الطبيعية في خط إنتاج روبوتات المحادثة . لتوضيح ذلك ، سنستعرض عينة من محادثة حول حجز المطعم وسنصف كيفية تصميم مكونات مختلفة لفهم السياق.

يوضح الشكل أدناه مثالاً لمستخدم يبحث عن حجز مطعم. كما نرى ، هناك تسميات متاحة لكل إجابة. تشير التسميات إلى المقاصد (intents ) والكيانات (entities ) لهذه الاستجابات. نريد استخدام هذه التعليقات التوضيحية لتدريب نماذج تعلم الألة الخاصة بنا.

قبل أن ندخل في النموذج ، سنحدد رسميًا مهمتين طبيعيتين للفهم تتعلقان بفهم سياق الحوارات. نظرًا لأن هذا يتضمن فهم الفروق الدقيقة في اللغة تحتها ، تُنسب هذه أيضًا إلى مهام فهم اللغة الطبيعية (natural language understanding) أو (NLU).

تصنيف نمط الحوار

تصنيف نمط الحوار(Dialog Act)  هو مهمة لتحديد كيف يلعب نطق المستخدم دورًا في سياق الحوار. هذا يعرف بـ “النمط” الذي يقوم به المستخدم. على سبيل المثال ، من الأمثلة البسيطة على أنماط الحوار تحديد سؤال “نعم / لا”. إذا سأل المستخدم ، “هل ستذهب إلى المدرسة اليوم؟” ، فسيتم تصنيف هذا على أنه سؤال نعم / لا. 

من ناحية أخرى ، إذا سأل المستخدم “ما هو عمق المحيط؟” ، فقد لا يتم تصنيف ذلك على أنه سؤال بنعم / لا. لقد رأينا أن المقاصد أو أنماط الحوار مهمة لبناء روبوت محادثة ، حتى في Cloud APIs. يساعد تحديد النية على فهم ما يطلبه المستخدم واتخاذ الإجراءات وفقًا لذلك.

يمكن إعادة صياغة هذا باعتباره مشكلة تصنيف: بالنظر إلى الكلام المنطوق في الحوار ، قم بتصنيفه إلى أنماط حوار أو تسميات. في مثالنا من الشكل أعلاه ، نحدد مهمة توقع إجراء حوار حيث تتضمن التسميات إعلام وطلبًا وما إلى ذلك.

 العبارة المنطوقة “Where is it” يمكن تصنيفها على أنه نمط حوارا “طلب”. من ناحية أخرى ، يمكن تصنيف العبارة “I’m looking for a cheaper restaurant ” على أنها نمط حوار “إبلاغ”.

تحديد الخانات (Identifying Slots)

بمجرد استخراج المقاصد ، نريد الانتقال إلى استخراج الكيانات. يعد استخراج الكيانات مهمًا أيضًا لإنشاء استجابات صحيحة ومناسبة لإدخال المستخدم. لقد رأينا  في المقال السابق في مثال Dialogflow أن استخراج الكيانات جنبًا إلى جنب مع المقاصد يخلق فهمًا كاملاً لإدخال المستخدم.

في المثال الموضح بالشكل أعلاه – “I’m looking for a cheaper restaurant” – نريد تحديد “أرخص” على أنه شريحة أسعار ونأخذ قيمتها حرفياً – أي أن قيمة الشريحة “أرخص”. لقد رأينا مهامًا مماثلة في هذا المقال ، حيث تعلمنا كيفية استخراج الكيانات من الجمل. يمكننا اتباع نهج مماثل (أي نهج وضع العلامات التسلسلية) هنا أيضًا لاستخراج هذه الكيانات.

في مثال Dialogflow الخاصة بنا ، رأينا أنه يجب تحديد الفتحات و تعريفها مسبقًا. ولكن هنا ، نريد بناء هذا المكون بمفردنا باستخدام خوارزمية تعلم الألة. تذكر الخوارزميات التي تمت مناقشتها في سياق التعرف على الكيانات المسماة. سنستخدم خوارزميات مماثلة لاكتشاف الفتحات ووضع العلامات. سنستخدم مكتبة تصنيف تسلسل مفتوحة المصدر تسمى sklearn-crfsuite .

تتمثل إحدى عيوب هذه الأساليب في أنها تحتاج إلى الكثير من البيانات المصنفة لاكتشاف كل من الهدف والكيان. أيضًا ، نحتاج إلى نماذج مخصصة لكلتا المهمتين. هذا يمكن أن يجعل النظام بطيئا أثناء النشر. يعد الحصول على تسميات دقيقة للكيانات مكلفًا أيضًا. تحد هذه المشكلات من قابلية التوسع في خط الإنتاج لمزيد من المجالات.

كشفت الأبحاث الحديثة [هنا] حول فهم اللغة المنطوقة أن الفهم المشترك والتتبع أفضل من التصنيف الفردي وأجزاء تسمية التسلسل. هذا النموذج المشترك خفيف الوزن عند النشر مقارنة بالنماذج الفردية. للنمذجة المشتركة ، يمكننا استخدام حالات الحوار ، وهي “inform(price – cheap)” في مثالنا أعلاه.

 يمكننا أن نهدف إلى ترتيب أو تسجيل كل زوج مرشح بالاشتراك مع نمط الحوار (عند الجمع ، حالة الحوار (dialog state) ) لتحديد الحالة بشكل مشترك. يعتبر التحديد المشترك أكثر تعقيدًا ويتطلب تقنيات تعلم تمثيلية أفضل الآن بعد أن ناقشنا مكونات نظام فهم معالجة اللغة الطبيعية (Natural Language Understanding ) ، دعنا ننتقل إلى إنشاء الاستجابة.

توليد الاستجابة

بمجرد تحديد الفتحات والهدف ، فإن الخطوة الأخيرة هي أن يقوم نظام الحوار بإنشاء استجابة مناسبة. هناك العديد من الطرق لإنشاء استجابة: الردود الثابتة ، واستخدام القوالب ، والتوليد التلقائي.

  • ردود ثابتة

تستخدم روبوتات الأسئلة الشائعة بشكل أساسي الردود الثابتة. استنادًا إلى الغرض من الخانات الزمنية وقيمها ، يتم إجراء بحث في القاموس على مجموعة من الاستجابات واسترداد أفضل استجابة. تتمثل إحدى الحالات البسيطة في تجاهل معلومات الخانة والحصول على رد واحد لكل مقصد. للاسترداد الأكثر تعقيدًا ، يمكن إنشاء آلية ترتيب تصنف مجموعة الاستجابات بناءً على المقصد المكتشفة وأزواج قيمة الخانة (أو حالة الحوار).

  • استخدام القوالب

لجعل الردود ديناميكية ، غالبًا ما يتم اتباع نهج قائم على القوالب. تكون القوالب مفيدة جدًا عندما تكون استجابة المتابعة سؤالًا توضيحيًا. يمكن استخدام قيم الفتحات للتوصل إلى سؤال متابعة أو إجابة مبنية على الحقائق. على سبيل المثال ، يمكن إنشاء “The House serves cheap Thai food” باستخدام قالب كما يلي :

<restaurant name> serves <price-value> <food-value> food

 بمجرد تحديد الخانات وقيمها ، نقوم بتعبئة هذا القالب لتوليد استجابة مناسبة في النهاية.

  • التوليد التلقائي

يمكن تعلم توليد لغة أكثر طبيعية و طلاقة باستخدام نهج قائم على البيانات. عند الحصول على حالة الحوار ، يمكن بناء نموذج توليدي شرطي يأخذ حالة الحوار كمدخل ويولد الاستجابة التالية للوكيل. يمكن أن تكون هذه النماذج نماذج رسومية أو نماذج لغة قائمة على التعلم العميق .

الآن بعد أن تعمقنا في المكونات المختلفة لنظام الحوار ، دعنا نتصفح أمثلة لتصنيف أنماط الحوار وتوقعات الخانات.

أمثلة الحوار 

الآن ، سنستعرض أمثلة من مجموعات بيانات مختلفة من حوارات العالم الحقيقي المتاحة للجمهور ونناقش استخدامها لنمذجة جوانب مختلفة من نظام الحوار. ثم سنستخدم اثنتين من مجموعات البيانات هذه لإظهار كيفية تنفيذ نماذج لمهمتين وصفناهما لفهم السياق: توقع نمط الحوار أو تصنيف المقصد وتحديد الفتحة أو اكتشاف الكيان. سنستكشف نموذجين لكل مهمة ونبين من خلال المقارنات كيف يمكن تحسين هذه النماذج تدريجيًا. 

مجموعات البيانات

هنا ملخص موجز لمجموعات البيانات المختلفة التي يتم استخدامها لقياس الخوارزميات لمهام الحوار الموجهة نحو الهدف. نظرًا لأننا مهتمون بمهام فهم معالجة اللغة الطبيعية (NLU)  المختلفة في الحوارات ، فإننا نقدم أربع مجموعات بيانات لمربعات الحوار الموجهة نحو الهدف والتي تعمل كمعايير لمهام NLU القائمة على الحوار.

أولا: مجموعة بيانات تذاكر الطيران (ATIS ) 

معيار حجز تذاكر الطيران لتصنيف المقصد وملء الخانات الزمنية. هذه مجموعة بيانات ذات مجال واحد ، وبالتالي فإن الكيانات والأهداف مقصورة على مجال واحد.

ثانيا : مجموعة البيانات متعدد المجالات (SNIPS )

لتصنيف المقصد وملء الخانات . هذه مجموعة بيانات متعددة المجالات ، وبالتالي تنتمي الكيانات إلى مجالات متعددة. تشكل مجموعات البيانات متعددة المجالات تحديًا في وضع النماذج نظرًا لتنوعها.

ثالثا: مجموعة بيانات المطاعم (DSTC )

لتتبع حالة الحوار أو التحديد المشترك للمقاصد والخانات. هذا بالمثل مجموعة بيانات ذات نطاق واحد ، ولكن يتم التعبير عن الكيانات بشكل أكبر من حيث التعليقات التوضيحية وتحتوي على المزيد من البيانات الوصفية.

رابعاً :  مجموعة البيانات متعدد المجالات (MultiWoZ  )

لتتبع حالة الحوار أو التحديد المشترك للقصد و الخانات التي تمتد عبر مجالات متعددة. لسبب التباين المماثل ، تعد نمذجة مجموعة البيانات هذه أكثر صعوبة من نمذجة مجموعات ذات نطاق واحد.

على الرغم من وجود العديد من مجموعات البيانات مفتوحة المصدر ، إلا أنه لا يوجد سوى عدد قليل من مجموعات البيانات التي تعكس طبيعة المحادثات البشرية. تعاني مجموعات البيانات التي تم جمعها من قبل المعلقين عبر الإنترنت مثل Mechanical Turkers من محادثة فكرية وفرضية ، مما يؤثر على جودة الحوار. أيضًا ، لا تزال مجموعات بيانات الحوار الخاصة بالمجال غير متاحة للعديد من المجالات ، مثل الرعاية الصحية والقانون وما إلى ذلك.

توقع نمط الحوار

تصنيف نمط الحوار أو اكتشاف المقصد هو المهمة التي وصفناها في القسم السابق كجزء من مكون فهم معالجة اللغة NLU في نظام الحوار. هذه مهمة تصنيف ، وسوف نتبع خط إنتاج التصنيف الخاص بنا.

تحميل مجموعة البيانات

سنستخدم ATIS (أنظمة معلومات سفر شركات الطيران) لمهمة الكشف عن المقصد. ATIS هي مجموعة بيانات تُستخدم بكثرة لفهم اللغة المنطوقة وأداء مهام NLU المختلفة. تتكون مجموعة البيانات من 4،478 حوار تدريب و 893 حوار اختبار بإجمالي 21 مقصد. لقد اخترنا 17 مقصد، والتي تظهر في كل من التدريب ومجموعة الاختبار. ومن ثم ، فإن مهمتنا هي مهمة تصنيف 17 فئة. هنا مثال واحد من مجموعة البيانات:

Query text: BOS please list the flights from charlotte to long beach arriving after lunch time EOS

Intent label: flight

النموذج 

شبكة لف رياضي عصبية (Convolutional neural network)

سنقوم في البداية بإستدعاء البيانات و تحهيزها ، و للقيام بذلك سنستعين بداله مساعده في ملف utils.py

import pandas as pd
import numpy as np

def get_data(filename):
    df = pd.read_csv(filename,delim_whitespace=True,names=['word','label'])
    beg_indices = list(df[df['word'] == 'BOS'].index)+[df.shape[0]]
    sents,labels,intents = [],[],[]
    for i in range(len(beg_indices[:-1])):
        sents.append(df[beg_indices[i]+1:beg_indices[i+1]-1]['word'].values)
        labels.append(df[beg_indices[i]+1:beg_indices[i+1]-1]['label'].values)
        intents.append(df.loc[beg_indices[i+1]-1]['label'])    
    return np.array(sents, dtype=object), np.array(labels, dtype=object), np.array(intents, dtype=object)

def get_data2(filename):
    with open(filename) as f:
        contents = f.read()
    sents,labels,intents = [],[],[]
    for line in contents.strip().split('\n'):
        words,labs = [i.split(' ') for i in line.split('\t')]
        sents.append(words[1:-1])
        labels.append(labs[1:-1])
        intents.append(labs[-1])
    return np.array(sents, dtype=object), np.array(labels, dtype=object), np.array(intents, dtype=object)

read_method = {'Data/data2/atis-2.dev.w-intent.iob':get_data,
               'Data/data2/atis.train.w-intent.iob':get_data2,
               'Data/data2/atis.test.w-intent.iob':get_data,
              'Data/data2/atis-2.train.w-intent.iob':get_data2}

def fetch_data(fname):
    func = read_method[fname]
    return func(fname)

بعد ذلك سنقوم بإستدعاء و تجهيز الملفات كالأتي

# إستيراد المكتبات
import os
import sys
import numpy as np
import random
from numpy.core.numeric import indices
import pandas as pd

from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.layers import Dense, Input, GlobalMaxPooling1D
from tensorflow.keras.layers import Conv1D, MaxPooling1D, Embedding, LSTM
from tensorflow.keras.models import Model, Sequential
from tensorflow.keras.initializers import Constant

from sklearn.preprocessing import LabelEncoder
random.seed(0)#لإعادة إنتاج نفس النتائج

##################################
############# تحميل بيانات التدريب ########

# سيتم الإستعادنه بوظيفة مساعده
from Data.utils import fetch_data, read_method

sents, labels, intents = fetch_data('Data/data2/atis.train.w-intent.iob')
train_sentense = [" ".join(i) for i in sents]

train_texts = train_sentense
train_label = intents.tolist()

vals = []

for i in range (len(train_label)):
    if "#" in train_label[i] :
        vals.append(i)
for i in vals[::-1]:
    train_label.pop(i)
    train_texts.pop(i)

print ("Number of training sentences :",len(train_texts))
print ("Number of unique intents :",len(set(train_label)))

for i in zip(train_texts[:5], train_label[:5]):
    print(i)


##################################
######## تحميل بيانات الإختبار ########

from Data.utils import fetch_data, read_method

sents, labels, intents = fetch_data('Data/data2/atis.test.w-intent.iob')
test_sentense = [" ".join(i) for i in sents]

test_texts = test_sentense
test_label = intents.tolist()
new_label = set(test_label) - set(train_label)

vals = []

for i in range (len(test_label)):
    if "#" in test_label[i] :
        vals.append(i)
    elif test_label[i] in new_label:
        print(test_label[i])
        vals.append(i)
for i in vals[::-1]:
    test_label.pop(i)
    test_texts.pop(i)

print ("Number of test sentences :",len(test_texts))
print ("Number of unique intents :",len(set(test_label)))

for i in zip(test_texts[:5], test_label[:5]):
    print(i)

##################################
######## المعالجة المسبقة ########

MAX_SEQUENCE_LENGTH = 300
MAX_NUM_WORDS = 20000 
EMBEDDING_DIM = 100 
VALIDATION_SPLIT = 0.3

tokenizer = Tokenizer(num_words=MAX_NUM_WORDS)
tokenizer.fit_on_texts(train_texts)
# تحويل النص إلى متجهة
train_sequence = tokenizer.texts_to_sequences(train_texts)
test_sequence = tokenizer.texts_to_sequences(test_texts)
word_index = tokenizer.word_index
print('Found %s unique tokens.' % len(word_index))


label_encoder = LabelEncoder()
label_encoder.fit(train_label)
train_label = label_encoder.transform(train_label)
test_label = label_encoder.transform(test_label)

# تحويل البيانات لتسلسل ليتم تغذيتها في نموذح الشبكة العصبية 
trainvalid_data = pad_sequences(train_sequence, maxlen=MAX_SEQUENCE_LENGTH)
test_data = pad_sequences(test_sequence,maxlen=MAX_SEQUENCE_LENGTH)
trainvalid_label = to_categorical(train_label)
test_label = to_categorical(np.asarray(test_label), num_classes=trainvalid_label.shape[1])


# تقسيم بيانات التدريب إلى تدريب و تحقق
# هذه البيانات التي سيتم إستخدامها لتدريب الشبكة العصبية
indices = np.arange(trainvalid_data.shape[0])
np.random.shuffle(indices)
trainvalid_data = trainvalid_data[indices]
trainvalid_label = trainvalid_label[indices]
num_validation_sample = int(VALIDATION_SPLIT * trainvalid_data.shape[0])
x_train = trainvalid_data[:-num_validation_sample]
y_train = trainvalid_label[:-num_validation_sample]
x_val = trainvalid_data[-num_validation_sample:]
y_val = trainvalid_label[-num_validation_sample:]

print('Splitting the train data into train and valid is done')

# بناء مصفوفة التضمين
print('Preparing embedding matrix.')
# أولا نقوم بربط الكلمات في مجموعة التضمين إلى متجهات التضمين 
#Glove 6B  سنستخدم 
BASE_DIR = 'Data'
GLOVE_DIR = os.path.join(BASE_DIR, 'glove.6B')

embedding_index = {}
with open(os.path.join(GLOVE_DIR, 'glove.6B.100d.txt'), encoding="utf-8") as f:
    for line in f:
        values = line.split()
        word = values[0]
        coef = np.asarray(values[1:], dtype='float32')
        embedding_index[word] = coef
print('Found %s word vectors in Glove embeddings.' % len(embedding_index))

# تجهيز مصفوفة التضمين 
# word_index الصفوف هي الكلمات من
# glove  أما الأعمده فهي التضمينات من 

num_words = min(MAX_NUM_WORDS, len(word_index)) + 1
embedding_matrix = np.zeros((num_words, EMBEDDING_DIM)) 
for word , i in word_index.items() :
    if i > MAX_NUM_WORDS:
        continue
    embedding_vector = embedding_index.get(word)
    if embedding_vector is not None:
        embedding_matrix[i] = embedding_vector

# تحميل التضمينات المدربة في طبقة التضمين 
embedding_layer = Embedding(num_words,
                            EMBEDDING_DIM,
                            embeddings_initializer=Constant(embedding_matrix),
                            input_length=MAX_SEQUENCE_LENGTH,
                            trainable=False)
                        
print("Preparing of embedding matrix is done")

نظرًا لأنها مهمة تصنيف ، سنستخدم إحدى تقنيات تعلم الألة التي استخدمناها في هذا المقال : نموذج شبكة لف رياضي عصبية (CNN ). يعد استخدام CNN مفيدًا هنا لأنه يلتقط سمات n-gram عبر تمثيلاتها الكثيفة. تشير N-grams مثل “قائمة الرحلات الجوية” إلى تسمية “رحلة”:

cnnmodel = Sequential()
cnnmodel.add(Embedding(MAX_NUM_WORDS, 128))
cnnmodel.add(Conv1D(128, 5, activation='relu'))
cnnmodel.add(MaxPooling1D(5))
cnnmodel.add(Conv1D(128, 5, activation='relu'))
cnnmodel.add(MaxPooling1D(5))
cnnmodel.add(Conv1D(128, 5, activation='relu'))
cnnmodel.add(GlobalMaxPooling1D())
cnnmodel.add(Dense(128, activation='relu'))
cnnmodel.add(Dense(len(trainvalid_label[0]), activation='softmax'))
cnnmodel.compile(loss='categorical_crossentropy',
                 optimizer='rmsprop',
                 metrics=['acc'])
cnnmodel.summary()

# تدريب النموذج 
cnnmodel.fit(x_train, y_train,
          batch_size=128,
          epochs=1, validation_data=(x_val, y_val))

# التحقق على بيانات الإختبار
score, acc = cnnmodel.evaluate(test_data, test_label)
print('Test accuracy with CNN:', acc)

نحصل على دقة تصل إلى 72٪ باستخدام شبكة CNN في الاختبار ، بمتوسط ​​جميع الفئات.

 شبكة عصبية متكررةِ (Recurrent neural network)

إذا استخدمنا نموذج شبكة عصبية متكررة  RNN  ، فإن الدقة تصل إلى 96٪. نعتقد أن RNN قادرة على التقاط ترابط الكلمات عبر جملة الإدخال. تلتقط RNN أهمية الكلمة فيما يتعلق بالسياق الذي تمت رؤيته من قبل. 

rnnmodel2 = Sequential()
rnnmodel2.add(embedding_layer)
rnnmodel2.add(LSTM(128, dropout=0.2, recurrent_dropout=0.2))
rnnmodel2.add(Dense(len(trainvalid_label[0]), activation='sigmoid'))
rnnmodel2.compile(loss='binary_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])

rnnmodel2.summary()

print('Training the RNN')
rnnmodel2.fit(x_train, y_train,
          batch_size=32,
          epochs=1,
          validation_data=(x_val, y_val))
score, acc = rnnmodel2.evaluate(test_data, test_label,
                            batch_size=32)
print('Test accuracy with RNN:', acc)

نماذج المحولات 

كما نعلم ، فإن نماذج المحولات الحديثة المدربة مسبقًا (مثل BERT) أكثر قوة. لذلك ، سنحاول استخدام BERT لتحسين الأداء الذي تم الحصول عليه حتى الآن. يمكن أن يلتقط BERT السياق بشكل أفضل ويحتوي على المزيد من المعايير، لذا فهو أكثر تعبيرًا وينمذجة تعقيدات اللغة. لاستخدام BERT ، نستخدم مخطط تعميل بنمط BERT:

# إستدعاء المكتبات

import pickle
import io
import pandas as pd
import numpy as np
from tqdm import tqdm, trange
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
from seqeval.metrics import f1_score
from sklearn.preprocessing import LabelEncoder

import torch
from torch.utils.data import TensorDataset, DataLoader, RandomSampler, SequentialSampler
from torch.optim import Adam
from pytorch_pretrained_bert import BertTokenizer, BertConfig
from pytorch_pretrained_bert import BertAdam, BertForSequenceClassification

import tensorflow as tf
from tensorflow.keras.preprocessing.sequence import pad_sequences

# إستخدام معالج الرسومات
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
n_gpu = torch.cuda.device_count()
torch.cuda.get_device_name(0)


# البيانات
DATA_DIR="."
###################################
#########وظائف المعالجة المسبقة#######
def get_data(filename):
    df = pd.read_csv(filename,delim_whitespace=True,names=['word','label'])
    beg_indices = list(df[df['word'] == 'BOS'].index)+[df.shape[0]]
    sents,labels,intents = [],[],[]
    for i in range(len(beg_indices[:-1])):
        sents.append(df[beg_indices[i]+1:beg_indices[i+1]-1]['word'].values)
        labels.append(df[beg_indices[i]+1:beg_indices[i+1]-1]['label'].values)
        intents.append(df.loc[beg_indices[i+1]-1]['label'])    
    return np.array(sents),np.array(labels),np.array(intents)

def get_data2(filename):
    with open(filename) as f:
        contents = f.read()
    sents,labels,intents = [],[],[]
    for line in contents.strip().split('\n'):
        words,labs = [i.split(' ') for i in line.split('\t')]
        sents.append(words[1:-1])
        labels.append(labs[1:-1])
        intents.append(labs[-1])
    return np.array(sents),np.array(labels),np.array(intents)

read_method = {'atis-2.dev.w-intent.iob':get_data,
               'atis.train.w-intent.iob':get_data2,
               'atis.test.w-intent.iob':get_data,
              'atis-2.train.w-intent.iob':get_data2}

def fetch_data(fname):
    func = read_method[fname]
    return func(fname)


### قراءة البيانات 
print("Loading the training Data")
sents,labels,intents = fetch_data('atis.train.w-intent.iob')

train_sentences = [" ".join(i) for i in sents]

train_texts = train_sentences
train_labels= intents.tolist()

vals = []

for i in range(len(train_labels)):
    if "#" in train_labels[i]:
        vals.append(i)
        
for i in vals[::-1]:
    train_labels.pop(i)
    train_texts.pop(i)

print ("Number of training sentences :",len(train_texts))
print ("Number of unique intents :",len(set(train_labels)))

for i in zip(train_texts[:5], train_labels[:5]):
    print(i)

# تحويل البيانات إلى باندا

from sklearn.preprocessing import LabelEncoder
# 
df = pd.DataFrame(data =zip(train_labels,train_texts),columns=['Labels',"Text"])

lb_make = LabelEncoder()
df["Labels"] = lb_make.fit_transform(df["Labels"])

train_texts = list(df['Text'])
train_labels = list(df['Labels'])
print("Length of training labels:",len(train_labels))
print("Length of training texts:",len(train_texts))

# تحويل البيانات و حعلها في صيغة بيرت
query_data_train = list(train_texts)
sentences = ["[CLS] " + query + " [SEP]" for query in query_data_train]
print(sentences[0])

tokenizer = BertTokenizer.from_pretrained('bert-base-uncased', do_lower_case=True)
tokenized_texts = [tokenizer.tokenize(sent) for sent in sentences]
print ("Tokenize the first sentence:")
print (tokenized_texts[0])

# تحديد أقصى طول
MAX_LEN = 128
# حشو العملات المدخلة
input_ids = pad_sequences([tokenizer.convert_tokens_to_ids(txt) for txt in tokenized_texts],
                          maxlen=MAX_LEN, dtype="long", truncating="post", padding="post")
# نستعمل تعميل بيرت لتحويل العملات إلى أرقام الفهرس في معجم بيرت
input_ids = [tokenizer.convert_tokens_to_ids(x) for x in tokenized_texts]
input_ids = pad_sequences(input_ids, maxlen=MAX_LEN, dtype="long", truncating="post", padding="post")

# إنشاء أقنعة الإنتباه
attention_masks = []
for seq in input_ids:
    seq_mask = [float(i>0) for i in seq]
    attention_masks.append(seq_mask)
intent_data_label_train = train_labels

# تقسيم البيانات لتدريب و إختبار

train_inputs, validation_inputs, train_labels, validation_labels = train_test_split(input_ids, 
                                                                                    intent_data_label_train, 
                                                                                    random_state=2020, 
                                                                                    test_size=0.1)
train_masks, validation_masks, _, _ = train_test_split(attention_masks,
                                                       input_ids,
                                                       random_state=2020, 
                                                       test_size=0.1)
                                             
                                             
# Convert all of our data into torch tensors, the required datatype for our model
# تحويل جميع بياناتنا إلى تنسورات بايتورش
train_inputs = torch.tensor(train_inputs)
validation_inputs = torch.tensor(validation_inputs)
train_labels = torch.tensor(train_labels)
validation_labels = torch.tensor(validation_labels)
train_masks = torch.tensor(train_masks)
validation_masks = torch.tensor(validation_masks)

batch_size = 32

train_data = TensorDataset(train_inputs, train_masks, train_labels)
train_sampler = RandomSampler(train_data)
train_dataloader = DataLoader(train_data, sampler=train_sampler, batch_size=batch_size)
validation_data = TensorDataset(validation_inputs, validation_masks, validation_labels)
validation_sampler = SequentialSampler(validation_data)
validation_dataloader = DataLoader(validation_data, sampler=validation_sampler, batch_size=batch_size)

####################################
#################### بناء بيرت #######

model = BertForSequenceClassification.from_pretrained("bert-base-uncased", num_labels=17)
model.cuda()

# تحسين معايير بيرت
FULL_FINETUNING = True
if FULL_FINETUNING:
  param_optimizer = list(model.named_parameters())
  no_decay = ['bias', 'gamma', 'beta']
  optimizer_grouped_parameters = [
      {'params': [p for n, p in param_optimizer if not any(nd in n for nd in no_decay)],
      'weight_decay_rate': 0.01},
      {'params': [p for n, p in param_optimizer if any(nd in n for nd in no_decay)],
      'weight_decay_rate': 0.0}
  ]
else:
    param_optimizer = list(model.classifier.named_parameters()) 
    optimizer_grouped_parameters = [{"params": [p for n, p in param_optimizer]}]

optimizer = BertAdam(optimizer_grouped_parameters, lr=3e-5)


# وظيفة لحساب الدقة
def flat_accuracy(preds, labels):
    pred_flat = np.argmax(preds, axis=1).flatten()
    labels_flat = labels.flatten()
    return np.sum(pred_flat == labels_flat) / len(labels_flat)
  

train_loss_set = []
epochs = 4

# حلقة تدريب بيرت
for _ in trange(epochs, desc="Epoch"):  
  
  ###### التدريب ######
  
  model.train()  
  tr_loss = 0
  nb_tr_examples, nb_tr_steps = 0, 0
  # تدريب البيانات لحقبة واحدة
  for step, batch in enumerate(train_dataloader):
    batch = tuple(t.to(device) for t in batch)
    b_input_ids, b_input_mask, b_labels = batch
    optimizer.zero_grad()
    # التغذية الأمامية
    loss = model(b_input_ids, token_type_ids=None, attention_mask=b_input_mask, labels=b_labels)
    train_loss_set.append(loss.item())    
    # التغذية الرجعية
    loss.backward()
    optimizer.step()
    tr_loss += loss.item()
    nb_tr_examples += b_input_ids.size(0)
    nb_tr_steps += 1
  print("Train loss: {}".format(tr_loss/nb_tr_steps))
       
  ###### التحقق ######

  model.eval()
  eval_loss, eval_accuracy = 0, 0
  nb_eval_steps, nb_eval_examples = 0, 0
  # تجقق من البيانات لحقبة واحدة
  for batch in validation_dataloader:
    batch = tuple(t.to(device) for t in batch)
    b_input_ids, b_input_mask, b_labels = batch
    # إخبار النموذج بأن لا يحسب أو يحفظ المشتقات
    with torch.no_grad():
      logits = model(b_input_ids, token_type_ids=None, attention_mask=b_input_mask)    
    logits = logits.detach().cpu().numpy()
    label_ids = b_labels.to('cpu').numpy()
    tmp_eval_accuracy = flat_accuracy(logits, label_ids)    
    eval_accuracy += tmp_eval_accuracy
    nb_eval_steps += 1
  print("Validation Accuracy: {}".format(eval_accuracy/nb_eval_steps))

# طباعة أداء النموذح
plt.figure(figsize=(15,8))
plt.title("Training loss")
plt.xlabel("Batch")
plt.ylabel("Loss")
plt.plot(train_loss_set)
plt.show()

نظرًا لأن BERT مدرب مسبقًا ، فإن تمثيل المحتوى أفضل بكثير من أي نماذج ندربها من البداية ، مثل CNNs أو RNNs. نرى أن BERT يحقق دقة بنسبة 98.8٪ ، متغلبًا على كل من CNN و RNN في مهمة التنبؤ بفعل الحوار. 

تحديد الخانة

يعد تحديد الخانة مهمة أخرى وصفناها كجزء من مكون فهم اللغة الطبيعية (NLU) في نظام الحوار. وصفنا لماذا يمكننا أن نضع هذا كمهمة تصنيف تسلسل. نحتاج إلى إيجاد قيم الخانة مع الأخذ في الاعتبار الإدخال.

تحميل مجموعة البيانات

سنستخدم SNIPS لمهمة تحديد الخانة هذه. يحتوي على 16000 استعلام جماعي وهو معيار شائع لمهام تحديد الخانات. سنحمل كل من أمثلة التدريب والاختبار ، وهنا مثال واحد لمجموعة البيانات على النحو التالي:

Query text: [Play, Magic, Sam, from, the, thirties] # tokenized 

Slots: [O, artist-1, artist-2, O, O, year-1]

كما ناقشنا في المقال 14  من سلسلة مقدمة في معالجة اللغة الطبيعية  نحن نستخدم مخطط BIO لتعليق الخانات. هنا ، يشير O إلى “آخر” ، ويشير اartist-1 وartist-2 إلى كلمتين لاسم الفنان. الشيء نفسه ينطبق على العام.

النموذج

نموذج CRF ++

نظرًا لأنه يمكن النظر إلى مهمة تحديد الخانة على أنها مهمة تصنيف تسلسل ، فسنستخدم إحدى الأساليب الشائعة التي استخدمناها في المقال 14 : نموذج CRF ++ من حزمة sklearn. نستخدم أيضًا متجهات الكلمات بدلاً من إنشاء سمات مصنوعة يدويًا لتغذية نموذج CRF. CRFs هي تقنية شائعة لوضع العلامات التسلسلية وتستخدم بكثرة في استخراج المعلومات.

نحن نستخدم سمات الكلمات التي ستكون مفيدة لهذه المهمة بالذات. نرى أن سياق كل كلمة مهم بالإضافة إلى معنى الكلمة نفسها. لذلك ، نستخدم الكلمتين السابقتين والكلمتين التاليتين لكلمة معينة كسمات . نستخدم أيضًا متجهات التضمين المسترجعة من التضمينات المدربة مسبقًا من GloVe كسمات إضافية. يتم ربط سمات كل كلمة عبر الكلمات في الإدخال. يتم تمرير تمثيل الإدخال هذا إلى نموذج CRF لتسمية التسلسل:

# إستدعاء البيانات
import os
import json
import string

import numpy as np

from nltk.tag import pos_tag

from sklearn_crfsuite import CRF, metrics
from sklearn.metrics import make_scorer,confusion_matrix
from sklearn.metrics import f1_score,classification_report
from sklearn.pipeline import Pipeline

from pprint import pprint

from tensorflow.keras.preprocessing.text import Tokenizer

# تجهيز البيانات
train_loc = "Data/snips/train_PlayMusic_full.json"
test_loc = "Data/snips/validate_PlayMusic.json"

train_file = json.load(open(train_loc, encoding= "iso-8859-2"))
test_file = json.load(open(test_loc, encoding= "iso-8859-2"))


train_datafile = [i["data"] for i in train_file["PlayMusic"]]
test_datafile = [i["data"] for i in test_file["PlayMusic"]]

def convert_data(datalist):
    output = []
    for data in datalist:
        sent = []
        pos = []
        for phrase in data:
            words = phrase["text"].strip().split(" ")
            while "" in words:
                words.remove("")
            if "entity" in phrase.keys():
                label = phrase["entity"]
                labels = [label+"-{}".format(i+1) for i in range(len(words))]
            else:
                labels = ["O"] * len(words)
            sent.extend(words)
            pos.extend(labels)
        output.append([sent, pos])
        print(sent)
    return output

train_data = convert_data(train_datafile)
test_data = convert_data(test_datafile)

BASE_DIR = 'Data'
GLOVE_DIR = os.path.join(BASE_DIR, 'glove.6B')

MAX_SEQUENCE_LENGTH = 300
MAX_NUM_WORDS = 20000 
EMBEDDING_DIM = 100 
VALIDATION_SPLIT = 0.3

# تجهيز مصفوفة التضمينات

embeddings_index = {}
with open(os.path.join(GLOVE_DIR, 'glove.6B.100d.txt'), encoding="utf-8") as f:
    for line in f:
        values = line.split()
        word = values[0]
        coefs = np.asarray(values[1:], dtype='float32')
        embeddings_index[word] = coefs

print('Found %s word vectors in Glove embeddings.' % len(embeddings_index))

def get_embeddings(word):
    embedding_vector = embeddings_index.get(word)
    if embedding_vector is None:
        # words not found in embedding index will be all-zeros.
        embedding_vector = np.zeros(shape=(EMBEDDING_DIM, ))
    return embedding_vector

train_texts = [" ".join(i[0]) for i in train_data]
test_texts = [" ".join(i[0]) for i in test_data]

train_texts[0]

tokenizer = Tokenizer(num_words=MAX_NUM_WORDS)
tokenizer.fit_on_texts(train_texts)
train_sequences = tokenizer.texts_to_sequences(train_texts) 
test_sequences = tokenizer.texts_to_sequences(test_texts)
word_index = tokenizer.word_index
print('Found %s unique tokens.' % len(word_index))



# الحصول على السمات من الجملة
def sent2feats(sentence):
    feats = []
    sen_tags = pos_tag(sentence) 
    for i in range(0,len(sentence)):
        word = sentence[i]
        wordfeats = {}
       
        wordfeats['word'] = word
        if i == 0:
            wordfeats["prevWord"] = wordfeats["prevSecondWord"] = "<S>"
        elif i==1:
            wordfeats["prevWord"] = sentence[0]
            wordfeats["prevSecondWord"] = "</S>"
        else:
            wordfeats["prevWord"] = sentence[i-1]
            wordfeats["prevSecondWord"] = sentence[i-2]
       
        if i == len(sentence)-2:
            wordfeats["nextWord"] = sentence[i+1]
            wordfeats["nextNextWord"] = "</S>"
        elif i==len(sentence)-1:
            wordfeats["nextWord"] = "</S>"
            wordfeats["nextNextWord"] = "</S>"
        else:
            wordfeats["nextWord"] = sentence[i+1]
            wordfeats["nextNextWord"] = sentence[i+2]
        
        
        wordfeats['tag'] = sen_tags[i][1]
        if i == 0:
            wordfeats["prevTag"] = wordfeats["prevSecondTag"] = "<S>"
        elif i == 1:
            wordfeats["prevTag"] = sen_tags[0][1]
            wordfeats["prevSecondTag"] = "</S>"
        else:
            wordfeats["prevTag"] = sen_tags[i - 1][1]

            wordfeats["prevSecondTag"] = sen_tags[i - 2][1]
            
        if i == len(sentence) - 2:
            wordfeats["nextTag"] = sen_tags[i + 1][1]
            wordfeats["nextNextTag"] = "</S>"
        elif i == len(sentence) - 1:
            wordfeats["nextTag"] = "</S>"
            wordfeats["nextNextTag"] = "</S>"
        else:
            wordfeats["nextTag"] = sen_tags[i + 1][1]
            wordfeats["nextNextTag"] = sen_tags[i + 2][1]
            
        
        vector = get_embeddings(word)
        for iv,value in enumerate(vector):
            wordfeats['v{}'.format(iv)]=value
        
        
        feats.append(wordfeats)
    return feats

# Conll إستخراج السمات من بيانات 

def get_feats_conll(conll_data):
    feats = []
    labels = []
    for sentence in conll_data:
        feats.append(sent2feats(sentence[0]))
        labels.append(sentence[1])
    return feats, labels

### تدريب النموذج
def train_seq(X_train,Y_train,X_dev,Y_dev):
    crf = CRF(algorithm='lbfgs', c1=0.1, c2=10, max_iterations=50)
    
    crf.fit(X_train, Y_train)
    labels = list(crf.classes_)
    
    y_pred = crf.predict(X_dev)
    sorted_labels = sorted(labels, key=lambda name: (name[1:], name[0]))
    print(metrics.flat_f1_score(Y_dev, y_pred,average='weighted', labels=labels))
    print(metrics.flat_classification_report(Y_dev, y_pred, labels=sorted_labels, digits=3))
    
    get_confusion_matrix(Y_dev, y_pred,labels=sorted_labels)

# الحصول على مصفوفة الإرتباك
def print_cm(cm, labels):
    print("\n")
    """pretty print for confusion matrixes"""
    columnwidth = max([len(x) for x in labels] + [5])  # 5 is value length
    empty_cell = " " * columnwidth
    # Print header
    print("    " + empty_cell, end=" ")
    for label in labels:
        print("%{0}s".format(columnwidth) % label, end=" ")
    print()
    # Print rows
    for i, label1 in enumerate(labels):
        print("    %{0}s".format(columnwidth) % label1, end=" ")
        sum = 0
        for j in range(len(labels)):
            cell = "%{0}.0f".format(columnwidth) % cm[i, j]
            sum =  sum + int(cell)
            print(cell, end=" ")
        print(sum )

def get_confusion_matrix(y_true,y_pred,labels):
    trues,preds = [], []
    for yseq_true, yseq_pred in zip(y_true, y_pred):
        trues.extend(yseq_true)
        preds.extend(yseq_pred)
    print_cm(confusion_matrix(trues,preds,labels),labels)

# CRF تدريب النموذج مع 
print("Training a Sequence classification model with CRF")
feats, labels = get_feats_conll(train_data)
devfeats, devlabels = get_feats_conll(test_data)
train_seq(feats, labels, devfeats, devlabels)
print("Done with sequence model")


نحصل على نتيجة F1 تساوي 85.5 باستخدام نموذج CRF ++. 

نموذج Bert

على غرار مهمة التصنيف السابقة ، سنحاول استخدام BERT لتحسين الأداء الذي تم الحصول عليه حتى الآن. يمكن لـ BERT التقاط السياق بشكل أفضل ، حتى في حالة مهمة تسمية التسلسل (sequence labeling). نستخدم جميع التمثيلات المخفية لجميع الكلمات في الاستعلام للتنبؤ بتسمية لكل منها. ومن ثم ، في النهاية ، نقوم بإدخال تسلسل الكلمات في النموذج والحصول على سلسلة من التسميات (بنفس طول المدخلات) ، والتي يمكن استنتاجها على أنها خانات متوقعة بالكلمات كقيم:

# إستدعاء المكتبات
import string
import re
import json
import pandas as pd
import numpy as np
from tqdm import tqdm, trange

import torch
from torch.optim import Adam
from torch.utils.data import TensorDataset, DataLoader, RandomSampler, SequentialSampler
from pytorch_pretrained_bert import BertTokenizer, BertConfig
from pytorch_pretrained_bert import BertForTokenClassification, BertAdam


from tensorflow.keras.preprocessing.sequence import pad_sequences
from sklearn.model_selection import train_test_split


from seqeval.metrics import f1_score

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
n_gpu = torch.cuda.device_count()


# تحهيز البيانات
train_loc = "train_PlayMusic_full.json"
test_loc = "validate_PlayMusic.json"

train_file = json.load(open(train_loc, encoding= "iso-8859-2"))
test_file = json.load(open(test_loc, encoding= "iso-8859-2"))

train_datafile = [i["data"] for i in train_file["PlayMusic"]]
test_datafile = [i["data"] for i in test_file["PlayMusic"]]

# وظيفة مساعدة لمعالجة البيانات
def convert_data(datalist):
    output = []
    for data in datalist:
        sent = []
        pos = []
        for phrase in data:
            words = phrase["text"].strip().split(" ")
            while "" in words:
                words.remove("")
            if "entity" in phrase.keys():
                label = phrase["entity"]
                labels = [label+"-{}".format(i+1) for i in range(len(words))]
            else:
                labels = ["O"] * len(words)
            sent.extend(words)
            pos.extend(labels)
        output.append([sent, pos])
        # print(sent)
    return output

train_data = convert_data(train_datafile)
test_data = convert_data(test_datafile)

df_train = pd.DataFrame(train_data,columns=['sentence','label'])
df_test = pd.DataFrame(test_data,columns=['sentence','label'])

sentence = list(df_train['sentence'])+list(df_test['sentence'])
label  = list(df_train['label'])+list(df_test['label'])
unique_labels =[]
for i in label:

    unique_labels += i
  
labels = unique_labels 

unique_labels = set(unique_labels)

# نختاج إلى دمج العملات في جملة واحده
import re
def untokenize(words):
    """
    Untokenizing a text undoes the tokenizing operation, restoring
    punctuation and spaces to the places that people expect them to be.
    Ideally, `untokenize(tokenize(text))` should be identical to `text`,
    except for line breaks.
    """
    text = ' '.join(words)
    step1 = text.replace("`` ", '"').replace(" ''", '"').replace('. . .',  '...')
    step2 = step1.replace(" ( ", " (").replace(" ) ", ") ")
    step3 = re.sub(r' ([.,:;?!%]+)([ \'"`])', r"\1\2", step2)
    step4 = re.sub(r' ([.,:;?!%]+)$', r"\1", step3)
    step5 = step4.replace(" '", "'").replace(" n't", "n't").replace(
         "can not", "cannot")
    step6 = step5.replace(" ` ", " '")
    return step6.strip()

sentences_untokenized = [untokenize(i) for i in sentence]

# تجهيز نموذج بيرت
MAX_LEN = 75
bs = 32
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased', do_lower_case=True)
tokenized_texts = [["[CLS]"] +tokenizer.tokenize(sent)+ ["[SEP]"] for sent in sentences_untokenized]

# المعالجة المسبقة للتسميات
tags_vals = list(unique_labels)
tag2idx = {t: i for i, t in enumerate(tags_vals)}
tags_vals[:10]

# قص و حشو العملات 
input_ids = pad_sequences([tokenizer.convert_tokens_to_ids(txt) for txt in tokenized_texts],
                          maxlen=MAX_LEN, dtype="long", truncating="post", padding="post")

tags = pad_sequences([[tag2idx.get(l) for l in lab] for lab in label],
                     maxlen=MAX_LEN, value=tag2idx["O"], padding="post",
                     dtype="long", truncating="post")

# تجهيز أقنعة الإنتباه
attention_masks = [[float(i>0) for i in ii] for ii in input_ids]

# تقسيم البيانات
tr_inputs, val_inputs, tr_tags, val_tags = train_test_split(input_ids, tags, 
                                                            random_state=2018, test_size=0.1)
tr_masks, val_masks, _, _ = train_test_split(attention_masks, input_ids,
                                             random_state=2018, test_size=0.1)

# تحويل المدخلات إلى تنسورات بايتورش
tr_inputs = torch.tensor(tr_inputs)
val_inputs = torch.tensor(val_inputs)
tr_tags = torch.tensor(tr_tags)
val_tags = torch.tensor(val_tags)
tr_masks = torch.tensor(tr_masks)
val_masks = torch.tensor(val_masks)

train_data = TensorDataset(tr_inputs, tr_masks, tr_tags)
train_sampler = RandomSampler(train_data)
train_dataloader = DataLoader(train_data, sampler=train_sampler, batch_size=bs)

valid_data = TensorDataset(val_inputs, val_masks, val_tags)
valid_sampler = SequentialSampler(valid_data)
valid_dataloader = DataLoader(valid_data, sampler=valid_sampler, batch_size=bs)

# ضبط بيرت
model = BertForTokenClassification.from_pretrained("bert-base-uncased", num_labels=len(tag2idx))
model.cuda()
FULL_FINETUNING = True
if FULL_FINETUNING:
    param_optimizer = list(model.named_parameters())
    no_decay = ['bias', 'gamma', 'beta']
    optimizer_grouped_parameters = [
        {'params': [p for n, p in param_optimizer if not any(nd in n for nd in no_decay)],
         'weight_decay_rate': 0.01},
        {'params': [p for n, p in param_optimizer if any(nd in n for nd in no_decay)],
         'weight_decay_rate': 0.0}
    ]
else:
    param_optimizer = list(model.classifier.named_parameters()) 
    optimizer_grouped_parameters = [{"params": [p for n, p in param_optimizer]}]
optimizer = Adam(optimizer_grouped_parameters, lr=3e-5)

# الدقة 
def flat_accuracy(preds, labels):
    pred_flat = np.argmax(preds, axis=2).flatten()
    labels_flat = labels.flatten()
    return np.sum(pred_flat == labels_flat) / len(labels_flat)

# التدريب
epochs = 5
max_grad_norm = 1.0
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
n_gpu = torch.cuda.device_count()

train_loss_set = []

for _ in trange(epochs, desc="Epoch"):
    model.train()
    tr_loss = 0
    nb_tr_examples, nb_tr_steps = 0, 0
    for step, batch in enumerate(train_dataloader):
        batch = tuple(t.to(device) for t in batch)
        b_input_ids, b_input_mask, b_labels = batch
        # التغذية الأمامية
        loss = model(b_input_ids, token_type_ids=None,
                     attention_mask=b_input_mask, labels=b_labels)
        train_loss_set.append(loss.item())
        # التغذية الرجعية
        loss.backward()
        
        tr_loss += loss.item()
        nb_tr_examples += b_input_ids.size(0)
        nb_tr_steps += 1
        # قص المشتقات
        torch.nn.utils.clip_grad_norm_(parameters=model.parameters(), max_norm=max_grad_norm)
        
        optimizer.step()
        model.zero_grad()
    
    print("Train loss: {}".format(tr_loss/nb_tr_steps))


    model.eval()
    eval_loss, eval_accuracy = 0, 0
    nb_eval_steps, nb_eval_examples = 0, 0
    predictions , true_labels = [], []
    for batch in valid_dataloader:
        batch = tuple(t.to(device) for t in batch)
        b_input_ids, b_input_mask, b_labels = batch
        
        with torch.no_grad():
            tmp_eval_loss = model(b_input_ids, token_type_ids=None,
                                  attention_mask=b_input_mask, labels=b_labels)
            logits = model(b_input_ids, token_type_ids=None,
                           attention_mask=b_input_mask)
        logits = logits.detach().cpu().numpy()
        label_ids = b_labels.to('cpu').numpy()
        predictions.extend([list(p) for p in np.argmax(logits, axis=2)])
        true_labels.append(label_ids)
        
        tmp_eval_accuracy = flat_accuracy(logits, label_ids)
        
        eval_loss += tmp_eval_loss.mean().item()
        eval_accuracy += tmp_eval_accuracy
        
        nb_eval_examples += b_input_ids.size(0)
        nb_eval_steps += 1
    eval_loss = eval_loss/nb_eval_steps
    print("Validation loss: {}".format(eval_loss))
    print("Validation Accuracy: {}".format(eval_accuracy/nb_eval_steps))
    pred_tags = [tags_vals[p_i] for p in predictions for p_i in p]
    valid_tags = [tags_vals[l_ii] for l in true_labels for l_i in l for l_ii in l_i]
    print("F1-Score: {}".format(f1_score(pred_tags, valid_tags)))






لكننا وجدنا أن BERT يحقق 73= F1 فقط. قد يكون هذا بسبب وجود العديد من الكيانات المسماة في المدخلات التي لم يتم تمثيلها بشكل جيد بواسطة معايير BERT الأصلية. من ناحية أخرى ، كانت السمات التي حصلنا عليها لـ CRF قوية بما يكفي لمجموعة البيانات هذه لالتقاط الأنماط اللازمة. هذا مثال مثير للاهتمام حيث يتفوق نموذج أبسط على BERT. 

ختاما 

لقد تعلمنا حتى الآن كيفية إنشاء مكونات مختلفة لـ NLU من أجل حوار موجه نحو الهدف باستخدام مجموعات البيانات الشائعة. لقد رأينا كيف تعمل نماذج تعلم الألة المختلفة بشكل جيد نسبيًا في هذه المهام. باستخدام هذه ، سنتمكن من تشغيل مثل هذه النماذج المخصصة في مجموعة البيانات الخاصة بنا واستكشاف نماذج مختلفة لاختيار أفضلها.

كما رأينا من قبل وهنا أيضًا ، تساعد النماذج المدربة مسبقًا في الحصول على أداء أفضل مقارنة بنماذج DL الأخرى التي تم تعلمها من البداية. قد تكون هناك استثناءات ، لأن النماذج المدربة مسبقًا حساسة لحجم البيانات. قد تزيد النماذج المدربة مسبقًا على مجموعات البيانات الأصغر ، وقد يتم تعميم الميزات المصنوعة يدويًا بشكل جيد في هذه الحالات.

إضافة تعليق