يشير التعرف على الكيانات المسماة (named entity recognition) أو (NER) إلى مهمة إستخراج المعلومات لتحديد الكيانات في المستند. عادةً ما تكون الكيانات عبارة عن أسماء الأشخاص والمواقع والمؤسسات وسلاسل متخصصة أخرى ، مثل تعبيرات النقود والتواريخ والمنتجات وأسماء / أرقام القوانين أو المقالات وما إلى ذلك.
ضع في اعتبارك سيناريو ينشئ فيه المستخدم طلب بحث – “أين ولد ألبرت أينشتاين؟” – باستخدام بحث Google. لتتمكن من إظهار “Ulm، Germany” لهذا الاستعلام ، يحتاج محرك البحث إلى التعرف على أن ألبرت أينشتاين هو شخص قبل الذهاب للبحث عن مكان الولادة. هذا مثال على التعرف على الكيانات المسماة (NER) في العمل في تطبيق العالم الحقيقي.
يشير NER إلى مهمة إستخراج المعلومات لتحديد الكيانات في المستند. عادةً ما تكون الكيانات عبارة عن أسماء الأشخاص والمواقع والمؤسسات وسلاسل متخصصة أخرى ، مثل تعبيرات النقود والتواريخ والمنتجات وأسماء / أرقام القوانين أو المقالات وما إلى ذلك.
و يعد التعرف على الكيانات المسماة خطوة مهمة في خط الإنتاج للعديد من تطبيقات معالجة اللغة الطبيعية التي تتضمن استخراج المعلومات.
كما هو موضح في الشكل ، بالنسبة لنص معين ، من المتوقع أن يحدد NER أسماء الأشخاص والمواقع والتواريخ والكيانات الأخرى. فئات مختلفة من الكيانات المحددة هنا هي بعض من تلك المستخدمة بشكل شائع في تطوير نظام NER .
يعد NER شرطًا أساسيًا للقدرة على القيام بمهام إستخراج المعلومات الأخرى ، مثل استخراج العلاقة أو استخراج الأحداث . كما أن التعرف على الكيانات المسماة مفيد أيضًا في تطبيقات أخرى مثل الترجمة الآلية ، حيث لا يلزم بالضرورة ترجمة الأسماء أثناء ترجمة الجملة.
لذلك ، من الواضح أن هناك مجموعة من السيناريوهات في مشاريع معالجة اللغة الطبيعية حيث يعد NER إحدى العناصر الكبرى. فهو إحدى المهام الشائعة التي من المحتمل أن تواجهها في مشاريع معالجة اللغة الطبيعية في الصناعة. كيف نبني مثل هذا النظام ؟ يركز هذا المقال على هذا السؤال ، مع الأخذ في الاعتبار ثلاث حالات: بناء نظام NER الخاص بنا ، واستخدام المكتبات الجاهزة، واستخدام التعلم النشط.
بناء نظام التعرف على الكيانات المسماة
تتمثل إحدى الطرق البسيطة لبناء نظام NER في الاحتفاظ بمجموعة كبيرة من أسماء الأشخاص / المنظمات / المواقع الأكثر صلة بشركتنا (على سبيل المثال ، أسماء جميع العملاء ، والمدن في عناوينهم ، وما إلى ذلك) ؛ يشار إلى هذا عادةً باسم المعجم.
للتحقق مما إذا كانت كلمة معينة هي كيان مسمى أم لا ، ما عليك سوى البحث في المعجم. إذا تمت تغطية عدد كبير من الكيانات الموجودة في بياناتنا بواسطة معجم جغرافي ، فهذه طريقة رائعة للبدء ، خاصةً عندما لا يتوفر نظام NER حالي.
هناك بعض الأسئلة التي يجب مراعاتها مع مثل هذا النهج. كيف تتعامل مع الأسماء الجديدة؟ كيف نقوم بتحديث قاعدة البيانات هذه بشكل دوري؟ كيف نتبع الأسماء المستعارة ، أي الأشكال المختلفة لاسم معين (على سبيل المثال ، الولايات المتحدة الأمريكية ، الولايات المتحدة ، إلخ)؟
النهج الذي يتجاوز جدول البحث هو NER القائم على القواعد، والذي يمكن أن يعتمد على قائمة مجمعة من الأنماط استنادًا إلى عملات الكلمات (word tokens) وعلامات جزء من الكلام (pos tags) . على سبيل المثال ، يشير النمط “NNP was born ” ، حيث “NNP” هو علامة POS لاسم ، إلى أن الكلمة التي تم وضع علامة عليها “NNP” تشير إلى شخص.
يمكن برمجة هذه القواعد لتغطية أكبر عدد ممكن من الحالات لبناء نظام NER قائم على القواعد. توفر RegexNER من Stanford NLP و spaCy’s EntityRuler وظائف لتنفيذ NER المستندة إلى القواعد الخاصة بك.
النهج الأكثر عملية لـ NER هو تدريب نموذج تعلم ألة ، والذي يمكنه التنبؤ بالكيانات المسماة في نص خارج التدريب. لكل كلمة ، يجب اتخاذ قرار بشأن ما إذا كانت هذه الكلمة كيانًا أم لا ، وإذا كانت كذلك ، فما هو نوع الكيان.
من نواحٍ عديدة ، هذا مشابه جدًا لمشاكل التصنيف التي ناقشناها بالتفصيل سابقاً . الاختلاف الوحيد هنا هو أن NER هي مشكلة “تصنيف متسلسل (Sequence labeling)” . المصنفات النموذجية التي تحدثنا عنها تتنبأ بتسميات النصوص المستقلة عن السياق المحيط بها.
ضع في اعتبارك المصنف الذي يصنف الجمل في مراجعة الفيلم إلى فئات إيجابية / سلبية / محايدة بناءً على مشاعرهم. لا يأخذ هذا المصنف (عادة) في الحسبان مشاعر الجمل السابقة (أو اللاحقة) عند تصنيف الجملة الحالية.
في المصنف المتسلسل ، هذا السياق مهم. حالة الاستخدام الشائعة هي علامات جزء من الكلام (pos tags) ، حيث نحتاج إلى معلومات حول أجزاء الكلام من الكلمات المحيطة لتقدير جزء الكلام للكلمة الحالية.
يتم نمذجة NER تقليديًا على أنها مشكلة تصنيف تسلسلي ، حيث يعتمد تنبؤ الكيان للكلمة الحالية أيضًا على السياق. على سبيل المثال ، إذا كانت الكلمة السابقة هي اسم شخص ، فهناك احتمال كبير أن تكون الكلمة الحالية أيضًا اسم شخص إذا كانت اسمًا (على سبيل المثال ، الاسم الأول والأخير).
لتوضيح الفرق بين المصنف العادي ومصنف التسلسل ، ضع في اعتبارك الجملة التالية: “واشنطن ممطرة”. عندما يرى المصنف العادي هذه الجملة ويضطر إلى تصنيفها كلمة بكلمة ، عليه اتخاذ قرار بشأن ما إذا كانت واشنطن تشير إلى شخص (على سبيل المثال ، جورج واشنطن) أو ولاية واشنطن دون النظر إلى الكلمات المحيطة. من الممكن تصنيف كلمة “واشنطن” في هذه الجملة المعينة كمكان فقط بعد النظر إلى السياق الذي تُستخدم فيه. ولهذا السبب يتم استخدام مصنفات التسلسل لتدريب نماذج NER.
الحقول العشوائية الشرطية (Conditional random fields)
الحقول العشوائية الشرطية (Conditional random fields) أو (CRFs) هي واحدة من خوارزميات التدريب الشائعة لمصنف التسلسل. سنقوم باستخدام CRFs لتدريب نظام NER. سنستخدم CONLL-03 ، وهي مجموعة بيانات شائعة تُستخدم لتدريب أنظمة NER ، ومكتبة تصنيف تسلسل مفتوحة المصدر تسمى sklearn-crfsuite ،
جنبًا إلى جنب مع مجموعة من الميزات البسيطة القائمة على الكلمات وعلامات نقاط البيع ، التي توفر المعلومات السياقية التي نحتاجها لهذه المهمة.
لإجراء تصنيف التسلسل ، نحتاج إلى بيانات بتنسيق يسمح لنا بنمذجة السياق. تبدو بيانات التدريب النموذجية لـ NER مثل الشكل أدناه، وهي جملة من مجموعة بيانات CONLL-03.
التسميات الموجودة في الشكل تُعرف باسم تدوين BIO:
- تشير B إلى بداية الكيان (beginning of an entity)
- أما I ، داخل الكيان (inside an entity) ، تشير إلى متى تتكون الكيانات من أكثر من كلمة واحدة .
- و O ، أخرى (other) ، تشير إلى غير الكيانات
Peter Such هو اسم يتكون من كلمتين في المثال الموضح في الشكل أعلاه. وبالتالي ، يتم وضع علامة على “Peter” على أنها B-PER ، ويتم تمييز “Such ” على أنها I-PER للإشارة إلى أن هذا جزء من الكيان من الكلمة السابقة.
الكيانات المتبقية في هذا المثال ، Essex و Yorkshire و Headingley ، كلها كيانات مكونة من كلمة واحدة. لذلك ، نرى فقط B-ORG و B-LOC كعلامات خاصة بهم. بمجرد أن نحصل على مجموعة بيانات من الجمل المنظمة بهذه الطريقة ولدينا فئة خوارزمية مصنف التسلسل، كيف يجب علينا تدريب نظام NER؟
الخطوات هي نفسها تلك الخاصة بمصنفات النص:
- قم بتحميل مجموعة البيانات.
- استخراج السمات.
- تدريب المصنف
- تقييمه على مجموعة اختبار
تحميل مجموعة البيانات سهل ومباشر. تم تقسيم مجموعة البيانات هذه بالفعل إلى مجموعة تدريب / تحقق/ اختبار. لذلك ، سنقوم بتدريب النموذج باستخدام مجموعة التدريب.
دعونا نلقي نظرة على مثال باستخدام السمات المصنوعة يدويًا هذه المرة. ما هي السمات التي تبدو مناسبة بشكل حدسي لهذه المهمة؟ لتحديد أسماء الأشخاص أو الأماكن ، على سبيل المثال ، يمكن استخدام أنماط مثل ما إذا كانت الكلمة تبدأ بحرف كبير أو ما إذا كانت مسبوقة أو تالية بفعل / اسم ، وما إلى ذلك ، كنقاط بداية لتدريب نموذج NER.
نقوم أولاً بتحميل البيانات
from nltk.tag import pos_tag from sklearn_crfsuite import CRF, metrics from sklearn.metrics import make_scorer,confusion_matrix from pprint import pprint from sklearn.metrics import f1_score,classification_report from sklearn.pipeline import Pipeline import string """ تحميل بيانات التدريب / الاختبار. conll الإدخال : بيانات بتنسيق الإخراج: قائمة حيث كل عنصر عبارة عن قائمتين """ def load__data_conll(file_path): myoutput,words,tags = [],[],[] fh = open(file_path) for line in fh: line = line.strip() if "\t" not in line: #Sentence ended. myoutput.append([words,tags]) words,tags = [],[] else: word, tag = line.split("\t") words.append(word) tags.append(tag) fh.close() return myoutput
. يُظهر مقتطف الكود التالي وظيفة تستخرج علامات جزء من الكلام للكلمات السابقة والتالية لجملة معينة.
""" الحصول على سمات كل الكلمات في الجملة :السمات سياق الكلمة : نافذة طولها كلمتين على كلا جانبي الكلمة الحالية نافذة طولها علامتين على كلا جانبي الكلمة الحاليةو العلامة الحالية : POS سياق علامات الكلام المدخل : جملة كمجموعة من العملات المخرج : قائمة من القواميس كل قاموس يمثل سمات تلك الكلمة """ def sent2feats(sentence): feats = [] sen_tags = pos_tag(sentence) #This format is specific to this POS tagger! 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] feats.append(wordfeats) return feats
يمكنك أن ترى من متغير wordfeats في عينة الكود هذه ، يقوم تحويل كل كلمة إلى قاموس سمات ، وبالتالي ستبدو كل جملة كقائمة من القواميس (المتغير feats في الكود) ، والتي سيتم استخدامها بواسطة مصنف CRF.
يُظهر مقتطف الكود التالي وظيفة لتدريب نظام NER باستخدام نموذج CRF وتقييم أداء النموذج في مجموعة التطوير:
# إستخراج السمات def get_feats_cnoll(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) #Prints the total number of instances per cat at the end. # كتابة مصفوفة الإرتباك 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) def main(): train_path = 'Data/conlldata/train.txt' test_path = 'Data/conlldata/test.txt' conll_train = load__data_conll(train_path) conll_dev = load__data_conll(test_path) print("Training a Sequence classification model with CRF") feats, labels = get_feats_cnoll(conll_train) devfeat, devlabels = get_feats_cnoll(conll_dev) train_seq(feats, labels, devfeat, devlabels) print("Done with sequence model") if __name__ =='__main__': main()
الناتج
أعطى تدريب نموذج CRF هذا درجة F1 قدرها 0.92 على بيانات التطوير ، وهي درجة جيدة جدًا! يُظهر دفتر الملاحظات مقاييس تقييم أكثر تفصيلاً وكيفية حسابها. هنا ، أظهرنا بعض السمات الأكثر استخدامًا في تعلم نظام NER واستخدمنا طريقة تدريب شائعة ومجموعة بيانات متاحة.
من الواضح أن هناك الكثير الذي يتعين القيام به فيما يتعلق بضبط النموذج وتطوير سمات أفضل ؛ يعمل هذا المثال فقط على توضيح طريقة واحدة لتطوير نموذج NER سريعًا باستخدام مكتبة معينة في حال احتجت إلى ذلك ولديك مجموعة بيانات ذات صلة. MIT لإستخراج المعلومات هي مكتبة أخرى لتدريب أنظمة NER.
التطورات الحديثة في أبحاث NER إما تستبعد أو تزيد من نوع هندسة السمات التي قمنا بها في هذا المثال مع نماذج الشبكة العصبية. NCRF ++ هي مكتبة أخرى يمكن استخدامها لتدريب NER الخاص بك باستخدام معماريات شبكات عصبية مختلفة.
وهنا طريقة إستخدام نموذج BERT لتدريب نظام NER باستخدام نفس مجموعة البيانات
# إستدعاء المكتبات المهمة import string 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 #F1 إستدعاء مكتبة لحساب #from seqeval.metrics import f1_score """ تحميل بيانات التدريب / الاختبار. conll الإدخال : بيانات بتنسيق الإخراج: قائمة حيث كل عنصر عبارة عن قائمتين """ def load__data_conll(file_path): myoutput,words,tags = [],[],[] fh = open(file_path) for line in fh: line = line.strip() if "\t" not in line: #Sentence ended. myoutput.append([words,tags]) words,tags = [],[] else: word, tag = line.split("\t") words.append(word) tags.append(tag) fh.close() return myoutput """ الحصول على سمات كل الكلمات في الجملة :السمات سياق الكلمة : نافذة طولها كلمتين على كلا جانبي الكلمة الحالية نافذة طولها علامتين على كلا جانبي الكلمة الحاليةو العلامة الحالية : POS سياق علامات الكلام المدخل : جملة كمجموعة من العملات المخرج : قائمة من القواميس كل قاموس يمثل سمات تلك الكلمة """ def sent2feats(sentence): feats = [] sen_tags = pos_tag(sentence) #This format is specific to this POS tagger! 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] feats.append(wordfeats) return feats # معالجة البيانات train_path = 'Data/conlldata/train.txt' test_path = 'Data/conlldata/test.txt' conll_train = load__data_conll(train_path) conll_test = load__data_conll(test_path) #بيرت يريد منا أن نعالج البيانات بطريقة معينة df_train= pd.read_csv("Data/conlldata/train.txt", engine="python", delimiter="\t", header=None, encoding='utf-8', error_bad_lines=False) df_test= pd.read_csv("Data/conlldata/test.txt", engine="python", delimiter="\t", header=None, encoding='utf-8', error_bad_lines=False) # دمج df = pd.merge(df_train,df_test) label = list(df[1].values) #we will be using this to make a set of all unique labels #token_utils في untokenize نحتاح إلى إضافة كل العملات في جملة واحده ، سنستخدم وظيفة #https://github.com/commonsense/metanl 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() # معالجة النص بالنسبة لبيرت # تحويل البيانات إلى إطارات بيانات df_train = pd.DataFrame(conll_train,columns=["sentence","labels"]) df_test = pd.DataFrame(conll_test,columns=["sentence","labels"]) # الحصول على الجمل و المسميات الموجوده في التدريب و الإختبار sentenses = list(df_train['sentence'])+list(df_test['sentence']) labels = list(df_train['labels']) + list(df_test['labels']) # والتي يتوقها بيرت SEP و Cls نحتاج إلى تعميل الجملة ثم إضافة عملات #GPU تجهيز بايتورش لإستخدام device = torch.device("cuda" if torch.cuda.is_available() else "cpu") n_gpu = torch.cuda.device_count() # تحديد ثوابت بيرت MAX_LEN = 75 bs = 32 # بيرت يأتي مع معمل مدرب مسبقا بالإضافة إلى مفردات محددة tokenizer = BertTokenizer.from_pretrained('bert-base-uncased', do_lower_case=True) # تعميل النص tokenized_texts = list(map(lambda x: ['[CLS]'] + tokenizer.tokenize(x) + ['[SEP]'] , sentenses)) #print(tokenized_texts[0]) # المعالجة المسبقة للتسميات # تحويل العلامات إلى مؤشرات tags_vals = list(set(label)) tag2idx ={t: i for i , t in enumerate(tags_vals)} # نحتاج الآن إلى إعطاء بيرت معرفات إدخال ، وهي سلسلة من الأعداد الصحيحة التي تحدد بشكل فريد كل عملة مدخلة برقم الفهرس الخاص به. # قص و حشو العملات والملصقات بالطول المطلوب 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 labels], maxlen=MAX_LEN, value=tag2idx["O"], padding="post", dtype="long", truncating="post") # نستخدم أقنعة الإنتباه الخاصه بيرت # و التي تقوم بإخبار بيرت أي العملات يعطي يجب أن يتنبه لها من عدمها # https://huggingface.co/transformers/glossary.html#attention-mask 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 = 2020, test_size=0.2) tr_masks, val_masks, _, _ = train_test_split(attention_masks, input_ids, random_state = 2020, test_size=0.2) # بايتورش يتطلب أن يكون المدخل في هيئة تنسورات بايتورش 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) #print("train Dataloader is ready") val_data = TensorDataset(val_inputs, val_masks, val_tags) val_sampler = RandomSampler(val_data) valid_dataloader = DataLoader(val_data, sampler=val_sampler, batch_size=bs) #print("val Dataloader is ready") model = BertForTokenClassification.from_pretrained("bert-base-uncased", num_labels=len(tag2idx)) #GPU نقل معايير النموذج إلى model.cuda() # ضبط بير Full_FineTune= True if Full_FineTune: 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 in param_optimizer]}] # إضافة المحسن optimizer = Adam(optimizer_grouped_parameters) # الدقة def flat_accuracy(preds, labels): pred_flat = np.argmax(preds, axis=2).flatten() label_flat = labels.flatten() return np.sum(pred_flat == label_flat)/ len(label_flat) epochs = 4 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): # gpu إضافة الحزمة إلى batch = tuple(t.to(device).long() 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) # التغذية ألعكسية loss.backward() # track train loss tr_loss += loss.item() nb_tr_examples += b_input_ids.size(0) nb_tr_steps += 1 # gradient clipping torch.nn.utils.clip_grad_norm_(parameters=model.parameters(), max_norm=max_grad_norm) # update parameters optimizer.step() model.zero_grad() # print train loss per epoch print("Train loss: {}".format(tr_loss/nb_tr_steps)) # VALIDATION on validation set 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).long() 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))) import matplotlib.pyplot as plt plt.figure(figsize=(15,8)) plt.title("Training loss") plt.xlabel("Batch") plt.ylabel("Loss") plt.plot(train_loss_set) plt.show() #تثقييم النموذج model.eval() predictions = [] true_labels = [] eval_loss, eval_accuracy = 0, 0 nb_eval_steps, nb_eval_examples = 0, 0 for batch in valid_dataloader: batch = tuple(t.to(device).long() 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() predictions.extend([list(p) for p in np.argmax(logits, axis=2)]) label_ids = b_labels.to('cpu').numpy() 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 pred_tags = [[tags_vals[p_i] for p_i in p] for p in predictions] valid_tags = [[tags_vals[l_ii] for l_ii in l_i] for l in true_labels for l_i in l ] print("Validation loss: {}".format(eval_loss/nb_eval_steps)) print("Validation Accuracy: {}".format(eval_accuracy/nb_eval_steps)) #print("Validation F1-Score: {}".format(f1_score(pred_tags, valid_tags)))
قمنا بجولة سريعة حول كيفية تدريب نظام NER الخاص بنا. ومع ذلك ، في سيناريوهات العالم الحقيقي ، لن يكون استخدام النموذج المدرَّب بمفرده كافيًا ، حيث تستمر البيانات في التغير واستمرار إضافة كيانات جديدة ، وستكون هناك أيضًا بعض الكيانات أو الأنماط الخاصة بالمجال التي لم يتم رؤيتها بشكل عام في مجموعات بيانات التدريب.
وبالتالي ، فإن معظم أنظمة NER المنتشرة في سيناريوهات العالم الحقيقي تستخدم مزيجًا من نماذج تعلم الألة والمعاجم وبعض الأساليب التجريبية القائمة على مطابقة الأنماط لتحسين أدائها [هنا].
أنشاء أنظمة التعرف على الكيانات المسماة باستخدام مكتبة موجودة
مع كل هذا النقاش حول تدريب نظام التعرف على الكيانات المسماة قد يجعل بنائه ونشره يبدو وكأنه عملية طويلة (بدءًا من شراء مجموعة بيانات) ، لحسن الحظ ، تم إجراء أبحاث موسعة عن NER على مدار العقود القليلة الماضية ، ولدينا مكتبات جاهزة لتبدأ. ستانفورد نير، spaCy و AllenNLP هي بعض مكتبات معالجة اللغة الطبيعية المعروفة التي يمكن استخدامها لدمج نموذج NER مدرب مسبقًا في منتج برمجي. يوضح مقتطف الكود أدناه استخدام NER من spaCy:
import spacy nlp = spacy.load("en_core_web_lg") text_from_fig = "On Tuesday, Apple announced its plans for another major chunk of the money: It will buy back a further $75 billion in stock." doc = nlp(text_from_fig) for ent in doc.ents: if ent.text: print(ent.text, "\t", ent.label_)
سيظهر تشغيل مقتطف الكود هذا Tuesday كـ DATE ، و Apple كـ ORG (منظمة) ، و 75 مليار دولار كـ MONEY. بالنظر إلى أن نظام NER الخاص بـ spaCy يعتمد على أحدث نموذج عصبي مقترن ببعض أنماط المطابقة والاستدلال ، فهي نقطة انطلاق جيدة. ومع ذلك ، قد نواجه مشكلتين:
- كما ذكرنا سابقًا ، قد نستخدم NER في مجال معين ، وقد لا تلتقط النماذج المدربة مسبقًا الطبيعة الخاصة لمجالنا.
- في بعض الأحيان ، قد نرغب في إضافة فئات جديدة إلى نظام NER دون الحاجة إلى جمع مجموعة بيانات كبيرة لجميع الفئات الشائعة.
ماذا نفعل في مثل هذه الحالات؟
أنظمة NER باستخدام التعلم النشط
أفضل نهج لـ NER عندما نريد حلولًا مخصصة ولكن لا نريد تدريب كل شيء من البداية هو البدء بمنتج جاهز وإما زيادته باستخدام أساليب الاستدلال المخصصة لمجال مشكلتنا (باستخدام أدوات مثل كـ RegexNER أو EntityRuler) و / أو استخدم التعلم النشط باستخدام أدوات مثل Prodigy
يتيح لنا ذلك تحسين نموذج NER الحالي المدرب مسبقًا عن طريق وضع علامات يدويًا على بعض الأمثلة على الجمل التي تحتوي على فئات NER جديدة أو تصحيح بعض تنبؤات النموذج يدويًا واستخدامها لإعادة تدريب النموذج.
بشكل عام ، في معظم الحالات ، لا يتعين علينا دائمًا التفكير في تطوير نظام NER من البداية. إذا كان علينا تطوير نظام NER من البداية ، فإن أول شيء نحتاجه ، كما رأينا في هذا المقال، هو مجموعة كبيرة من البيانات المشروحة للجمل حيث يتم تمييز كل كلمة / عملة بفئتها (نوع الكيان أو غيره ).
بمجرد توفر مجموعة البيانات هذه ، فإن الخطوة التالية هي استخدامها للحصول على تمثيلات سمة يدوية و / أو عصبية وإطعامها إلى نموذج تسمية التسلسل. يتعامل الفصلان الثامن والتاسع في كتاب Speech and Language Processing: An Introduction to Natural Language Processing, Computational Linguistics and Speech Recognition مع طرق محددة للتعلم من مثل هذه التسلسلات. في حالة عدم وجود مثل هذه البيانات ، فإن NER القائم على القواعد هو الخطوة الأولى.
نصائح عملية
حتى الآن ، ألقينا نظرة سريعة على كيفية استخدام أنظمة NER الحالية ، وناقشنا بعض طرق زيادتها ، وناقشنا كيفية تدريب NER الخاص بنا من البداية. على الرغم من حقيقة أن NER الحديث دقيق للغاية (حيث تزيد درجات F1 عن 90٪ باستخدام طرق التقييم القياسية لـ NER في أبحاث معالجة اللغة الطبيعية) ، هناك العديد من المشكلات التي يجب وضعها في الاعتبار عند استخدام NER في تطبيقات البرامج الخاصة بنا. في ما يلي بعض الأمور التي يجب وضعها في عين الإعتبار:
- NER حساس للغاية لتنسيق المدخلات الخاصة به. إنه أكثر دقة مع النص العادي المنسق بشكل جيد مقارنة بمستند PDF ، على سبيل المثال ، والذي يلزم استخراج النص العادي منه أولاً. في حين أنه من الممكن إنشاء أنظمة NER مخصصة لنطاقات محددة أو لبيانات مثل التغريدات ، فإن التحدي مع ملفات PDF يأتي من الفشل في أن تكون دقيقة بنسبة 100٪ في استخراج النص منها مع الحفاظ على البنية. [هذا المقال] يوضح بعض التحديات مع استخراج PDF إلى نص. لماذا نحتاج إلى أن نكون دقيقين للغاية في استخراج البنية بشكل صحيح من ملفات PDF؟ في ملفات PDF ، الجمل الجزئية والعناوين والتنسيق أمور شائعة ، ويمكن أن تؤدي جميعها إلى إفساد دقة NER. لا يوجد حل واحد لهذا. تتمثل إحدى الطرق في إجراء معالجة مسبقة مخصصة لملفات PDF لاستخراج مجموعات نصية ، ثم تشغيل NER على المجموعة.
- كما أن NER حساس جدًا لدقة الخطوات السابقة في خط إنتاج المعالجة الخاص به: تقسيم الجملة ، و التعميل، وعلامات POS لذلك ، قد يكون من الضروري إجراء المعالجة المسبقة قبل تمرير جزء من النص إلى نموذج NER لاستخراج الكيانات.
ختاماً
على الرغم من أوجه القصور هذه ، يعد NER مفيد للغاية للعديد من سيناريوهات إستخراج المعلومات ، مثل وضع علامات على المحتوى والبحث وتعدين الوسائط الاجتماعية لتحديد ملاحظات العملاء حول منتجات معينة ، بينما يخدم NER (و KPE) المهمة المفيدة المتمثلة في تحديد الكلمات والعبارات والكيانات المهمة في المستندات ، تتطلب بعض تطبيقات معالجة اللغة الطبيعية مزيدًا من التحليل للغة ، مما يقودنا إلى المزيد من مهام معالجة اللغة الطبيعية المتقدمة. تتمثل إحدى مهام إستخراج المعلومات في توضيح الكيان أو ربط الكيان ، وهو موضوع القسم التالي.
إضافة تعليق