فصل ۱۶: پردازش زبان طبیعی با RNN و Attention | تولید متن شکسپیری با Character RNN

فصل ۱۶: پردازش زبان طبیعی با RNN و Attention | تولید متن شکسپیری با Character RNN

توسط admin | گروه هوش مصنوعی | 1405/06/01

نظرات 0

فصل ۱۶: پردازش زبان طبیعی با RNN و Attention | تولید متن شکسپیری با Character RNN

کتاب
Hands-On Machine Learning with Scikit-Learn, Keras & TensorFlow
بخش منبع
Chapter 16: Natural Language Processing with RNNs and Attention; Generating Shakespearean Text Using a Character RNN; Stateful RNN; Sentiment Analysis; Masking
صفحات این PDF
41-55
صفحات چاپی کتاب
577-591
جایگاه در مجموعه
64 / Machine_Learning_405_06; parent: Machine_Learning_405_06_01.html

فصل ۱۶: پردازش زبان طبیعی با RNN و Attention

در سال ۱۹۵۰ آلن تورینگ برای ارزیابی توانایی ماشین در شبیه‌سازی هوش انسانی، یک Task زبانی را پیشنهاد کرد: گفت‌وگویی متنی که در آن ماشین تلاش می‌کند انسان را قانع کند که خودش انسان است. هرچند آزمون تورینگ همهٔ جنبه‌های هوش را پوشش نمی‌دهد، انتخاب زبان تصادفی نبود؛ تسلط بر زبان یکی از پیچیده‌ترین توانایی‌های شناختی انسان است.

هدف کلی NLP ساخت سیستم‌هایی است که زبان نوشتاری و گفتاری را پردازش کنند، اما در عمل این هدف به Taskهای مشخص‌تر مانند Text Classification، Translation، Summarization و Question Answering شکسته می‌شود. فصل ابتدا RNNها را برای تولید متن، Sentiment Analysis و Neural Machine Translation به‌کار می‌گیرد و سپس به Attention، Transformer، GPT، BERT و ابزارهای Hugging Face می‌رسد.

تولید متن شکسپیری با Character RNN

ایدهٔ Char-RNN این است که Model در هر Time Step Character بعدی را پیش‌بینی کند. با تکرار این Prediction می‌توان متن جدید تولید کرد. Model نیازی به Ruleهای صریح Grammar ندارد؛ فقط از Task پیش‌بینی Character بعدی، Patternهایی دربارهٔ کلمه، فاصله، Punctuation و Syntax یاد می‌گیرد.

ساخت Dataset

متن کامل آثار شکسپیر Download می‌شود و با TextVectorization در سطح Character Encode می‌گردد:

import tensorflow as tf

shakespeare_url = "https://homl.info/shakespeare"
filepath = tf.keras.utils.get_file("shakespeare.txt", shakespeare_url)
with open(filepath) as f:
    shakespeare_text = f.read()

text_vec_layer = tf.keras.layers.TextVectorization(
    split="character", standardize="lower")
text_vec_layer.adapt([shakespeare_text])
encoded = text_vec_layer([shakespeare_text])[0]
encoded -= 2
n_tokens = text_vec_layer.vocabulary_size() - 2
dataset_size = len(encoded)

Tokenهای 0 و 1 برای Padding و Unknown رزرو شده‌اند، بنابراین با کم‌کردن 2 حذف می‌شوند. در این Corpus تعداد Characterهای متمایز 39 و تعداد کل Characterها بیش از 1.1 میلیون است.

برای Train، Sequence بلند به Windowهای طول ثابت تبدیل می‌شود؛ Input یک Window است و Target همان Window است که یک Character به آینده Shift شده است:

def to_dataset(sequence, length, shuffle=False, seed=None, batch_size=32):
    ds = tf.data.Dataset.from_tensor_slices(sequence)
    ds = ds.window(length + 1, shift=1, drop_remainder=True)
    ds = ds.flat_map(lambda window_ds: window_ds.batch(length + 1))
    if shuffle:
        ds = ds.shuffle(buffer_size=100_000, seed=seed)
    ds = ds.batch(batch_size)
    return ds.map(lambda window: (window[:, :-1], window[:, 1:])).prefetch(1)
16-1 - ساخت Dataset
شکل 16-1. ساخت Dataset
length = 100
train_set = to_dataset(encoded[:1_000_000], length=length,
                       shuffle=True, seed=42)
valid_set = to_dataset(encoded[1_000_000:1_060_000], length=length)
test_set  = to_dataset(encoded[1_060_000:], length=length)

Window کوتاه‌تر Training را سریع‌تر می‌کند، اما Model Patternهای طولانی‌تر از Window را نمی‌بیند؛ بنابراین Length یک Hyperparameter مهم است.

ساخت و Train کردن Char-RNN

model = tf.keras.Sequential([
    tf.keras.layers.Embedding(input_dim=n_tokens, output_dim=16),
    tf.keras.layers.GRU(128, return_sequences=True),
    tf.keras.layers.Dense(n_tokens, activation="softmax")
])
model.compile(loss="sparse_categorical_crossentropy",
              optimizer="nadam", metrics=["accuracy"])

Embedding، ID هر Character را به Vector 16بعدی تبدیل می‌کند. GRU برای هر Time Step یک Representation می‌سازد و Dense Layer احتمال 39 Character ممکن را Output می‌دهد. چون Targetها ID عددی هستند، sparse_categorical_crossentropy مناسب است.

برای اینکه Model بتواند String خام دریافت کند، یک Preprocessing Model روی آن قرار می‌گیرد که TextVectorization را اعمال و IDها را با Offset مناسب به Model اصلی می‌دهد.

تولید متن و پارامتر Temperature

اگر همیشه Character با بیشترین Probability انتخاب شود، متن به‌شدت Deterministic و تکراری می‌شود. برای کنترل Diversity، Logitها بر Temperature تقسیم می‌شوند و سپس Sampling انجام می‌شود:

def next_char(text, temperature=1):
    y_proba = model.predict([text])[0, -1:]
    rescaled_logits = tf.math.log(y_proba) / temperature
    char_id = tf.random.categorical(rescaled_logits, num_samples=1)[0, 0]
    return text_vec_layer.get_vocabulary()[char_id + 2]

def extend_text(text, n_chars=50, temperature=1):
    for _ in range(n_chars):
        text += next_char(text, temperature)
    return text

Temperature کوچک، Distribution را Sharp و Output را محافظه‌کار می‌کند؛ Temperature بزرگ Diversity را بیشتر می‌کند اما احتمال متن بی‌معنی نیز افزایش می‌یابد.

Stateful RNN

تا اینجا Model Stateless بود: Hidden State در پایان هر Batch دور ریخته می‌شد. برای یادگیری Patternهای طولانی‌تر می‌توان State را بین Batchهای متوالی نگه داشت. شرط مهم این است که Sequence مربوط به هر Instance در Batch بعدی دقیقاً ادامهٔ همان Instance در Batch قبلی باشد.

برای ساخت Dataset چنین ترتیبی، متن به چند بخش پیوسته تقسیم می‌شود و از هر بخش Sequence Fragmentهای متوالی گرفته می‌شود؛ سپس Fragmentهای متناظر در Batch کنار هم قرار می‌گیرند.

16-2 - Stateful RNN
شکل 16-2. Stateful RNN

در Model باید stateful=True تنظیم شود و Batch Size از ابتدا ثابت باشد؛ بنابراین batch_input_shape نیز مشخص می‌شود. در انتهای هر Epoch باید Stateها Reset شوند تا Epoch بعد از ابتدای متن شروع شود. این کار را می‌توان با Callback انجام داد.

Stateful RNN می‌تواند Dependencyهای طولانی‌تر را یاد بگیرد، اما ساخت Dataset و مدیریت State آن پیچیده‌تر است و Training را Sequence-sensitive می‌کند.

Sentiment Analysis روی IMDb

Task بعدی Classification Reviewهای فیلم به Positive و Negative است. Dataset IMDb با TensorFlow Datasets Load می‌شود؛ بخشی برای Train، بخشی برای Validation و بخش Test جدا نگه داشته می‌شود.

import tensorflow_datasets as tfds

raw_train_set, raw_valid_set, raw_test_set = tfds.load(
    name="imdb_reviews",
    split=["train[:90%]", "train[90%:]", "test"],
    as_supervised=True)
train_set = raw_train_set.shuffle(5000, seed=42).batch(32).prefetch(1)
valid_set = raw_valid_set.batch(32).prefetch(1)
test_set = raw_test_set.batch(32).prefetch(1)

Tokenization و TextVectorization

این بار Input در سطح Word پردازش می‌شود. Tokenization بر اساس Space ساده است، اما برای زبان طبیعی همیشه ایده‌آل نیست: عبارت‌های چندکلمه‌ای، Prefix/Suffix و Wordهای ناشناخته مشکل ایجاد می‌کنند. روش‌هایی مانند Subword Tokenization می‌توانند Wordها را به قطعه‌های کوچک‌تر بشکنند و Vocabulary را بهتر پوشش دهند. TensorFlow Text و Tokenizerهای Hugging Face گزینه‌های پیشرفته‌تری فراهم می‌کنند.

برای مثال IMDb، یک TextVectorization ساده با Vocabulary محدود استفاده می‌شود:

vocab_size = 1000
text_vec_layer = tf.keras.layers.TextVectorization(max_tokens=vocab_size)
text_vec_layer.adapt(train_set.map(lambda reviews, labels: reviews))

embed_size = 128
model = tf.keras.Sequential([
    text_vec_layer,
    tf.keras.layers.Embedding(vocab_size, embed_size),
    tf.keras.layers.GRU(128),
    tf.keras.layers.Dense(1, activation="sigmoid")
])
model.compile(loss="binary_crossentropy", optimizer="nadam",
              metrics=["accuracy"])

مشکل Padding و Masking

Reviewها طول‌های متفاوت دارند. وقتی TextVectorization آن‌ها را Batch می‌کند، Sequenceهای کوتاه با Token صفر Pad می‌شوند. اگر RNN این Zeroها را مثل Token واقعی پردازش کند، State مفید ممکن است تا انتهای Sequence خراب شود.

راه‌حل این است که Padding Tokenها Mask شوند. کافی است در Embedding از mask_zero=True استفاده شود:

tf.keras.layers.Embedding(vocab_size, embed_size, mask_zero=True)

Embedding یک Boolean Mask تولید می‌کند؛ Layerهای سازگار مانند SimpleRNN، GRU، LSTM، Bidirectional، Dense و TimeDistributed آن را دریافت و به Layer بعدی Propagate می‌کنند. اگر Mask تا Output ادامه پیدا کند، Time Stepهای Mask‌شده در Loss نیز صفر در نظر گرفته می‌شوند.

Conv1D Masking را به‌صورت مستقیم پشتیبانی نمی‌کند. در Modelهای پیچیده‌تر باید Mask را با Functional API یا Subclassing دستی محاسبه و به Layer مناسب ارسال کرد. اگر Input از Embedding شروع نشود، tf.keras.layers.Masking می‌تواند Time Stepهایی را که تمام Featureهای آن صفر هستند Mask کند.

برای Custom Layer نیز می‌توان Argument به نام mask در call() دریافت کرد و در صورت Propagate کردن Mask، supports_masking=True را تنظیم کرد؛ اگر Shape یا معنای Mask تغییر کند باید compute_mask() Override شود.

امتیاز کاربران به این مقاله

☆☆☆☆☆

0 نفر امتیاز داده اند. میانگین: 0.0 از 5

 

0 نظر

نظر محترم شما در مورد مقاله های وب سایت برنامه نویسی و پایگاه داده

نظرات محترم شما در خدمات رسانی بهتر ما را یاری می نمایند. لطفا اگر مایل بودید یک نظر ما را مهمان فرمائید. آدرس ایمیل و وب سایت شما نمایش داده نخواهد شد.

0 / 500

اطلاعات تماس

  • آدرس:اصفهان-خیابان ام کلثوم غربی - بعد خیابان تخم چی - بیست متر بعد از پیتزا ننه شب - کوچه تعمیر گاه سمار زغالی - پلاک 354 - درب مشکی - طبقه هفتم
  • آدرس ایمیل:najafzade@gmail.com
  • وب سایت:http://www.a00b.com/
  • تلفن ثابت:(+98)9131253620
  • تلفن همراه:09131253620