تولید متن (Text Generating) با تنسورفلو — به زبان ساده

۲۹۱ بازدید
آخرین به‌روزرسانی: ۱۲ شهریور ۱۴۰۲
زمان مطالعه: ۶ دقیقه
تولید متن (Text Generating) با تنسورفلو — به زبان ساده

انتشار «تنسورفلو ۲.۰» (TensorFlow 2.0) برای فعالان حوزه «یادگیری ماشین» (Machine Learning) بسیار جالب توجه بود. برای آشنایی با این کتابخانه قدرتمند و آگاهی از تغییراتی که در نسخه ۲.۰ داشته است، مطالعه مطلب «تنسورفلو (TensorFlow) — از صفر تا صد» توصیه می‌شود. در این مطلب، از کتابخانه تنسورفلو برای کار با نوع داده متنی و تولید متن استفاده شده است. متن استفاده شده در این مطلب از مخزن «پروژه گوتنبرگ» (Project Gutenberg) برداشته شده است و کلیه کدهای لازم برای پیاده‌سازی در مطلب آورده شده‌اند. با توجه به اینکه در ادامه از یک «شبکه عصبی بازگشتی» (Recurrent Neural Network) نیز استفاده شده، به علاقه‌مندان پیشنهاد می‌شود برای درک بهتر مفهوم این مطلب، به این منبع [+] مراجعه کنند و با این نوع از «شبکه‌های عصبی» (Neural Networks) بیشتر آشنا شوند. اما در ادامه، به روش تولید متن (Text Generating) که جزئی از حوزه پردازش زبان طبیعی (NLP) است با تنسورفلو می‌پردازیم.

راه‌اندازی

ابتدا باید موارد مورد نیاز برای انجام این پروژه را دانلود کرد؛ با توجه به اینکه از تنسورفلو GPU در ادامه استفاده خواهد شد، در کد زیر از دستور !pip install tensorflow-gpu==2.0.0-alpha0 استفاده شده است.

بنابراین، طی فرایند نصب، نیاز است که GPU‌های موجود را تایید کرد (برای انجام این پروژه حقیقتا نیاز به GPU است، زیرا استفاده از آن منجر به صرفه‌جویی قابل توجهی در زمان می‌شود).

1from __future__ import absolute_import, division, print_function, unicode_literals
2!pip install tensorflow-gpu==2.0.0-alpha0
3import tensorflow as tf
4import numpy as np
5import os
6import datetime
7from tensorflow.python.client import device_lib
8print(device_lib.list_local_devices())
9print(“TensorFlow version:, tf.__version__)

اگر همه چیز به ترتیب خوبی پیش رفته باشد، کاربر می تواند مشاهده کند که در حال استفاده از TensforFlow 2.0.0-alpha0 است و اگر کد بالا را روی Google’s Colab اجرا کند، بخش جذابی از دستگاه GPU که Tesla T4 نامیده می‌شود مورد استفاده قرار می‌گیرد. بهتر است کاربر لیست دستگاه‌ها را به ویژه در Colab بررسی کند، زیرا گاهی فراموش می‌کند تا نوع «سیستم زمان اجرا» ( Runtime System) را تغییر دهد؛ بنابراین، چک کردن لیست دستگاه‌ها به این کار کمک کرده و به نوعی یادآوری آن است.

در این مثال، Colab با توجه به اینکه tensorflow-gpu دانلود و نصب شده، از GPU برای انجام کارها استفاده می‌کند؛ در غیر این صورت، پیش‌فرض آن CPU است. برای اجرای کد در Google Colab [+]، یا باید نوت‌بوک را به طور مستقیم از طریق منو File > Upload Notebook روی سایت Colab آپلود و یا به سادگی روی آیکون مربوطه در سمت چپ بالای نوت‌بوک کلیک کرد. برای دانلود متن در Colab، می‌توان از قطعه کد زیر استفاده کرد:

1from google.colab import files
2uploaded = files.upload()
3for fn in uploaded.keys():
4    print(‘User uploaded file{name}with length {length}    bytes.format(name=fn, length=len(uploaded[fn])))

قطعه کدهای موجود در Colab منابع خوبی برای راهکارهای کوچک هستند. فایل متنی بارگذاری شده، در سربرگ فایل خواهد بود؛ حتی نیاز نیست که کاربر خودش بررسی کند که فایل در آنجا قرار دارد یا نه، زیرا پس از آپلود شدن فایل، به کاربر در این رابطه اطلاع‌رسانی می‌شود.

کارها و فایل‌های کاربران در Colab کوتاه مدت هستند و بار بعدی که فرد وارد حساب کاربری خود می‌شود، از بین رفته‌اند. بنابراین ذخیره کردن کارها در جای دیگر پیش از خروج از Colab، الزامی است.

1import os
2path = os.getcwd()
3text = open(path +/Iliad_v3.txt’,  ‘rb’).read().decode(encoding=’utf-8')
4print(“Text is {} characters long.format(len(text)))

طول متن باید ۸۸۶,۸۰۹ کاراکتر باشد، بنابراین نمونه بزرگی نیست. از روی کنجکاوی، می‌توان تعداد کلمات را نیز بررسی کرد:

1words = [w for w in text.split(‘ ‘) if w.strip() != ‘’ or w == ‘\n’]
2print(“Text is {} words long”.format(len(words)))

باید ۱۵۳۲۶۰ کلمه در متن وجود داشته باشد؛ بنابراین آنقدرها هم که به نظر می‌رسید متن طولانی نیست. فقط برای حصول اطمینان  از اینکه متن آنچه را بدان داده شده می‌خواند، سرعت ۱۰۰ کلمه ابتدایی متن بررسی می‌شود.

1print(text[:100])
1achilles' wrath, to greece the direful spring
2of woes unnumber'd, heavenly goddess, sing!
3that

آماده‌سازی متن

برای آماده‌سازی متن، باید یک بردار از کاراکترهای یکتای مرتب ساخت. در این متن، ۳۴ مورد از آن‌ها باید وجود داشته باشد.

1vocab = sorted(set(text))
2print (‘There are {} unique characters’.format(len(vocab)))
3char2int = {c:i for i, c in enumerate(vocab)}
4int2char = np.array(vocab)
5print(‘Vector:\n’)
6for char,_ in zip(char2int, range(len(vocab))):
7    print(‘ {:4s}: {:3d},’.format(repr(char), char2int[char]))

در ادامه، نگاهی به نمونه نگاشت شده برای مشاهده ارائه‌های عددی از این متن انداخته می‌شود:

1text_as_int = np.array([char2int[ch] for ch in text], dtype=np.int32)
2print ({}\n mapped to integers:\n {}.format(repr(text[:100]), text_as_int[:100]))

داده‌های آموزش و اعتبارسنجی از متن ساخته می‌شوند (باید اطمینان حاصل کردد که بخش آموزش قابل تقسیم به اندازه دسته باشد؛ که در اینجا ۶۴ است) و سپس بررسی می‌شود که آیا شکل‌ها همانطور که انتظار می‌رفت هستند یا خیر.

1tr_text = text_as_int[:704000] 
2val_text = text_as_int[704000:] 
3print(text_as_int.shape, tr_text.shape, val_text.shape)

ساخت مدل تولید متن (Text Generating) با تنسورفلو

در اینجا سعی شده تا کلیه موارد قابل تنظیم در یک جا قرار بگیرند تا دسترسی به آن‌ها هنگامی که نیاز به انجام چندین تغییر است، آسان شود.

1batch_size = 64
2buffer_size = 10000
3embedding_dim = 256
4epochs = 50
5seq_length = 200
6examples_per_epoch = len(text)//seq_length
7#lr = 0.001 #will use default for Adam optimizer
8rnn_units = 1024
9vocab_size = len(vocab)

اکنون، باید مجموعه داده‌های آموزش و اعتبارسنجی را آماده و سپس شکل آن‌ها را بررسی کرد.

1tr_char_dataset = tf.data.Dataset.from_tensor_slices(tr_text)
2val_char_dataset = tf.data.Dataset.from_tensor_slices(val_text)
3tr_sequences = tr_char_dataset.batch(seq_length+1, drop_remainder=True)
4val_sequences = val_char_dataset.batch(seq_length+1, drop_remainder=True)
5def split_input_target(chunk):
6    input_text = chunk[:-1]
7    target_text = chunk[1:]
8    return input_text, target_text
9tr_dataset = tr_sequences.map(split_input_target).shuffle(buffer_size).batch(batch_size, drop_remainder=True)
10val_dataset = val_sequences.map(split_input_target).shuffle(buffer_size).batch(batch_size, drop_remainder=True)
11print(tr_dataset, val_dataset)

در نهایت، مدل ساخته می‌شود. در اینجا، از دو لایه LSTM استفاده می‌شود.

1def build_model(vocab_size, embedding_dim, rnn_units, batch_size):
2     model = tf.keras.Sequential([
3     tf.keras.layers.Embedding(vocab_size, embedding_dim,
4     batch_input_shape=[batch_size, None]),
5     tf.keras.layers.Dropout(0.2),
6     tf.keras.layers.LSTM(rnn_units,
7     return_sequences=True,
8     stateful=True,
9     recurrent_initializer=’glorot_uniform’),
10     tf.keras.layers.Dropout(0.2), 
11     tf.keras.layers.LSTM(rnn_units,
12     return_sequences=True,
13     stateful=True,
14     recurrent_initializer=’glorot_uniform’),
15     tf.keras.layers.Dropout(0.2),
16     tf.keras.layers.Dense(vocab_size)
17 ])
18 
19return model
20model = build_model(
21    vocab_size = len(vocab),
22    embedding_dim=embedding_dim,
23    rnn_units=rnn_units,
24    batch_size=batch_size)

اگر این مدل در «پلتفرم گوگل کلود» (Google Cloud Platform | GCP) تنظیم شود، امکان دارد که کاربر با پیغام خطای زیر مواجه شود.

<tensorflow.python.keras.layers.recurrent.UnifiedLSTM object …>: Note that this layer is not optimized for performance. Please use tf.keras.layers.CuDNNLSTM for better performance on GPU.

اگرچه، CuDNNLSTM موجود نیست، امکان دارد این خطا به خاطر استفاده از نسخه قبلی تنسورفلو باشد؛ زیرا LSTM در حال حاضر برای کارایی در نسخه ۲.۰ بهینه شده است؛ دلیل دیگر وقوع این خطا ممکن است در دسترس قرار نگرفتن CuDNNLSTM باشد.

اجرای مدل

در ادامه، کد مربوط به بررسی شکل خروجی و مدل، و تعریف «زیان» (Loss) آمده است.

1model.summary()
2for input_example_batch, target_example_batch in tr_dataset.take(1):
3    example_batch_predictions = model(input_example_batch)
4    print(example_batch_predictions.shape)
5def loss(labels, logits):
6    return tf.keras.losses.sparse_categorical_crossentropy(labels,    logits, from_logits=True)
7example_batch_loss  = loss(target_example_batch, example_batch_predictions)
8print("Loss:      ", example_batch_loss.numpy().mean())

اکنون از بهینه‌ساز «آدام» (Adam Optimizer) استفاده می‌شود و آموزش پس از ۱۰ دوره، هنگامی که خطای اعتبارسنجی بهبود پیدا نکرد، متوقف می‌شود.

1optimizer = tf.keras.optimizers.Adam()
2model.compile(optimizer=optimizer, loss=loss)
3patience = 10
4early_stop = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=patience)

اکنون، باید برای ذخیره‌سازی چک‌پوینت‌ها (Checkpoints) و اجرای برنامه، پوشه ساخت.

1checkpoint_dir =./checkpoints’+ datetime.datetime.now().strftime(“_%Y.%m.%d-%H:%M:%S”)
2checkpoint_prefix = os.path.join(checkpoint_dir, “ckpt_{epoch})
3checkpoint_callback=tf.keras.callbacks.ModelCheckpoint(
4    filepath=checkpoint_prefix,
5    save_weights_only=True)
6history = model.fit(tr_dataset, epochs=epochs, callbacks=[checkpoint_callback, early_stop] , validation_data=val_dataset)
7print (“Training stopped as there was no improvement after {} epochs”.format(patience))

در این مثال، آموزش روی «دوره» (Epoch) بیست و پنج متوقف می‌شود. این یعنی آخرین باری که خطای اعتبارسنجی بهبود پیدا کرده، دوره 25 بوده است. نمودار زیر، آنچه به وقوع پیوسته را به تصویر می‌کشد؛ در واقع، در ابتدا خطای اعتبارسنجی حتی بهتر از خطای آموزش بوده است (زیان اعتبارسنجی برای یک دوره با استفاده از بهبود مدل آموزش محاسبه می‌شود، زیرا در پایان دوره و جایی که زیان آموزش با استفاده از میانگین زیان‌ها در همه دسته‌ها در دسترس است و در آغاز آن، ممکن است منجر به کمتر بودن خطای اعتبارسنجی شود).

اما در طول زمان، خطای مجموعه‌های آموزش و اعتبارسنجی واگرا می‌شوند و خطای آموزش شروع به پایین رفتن می‌کند، در حالیکه خطای اعتبارسنجی ابتدا ثابت می‌ماند و پس از مدتی بیشتر می‌شود.

تولید متن (Text Generating) با تنسورفلو

تولید متن

اکنون، باید وزن‌ها را از آخرین چک‌پوینت‌ها به روز رسانی کرد (یا خط load.weights را در چک‌پوینت دیگری تنظیم کرد) و یک متن هزار کاراکتری تولید کرد.

1model = build_model(vocab_size, embedding_dim, rnn_units, batch_size=1)
2model.load_weights(tf.train.latest_checkpoint(checkpoint_dir)) 
3model.build(tf.TensorShape([1, None]))
4def generate_text(model, start_string):
5    
6    print('Generating with seed: "' + start_string + '"')
7  
8    num_generate = 1000
9    input_eval = [char2int[s] for s in start_string]
10    input_eval = tf.expand_dims(input_eval, 0)
11    text_generated = []
12    temperature = 1.0
13    model.reset_states()
14    for i in range(num_generate):
15        predictions = model(input_eval)
16        predictions = tf.squeeze(predictions, 0)
17        predictions = predictions / temperature
18        predicted_id = tf.random.categorical(predictions,      num_samples=1)[-1,0].numpy()
19        input_eval = tf.expand_dims([predicted_id], 0)
20        text_generated.append(int2char[predicted_id])
21    return (start_string + ''.join(text_generated))
22print(generate_text(model, start_string="joy of gods"))

خروجی دریافتی به صورت زیر است.

تولید متن (Text Generating) با تنسورفلو

این خروجی، برای ۱۰ دقیقه پردازش با یک GPU تنها و با استفاده از متن نمونه کمتر از یک میلیون کاراکتر، نسبتا خوب محسوب می‌شود.

اگر مطلب بالا برای شما مفید بوده، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

^^

بر اساس رای ۲ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
towardsdatascience
۴ دیدگاه برای «تولید متن (Text Generating) با تنسورفلو — به زبان ساده»

این قسمت رو نفهمیدم:
«داده‌های آموزش و اعتبارسنجی از متن ساخته می‌شوند (باید اطمینان حاصل کردد که بخش آموزش قابل تقسیم به اندازه دسته باشد؛ که در اینجا ۶۴ است) و سپس بررسی می‌شود که آیا شکل‌ها همانطور که انتظار می‌رفت هستند یا خیر.»
عدد 64 از کجا آمد؟
و توی این سه خط عدد 704000 از کجا ؟
tr_text = text_as_int[:704000]
val_text = text_as_int[704000:]
print(text_as_int.shape, tr_text.shape, val_text.shape)
از این قسمت به بعد توضیحات بسیار کم است و پیچیدگی بسیار زیاد. اما این چیزی از ارزش های شما کم نمی کند.

با سلام و احترام خدمت شما؛
از ارائه بازخوردتون راجع به این نوشتار سپاسگزاریم.

منظور از «متن» در این جمله، به دیتاست شبکه عصبی اشاره داره و به بیان دیگه این داده‌ها (متن) برای آموزش و ارزیابی شبکه استفاده می‌شه.
با مراجعه به ریپوزیتوری «+» می‌تونید به کدها و همین‌طور «متن» پاک‌سازی شده از پروژه گوتنبرگ دسترسی داشته باشید.
«اندازه بسته» یا همون «batch_size»، یکی از هایپرپارامترهای شبکه هستش و به تعداد نمونه‌های آموزشی در یک دسته، که به‌طور همزمان به‌عنوان ورودی وارد شبکه می‌شه، اشاره داره و میزان اون می‌تونه روی دقت نتیجه تأثیر بذاره (می‌تونید اعداد دیگه رو با شرطی که گفته امتحان کنید).
از اونجایی‌که نمیتونیم کل دیتامون رو یکباره وارد حافظه کنیم (بخاطر هزینه‌بر بودن)، بنابراین اون‌ها رو در قالب batch_size دسته دسته می‌کنیم و شبکه هر بار یکی از دسته‌ها رو برای به‌روزرسانی خودش استفاده می‌کنه.
قسمتی از دیتا یا «متن» رو در tr_text به‌عنوان «داده‌های آموزشی» و قسمتی رو در val_text برای «ارزیابی» قرار دادیم.

از همراهی شما با مجله فرادرس بسیار خوشنودیم.

مطلب نسبتا خوب بود. ممنون
اگر لینک داده هایی که از پروژه گوتبرگ هم گرفتید میگذاشتید خیلی خوب میشد.

با سلام؛

منبع تمامی مطالب مجله فرادرس اگر ترجمه باشند در انتهای مطلب و پیش از نام نویسنده آورده شده‌اند.

با تشکر از همراهی شما با مجله فرادرس

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *