در نوشتار حاضر با انواع بردار کلمات، نحوهی ایجاد آنها در پایتون و بکارگیریشان به همراه شبکههای عصبی در keras آشنا خواهید شد. روشهای پردازش زبان طبیعی برای مدتی طولانی از مدل vectorspace برای نمایش کلمات استفاده میکردند. بردارهای رمزگذاریشدهی وان-هات به طور متداول به کار برده میشوند. البته روش «سبد واژگان» نیز عملکرد بسیار موفقی در بسیاری از امور داشته است. به تازگی، روشهای جدیدی برای نمایش کلمات در vectorspace پیشنهاد شده و شاهد ارتقای عملکرد چشمگیری در بسیاری از امور پردازش زبان طبیعی بودهایم. جزئیات این روشها به همراه چگونگی ساخت بردارها در پایتون در نوشتار حاضر توضیح داده خواهد شد.
سبد واژگان پیوسته
مفهوم اصلی این ابزار در فرضیه توزیع نهفته است که بیان میدارد: «معنای کلمه از واژههای کناریِ آن قابل استنباط است.» از این رو، سبد واژگان پیوسته قصد دارد از بافت کلمات برای پیشبینی احتمال بکارگیری کلمات استفاده کند.
تصویر فوق، مدل CBOW سادهای با یک کلمه نشان میدهد. پس، ورودی عبارت است از واژه بافت رمزگذاری شده وانهات و اندازه پنهان NN و اندازه کلمه VV. پس از اینکه این شبکه کوچک آموزش دید، ستونهای W^\primeW′ میتواند بردار NN بُعدی را برای همه کلمات نشان دهد. بردار کلمات به این طریق به کار برده میشود.
هدف ساختن بردار برای یک کلمه هم همین است که نشان دهیم یک کلمه از نظر معنایی چقدر و از چه ابعادی به دیگر کلمات شبیه است. با همین ایدهی ساده میتوانید بردارهای کلمهی خودتان را بسازید. به عنوان مثال میتوانید برای هر کلمهی منحصربهفرد متن را جستجو کنید و مثلا دو کلمه قبل و دو کلمه بعدش را ثبت کرده و نهایتا برای کلماتتان یک جدول همرخداد بسازید.
بارگذاری دیتاست
دیتاست ما از دوره مسابقات «Toxic Comment Classification Challenge» به دست آمده است. در این مسابقات افراد موظفاند مدل چندجانبهای بسازند که قادر به شناسایی انواع مختلفی از مسائل مثل تهدیدها، توهینها، نفرت هویتی و محتوای نامناسب باشد. در ابتدا به پیشپردازشِ کامنتها و آموزش بردار کلمات میپردازیم. سپس، لایه تعبیهگذاری keras را با بردار کلماتِ از پیش آموزشیافته به اجرا در آورده و عملکرد آن را با فرایند تعبیه تصادفی مقایسه میکنیم. شبکه حافظه بلند کوتاهمدت نیز در این راستا به کار برده میشود.
۱ ۲ ۳ ۴ ۵ | import pandas as pd import numpy as np import matplotlib.pyplot as plt plt.style.use("ggplot") |
۱ ۲ ۳ ۴ | path = 'data/' TRAIN_DATA_FILE = path + 'train.csv' TEST_DATA_FILE = path + 'test.csv' |
۱ ۲ | train_df = pd.read_csv(TRAIN_DATA_FILE) test_df = pd.read_csv(TEST_DATA_FILE) |
۱ | train_df.head(۱۰) |
پیشپردازش متن
حال، نوبت به پردازش کامنتها رسیده است. برای انجام این کار، “URL”را جایگزین URLها و “IPADDRESS” را جایگزین آدرسهای IP میکنیم. سپس، متن را توکن بندی کرده و با حروف کوچک مینویسیم.
۱ ۲ ۳ ۴ ۵ ۶ ۷ ۸ ۹ ۱۰ ۱۱ ۱۲ ۱۳ ۱۴ ۱۵ ۱۶ ۱۷ ۱۸ ۱۹ ۲۰ ۲۱ ۲۲ ۲۳ ۲۴ ۲۵ ۲۶ ۲۷ ۲۸ ۲۹ ۳۰ ۳۱ ۳۲ ۳۳ ۳۴ ۳۵ ۳۶ ۳۷ ۳۸ ۳۹ ۴۰ ۴۱ ۴۲ ۴۳ ۴۴ ۴۵ ۴۶ ۴۷ ۴۸ ۴۹ ۵۰ ۵۱ ۵۲ | ######################################## ## process texts in datasets ######################################## print('Processing text dataset') from nltk.tokenize import WordPunctTokenizer from collections import Counter from string import punctuation, ascii_lowercase import regex as re from tqdm import tqdm # replace urls re_url = re.compile(r"((http|https)\:\/\/)?[a-zA-Z0-9\.\/\?\:@\-_=#]+\ .([a-zA-Z]){2,6}([a-zA-Z0-9\.\&\/\?\:@\-_=#])*", re.MULTILINE|re.UNICODE) # replace ips re_ip = re.compile("\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}") # setup tokenizer tokenizer = WordPunctTokenizer() vocab = Counter() def text_to_wordlist(text, lower=False): # replace URLs text = re_url.sub("URL", text) # replace IPs text = re_ip.sub("IPADDRESS", text) # Tokenize text = tokenizer.tokenize(text) # optional: lower case if lower: text = [t.lower() for t in text] # Return a list of words vocab.update(text) return text def process_comments(list_sentences, lower=False): comments = [] for text in tqdm(list_sentences): txt = text_to_wordlist(text, lower=lower) comments.append(txt) return comments list_sentences_train = list(train_df["comment_text"].fillna("NAN_WORD").values) list_sentences_test = list(test_df["comment_text"].fillna("NAN_WORD").values) comments = process_comments(list_sentences_train + list_sentences_test, lower=True) |
۱ ۲ ۳ ۴ | پردازش دیتاست متن ۱۰۰%|██████████| ۳۱۲۷۳۵/۳۱۲۷۳۵ [۰۰:۱۷<۰۰:۰۰, ۱۸۰۳۰.۲۸it/s] |
۱ | print("The vocabulary contains {} unique tokens".format(len(vocab))) |
مدلسازیِ بردار کلمات با Gensim
اکنون، آمادهی آموزش بردار کلمات هستیم. کتابخانهی genism در پایتون مورد استفاده قرار میگیرد که از دستههای مختلفی برای امور پردازش زبان طبیعی پشتیبانی میکند.
۱ | from gensim.models import Word2Vec |
۱ | model = Word2Vec(comments, size=۱۰۰, window=۵, min_count=۵, workers=۱۶, sg=۰, negative=۵) |
۱ | word_vectors = model.wv |
۱ | print("Number of word vectors: {}".format(len(word_vectors.vocab))) |
اجازه دهید بینیم آیا بردار کلماتی که به لحاظ معنایی منطقی باشند، آموزش دادهایم؟
۱ | model.wv.most_similar_cosmul(positive=['woman', 'king'], negative=['man']) |
۱ ۲ ۳ ۴ ۵ ۶ ۷ ۸ ۹ ۱۰ | [('prince', ۰.۹۸۴۹۳۳۴۳۵۹۱۶۹۰۰۶), ('queen', ۰.۹۶۸۴۱۰۷۸۹۹۶۶۵۸۳۳), ('princess', ۰.۹۵۱۸۵۸۲۸۲۰۸۹۲۳۳۴), ('bishop', ۰.۹۳۸۰۳۱۳۱۵۸۰۳۵۲۷۸), ('duke', ۰.۹۳۶۸۳۹۱۶۳۳۰۳۳۷۵۲), ('duchess', ۰.۹۳۵۳۰۹۰۵۲۴۶۷۳۴۶۲), ('victoria', ۰.۹۲۰۸۰۹۸۰۵۳۹۳۲۱۹), ('mary', ۰.۹۱۸۰۵۵۲۳۶۳۳۹۵۶۹۱), ('mayor', ۰.۹۱۲۷۰۴۳۴۸۵۶۴۱۴۸), ('prussia', ۰.۹۱۰۰۲۹۷۰۹۳۳۹۱۴۱۸)] |
خب، شرایط تا بدین جای کار خوب به نظر میرسد. اکنون، میتوان سراغ مدل طبقهبندی متن بعدی رفت.
تعبیهگذاری در keras
در ابتدا، باید کامنتهای توکن بندی شده را با طول معینی برش یا pad بدهیم.
۱ ۲ | MAX_NB_WORDS = len(word_vectors.vocab) MAX_SEQUENCE_LENGTH = ۲۰۰ |
۱ ۲ ۳ ۴ ۵ ۶ ۷ ۸ ۹ ۱۰ ۱۱ ۱۲ ۱۳ ۱۴ ۱۵ ۱۶ ۱۷ ۱۸ ۱۹ | from keras.preprocessing.sequence import pad_sequences word_index = {t[۰]: i+۱ for i,t in enumerate(vocab.most_common(MAX_NB_WORDS))} sequences = [[word_index.get(t, ۰) for t in comment] for comment in comments[:len(list_sentences_train)]] test_sequences = [[word_index.get(t, ۰) for t in comment] for comment in comments[len(list_sentences_train):]] # pad data = pad_sequences(sequences, maxlen=MAX_SEQUENCE_LENGTH, padding="pre", truncating="post") list_classes = ["toxic", "severe_toxic", "obscene", "threat", "insult", "identity_hate"] y = train_df[list_classes].values print('Shape of data tensor:', data.shape) print('Shape of label tensor:', y.shape) test_data = pad_sequences(test_sequences, maxlen=MAX_SEQUENCE_LENGTH, padding="pre", truncating="post") print('Shape of test_data tensor:', test_data.shape) |
۱ ۲ ۳ ۴ ۵ ۶ | Using TensorFlow backend. Shape of data tensor: (۱۵۹۵۷۱, ۲۰۰) Shape of label tensor: (۱۵۹۵۷۱, ۶) Shape of test_data tensor: (۱۵۳۱۶۴, ۲۰۰) |
حالا، سرانجام ماتریس تعبیه را ایجاد میکنیم. این ماتریس در اختیار لایه تعبیه keras قرار خواهد گرفت. میتوانید از همان کد برای اجرای فرایند تعبیه با Glove یا سایر بردار کلماتِ از پیش آموزش دیده استفاده کنید.
۱ ۲ ۳ ۴ ۵ ۶ ۷ ۸ ۹ ۱۰ ۱۱ ۱۲ ۱۳ | WV_DIM = ۱۰۰ nb_words = min(MAX_NB_WORDS, len(word_vectors.vocab)) # we initialize the matrix with random numbers wv_matrix = (np.random.rand(nb_words, WV_DIM) - ۰.۵) / ۵.۰ for word, i in word_index.items(): if i >= MAX_NB_WORDS: continue try: embedding_vector = word_vectors[word] # words not found in embedding index will be all-zeros. wv_matrix[i] = embedding_vector except: pass |
تنظیم رده بندی برای دادگان کامنت
۱ ۲ ۳ ۴ | from keras.layers import Dense, Input, CuDNNLSTM, Embedding, Dropout,SpatialDropout1D, Bidirectional from keras.models import Model from keras.optimizers import Adam from keras.layers.normalization import BatchNormalization |
باید از شبکه حافظه بلند کوتاهمدت با دراپاوت و نرمالسازی دستهای استفاده کرد. توجه داشته باشید که keras با روش CUDA به اجرا در میآید که سریعتر از حالت معمول در GPU اجرا میشود.
۱ ۲ ۳ ۴ ۵ ۶ ۷ ۸ ۹ ۱۰ ۱۱ ۱۲ ۱۳ ۱۴ ۱۵ ۱۶ ۱۷ ۱۸ ۱۹ ۲۰ ۲۱ ۲۲ ۲۳ ۲۴ ۲۵ | wv_layer = Embedding(nb_words, WV_DIM, mask_zero=False, weights=[wv_matrix], input_length=MAX_SEQUENCE_LENGTH, trainable=False) # Inputs comment_input = Input(shape=(MAX_SEQUENCE_LENGTH,), dtype='int32') embedded_sequences = wv_layer(comment_input) # biGRU embedded_sequences = SpatialDropout1D(۰.۲)(embedded_sequences) x = Bidirectional(CuDNNLSTM(۶۴, return_sequences=False))(embedded_sequences) # Output x = Dropout(۰.۲)(x) x = BatchNormalization()(x) preds = Dense(۶, activation='sigmoid')(x) # build the model model = Model(inputs=[comment_input], outputs=preds) model.compile(loss='binary_crossentropy', optimizer=Adam(lr=۰.۰۰۱, clipnorm=.۲۵, beta_1=۰.۷, beta_2=۰.۹۹), metrics=[]) |
۱ ۲ | hist = model.fit([data], y, validation_split=۰.۱, epochs=۱۰, batch_size=۲۵۶, shuffle=True) |
۱ ۲ ۳ ۴ ۵ ۶ ۷ ۸ ۹ ۱۰ ۱۱ ۱۲ ۱۳ ۱۴ ۱۵ ۱۶ ۱۷ ۱۸ ۱۹ ۲۰ ۲۱ | Train on ۱۴۳۶۱۳ samples, validate on ۱۵۹۵۸ samples Epoch ۱/۱۰ ۱۴۳۶۱۳/۱۴۳۶۱۳ [==============================] - ۱۵s ۱۰۳us/step - loss: ۰.۱۶۸۱ - val_loss: ۰.۰۵۵۱ Epoch ۲/۱۰ ۱۴۳۶۱۳/۱۴۳۶۱۳ [==============================] - ۱۴s ۹۷us/step - loss: ۰.۰۵۷۴ - val_loss: ۰.۰۵۰۲ Epoch ۳/۱۰ ۱۴۳۶۱۳/۱۴۳۶۱۳ [==============================] - ۱۴s ۹۷us/step - loss: ۰.۰۵۲۲ - val_loss: ۰.۰۴۸۶ Epoch ۴/۱۰ ۱۴۳۶۱۳/۱۴۳۶۱۳ [==============================] - ۱۴s ۹۶us/step - loss: ۰.۰۵۰۱ - val_loss: ۰.۰۴۶۶ Epoch ۵/۱۰ ۱۴۳۶۱۳/۱۴۳۶۱۳ [==============================] - ۱۴s ۹۷us/step - loss: ۰.۰۴۷۲ - val_loss: ۰.۰۴۶۴ Epoch ۶/۱۰ ۱۴۳۶۱۳/۱۴۳۶۱۳ [==============================] - ۱۴s ۹۷us/step - loss: ۰.۰۴۶۰ - val_loss: ۰.۰۴۴۸ Epoch ۷/۱۰ ۱۴۳۶۱۳/۱۴۳۶۱۳ [==============================] - ۱۴s ۹۷us/step - loss: ۰.۰۴۴۸ - val_loss: ۰.۰۴۴۸ Epoch ۸/۱۰ ۱۴۳۶۱۳/۱۴۳۶۱۳ [==============================] - ۱۴s ۹۷us/step - loss: ۰.۰۴۳۸ - val_loss: ۰.۰۴۵۳ Epoch ۹/۱۰ ۱۴۳۶۱۳/۱۴۳۶۱۳ [==============================] - ۱۴s ۹۸us/step - loss: ۰.۰۴۳۱ - val_loss: ۰.۰۴۴۶ Epoch ۱۰/۱۰ ۱۴۳۶۱۳/۱۴۳۶۱۳ [==============================] - ۱۴s ۹۷us/step - loss: ۰.۰۴۲۵ - val_loss: ۰.۰۴۵۴ |
حال نوبت به مقایسه زیان آموزش و زیان اعتبارسنجی رسیده است.
۱ ۲ ۳ ۴ ۵ ۶ | history = pd.DataFrame(hist.history) plt.figure(figsize=(۱۲,۱۲)); plt.plot(history["loss"]); plt.plot(history["val_loss"]); plt.title("Loss with pretrained word vectors"); plt.show(); |
به نظر میرسد زیان رو به کاهش است، اما کماکان میتوان به عملکردی بهتر از این دست یافت. میتوانید به طور دلخواه از روشهای تعبیه، دراپ اوت و معماری شبکه استفاده کنید. اکنون، قصد داریم بردار کلماتِ از پیش آموزش دیده را با تعبیههای تصادفی مقایسه کنیم.
۱ ۲ ۳ ۴ ۵ ۶ ۷ ۸ ۹ ۱۰ ۱۱ ۱۲ ۱۳ ۱۴ ۱۵ ۱۶ ۱۷ ۱۸ ۱۹ ۲۰ ۲۱ ۲۲ ۲۳ ۲۴ ۲۵ | wv_layer = Embedding(nb_words, WV_DIM, mask_zero=False, # weights=[wv_matrix], input_length=MAX_SEQUENCE_LENGTH, trainable=False) # Inputs comment_input = Input(shape=(MAX_SEQUENCE_LENGTH,), dtype='int32') embedded_sequences = wv_layer(comment_input) # biGRU embedded_sequences = SpatialDropout1D(۰.۲)(embedded_sequences) x = Bidirectional(CuDNNLSTM(۶۴, return_sequences=False))(embedded_sequences) # Output x = Dropout(۰.۲)(x) x = BatchNormalization()(x) preds = Dense(۶, activation='sigmoid')(x) # build the model model = Model(inputs=[comment_input], outputs=preds) model.compile(loss='binary_crossentropy', optimizer=Adam(lr=۰.۰۰۱, clipnorm=.۲۵, beta_1=۰.۷, beta_2=۰.۹۹), metrics=[]) |
۱ ۲ | hist = model.fit([data], y, validation_split=۰.۱, epochs=۱۰, batch_size=۲۵۶, shuffle=True) |
۱ ۲ ۳ ۴ ۵ ۶ ۷ ۸ ۹ ۱۰ ۱۱ ۱۲ ۱۳ ۱۴ ۱۵ ۱۶ ۱۷ ۱۸ ۱۹ ۲۰ ۲۱ | Train on ۱۴۳۶۱۳ samples, validate on ۱۵۹۵۸ samples Epoch ۱/۱۰ ۱۴۳۶۱۳/۱۴۳۶۱۳ [==============================] - ۱۴s ۹۹us/step - loss: ۰.۱۸۰۰ - val_loss: ۰.۱۰۸۵ Epoch ۲/۱۰ ۱۴۳۶۱۳/۱۴۳۶۱۳ [==============================] - ۱۴s ۹۷us/step - loss: ۰.۱۱۱۷ - val_loss: ۰.۱۰۶۳ Epoch ۳/۱۰ ۱۴۳۶۱۳/۱۴۳۶۱۳ [==============================] - ۱۴s ۹۸us/step - loss: ۰.۱۰۶۳ - val_loss: ۰.۰۹۹۰ Epoch ۴/۱۰ ۱۴۳۶۱۳/۱۴۳۶۱۳ [==============================] - ۱۴s ۹۷us/step - loss: ۰.۰۹۹۳ - val_loss: ۰.۱۱۳۱ Epoch ۵/۱۰ ۱۴۳۶۱۳/۱۴۳۶۱۳ [==============================] - ۱۴s ۹۸us/step - loss: ۰.۰۹۵۱ - val_loss: ۰.۰۹۹۹ Epoch ۶/۱۰ ۱۴۳۶۱۳/۱۴۳۶۱۳ [==============================] - ۱۴s ۹۷us/step - loss: ۰.۰۹۲۲ - val_loss: ۰.۰۹۰۷ Epoch ۷/۱۰ ۱۴۳۶۱۳/۱۴۳۶۱۳ [==============================] - ۱۴s ۹۸us/step - loss: ۰.۰۹۰۲ - val_loss: ۰.۰۸۶۷ Epoch ۸/۱۰ ۱۴۳۶۱۳/۱۴۳۶۱۳ [==============================] - ۱۴s ۹۷us/step - loss: ۰.۰۸۶۸ - val_loss: ۰.۰۸۵۰ Epoch ۹/۱۰ ۱۴۳۶۱۳/۱۴۳۶۱۳ [==============================] - ۱۴s ۹۷us/step - loss: ۰.۰۸۳۸ - val_loss: ۰.۱۰۶۰ Epoch ۱۰/۱۰ ۱۴۳۶۱۳/۱۴۳۶۱۳ [==============================] - ۱۴s ۹۷us/step - loss: ۰.۰۸۲۱ - val_loss: ۰.۰۸۶۰ |
۱ ۲ ۳ ۴ ۵ ۶ | history = pd.DataFrame(hist.history) plt.figure(figsize=(۱۲,۱۲)); plt.plot(history["loss"]); plt.plot(history["val_loss"]); plt.title("Loss with random word vectors"); plt.show(); |
آنطور که ملاحظه میکنید، زیان به کُندی در حال کاهش است و زیان اعتبارسنجی پایداری کافی را ندارد.
نظرات