تصنيف النص : استخدام التضمينات العصبية

استخدام التضمينات العصبية في تصنيف النص

في  المقال السابق ، ناقشنا تقنيات هندسة السمات باستخدام الشبكات العصبية ، مثل تضمينات الكلمات ، و تضمينات الحروف ، وتضمينات  المستندات. تتمثل ميزة استخدام السمات القائمة على التضمين في أنها تنشئ تمثيلًا كثيفًا ومنخفض الأبعاد للسمات بدلاً من البنية المتناثرة عالية الأبعاد لـ BoW / TF-IDF وغيرها من السمات. هناك طرق مختلفة لتصميم واستخدام السمات بناءً على التضمينات العصبية. في هذا المقال، دعنا نلقي نظرة على بعض طرق استخدام تمثيلات التضمين هذه لتصنيف النص.

تضمينات الكلمات (Word Embeddings) 

تم استخدام الكلمات و n-grams بشكل أساسي كسمات  في تصنيف النص لفترة طويلة. تم اقتراح طرق مختلفة لإنشاء متجهات الكلمات . في السنوات القليلة الماضية ، أصبحت المعماريات المبنية على الشبكة العصبية شائعة “لتعلم” تمثيلات الكلمات ، والتي تُعرف باسم “تضمين الكلمة”.

 دعنا الآن نلقي نظرة على كيفية استخدام تضمين الكلمات كسمات لتصنيف النص. سنستخدم مجموعة بيانات الجمل المصنفة حسب العاطفة من UCI ، والتي تتكون من 1500 جملة ذات مشاعر إيجابي و 1500 أخرى سلبية من Amazon و Yelp و IMDB. 

يظل تحميل البيانات النصية ومعالجتها مسبقًا خطوة شائعة. ومع ذلك ، بدلاً من إنشاء متجهات النصوص باستخدام السمات المعتمدة على  حقيبة الكلمات BoW ، سنعتمد الآن على نماذج التضمين العصبي. كما ذكرنا سابقًا ، سنستخدم نموذج تضمين مُدرَّب مسبقًا. Word2vec هي خوارزمية شائعة ناقشناها في هذا المقال  لتدريب نماذج تضمين الكلمات. هناك العديد من نماذج Word2vec المدربة مسبقًا والمدربة على مجموعات كبيرة متاحة على الإنترنت. هنا ، سنستخدم واحدًا من Google. يوضح مقتطف الكود التالي كيفية تحميل هذا النموذج في Python باستخدام gensim:

import glob
import os 
from time import time 

from nltk.tokenize import word_tokenize
from nltk.corpus import stopwords
from string import punctuation

import numpy as np 
from gensim.models import Word2Vec, KeyedVectors
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report

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

read_files = glob.glob("Data/sentiment labelled sentences/*.txt")

with open("sentiment_sentences.txt", "wb") as outfile:
    for f in read_files:
        with open(f, "rb") as infile:
            outfile.write(infile.read())

# word2vec تحميل البيانات و نموذج 
model = 'src/GoogleNews-vectors-negative300.bin.gz'
data = 'sentiment_sentences.txt'

%time w2v_model = KeyedVectors.load_word2vec_format(model, binary=True)
print('done loading Word2Vec')

# قراءة البيانات و التصنيفات

texts = []
cats = []

fh = open(data)
for line in fh:
    text, sentiment = line.split("\t")
    texts.append(text)
    cats.append(sentiment)

# معاينة النموذج 
word2vec_vocab = w2v_model.vocab.keys()
word2vec_vocab_lower = [item.lower() for item in word2vec_vocab]
print(len(word2vec_vocab))

# معايبة مجموعة البيانات
print(len(cats), len(texts))
print(texts[1])
print(cats[1])

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

# إنشاء متجه خاص باحتساب متوسط جميع التضمينات لجميع الجمل

def embedding_features(list_of_lists):
    dim = 300
    zero_vector = np.zeros(dim)
    features = []
    for tokens in list_of_lists:
        features_for_this = np.zeros(dim)
        count_for_this = 1
        for token in tokens:
            if token in w2v_model:
                
                features_for_this += w2v_model[token]
                count_for_this += 1
        features.append(features_for_this/count_for_this)
    return features

train_vectors = embedding_features(texts_processed)
print(len(train_vectors))

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

# تدريب النموذج 
classifier = LogisticRegression(random_state=1)
train_data, test_data, train_cats, test_cats = train_test_split(train_vectors, cats)
classifier.fit(train_data, train_cats)
print("Accuracy: ", classifier.score(test_data, test_cats))
preds = classifier.predict(test_data)
print(classification_report(test_cats, preds))

وهنا النتيجة

Accuracy:  0.832
              precision    recall  f1-score   support

          0
       0.83      0.84      0.84       384
          1
       0.83      0.83      0.83       366

    accuracy                           0.83       750
   macro avg       0.83      0.83      0.83       750
weighted avg       0.83      0.83      0.83       750

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

الكلمات الفرعية للتضمينات و fastText

إن تضمين الكلمة ، كما يشير الاسم ، يتعلق بتمثيل الكلمات. حتى التضمينات الجاهزة يبدو أنها تعمل جيدًا في مهام التصنيف ، كما رأينا سابقًا. ومع ذلك ، إذا كانت إحدى الكلمات في مجموعة البيانات الخاصة بنا غير موجودة في مفردات النموذج المدرب مسبقًا ، فكيف سنحصل على تمثيل لهذه الكلمة؟ تُعرف هذه المشكلة عمومًا بأنها خارج المفردات (out of vocabulary)  أو (OOV). في مثالنا السابق ، تجاهلنا مثل هذه الكلمات من استخراج السمات. هل هناك طريقة أفضل؟

تضمينات fastText تستند إلى فكرة إثراء تضمينات الكلمات بمعلومات على مستوى الكلمات الفرعية. وبالتالي ، تمثيل التضمين لكل كلمة يتم تمثيله كمجموع لتمثيلات الأحرف الفردية n-grams. في حين أن هذا قد يبدو وكأنه عملية أطول مقارنةً بتقدير التضمينات على مستوى الكلمات فقط ، إلا أن له ميزتين:

  • يمكن لهذا الأسلوب التعامل مع الكلمات التي لم تظهر في بيانات التدريب (OOV).
  • التعلم سريع للغاية حتى في مجموعة البيانات الضخمة.

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

سنعمل مع مجموعة بيانات DBpedia. إنها مجموعة بيانات متوازنة تتكون من 14 تصنيف، مع 40.000 مثال  تدريب و 5000 مثال اختبار لكل صنف. وبالتالي ، فإن الحجم الإجمالي لمجموعة البيانات هو 560.000 تدريب و 70.000 اختبار. من الواضح أن هذه مجموعة بيانات أكبر بكثير مما رأيناه من قبل. هل يمكننا بناء نموذج تدريب سريع باستخدام fastText؟ 

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

import pandas as pd
#تحميل البيانات
df_train =pd.read_csv('Data/dbpedia_csv/train.csv' , header=None, names=['class', 'name', 'description'])
df_test =pd.read_csv('Data/dbpedia_csv/test.csv' , header=None, names=['class', 'name', 'description'])

# طباعة البيانات
print("Train:{} Test:{}".format(df_train.shape,df_test.shape))

# إنشاء قاموس
class_dict={
            1:'Company',
            2:'EducationalInstitution',
            3:'Artist',
            4:'Athlete',
            5:'OfficeHolder',
            6:'MeanOfTransportation',
            7:'Building',
            8:'NaturalPlace',
            9:'Village',
            10:'Animal',
            11:'Plant',
            12:'Album',
            13:'Film',
            14:'WrittenWork'
        }

# ربط التصنيفات
df_train['class_name'] = df_train['class'].map(class_dict)

######## تنظيف النص #######
def clean_text(text, normalize=True):
    
    # سنقوم بعمل تنظيف بعض المشاكل اللتي قد تواجهنا في النص
    s = str(text).replace(',',' ').replace('"','').replace('\'',' \' ').replace('.',' . ').replace('(',' ( ').\
            replace(')',' ) ').replace('!',' ! ').replace('?',' ? ').replace(':',' ').replace(';',' ').lower()
    
    # تسوية  النص
    if normalize:
        s = s.normalize('NFKD').str.encode('ascii', 'ignore').str.decode('utf-8')
    return s

# إنشاء وظيفة بحيث نستخدم وظيفة التنشيط العلوية على البيانات
def clean_df(data, clean_texts=False, shuffle_texts=False, encode_texts=False,label_prefix='__class__'):
    # تعريف البيانات الجديدية
    df = data[['name', 'description']].copy(deep=True)
    df['class'] = label_prefix + data['class'].astype(str) + ' '
    
    # تنظيف البيانات
    if clean_texts:
        df["name"] = df['name'].apply(lambda x: clean_text(x, encode_texts))
        df["description"] = df['description'].apply(lambda x: clean_text(x, encode_texts))
    
    # تغيير ترتيب البيانات
    if shuffle_texts:
        df.sample(frac=1).reset_index(drop=True)
    return df

# تحويل البيانات بإستحدام الوظيفة العلوية 
df_train_cleaned = clean_df(df_train, True, True)
df_test_cleaned = clean_df(df_test, True, True)

# حفظ الملفات 
train_file = 'Data/dbpedia_csv/dbpedia_train.csv'
df_train_cleaned.to_csv(train_file, header=None, index=False, columns=['class', 'name', 'description'])

test_file = 'Data/dbpedia_csv/dbpedia_test.csv'
df_test_cleaned.to_csv(test_file, header=None, index=False, columns=['class', 'name', 'description'])

# fasttext الأن بعد أن حفظنا الملفات بصيغة بمقدور 
# التعامل معها ، حان الوقت لإستخدامه
from fasttext import train_supervised 

model = train_supervised(input=train_file, label="__class__", lr=1.0, epoch=75, loss='ova', wordNgrams=2, dim=200, thread=2, verbose=100)

for k in range(1,6):
    results = model.test(test_file, k=k)
    print(f"test Sample:{results[0]} Precision@{k}: {results[1]*100:2.4f} Recall@{k} : {results[2]*100:2.4f}")



إذا قمنا بتشغيل هذا الكود، فسنلاحظ أننا نقترب من 98٪ دقة واستدعاء.

for k in range(1,6):
    results = model.test(test_file, k=k)
    print(f"test Sample:{results[0]} Precision@{k}: {results[1]*100:2.4f} Recall@{k} : {results[2]*100:2.4f}")

عندما يكون لدينا مجموعة بيانات كبيرة ، وعندما يبدو التعلم غير ممكن باستخدام الأساليب الموضحة حتى الآن ، فإن FastText يعد خيارًا جيدًا لاستخدامه لإعداد أساس عمل قوي. ومع ذلك ، هناك أمرواحد يجب مراعاته عند استخدام fastText ، كما كان الحال مع عمليات التضمين في Word2vec: فهو يستخدم تضمينات مدربة مسبقًا بعدد n-gram. وهكذا ، عندما نحفظ النموذج المدرَّب ، فإنه يُحمل معه قاموس التضمينات بالحرف n-gram بالكامل. ينتج عن هذا نموذج ضخم ويمكن أن يؤدي إلى مشكلات هندسية. ومع ذلك ، فإن تطبيق fastText يأتي أيضًا مع خيارات لتقليل أثر الذاكرة لنماذج التصنيف مع الحد الأدنى من تقليل أداء التصنيف. يقوم بذلك عن طريق تقليم المفردات واستخدام خوارزميات الضغط. قد يكون استكشاف هذه الاحتمالات خيارًا جيدًا في الحالات التي تكون فيها أحجام النماذج الكبيرة قيدًا.

تضمينات المستندات 

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

سنستخدم مجموعة بيانات تسمى “Sentiment Analysis: Emotion in Text” ، والتي تحتوي على 40000 تغريدة معنونة بـ 13 تصنيفًا تدل على مشاعر مختلفة. لنأخذ التصنيفات الثلاثة الأكثر شيوعًا في مجموعة البيانات هذه – الحيادية والقلق والسعادة – ونبني مُصنّفًا نصيًا لتصنيف التغريدات الجديدة في إحدى هذه الفئات الثلاث.

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

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

import pandas as pd
import nltk 
nltk.download('stopwords')
from nltk.tokenize import TweetTokenizer
from nltk.corpus import stopwords
from sklearn.model_selection import train_test_split
from gensim.models.doc2vec import Doc2Vec, TaggedDocument


# تحميل البيانات 
filepath = "Data/Sentiment and Emotion in Text/train_data.csv"
df = pd.read_csv(filepath)

# سنأخذ أعلى ثلاثة و نترك الباقي
shortlist = {"worry", "neutral", "sadness"}
df_subset = df[df["sentiment"].isin(shortlist)]

#strip_handles يقوم بحذف معرفات الحسابات
#preserve_case=False بالنسبة للغة الإنجليزية يقوم بتصغير الحروف
tweeter = TweetTokenizer(strip_handles=True, preserve_case=False)
mystopwords = set(stopwords.words("english"))

#دالة لتعميل التغريدة و إزالة كلمات التوقف و الأرقام 
# قد يكون الاحتفاظ بعلامات الترقيم ورموز التعبيرات مناسبًا لهذه المهمة
def preprocess_corpus(texts):
    def remove_stops_digits(tokens):
        # وظيفة متشعبة تزيل كلمات الإيقاف والأرقام من قائمة الرموز
        return [token for token in tokens if token not in mystopwords and not token.isdigit()]
    # هنا يتم معالجة مخرج مُعمل التغريدات أكثر 
    return [remove_stops_digits(tweeter.tokenize(content)) for content in texts]
#df_subset يحتوي على التلاث تصنيفات التي إخترناها
mydata = preprocess_corpus(df_subset['content'])
mycats = df_subset['sentiment']
print(len(mydata), len(mycats))

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

يتضمن الجزء الأول من هذه العملية تحويل البيانات إلى تنسيق يمكن قراءته بواسطة تطبيق Doc2vec ، والذي يمكن القيام به باستخدام فئة TaggedDocument. يتم استخدامه لتمثيل مستند كقائمة من العملات (tokens) ، متبوعة بعلامة (tag) ، والتي في أبسط أشكالها يمكن أن تكون مجرد اسم الملف أو معرف المستند.

 دعنا الآن نرى كيفية تدريب مصنف Doc2vec للتغريدات من خلال مقتطف الشفرة أدناه:

# قسّم البيانات إلى تدريب واختبار 
train_data, test_data, train_cats, test_cats = train_test_split(mydata, mycats, random_state=1234)

# doc2vec تجهيز البيانات 
train_doc2vec = [TaggedDocument((d), tags=[str(i)]) for i, d in enumerate(train_data)]

# لتعلم تمثيلات التغريدات بإستخدام بيانات التدريب فقط doc2vec تدريب نموذج
# https://radimrehurek.com/gensim/models/doc2vec.html
model = Doc2Vec(vector_size=50, alpha=0.025, min_count=5, dm=1, epochs=100)
model.build_vocab(train_doc2vec)
model.train(train_doc2vec, total_examples=model.corpus_count, epochs=model.epochs )
model.save("Model/d2v.model")

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

  •  يشير vector_size إلى أبعاد التضمينات المكتسبة ؛ 
  • alpha  هو معدل التعلم. 
  • min_count هو الحد الأدنى لتكرار الكلمات المتبقية في المفردات ؛ 
  • dm ، الذي يرمز إلى الذاكرة الموزعة ، هو أحد متعلمي التمثيل المطبق في Doc2vec (الآخر هو dbow ، أو حقيبة الكلمات الموزعة) ؛
  •  epochs هي عدد مرات التدريب. هناك بعض المعلمات الأخرى التي يمكن تخصيصها. على الرغم من وجود بعض الإرشادات حول اختيار المعلمات المثلى لتدريب نماذج Doc2vec ، إلا أنه لم يتم التحقق من صحتها بشكل شامل ، ولا نعرف ما إذا كانت الإرشادات تعمل مع التغريدات.

أفضل طريقة لمعالجة هذه المشكلة هي استكشاف مجموعة من القيم للقيم التي تهمنا (على سبيل المثال ، dm مقابل dbow ، وأحجام المتجهات ، ومعدل التعلم) ومقارنة النماذج المتعددة. كيف نقارن هذه النماذج لأنها تتعلم فقط تمثيل النص؟ طريقة واحدة للقيام بذلك هي البدء في استخدام هذه التمثيلات المكتسبة في مهمة – في هذه الحالة ، تصنيف النص. يمكن استخدام دالة infer_vector في Doc2vec لاستنتاج تمثيل المتجه لنص معين باستخدام نموذج مدرب مسبقًا. نظرًا لوجود قدر من العشوائية بسبب اختيار مدخلات الضبط (hyper parameters) ، تختلف المتجهات المستخلصة في كل مرة نقوم باستخراجها. لهذا السبب ، للحصول على تمثيل ثابت ، نقوم بتشغيله عدة مرات (تسمى الخطوات) ونجمع المتجهات. دعنا نستخدم النموذج الذي تم تعلمه لاستنتاج سمات لبياناتنا وتدريب مصنف الانحدار اللوجستي:

model = Doc2Vec.load("Model/d2v.model")
# إستدلال تمثيل السمات للتدريب واختبار البيانات باستخدام النموذج المدرّب

train_vectors = [model.infer_vector(list_of_tokens, steps=50) for list_of_tokens in train_data]
test_vectors = [model.infer_vector(list_of_tokens, steps=50) for list_of_tokens in train_data]

# استخدم أي مصنف عادي مثل الانحدار اللوجستي
from sklearn.linear_model import LogisticRegression

# بما أن التصنيفات غير متساوية 
# https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html
myclass = LogisticRegression(class_weight="balanced")
myclass.fit(train_vectors, train_cats)

pred = myclass.predict(test_vectors)

from sklearn.metrics import classification_report, confusion_matrix
print(classification_report(test_cats, preds))

والناتج

             precision    recall  f1-score   support

   happiness       0.35      0.53      0.42       713
     neutral       0.48      0.55      0.51      1595
       worry       0.61      0.40      0.48      1882

    accuracy                           0.48      4190
   macro avg       0.48      0.50      0.47      4190
weighted avg       0.51      0.48      0.48      4190

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

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

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

حتى الآن ، رأينا مجموعة من عروض السمات وكيف أنها تلعب دورًا في تصنيف النص باستخدام خوارزميات تعلم الألة. 

إضافة تعليق