یادگیری بدون نظارت (Unsupervised Learning)
همانطور که پیشتر بحث شده بود، یکی از حوزه های یادگیری ماشین، یادگیری بدون نظارت است که در آن دادهها برچسب ندارند. در یادگیری بدون نظارت هدف اصلی، یادگیری ساختار دادهها میباشد.
یادگیری بدون نظارت (Unsupervised Learning)
همانطور که پیشتر بحث شده بود، یکی از حوزه های یادگیری ماشین، یادگیری بدون نظارت است که در آن دادهها برچسب ندارند. در یادگیری بدون نظارت هدف اصلی، یادگیری ساختار دادهها میباشد.
Style Transferیکی از کاربردهای Deep Learning در هنر است که مشابه Deep Dream میباشد.
در این کاربرد یک عکس content و یک عکس style داریم. هدف این شبکه ساختن عکسی است که image آن از عکس content و style آن از عکس style گرفته شده باشد. یعنی content را حفظ کرده و آنرا به سبک style ببرد.
به طور کلی به object ها content و به .pattern,texture,color,. ها style گفته میشود.
نمونه ای از خروجیهای این شبکه :
آنچه ConvNet ها یاد میگیرند
همانطور که میدانید لایههای اول به featureهای generalتر مانند خط و لبه و لایههای آخر به featureهای abstractتر حساساند.
از visualization برای درک اینکه هر لایه چه تفسیری از image دارد، استفاده میشود. و این به عنوان hyper parameter برای style trafnsfer استفاده میشود. (برای مثال: استفاده از کدام لایه style را بهتر حفظ میکند.)
در مقاله Zeiler & Fergus سه متد درباره visualization معرفی شده است. که در اینجا به یکی از آنها میپردازیم.
برای درک بهتر این مبحث ابتدا برخی از مساله هایی که با کمک RNN ها قابل حل است را بررسی می کنیم:
Object detection یا تشخیص شی در تصویر چیست ؟
تشخیص اشیا یک فناوری کامپیوتری است که در ارتباط با کامپیوتر ویژن و پردازش تصویر است که با شناسایی نمونه هایی از اشیاء معنایی یک کلاس خاص (مانند انسان ها، ساختمان ها و اتومبیل ها) در تصاویر و فیلم های دیجیتال مورد بررسی قرار می گیرد.
در واقع در این فناوری ماشین تصویر را میبیند و پس از پردازش هایی مکان اشیا تعریف شده و کلاس یا دسته بندی آنها را نیز مشخص میکند.
در واقع در این فناوری ماشین تصویر را میبیند و پس از پردازش هایی مکان اشیا تعریف شده و کلاس یا دسته بندی آنها را نیز مشخص میکند.
وظایف اصلی بینایی ماشین را میتوان به موارد زیر تقسیم کرد:
روش ها و الگوریتم های موجود :
1 – روش های اولیه و سنتی :
در این روش ها مسئله را با رویکرد های ساده تری انجام میدهند. بعنوان مثال ابتدا تصویر را به یک شبکه سگمنتیشن داده و با روش های موجود به سگمنت کردن اشیا موجود در تصویر پرداخته و پس از مشخص شدن محدوده اشیا در تصویر هر کدام را جداگانه به یک شبکه دسته بند میدهیم تا کلاس آن نیز مشخص شود.
این روش ها زمان بر بوده و عملا برای تشخیص اشیا به صورت real time کارآمد نیستند. برای مثال روش selective search
بعد از مدت ها و برای بهبود عملکرد این روش ها ایده هایی برای نحوه سگمنت کردن و در نهایت تشخیص اشیا مطرح شد که از جمله آنها میتوان روش roi pooling را نامبرد. که برای سگمت کردن تصویر ورودی از لایه ای کانولوشنالی شبکه های عمیق بهره میبردند. زیرا روش های قبلی با استفاده از محاسبه میزان تفاوت بین پیکسل های تصویر و تمایز قائل شدن بین آنها انجام میشد که دیگر منسوخ شدند.
2 – روش های مدرن و پیشرفته :
در این الگوریتم ها ایده هایی برای این مسئله مطرح شد که سعی بر این داشتند تا ماشین تصویر ورودی را فقط یک بار دیده و با استفاده از ترکیب شبکه های عمیق کانولوشنالی و تمام متصل به صورت موازی فیچر های لازم از تصویر استخراج شده و درنهایت به دسته بندی پرداخته شود تا از لحاظ دقت و سرعت پیشرفت بسیاری داشته باشد.
از جمله این روش ها و الگوریتم ها میتوان روش YOLO و SSD را نامبرد.
که در ادامه به بررسی آنها میپردازیم.
YOLO : You Only Look Once
کاملترین سیستم بلادرنگ (Real time) در یادگیری عمیق و حل مسائل تشخیص تصویر است. همانطور که در تصویر زیر مشاهده میکنید، این الگوریتم ابتدا تصویر را به بخشهای مختلف تقسیم میکند و هر بخش را علامتگذاری میکند، سپس الگوریتم شناسایی را به صورت موازی برای تمامی این بخشها اجرا میکند تا ببیند هر بخش به کدام دستهبندی تعلق میگیرد. بعد از شناسایی کامل اشیا، آنها را به هم متصل میکند تا دو هر شیء اصلی یک جعبه باشد.
همه این کارها به صورت موازی انجام میشوند؛ در نتیجه به صورت بلادرنگ است و میتواند تا 40 تصویر را در یک ثانیه پردازش کند. با اینکه این مدل کمی نسبت به RCNN عملکرد ضعیفتری دارد، ولی به دلیل بلادرنگ بودن آن، میتوان از آن برای حل مشکلات روزانه استفاده کرد. در زیر تصویری از معماری YOLO را مشاهده میکنید.
نحوه تشخیص شی در تصویر توسط الگوریتم yolo :
در شبکه ی YOLO تصاویر به صورت فرضی به N در N ناحیه تقسیم می شوند. به ازای هر کدام از این نواحی فرض می شود که K شی امکان حضور دارند. شبکه به صورتی طراحی شده است که در نهایت به ازای تمامی نواحی امکان وجود اشیا در آن ناحیه ها – دسته ی آن شی و همچنین مختصات کادر دور شی را پیش بینی می کند.
با توجه به اینکه کادر دور هر شی ۴ مختصات دارد و همچنین دسته ی مورد نظر شی یک بردار C تایی هست و همین طور یک عدد برای احتمال وجود شی داریم پس به ازای هر شی یک تنزور (4 + 1 + C) تایی مورد نیاز است.
با توجه به اینکه فرض شد در هر تصویر N در N ناحیه وجود دارد و هر ناحیه احتمال حضور K شی به صورت بیشینه وجود دارد و هر شی نیز تزوری (4 + 1 + C) نیاز دارد پس در انتهای شبکه تنزوری KNN(C+1+4) وجود دارد که به پیشبینی کادر اشیا و احتمال آنها و دسته آنها می پردازد.
در نسخه نخست این شبکه پس از لایه های کانولوشن ویژگی ها خطی شده و به لایه های تمام متصل تحویل داده شده و در نهایت پیشبینی ها انجام می شوند در حالی که در نسخه های بعدی به جای ایجاد تنزوری به این ابعاد حجیم یک کرنل کانولوشن با ابعاد K(C+1+4) به نحوی بر روی ویژگی های آخرین لایه ی کانولوشن کانوالو می شود که ناحیه های ادراکی آن (Receptive Field) بر روی هر یک از N*N ناحیه از تصور ورودی قرار بگیرند و عملیات پیشبینی انجام شود.
تفاوت عمده این شبکه با شبکه ی RCNN این است که این شبکه به صورت سراسری (End-to-End) کار می کند و بحث تشخیص مکان اشیا را بدون نیاز به الگوریتم های بیرونی (Selective Search در RCNN) حل می کند و سرعت بسیار بالایی دارد و تماما با استفاده از شبکه های عصبی پیاده سازی شده و در نتیجه قابلیت موازی سازی بسیار مناسبی بر روی پردازنده هایی مثل GPU ها و FPGAها را داراست.
در فرایند اموزش شبکه yolo مختصات نقاطی را که میابد به صورت زیر تقسیم شده و با کاست فانکشن مناسب سعی در بهبود پبش بینی اشیا موجود در تصویر میکند
Cost function :
نتایج نهایی تشخیص اشیا روی تصویر توسط YOLO :
زمانی که برای اولین بار یک شبکه کانولوشنی در مسابقه Image Net برنده شد، توجه همگان به مبحث یادگیری ماشین و در ادامه یادگیری عمیق جلب شد. دیگر همگان راه حل تمام مشکل ها را در این زمینه جست جو میکردند؛ ولی فراموش میکردند که مسابقه Image Net دیتای عظیمی در اختیار شرکت کنندگان قرار میدهد و برگ برنده شبکه های عمیق نیز همین دیتای زیاد است. در صورتی که برای خیلی از مشکلات این حجم از اطلاعات در دسترس نیست.
از طرفی آموزش یک شبکه عمیق با دیتای زیاد از دست همه ساخته نیست. زیرا این کار نیاز به قدرت پردازشی زیادی دارد.
این جا بود که استفاده از مدل های pre-trained به کمک افرادی آمد که از دیتا و قدرت پردازشی محدودی برخوردار بودند. شبکه هایی که با دیتای مسابقه Image Netآموزش داده شده اند، در اختیار همه قرار دارد و دیگر نیازی نیست که تمام مسیر را از اول طی کنیم. کافیست که یک شبکه را بر داریم و با استفاده از دو روش feature extraction و fine-tuning برای کار خودمان اختصاصی کنیم.
ولی اگر مسأله ما به تصویر مربوط نباشد چه؟ دیتای به این عظیمی برای متن را از کجا بیاوریم؟ چگونه شبکه را آموزش دهیم؟
برای استفاده از مدل های pre-trained در مسائلی که با متن سر و کار دارند، ابتدا به سراغ word embedding ها رفتیم. به ما کمک کردند و تغییر محسوسی در دقت شبکه ها ایجاد کردند. ولی اصلا عمیق نبودند و حاوی اطلاعات کمی بودند. کمک آن ها موثر ولی محدود بود.
در سال ۲۰۱۸ این مسیر برای مسأله های متنی یا به طور دقیقتر NLP نیز در دسترس قرار گرفت. شبکه بزرگی با دیتای زیاد (Wikipedia + BookCorpus) توسط مهندسان گوگل آموزش داده شد و در دسترس همه قرار گرفت. حالا یک شبکه بسیار قدرتمند برای بهره گیری در مسائل متنی در اختیار داریم. این شبکه Bidirectional Encoder Representations from Transformers یا BERT نام دارد. …
Word embedding یکی از روش های represent کلمات است.که الگوریتم ما را قادر میسازد تا روابط و شباهت های بین کلمات را بصورت خودکار بفهمد. تا کنون ما برای تعریف زبان از یک دیکشنری 10000 تایی از کلمات استفاده میکردیم. و برای نشان دادن هر کلمه از یک بردار 1-hot استفاده میکردیم؛ برای مثال کلمه man کلمه 5391 در دیکشنری است و 1-hot vector آن را بصورت O5391 نشان میدهیم که index 5391 آن یک و بقیه صفر است و به همینصورت بقیه کلمات دیکشنری.
از نقاط ضعف این representation میتوان به این اشاره کرد که کلمات را بصورت یک چیز ذخیره میکند و اجازه نمیدهد الگوریتم ان را تعمیم دهد.
برای مثال:
مدل میتواند تشخیص دهد که در جای خالی کلمه juice را قرار دهد ولی اگر جمله بعدی را هم آموزش نداده باشیم نمیتواند juice را پیش بینی کند چون apple و orange رابطه نزدیکی نسبت ب بقیه کلمات با هم ندارند.
ضرب داخلی بین هر دو وکتور 1-hot صفر است پس فاصله اقلیدسی بین همه آن ها یکسان است.
پس بجای 1-hot representation از featurized representation برای هر کدام از کلمات دیکشنری استفاده میکنیم.
به اینصورت که هر کلمه در دیکشنری به طور مثال 300، feature با نوع اعشاری دارد. مثل جنسیت، سلطنتی بودن، خوراکی بودن و… .برای هر کلمه یک وکتور 300 بعدی از کلمات در نظر میگیریم و آن را با e و index آن کلمه در دیکشنری نشان میدهیم. بطور مثال feature vector کلمه man را بصورت نشان میدهیم.
اگر مثال قبلی را در نظر بگیریم میبینیم که apple و orange ، feature های شبیه به هم زیادی دارند که generalize بین این دو کلمه برای الگوریتممان راحت تر است.به این representation، word embeddings میگوییم.
برای تجسم word embedding از الگوریتم t-SNE استفاده میکنیم تا بعدهای بردار ویژگی را به دو بعد کاهش دهیم.
همانطور که میبینیم کلمات مرتبط تر به هم نزدیکتر هستند.
با یک مثال از شناسایی اسم میبینیم که چطور از feature representation هایی که از هر کدوم از لغات استخراج کردیم استفاده کنیم.
Sally Johnson اسم یک شخص است. بعد از آموزش این جمله، مدل باید بداند که
“Robert Lin is an apple farmer” شامل Robert Lin به عنوان یک اسم است.
حالا اگر مدل را با جمله “Robert Lin is a durian cultivator” تست کنیم، شبکه باید اسم ها را تشخیص حتی با وجود اینکه کلمه durian را در طی آموزش ندیده باشد. این توانایی word representation است. الگوریتم هایی که از یادگیری به وسیله word embedding استفاده میکنند میتوانند میلیاردها لغت بدون برچسب را بررسی کند و بازنمایی آنهارا یاد بگیرد.
یک ویژگی مثبت word embedding نسبت به 1-hot کاهش ابعاد ورودی است. بردار 10000 بعدی 1-hot در مقایسه با بردار 300 بعدی ویژگی!
Word embedding رابطه جالبی با taskهای face recognition دارد:
در این مسئله ما هر کدام از تصاویر را به یک وکتور encode کردیم و شباهت این وکتورها را بررسی کردیم. در اینجا words encoding وembedding معنی مشابهی دارند.
در word embedding ما برای هر کدوم از کلماتی ک در دیکشنری وجود دارند یک representation داریم بر خلاف image encoding که ما هر تصویر جدید را به یک وکتور n بعدی map میکنیم.
یکی از خواص شگفت انگیز word embedding، analogy (تناسب) است.
جدول Word embedding زیر را در نظربگیرید:
میتونیم با استفاده از رابطه man ==> woman رابطه ؟؟ <== king را هم استدلال کنیم؟
بردار e manو e womn از هم کم میکنیم که وکتور [-2 0 0 0] حاصل میشود. بهمین صورت برای بردار امبدینگ های king و queen بردار [-2 0 0 0] بدست میاید.این بردار را میتوانیم بردار جنسیت در نظر بگیریم!
مسئله را به صورت فرمول زیر مینویسیم:
بیان بالا را به صورت فرمول ریاضی میتوانیم بصورت زیر بنویسیم:
که بهترین جواب بردار امبدینگ queen است.
Cosine similarity:
میتوانیم از فاصله اقلیدسی هم به عنوان یک similarity function استفاده کنیم.
وقتی یک الگوریتم برای یادگیری word embedding پیاده سازی میکنیم، آنچه در نهایت یاد میگیرد یک ماتریس embedding است.
فرض کنید یک دیکشنری ده هزار کلمه ای داریم. الگوریتم یک ماتریس (300, 10000) بنام E میسازد.
با ضرب بردار 1-hot و ماتریس امبدینگ میتوان بردار امبدینگ را پیدا کرد.
1 2 3 |
from keras.layers import Embedding embedding_layer = Embedding(1000, 64) |
در این مثال به پیاده سازی طبقه بند برای نظرات سایت imdbمیپردازیم.
دانلود کردن مجموعه داده imdb از datasetهای tensorflow
1 2 3 4 5 6 7 8 |
import tensorflow as tf from tensorflow import keras import numpy as np imdb = keras.datasets.imdb (train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000) |
آرگومان num_words = 10000 ، برای ده هزار کلمه پرتکرار در دیتای اموزشی.
داده ها ب صورت یک آرایه از اعداد صحیح است که هر کدوم از این اعداد صحیح به یک واژه دلالت میکند و برچسب ها هم بصورت 1 برای نظرات مثبت و 0 برای نظرات منفی است.
با توجه به این که هر کدام از داده ها به صورت مجموعه ای از اعداد صحیح هستند، برای تبدیل دوباره به کلمات از یک تابع کمکی استفاده میکنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# A dictionary mapping words to an integer index word_index = imdb.get_word_index() # The first indices are reserved word_index = {k:(v+3) for k,v in word_index.items()} word_index["<PAD>"] = 0 word_index["<START>"] = 1 word_index["<UNK>"] = 2 # unknown word_index["<UNUSED>"] = 3 reverse_word_index = dict([(value, key) for (key, value) in word_index.items()]) def decode_review(text): return ' '.join([reverse_word_index.get(i, '?') for i in text]) |
که ما از مورد دوم استفاده میکنیم با ابعاد max_length * num_reviews.
برای مساوی کردن طول ها از pad_sequences استفاده میکنیم که طول طولانی ترین جمله را 256 در نظر میگیریم و اگر جمله کوچکتر ازین بود بقیه را با توکن <pad> پر میکنیم.
1 2 3 4 5 6 7 8 9 |
train_data = keras.preprocessing.sequence.pad_sequences(train_data, value=word_index["<PAD>"], padding='post', maxlen=256) test_data = keras.preprocessing.sequence.pad_sequences(test_data, value=word_index["<PAD>"], padding='post', maxlen=256) |
در این مثال از rnn ها استفاده نکردیم و از یک شبکه dense ساده استفاده میکنیم.
1 2 3 4 5 6 7 8 |
# input shape is the vocabulary count used for the movie reviews (10,000 words) vocab_size = 10000 model = keras.Sequential() model.add(keras.layers.Embedding(vocab_size, 16)) model.add(keras.layers.GlobalAveragePooling1D()) model.add(keras.layers.Dense(16, activation=tf.nn.relu)) model.add(keras.layers.Dense(1, activation=tf.nn.sigmoid)) |
برای loss function از binary_crossentropy استفاده میکنیم.
1 2 3 |
model.compile(optimizer=tf.train.AdamOptimizer(), loss='binary_crossentropy', metrics=['accuracy']) |
1 2 3 4 5 6 |
history = model.fit(partial_x_train, partial_y_train, epochs=40, batch_size=512, validation_data=(x_val, y_val), verbose=1) |
1 2 3 |
results = model.evaluate(test_data, test_labels) print(results) |
استفاده از history خود کراس
1 2 |
history_dict = history.history history_dict.keys() |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import matplotlib.pyplot as plt %matplotlib inline acc = history.history['acc'] val_acc = history.history['val_acc'] loss = history.history['loss'] val_loss = history.history['val_loss'] epochs = range(1, len(acc) + 1) # "bo" is for "blue dot" plt.plot(epochs, loss, 'bo', label='Training loss') # b is for "solid blue line" plt.plot(epochs, val_loss, 'b', label='Validation loss') plt.title('Training and validation loss') plt.xlabel('Epochs') plt.ylabel('Loss') plt.legend() plt.show() |
همانطور که میبینیم training loss کم شده ولی validation loss ، overfit شده.
1 2 3 4 5 6 7 8 9 10 11 12 |
plt.clf() # clear figure acc_values = history_dict['acc'] val_acc_values = history_dict['val_acc'] plt.plot(epochs, acc, 'bo', label='Training acc') plt.plot(epochs, val_acc, 'b', label='Validation acc') plt.title('Training and validation accuracy') plt.xlabel('Epochs') plt.ylabel('Accuracy') plt.legend() plt.show() |
در این نمودار هم validation accuracy ، overfit شده.
برای جلوگیری از overfit میتونیم از راه های مختلفی استفاده کنیم :
الگوریتم Word2vec یک گروه از مدلهای مرتبط با پردازش متن است که برای تولید کلمه جاسازی استفاده میشود. این مدلها شبکههای عصبی هستند که برای آموزش بازسازی مفاهیم زبانی کلمات به کار میروند.
حال به روش های زیرتوجه کنید:
می دانیم که context به nتعدادکلماتی که برای پیش بینی شبکه لازم است واین n تعداد هایپر پارامتر هستند.
مثلا در شکل بالا n = 4قرار داده ایم وبرای پیش بینی juice ،چهار کلمه قبل از آن را انتخاب کرده ایم این روشی است که در سال 2003توسطBengioمطرح گردیدودر حال حاضر روش های بهتری ارایه شده است که به آن ها می پردازیم.
بعنوان مثال در شکل بالا هدف این است که juice راپیش بینی کنیم وبراساس contextهای متفاوت که در شکل بالا اورده شده میتوان به این هدف رسید.(همان طورکه در شکل قبل Last 4 wordsرا مشاهده کردیم.)
روش 4words on left & right :در این روش علاوه بر 4کلمه قبل،4کلمه ی بعدی رو هم context در نظر میگیریم.
روش Last 1 word:یک کلمه مونده به targetروcontext در نظرمی گیریم.
روش Nearby 1 word: یک پنجره context را 5 تا کلمه ای یا 10 تایی قبل وبعد targetبرمیداریم وسپس از این پنجره بعنوان رندوم یک کلمه انتخاب می شود،که درمثال بالا glass به عنوان context به صورت رندوم انتخاب شده است.به این روش skip-grams گویندکه همان word2vec راتشکیل می دهد.این ایده خیلی ساده ای است ولی در نهایت embedding خوبی خواهیم داشت.
پس در این روش یکسری context و target داریم که با n تا کلمه قبل وبعد وبا انتخاب یک کلمه ازمیانn تا به صورت رندوم میتوان targetرا تشخیص داد.این ایده در 2013 توسط Micolov مطرح گردید.
در شکل بالا یک Fake Task داریم که اگر یک context را بدهیم target را پیش بینی خواهد کردمثلا در شکل بالا در پنجره های ابی رنگ context ودر سفید رنگ targetرا خواهیم داشت وپنجره اول دو کلمه quick ,brown هم targetهستند.درپنجره های دیگر شکل هایپر پارامتر های متفاوتی را بسته به سلیقه های کد نویسی متفاوت مشاهده می کنیم.دراین شکل چون وزن های شبکه بروز می شوند target هم میتواند دو مقدار داشته باشد وهیچ مشکلی پیش نمی اید چون هدف بدست اوردن بردارembedding بامعنی است.
در نهایت شبکه شکل بالا را خواهیم داشت که
input vector: بسته به دیکشنری سایزمتفاوتی دارد که در شکل بالا 10000تایی ست.
hidden layer:به یک لایه embeddingوصل می شودکه مقدارآن هایپرپارامتراست وچون googleاین مقدار را300درنظر گرفته ماهم همان مقدار را درنظر می گیریم.
output layer:بردار one-hotاست که 10000تا مقدار نورون داردکه هر کدام به ازای یک کلمه تنها یکی ازآن ها مقدار یک می شود.
در این taskکه در شکل بالا توضیح داده شد برخلاف لایه های FC های قدیم activation نمی گزاریم و برای هر چه راحت شدن وپیچیدگی کمتر liner استفاده کردیم.
به مدل زیر توجه کنید که
دراین مدل vocab size=10000 است که از context که orange است ودر دیکشنری شماره6257 را دارد،می خواهیم به target که juice است ودر index=4834 است برسیم که در واقع از X به Y یک Mapping خواهیم داشت.
Ocبرابر one hot کلمه ی c است که در یک بردار embedding رندوم ضرب می شود وec که embedding کلمه c است سپس از یک لایه softmax عبور می دهیم ودر نهایت کلمه predict شده به دست می اید.
در فرمول softmax عکس می بینیم که مقدار خیلی بزرگی دارد که ممکن است train نشود چون سیگما مخرج یک جمع فوق العاده سنگینی است که علاوه بر آن ضرب وکتورایز هم داخلش داریم وتمام این عملیات های ریاضی فوق العاده باعث کاهش سرعت می شوند.
برای حل مشکل بالا ایده های زیر ارایه شده است:
استفاذه از softmax باینری یعنی برای رسیدن به 10000 تا داده یک درخت می سازیم وآن را به دو قسمت مساوی تقسیم میکنیم که هر شاخه درخت5000 داده خواهد داشت، برای یافتن داده ابتدا بررسی میکنیم در کدام شاخه درخت باید بگردیم سپس ان شاخه را ادامه ودر مرحله ی بعدی 2500 تا داده ی بعدی را بررسی میکنیم وهمین طوری میگردیم تا به کلمه نهایی برسیم که در شکل با رنگ بنفش نشان داده شده است با این روش سرعت بالا تر می رود.
ایده ی دیگر همانند درخت Decision Tree می توان بر اساس احتمالات درخت را چید وبعنوان مثال کلمات پر تکرار همانند the و a را در ابتدای درخت قرار داد وکلماتی که خیلی پر تکرار نیستند رو درانتهای درخت بگزاریم.
بازهم ایده های بالا مشکلاتی دارند وروش بهتری به نام Negative Sampling مطرح شد:
دراین روش کار را ساده تر کردیم چون قبلا برای softmax سختی محاسبه را داشتیم حال ان راتبدیل به sigmoid میکنیم، همانطور که در شکل بالا میبینیم لغت juice به صورت رندوم به عنوان target انتخاب و مقدارش در جدول یک می شود وبقیه ی حروف مقدار صفر را میگیرند.البته لازم به ذکر است که در این روش چون به صورت رندوم target انتخاب می شود ممکن است target اشتباه انتخاب شود وبا مشکل مواجه شویم،ولی چون دیتا ست بزرگی در اختیار داریم این مشکل در مراحل بعدتر حل می شود.
مدل تبدیل به شکل بالا می شود که یکی از کلمات context است وفقط یکی از بقیه کلمات بعنوان target انتخاب میشود ومقدار یک میگیرد پس درواقع یکsigmoid tag خواهیم داشت وبا دادن کلمه ی orange کلمه juiceحاصل می شود.اگر لغت king داده شود همان sigmoid مخصوص این کلمه خطا را برمیگرداند که متوجه میشویم این کلمه sampleنیست وغلط است.درواقع دراین مثال هر بار 5نورون سیگموید دخیل هستند وخطا ها را برمیگردانند.یعنی با دادن کلمه orange در شبکه تمام 10000نورون در شکل یک مقداری میگیرند وبرای انتخاب کلمه juice یا king برای خروجی هرکدام که مقدار بیشتری داشت بامقدارtarget مقایسه ودر نهایت انتخاب میشود و اختلاف این مقدار ها در شبکه backpropagat میشود.
برای انتخاب کلمات براساس احتمالاتی که درجمله کلمات داشتند بیشترین راانتخاب میکردیم،حال میخواهیم کلماتی که در دیکشنری پرتکرار هستند نسبت به هم نزدیکتر باشند واحتمال بیشتری برای انتخاب داشته باشند.برای این روش مشکلی که داریم این است که کلمات پر تکرار همانند aو the و of همیشه پرتکرارهستند و negative هارا همیشه تشکیل خواهند دادپس کلمات پرتکرار با احتمال (f(Wi، احتمال بیشتری خواهند داشت.روش دیگر این است به تمام کلمات احتمال برابری را نسبت می دهیم ولی مشکل این روش این است که کلمات پرتکرار بهتر یاد گرفته نمی شود.
برای حل دو روش بالا فرمول (P(Wi مطرح شد که نسبت احتمال کلمات را نسبت به کلمات پرتکرار حفظ می کند وکلماتی که بیشترانتخاب می شدند احتمال بیشتری نسبت به کم تکرار ها خواهند گرفت.(برای درک فرمول ونسبت ها میتوانید تابع xبه توان 3/4وxرا باهم ببینید)
gloveروشی برای word embedding درست کردن است دریک task است که بعدها این word embeddingبتواند مورد استفاده قرار بگیرد.این روش درسال 2014 توسط استندفوری ها مطرح شد.
به صورت کامل نمی توان گفت که gloveبهترازword2vecهست.
درجمله ای که در شکل بالا می بینیم همانطور که قبلا گفته شد یک context و target انتخاب می کردیم دراین روش الگوریتمی ساخته می شود که embeddingها را باروش ساده تر یاد بگیریم:
Xij=تعداددفعاتی که در کل دیتا ست context iام با context jام دیده شده است.
Xij=Xjiتنها در صورتی این دو باهم برابرندکه پنجره ای که برایcontextدر نظر گرفته می شود n لغت قبل وبعد را شامل شودمثلا یک لغت قبل وبعد یادرمثال های قبل 4لغت قبل وبعد را بعنوان پنجره contextدر نظر گرفته بودیم.
درفرمول بالا
ej= بردارembadding لغت ای که اندیس jدارد.
θi=وزن های پارامترهای یک نورون iهست.
(f(Xij=وزن (اطلاعات بیشتر در کورس deep learning nlp استندفوردجلسه سوم)
هدف به دست اوردن وابستگی بین iوjاست که log آن برابر تعداددفعاتی است که i و j با هم دیده شده اند.
در ابتدا هم i و j به ازای تمام کلمات minimize می شوند.
bi و bj هم بایاس هستند.
کاربرد(f(Xij :
اگر پنجره context اندازه قبل وبعد از context برابری داشته باشندej و θi هردو یک چیز یکسانی را یادمیگیرندوهردو خواصیتی که می خواستیم را پیدا می کنند پس در فرمول نهایی میانگین این دو را در نظرمیگیریم.
چون با مشکل کوچک بودن دیتا ست مواجه هستیم پس در این قسمت word embedding ازقبل اموزش داده شده ی glove را انتخاب میکنیم.
ابتدا دیتا ست را دانلود میکنیم و ان را unzip کرده وسپس وکتورهای 100 بعدی این مجموعه را انتخاب می کنیم دراین مرحله ابتدا هر خط از دیتا ست را با line.split جدا کرده وخود لغت را از دیتاست که [value[0 دارد داخل word می ریزیم و [:values[1 که vectorهای تکرارword را تشکیل میدهندو در اینجا embadding_index را شامل می شوند.درنهایت فایل باز شده را میبندیم.
1 2 3 4 5 6 7 8 9 10 11 12 |
glove_dir = 'D:/data/' embeddings_index = {} f = open(os.path.join(glove_dir, 'glove.6B.100d.txt'), encoding="utf8") for line in f: values = line.split() word = values[0] coefs = np.asarray(values[1:], dtype='float32') embeddings_index[word] = coefs f.close() print('Found %s word vectors.' % len(embeddings_index)) |
در این گام یک ماتریس embedding vector را شامل می شود یعنی از فضای دیکشنری موجود به فضای embedding تبدیل کنیم.از قبل می دانیم max_word=10000 تا لغت پرکاربرد دیکشنری هست وبرای این ها ماتریس embedding vectorرا می سازیم که (len=10000 , shap=(10000 ,100 است.
1 2 3 4 5 6 7 8 9 |
embedding_dim = 100 embedding_matrix = np.zeros((max_words, embedding_dim)) for word, i in word_index.items(): embedding_vector = embeddings_index.get(word) if i < max_words: if embedding_vector is not None: # Words not found in embedding index will be all-zeros. embedding_matrix[i] = embedding_vector |
مدل زیر را برای شبکه در نظر گرفتیم:
1 2 3 4 5 6 7 8 9 |
from keras.models import Sequential from keras.layers import Embedding, Flatten, Dense model = Sequential() model.add(Embedding(max_words, embedding_dim, input_length=maxlen)) model.add(Flatten()) model.add(Dense(32, activation='relu')) model.add(Dense(1, activation='sigmoid')) model.summary() |
لایه ی embedding_matri را به صورت رندوم با تابع set_weighte وزن هایش را لود میکنیم.
trainable = False قرارمی دهیم چون در ابتدا خطا زیاد است و می خواهیم بردار pre_train را خراب نکند.
1 2 |
model.layers[0].set_weights([embedding_matrix]) model.layers[0].trainable = False |
مدل را compile , fit کرده ودر نهایت مدل را save میکنیم.در نهایت با مشکل overfitting مواجه میشویم چون accuracy دیتای train=1و validation=0.57 میشود چون مدل شبکه به خوبی ساخته نشده وترتیب کلمات را یاد نمی گیرد.
1 2 3 4 5 6 7 8 |
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc']) history = model.fit(x_train, y_train, epochs=10, batch_size=32, validation_data=(x_val, y_val)) model.save_weights('pre_trained_glove_model.h5') |
دراینده با مباحث RNN ها خواهیم دید که perfomance را بهبود خواهیم داد.
آمار در github :
تا 30 سپتامبر سال 2018 تنسرفلو با 111k استار و 68k فورک در رده اول قرار داشته است و این نشان می دهد استقبال از این کتابخانه بسیار زیاد است. یکی از دلایل استفاده از آن، این است که تنسرفلو کتابخانه ای است که برای deployment بسیار مناسب است. همچنین export اندروید و ios در آن بسیار راحت است. تنسرفلو برای محصول عملی و pytorch برای تست و مقاله دادن به کار می رود.
آمار جست و جو در گوگل:
این نمودار نشان می دهد میزان سرچ تنسرفلو روز به روز در حال افزایش است. دو کتابخانه ای که در رده ی دوم و سوم قرار دارند کراس و pytorch است.
مبانی تنسرفلو:
تنسرفلو فریم ورک یادگیری ژرف گوگل است که در نوامبر سال 2015 به صورت متن باز منتشر شده است. تنسرفلو می تواند عملیات محاسباتی بهینه و مشتق گیری را حساب کند، همچنین گرادیان گراف را به صورت اتوماتیک محاسبه می کند.
تنسور:
یک آرایه ی n بعدی را به اصطلاح tensor می گویند.
گراف جریان داده:
تنسرفلو برای این که بتواند توزیع پذیری داشته باشد و performance را بالا ببرد مدل برنامه نویسی graph base را ارائه داده و محاسبات را از اجرا جدا کرده است.
مثال: با توجه به شکل زیر دو نود برای input ، یک نود برای mul و یک نود برای add داریم. خروجی نود mul و add می توانند به یک نود add دیگر بروند. حالا می توانیم به input ها عدد بدهیم و خروجی را ببینیم.
گام اول: تعریف یک گراف محاسباتی (در این گراف هیچ عددی وجود ندارد)
گام دوم: تعریف یک session که اجازه ی ورودی دادن، اجرای عملیات و گرفتن خروجی را می دهد. (وقتی session ساخته می شود به آن سخت افزار اختصاص داده می شود)
مثال:
1 2 |
import tensorflow as tf a = tf.add(2, 3) |
با نوشتن کد بالا یک گراف به شکل زیر ساخته می شود:
نکته: اگر به صورت صریح رئوس گراف محاسباتی را نامگذاری نکنید، تنسورفلو به صورت خودکار به آنها نام نسبت میدهد. برای مثال در این جا به نود ها نام های x و y داده شده است.
هر زمان که به آن session اختصاص داده شود به x عدد 2 و به y عدد 3 را می دهد و خروجی 5 تولید می شود.
برنامه ساده استفاده از numpy :
بازنویسی با tensorflow :
1 2 3 4 |
import tensorflow as tf tf.InteractiveSession() a = tf.zeros((2,2)); b = tf.ones((2,2)) tf.reduce_sum(b, reduction_indices=1).eval() |
1 2 |
out: [(([([(TensorShape([Dimension(2), Dimension(2 |
1 |
()a.get_shape |
1 2 |
out: array([ 2., 2.], dtype=float32 |
1 |
(()tf.reshape(a, (1, 4)).eval |
1 2 |
Out: (array([[ 0., 0., 0., 0.]], dtype=float32 |
()Tf.InteractiveSession : برای ساخت session است. می توانیم به آن ورودی دهیم و مشخص کنیم از چه مقدار از gpu استفاده کند. در صورتی که ورودی به آن داده نشود از کل gpu موجود استفاده می کند.
InteractiveSession سشنی است که در محیط های jupyter notebook و محیط هایی که تعاملی است استفاده می شود ولی چون overhead آن زیاد است برای deployment استفاده نمی شود و در آنجا session معمولی کاربرد دارد.
به جای np.zeros و np.ones ، tf.zeros و tf.ones قرار گرفته ولی سینتکس آن تغییری نکرده است.
به جای sum از tf.reduce_sum استفاده شده است (تاکید می کند که dimention در حال کوچک شدن است) همچنین به جای axis از reduction_indices استفاده می شود.
Eval : معمولا وقتی interactivesession داریم از eval استفاده می شود. eval سشن فعالی که وجود دارد را پیدا کرده و آن را اجرا می کند. چون eval را صدا زدیم آرایه ی [2. , 2.] را برمی گرداند و مشخص می کند datatype آن float32 است.
()Get_shape : یک TensorShape برمی گرداند که مثل tupple ها در پایتون رفتار می کنند.
سپس reshape شده و eval دوباره صدا زده می شود تا گراف اجرا شود.
مقایسه استفاده از numpy و tensorflow :
تنسورفلو نیاز به ارزیابی صریح دارد. یعنی برای مثال اگر یک numpy از ماتریس صفر 2 در 2 تعریف شود بعد از پرینت کردن آن، 4 تا صفر را نمایش می دهد اما اگر این ماتریس با تنسورفلو تعریف شود در خروجی یک گراف محاسباتی ساخته می شود که تا زمانی ارزیابی نشود هیچ مقدار عددی ندارد.
1 2 |
((a = np.zeros((2,2 ((ta = tf.zeros((2,2 |
1 2 3 |
(print(a [[ 0. 0.] [ 0. 0.]] |
1 2 |
(print(ta (Tensor("zeros_1:0", shape=(2, 2), dtype=float32 |
با فرض این که یک session فعال ساخته شده باشد اگر eval صدا زده شود ta اجرا می شود.
1 2 3 |
<strong>(()print(ta.eval </strong>[[ 0. 0.] [ 0. 0.]] |
یک شیء session محیطی که در آن اشیاء تنسور ارزیابی می شوند را کپسولهسازی میکند. یعنی بعد از ساختن گراف یک session می سازیم که این session اجازه می دهد اشیا داخل آن اجرا و evaluate شوند.
مثال:
1 2 3 4 5 6 7 8 9 10 |
(a = tf.constant(5.0 (b = tf.constant(6.0 c = a * b :with tf.Session() as sess ....: print(sess.run(c)) ....: print(c.eval()) ....: 30.0 30.0 |
Tf.constant یک عدد constant تعریف می کند. در این مثال از with block برای ساخت session استفاده شده که اصطلاحا به آن contex manager گفته می شود. وقتی یک session ساخته می شود باید حتما آن را close کنیم اما وقتی از contex manager استفاده می شود بعد از خارج شدن از tab ، session خود به خود بسته می شود. برای اجرا شدن session از دستور sess.run استفاده شده که همان کار eval را انجام می دهد.
گراف که ساخته می شود چیزی مانند شکل زیر است:
بعد از این که session به آن اختصاص داده شد سخت افزار را در اختیار می گیرد و بعد از آن اصطلاحا عمل feed و fetch انجام می شود. جایی که input را می دهیم اصطلاحا به شبکه feed می شود و هر خروجی که بخواهیم را fetch می کنیم.
هر session منابعی را در اختیار می گیرد که gpu ، cpu و ram از مهم ترین آن هاست. این منابع پس از استفاده باید حتما آزاد شوند. همچنین می توانیم مشخص کنیم که هر session چه مقدار از این منابع را می تواند استفاده کند.
یک سینتکس دیگر این است که می توان از contex manager استفاده کرد. به این صورت که دستورات داخل indent نوشته می شود و هر وقت تمام شد خودش session را می بندد.
همچنین می توان از InteractiveSession استفاده کرد که فقط برای notebook کاربرد دارد.
مثال:
1 2 3 4 5 6 7 |
x = tf.constant(2) y = 3 op1 = tf.add(x, y) op2 = tf.mul(x, y) op3 = tf.pow(op2, op1) with tf.Session() as sess: op3 = sess.run(op3) |
هر عملیاتی که تعریف می کنیم یک نود در گراف ساخته می شود.
(x=tf.constant(2 و y=3 از لحاظ سینتکس هیچ فرقی باهم ندارند.
مثال:
1 2 3 4 5 6 7 8 |
x = 2 y = 3 op1 = tf.add(x, y) op2 = tf.mul(x, y) useless = tf.mul(x, op1) op3 = tf.pow(op2, op1) with tf.Session() as sess: op3 = sess.run(op3) |
چون خروجی خواسته شده op3 است که برابر با op2 به توان op1 است، نودی که به اسم useless نامگذاری شده در اجرا در نظر گرفته نمی شود و ارزیابی نمی شود بنابراین سربار اجرایی ندارد.
اگر به session.run یک لیست داده شود یک tupple برگردانده می شود.
1 2 3 4 5 6 7 8 |
x = 2 y = 3 op1 = tf.add(x, y) op2 = tf.mul(x, y) useless = tf.mul(x, op1) op3 = tf.pow(op2, op1) with tf.Session() as sess: op3, not_useless = sess.run([op3, useless]) |
بنابراین در این مثال نود useless هم نیاز به ارزیابی دارد.
Variable ها در تنسورفلو:
برای چیزی که معمولا قرار است train شود از variable ها استفاده می شود. هنگامی که مدل را آموزش می دهیم برای نگه داری پارامتر ها نیاز به تعریف متغیر داریم. یعنی معمولا trainable هستند.
در ساختن شبکه عصبی و شبکه کانولوشنی نیاز به variable داریم که بتوان مقادیر آن را عوض کرد.
نکته: Weight و bios ها variable در نظر گرفته می شوند.
Varaiable ها در مواردی به کار می روند که نیاز به به روز رسانی متغیر باشد.
استفاده از کراس در تنسرفلو:
با دستور tf.keras میتوان بدون نصب کردن keras ، در محیط تنسورفلو به آن دسترسی داشت. کراس در محیط تنسورفلو function های بیشتری نیز دارد. برای مثال اگر بخواهیم چند ورودی و چند خروجی داشته باشیم یا از چند سرور ورودی بگیریم یا بخواهیم شبکه را به صورت multi gpu فیت کنیم می توانیم از api تنسورفلو به نام dataset استفاده کنیم.
مثال:
1 2 3 4 5 6 |
W1 = tf.ones((2,2)) W2 = tf.Variable(tf.zeros((2,2)), name="weights") with tf.Session() as sess: print(sess.run(W1)) sess.run(tf.initialize_all_variables()) print(sess.run(W2)) |
1 2 3 4 |
[[ 1. 1.] :out [ 1. 1.]] [[ 0. 0.] [ 0. 0.]] |
W1 یک contant است.
W2 یک variable است. (یک ماتریس 2 در 2 که از صفر پر شده)
اگر بخواهیم از variable ها استفاده کنیم حتما باید ابتدا آن ها را initialize کنیم. با دستور tf.initialize_all_variables() تمام variable هایی که تعریف کردیم initialize می شوند.
مقایسه variable و constant :
1 2 3 4 5 6 |
W = tf.Variable(tf.zeros((2,2)), name="weights") R = tf.Variable(tf.random_normal((2,2)), name="random_weights") with tf.Session() as sess: ....: sess.run(tf.global_variables_initializer()) ....: print(sess.run(W)) ....: print(sess.run(R)) |
variable را می توان با ثابت ها (“name=”weights) یا مقادیر تصادفی (“name=”random_weights) مقداردهی اولیه کرد.
با نوشتن tf.global_variables_initializer تمام متغیر ها با مقادیر از قبل مشخص شده مقداردهی اولیه می شوند.
به روز رسانی یک متغیر:
1 2 3 4 5 6 7 8 9 |
state = tf.Variable(0, name="counter") new_value = tf.add(state, tf.constant(1)) update = tf.assign(state, new_value) with tf.Session() as sess: sess.run(tf.global_variables_initializer()) print(sess.run(state)) for _ in range(3): sess.run(update) print(sess.run(state)) |
در ابتدا یک state تعریف شده که مقدار اولیه آن صفر است.
یک متغیر به نام new_value تعریف شده که مجموع یک تنسور که داخل آن یک قرار دارد و تغییر نمی کند با state است. این متغیر معادل new_value = state + 1 است.
در داخل update یک tf.assign تعریف شده که مقدار تنسور new_value را داخل state می ریزد و معادل state = new_value است.
سپس برای اجرا شدن operation های ساخته شده یک session ایجاد می شود.
با اجرای قطعه کد sess.run(tf.global_variables_initializer()) یک state با مقدار صفر ساخته می شود.
داخل فور update اجرا می شود که یک واحد به state اضافه می کند بنابراین خروجی این قطعه کد …,0,1,2,3 است.
پیاده سازی تنسرفلو (نت بوک 35: intro to tensorflow):
تعریف کتابخانه:
1 |
import tensorflow as tf |
نمایش ورژن تنسرفلو:
1 |
tf.__version__ |
1 2 3 4 |
hello = tf.constant("Hello, TensorFlow!") sess = tf.Session() print(sess.run(hello)) |
ابتدا یک tf.constant سپس یک session ساخته شده است. خروجی این قطعه کد b’Hello, Tensorflow’ است. ( b نشان دهنده ی byte literal است.)
1 2 |
node1 = tf.constant(3.0, dtype=tf.float32) node2 = tf.constant(4.0) # also tf.float32 implicitly |
سپس دو constant دیگر با مقادیر3.0 و 4.0 ساخته شده. در داخل constant ها می توان نوع آن ها را مشخص کرد. datatype آن ها به صورت پیش فرض float32 است.
معمولا در فرایندtraining از float های بزرگی استفاده می شود زیرا در عملیات به روز رسانی به ما کمک می کند. اما معمولا در inference وقتی کار با گراف تمام شد و فقط می خواهیم عکس را بگیریم و خروجی را ببینیم نیاز به دقت بالایی ندارد.
یکی از بهینه سازی هایی که در تنسورفلو انجام می شود این است که در گرافی که train شده float64 ها را برمی دارد و با یک int یا float کوچک تر جایگزین می کند تا حجم گراف پایین بیاید.
سپس session ایجاد شده را با دستور sess.close() می بندیم.
نمایش node1 و node2 قبل از ساختن session :
1 2 |
print(node1, node2) out: Tensor("Const_1:0", shape=(), dtype=float32) Tensor("Const_2:0", shape=(), dtype=float32) |
نمایش node1 و node2 بعد از ساختن session :
نکته: چون session جاری مشخص است هم می توانیم هم از eval و هم از sess.run استفاده کنیم.
1 2 3 4 5 |
sess = tf.Session() print(sess.run([node1, node2])) sess.close() out: [3.0, 4.0] |
مثال:
1 2 3 4 |
with tf.Session() as sess: node3 = tf.add(node1, node2 , name='my_add') print("node3:", node3) print("sess.run(node3):", sess.run(node3)) |
در ابتدا یک نود add تعریف شده است. با اجرای دستور (print(node3 اسم تنسور برگردانده می شود.
1 |
(out: node3: Tensor("my_add:0", shape=(), dtype=float32 |
اما با اجرای دستور ((print(sess.run(node3 یک session ساخته شده و مقادیر node1 و node2 با هم جمع می شوند.
1 |
out: sess.run(node3): 7.0 |
Fetch کردن (واکشی):
1 2 3 4 5 6 7 8 |
input1 = tf.constant(3.0) input2 = tf.constant(2.0) input3 = tf.constant(5.0) intermed = tf.add(input2, input3) mul = tf.mul(input1, intermed) with tf.Session() as sess: result = sess.run([mul, intermed]) print(result) |
به قطعه کد ([result = sess.run([mul, intermed اصطلاحا fetch کردن می گویند. در واقع فراخوانی (sess.run(var در یک نشست مقدار آن متغیر را می دهد. می توان چندین متغیر را همزمان fetch کرد.
1 |
out: result = sess.run([mul, intermed]) |
مثال:
1 2 3 4 5 6 7 8 |
input1 = tf.constant(3.0) input2 = tf.constant(2.0) input3 = tf.constant(5.0) intermed = tf.add(input2, input3) mul = tf.mul(input1, intermed) with tf.Session() as sess: result = sess.run([mul, intermed]) print(result) |
در این کد نیز همانند قبل یک گراف ساخته شده است. دو constant به نام های input2 و input3 ، intermediate را ساختند. همچنین tf.mul یک نود است و با اجرای (sess.run(mul,intermed کل گراف ارزیابی می شود.
تمام تنسور های استفاده شده در کد های بالا به صورت دستی تعریف می شدند.
انواع تنسور های استفاده شده تا اینجا:
1. constant
2. variable
نحوه ورودی دادن بیرون از تنسرفلو:
یک راه ساده استفاده از numpy است. می توان numpy را تبدیل به تنسور کرد و تنسور را به شبکه feed کرد. مشکل این روش این است که برای داده های زیاد جواب نمی دهد زیرا نمی توان همه ی داده ها را در ram لود و آن ها را به تنسور تبدیل کرد. به عبارت دیگر ورودی با استفاده از numpy امکان پذیر و راحت است اما مقیاس پذیر نیست.
مثال (ورودی با استفاده از numpy ):
1 2 3 4 |
a = np.zeros((3,3)) ta = tf.convert_to_tensor(a) with tf.Session() as sess: print(sess.run(ta)) |
1 2 3 |
[.out: [[ 0. 0. 0 [ 0. 0. 0.] [ 0. 0. 0.]] |
روش دیگر استفاده از placeholder و feed dictionary است.
Placeholder : یک نوع تنسور هستند که منتظر ورودی اند.
در ابتدای ساخت گراف placeholder ها بر خلاف variable ها و constant ها هیچ مقداری ندارند و منتظر کاربر می مانند که آن ها را مقدار دهی کند.
1 2 3 4 5 6 7 |
input1 = tf.placeholder(tf.float32) input2 = tf.placeholder(tf.float32) output = tf.mul(input1, input2) with tf.Session() as sess: print(sess.run([output], feed_dict={input1:[7.], input2:[2.]})) [array([ 14.], dtype=float32)] |
دو placeholder با نام های input1 و input2 تعریف شده که نوع آن ها floate32 است.
ورودی های این گراف در حال حاضر مشخص نیستند. اگر بعد از ساختن session فقط sess.run() نوشته شود (عملیات fetch کردن)، متوجه نمی شود با چه مقداری باید مقداردهی اولیه را انجام دهد. در این جا باید از feed dictionary استفاده شود.
Feed dict مشخص می کند نود ساخته شده به چه placeholder هایی نیاز دارد و سپس این placeholder ها را به آن پاس می دهد.
1 2 3 4 |
a = tf.placeholder(tf.float32) b = tf.placeholder(tf.float32) adder_node = a + b # + provides a shortcut for tf.add(a, b) adder_node |
1 2 3 |
out: 7.5 [3. 7.] |
1 2 |
add_and_triple = adder_node * 3. print(sess.run(add_and_triple, {a: 3, b: 4.5})) |
این قطعه کد خروجی کد قبلی را در 3 ضرب کرده است.
1 |
out: 22.5 |
1 2 3 4 5 6 7 |
W = tf.Variable([.3], dtype=tf.float32) b = tf.Variable([-.3], dtype=tf.float32) x = tf.placeholder(tf.float32) linear_model = W*x + b init = tf.global_variables_initializer() sess.run(init) print(sess.run(linear_model, {x:[1,2,3,4]})) |
ابتدا دو variable و یک placeholder تعریف شده است. Variable ها (w و b ) می توانند بعدا به روز شوند و placeholder ها (x ) باید از کاربر گرفته شوند.
سپس global_variables_initializer برای مقدار دهی متغیر ها صدا زده شده و بعد از آن linear_model با 1,2,3,4 فراخوانی شده است.
1 |
out: [0. 0.3 0.6 0.90000004] |
1 2 3 4 5 |
state = tf.Variable(0, name="counter") new_value = tf.add(state, tf.constant(1)) update = tf.assign(state, new_value) sess = tf.Session() sess.run(tf.global_variables_initializer()) print(sess.run(update)) |
1 |
out: 1 |
در این کد با اجرای sess.run(update) ابتدا خروجی یک است و هر بار که اجرا می شود یک واحد به آن اضافه می شود.
1 |
print(sess.run(update)) |
1 |
out: 2 |
1 2 3 4 5 6 7 8 |
Sess.close() x = tf.Variable(0, name='counter') inc = tf.assign_add(x, 1, name='increment') with tf.Session() as sess: sess.run(tf.global_variables_initializer()) for _ in range(5): print (sess.run(inc)) |
assign_add یک واحد به متغیر x اضافه می کند.
1 2 3 4 5 6 |
out: 1 2 3 4 5 |
میانگین گیری (tf.reduce_mean):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
x = tf.constant([[1.0,2.0], [3.0,4.0]]) Mean1 = tf.reduce_mean(x) Mean2 = tf.reduce_mean(x,0) Mean3 = tf.reduce_mean(x,1) with tf.Session() as sess: result1 = sess.run(Mean1) result2 = sess.run(Mean2) result3 = sess.run(Mean3) print(result1) print(result2) print(result3) |
1 2 3 4 |
out: 2.5 [2. 3.] [1.5 3.5] |
1 |
type(result1) |
1 2 |
out: numpy.float32 |
ضرب ماتریسی (tf.reduce_mean):
1 2 3 |
a = tf.constant([1, 2, 3, 4, 5, 6], shape=[2, 3]) b = tf.constant([7, 8, 9, 10, 11, 12], shape=[3, 2]) sess = tf.Session() |
1 |
sess.run(a) |
1 2 |
out: array([[1, 2, 3], [4, 5, 6]]) |
1 |
sess.run(b) |
1 2 3 |
out: array([[ 7, 8], [ 9, 10], [11, 12]]) |
1 2 |
c = tf.matmul(a, b) sess.run(c) |
matmul برای ضرب ماتریسی استفاده می شود.
1 2 |
out: array([[ 58, 64], [139, 154]]) |
1 2 |
d=c+tf.constant([10,2]) sess.run(d) |
1 2 |
out: array([[ 68, 66], [149, 156]]) |
بیشینه گیری (tf.argmax):
1 2 3 4 5 6 7 8 9 |
x = tf.constant([[1.0,2.0], [4.0,3.0]]) m1=tf.argmax(x,0) m2=tf.argmax(x,1) with tf.Session() as sess: r1=sess.run(m1) r2=sess.run(m2) print(r1) print(r2) |
1 2 3 |
out: [1 1] [1 0] |
آموزش تنسربرد:
گاهی اوقات کار با شبکه های عصبی و دیگر شبکه ها بسیار پیچیده می شود بنابراین تنسرفلو مجموعه ای از ابزار های تجسمی به نام تنسربرد را ارائه داده تا بتوانیم نمودار های تنسرفلو را در این محیط شبیه سازی کنیم و متغیر های مربوط به آن را نمایش دهیم.
نصب و راه اندازی تنسربرد:
ابتدا باید پکیج تنسرفلو را با دستور pip install tensorflow روی پایتون نصب کنیم. سپس برای اجرا در در محل ذخیره کتابخانه تنسرفلو، دستور tensorboard –logdir asset را در ترمینال وارد می کنیم. این دستور نشان می دهد تنسربرد بر روی local host قابل دسترسی است.
نمایش گراف در تنسربرد:
مراحل:
ابتدا برنامه خود را تعریف کرده و متغیر ها را مقدار دهی می کنیم:
سپس یک summary برای ذخیره نتایج تعریف می کنیم:
در مرحله بعد یک session ساخته و برنامه نوشته شده را در تنسربرد نمایش می دهیم:
داشبورد های تنسربرد:
scalar:
برای نمایش آمار وابسته به زمان استفاده می شود. برای مثال در مشاهده عملکرد loss function کاربرد دارد.
histogram:
این داشبورد در تنسربرد نشان می دهد چگونه توزیع آماری تنسور در طول زمان متغیر است. این داده ها از طریق tf.summary.histogram نمایش داده می شوند.
distribution:
در این داشبورد از tf.summary.histogram استفاده می شود که درصد توزیع روی داده ها را نمایش می دهد.
graph:
این داشبورد عمدتا برای بررسی مدل تنسرفلو استفاده می شود.
image:
این داشبورد عکس هایی که با دستور tf.summary.image و در فرمت png ذخیره شده است را نمایش می دهد.
audio:
این داشبورد یک ابزار قوی برای تعبیه ویجت های صوتی برای مخاطبان است که از طریق tf.summary.audio نمایش داده می شود.
projector:
اساسا پروژکتور تعبیه شده در تنسربرد برای داده های چند بعدی استفاده می شوند.
text:
این داشبورد متن را از طریق tf.summary.text ذخیره می کند و شامل مواردی مانند پیوند ها، فهرست ها و جداول می باشد.
مدل seq2seq یکی از مدل های مورد استفاده در سیستمهای تولید پاسخ است که می تواند احتمال تولید پاسخ مناسب را بیشینه کند.
مدل رمزنگار-رمزگشا LSTM (encoder-decoder) یک شبکه عصبی بازگشتی برای مسائلی است که یک دنباله را به دنباله دیگر نگاشت میکند. (یک دنباله به عنوان ورودی دریافت می کند و خروجی، دنباله دیگری است.) به همین دلیل به آن مدل seq2seq نیز گفته میشود. در مسائل seq2seq دنباله ورودی و خروجی میتوانند دارای طول های متفاوتی باشند به همین دلیل این مسائل از اهمیت زیادی برخوردار بوده و چالش برانگیزند.
مدل seq2seq، در مسائلی چون ترجمه ماشینی، خلاصهکردن متن و تشخیص گفتار کاربرد دارد. به طور مثال در ترجمه ماشینی سنتی، جملهها به چند قسمت تقسیم میشوند و هر قسمت به صورت جداگانه ترجمه میشود که منجر به اختلال در خروجی ترجمه میگردد. این در حالی است که انسان ها برای ترجمه یک جمله ابتدا معنای آن را متوجه میشوند سپس جمله را ترجمه میکنند. مدل seq2seq در ترجمه ماشینی از روش انسان گونهی ترجمه پیروی میکند.
این معماری از ترکیب دو مدل تشکیل شده است:
۱) رمزنگار (Encoder)
۲) رمزگشا (Decoder)
شبکه رمزگشا(decoder)، خروجی (state) شبکه رمزنگار (encoder) را به عنوان ورودی دریافت می کند.
مثلا جمله ای یا سوالی به عنوان ورودی به شبکه بدهیم و پاسخی به عنوان خروجی دریافت کنیم.
مثال : جمله فرانسوی زیر را در نظر بگیرید قصد داریم این جمله به عنوان ورودی به Encoder بدهیم و معادل یا ترجمه آن را به انگلیسی از Decoder دریافت کنیم.
Image-captioning به روش Encoder-Decoder
با وارد کردن یک عکس به عنوان ورودی به یک شبکه pretrained مثل (VGG16,AlexNet,…) و پس از استخراج ویژگی ها، لایه آخر که softmax می باشد را حذف می کنیم و لایه ماقبل آن (۴۰۹۶تایی) را به عنوان ورودی به شبکه بعدی (Decoder) می دهیم.
ماشین ترجمه ( Mashine translation )، به عنوان ساختمان یک مدل زبان شرطی (conditional language model) عمل می کند.خروجی هر مرحله، ورودی مرحله بعدی است که می خواهیم p در آن به حداکثر مقدار برسد.
یک Mashine translation و یک language model از نظر ساختار و کاربرد با یکدیگر فرقی ندارند و دو شبکه باهم برابر هستند. هر دو زبان مبدا را به عنوان ورودی دریافت می کنند و زبان مقصد را به عنوان خروجی تولید (generate) می کنند.تنها تفاوت آن ها در این است که language model یک وکتور ۰ را به عنوان ورودی اولیه می گیرد ولی Mashine translation ، خروجی (state) مرحله قبلی را به عنوان ورودی می گیرد.
مدل زبان شرطی، احتمال تولید جمله خروجی را به شرط دریافت جمله ورودی برمی گرداند.
الگوریتم حریصانه (greedy)
الگوریتم حریصانه شبیه روشهای پویا اغلب برای حل مسائل بهینه سازی استفاده میشوند.این الگوریتم بهترین انتخاب را با توجه به شرایط مسئله انجام میدهد به امید آنکه با ادامهٔ همین روش بهینهسازی انجام شود.,ولی در مدل زبان شرطی (Conditional Languge model) اغلب به پاسخ درستی نمی رسیم.
به عنوان مثال جمله ی فرانسوی زیر را در نظر بگیرید :
از بین ترجمه های زیر ، ترجمه ی اول به عنوان ترجمه درست می باشد ولی با استفاده از الگوریتم حریصانه ترجمه دوم به عنوان ترجمه درست انتخاب می شود که نشان دهنده ی آن است که الگوریتم حریصانه روش مناسبی نیست.
الگوریتم Beam Search
در این الگوریتم پارامتری به نام beam width داریم که به تعداد آن محتمل ترین ها را در مرحله اول برمیداریم به طول مثال همان طور که در شکل زیر می بینید فرض کنید یک دیکشنری ۱۰۰۰۰تایی داشته باشیم و B = ۳ باشد در این صورت ۳ کلمه را به عنوان محتمل ترین انتخاب می کنیم و احتمال آن را محاسبه می کنیم.
در مرحله بعد هر کدام از کلماتی که انتخاب کردیم از ۱۰۰۰۰تا کلمه دیگر باید کلمه محتمل بعدی را انتخاب کنند که در این حالت ۳۰۰۰۰تا حالت داریم و این مراحل را تا جایی ادامه می دهیم که به <EOS> برسیم و احتمالات آن هارا محاسبه می کنیم.
این روش مشکلاتی نیز دارد. زمانی که تعدادی عدد بین ۰ و ۱ در هم ضرب شود عددی که به دست می آید آنقدر کوچک می شود که ممکن است کامپیوتر قادر به ذخیره سازی آن نباشد.(under flow رخ می دهد.)
برای حل این مشکل از تابع لگاریتم که روند افزایشی-صعودی دارد استفاده می کنیم.(تابع لگاریتم بین ۰ و ۱ مقادیر منفی دارد.)به صورت زیر :
در این مرحله هنوز وابسته به طول خروجی می باشد برای حل این مشکل تقسیم بر طول جمله می کنیم.(به صورت زیر)
اصلاحات روش Beam search
در production اعداد بیشتری را برای Beam width می گذاریم ولی در research اعداد کمتری می گذاریم.کوچک یا بزرگ گذاشتن Beam width هر کدام مزایا و معایبی دارد.
زمانی که Beam width را بزرگ انتخاب کنیم از فضای کلی مسئله فضای بیشتری را پوشش می دهد و احتمال اینکه به جواب درست برسیم بیشتر است و نتیجه بهتری می دهد ولی سرعت آن کمتر می باشد.
زمانی که Beam width را کوچک انتخاب کنیم احتمال اینکه به جواب درست برسیم کمتر است و فضای کمتری از مسئله را پوشش می دهد ولی سرعت آن بیشتر می باشد.برخلاف الگوریتم های BFS و DFS که بهترین ها را انتخاب می کنند این روش لزوما بهترین ها را انتخاب نمی کند.
آنالیز ارورهای روش Beam search
مثال:
جمله فرانسوی زیر را در نظر داشته باشید.ترجمه ی اول، ترجمه ی درست و ترجمه ی دوم، ترجمه ای است که شبکه تولید کرده است.
دو حالت برای اشتباه ترجمه کردن توسط شبکه وجود دارد :
۱)RNN(تعداد دیتای آموزش دیده کم باشد.)
۲)Beam width(مقدار آن کم تعیین شده باشد.)
اگر حالت زیر پیش بیاید مشکل از مقدار Beam width می باشد و باید مقدار بیشتری را انتخاب کنیم. واگر حالت زیر پیش بیاید مشکل از RNN هاست و باید آن ها را اصلاح کنیم.
در نهایت این مقایسه برای تمامی جملات ترجمه شده صورت می گیرد که مشکل مشخص شود و اصلاحات صورت بگیرد.
caption generation یا تولید یک توصیف به عنوان یک چالش در هوش مصنوعی مطرح شده است که در آن باید یک توصیف متنی برای یک عکس دلخواه داده شده به آن تولید کند .
این عملیات به هر دو روش از بینایی کامپیوتر برای درک محتوای تصویر و یک مدل زبان از زمینه پردازش زبان طبیعی برای تبدیل درک یک تصویر به کلمات برای تولید یک عنوان یا توصیف در جهت درست است .
به تازگی روش های یادگیری عمیق روی نمونه هایی از این چالش به نتایج پیشرفته ای دست یافته اند .
آنچه در این روش ها تاثیر گذار است یک مدل end to end به منظور پیش بینی یک عنوان یا توصیف با توجه به یک عکس است بجای نیاز به تهیه داده های پیچیده و یا یک pipeline از مدل های طراحی شده خاص .
در این آموزش شما یاد می گیرید که چگونه یک مدل یادگیری عمیق را در جهت تولید یک عنوان برای یک عکس ، از ابتدا ایجاد کنید .
این آموزش به 7 بخش تقسیم میشود که عبارت است از :
نیازمندی های این آموزش :
یک مجموعه داده خوب برای استفاده در هنگام گرفتن عناوین تصویر ، مجموعه داده Flickr8k است . به دلیل اینکه این مجموعه داده واقعی و نسبتا کوچک است به همین خاطر شما میتوانید آن را دانلود کنید و روی سیستم خودتان (روی CPU) مدل های مختلف بسازید .
نویسندگان مقاله ای که این مجموعه دیتاست را در اختیار ما قرار داده اند ، این مجموعه داده را بصورت زیر شرح میدهند :
ما یک مجموعه معیار جدید برای توصیف تصویر و جستجو را که شامل 8000 تصویر است ارائه داده ایم که هر کدام با 5 عنوان مختلف مرتبط شده اند.
تصاویر از 6 گروه مختلف Flickr انتخاب شده و وابسته به هیج یک از افراد یا مکان های مشهور نیستند ، اما بصورت دستی انتخاب شده تا صحنه ها و موقعیت های مختلف و تفاوتشان را نشان دهند .
دیتاست به صورت رایگان در دسترس است و بعد از دانلود دیتاست شما 2 فایل دارید .
این مجموعه دارای مجموعه داده های از پش تعریف شده (6000 عکس) ، محموعه داده های توسعه داده شده (1000 عکس) و مجموعه داده آزمون (1000 عکس) است .
یک معیار برای ارزیابی مهارت مدل ، نمرات BLEU است . این معیار را در ادامه هنگام ارزیابی مدل بیشتر توضیح میدهیم .
در اینجا از یک مدل از پیش آموزش داده شده برای تفسیر محتوای عکس استفاده شده است . مدل های زیادی برای انتخاب وجود دارند . در اینجا از مدل oxford visual geometry یا VGG استفاده شده است .
keras این مدل از قبل آموزش داده شده را بطور مستقیم فراهم میکند . توجه داشته باشید که شما اولین بار که از این مذل استفاده میکنید ، کراس وزن ها را دانلود میکند که حدود 500 مگابایت است .
میتوان از این مدل به عنوان بخشی از یک الگوی بزرگنمایی تصویر استفاده کرد . مشکل آن است که این مدل بزرگ است و هر بار که میخواهیم ساختار یک مدل جدید زبان را آزمایش کنیم ، هر عکس در سرتاسر شبکه اجرا میشود .
در مقابل ما میتوانیم ویژگی های تصویر را از قبل با استفاده از مدل از پیش آموزش داده شده محاسبه کنیم و آن را در یک فایل ذخیره کنیم . سپس میتوان این ویژگی ها را بعدا load کنیم و آن ها را به عنوان ترجمه و تفسیر هر عکس در دیتاست به مدل خود feed کنیم.
این کار در VGG به همین صورت است با این تفتوت که در آنجا فقط یک بار انجام میگیرد .
این عملیات همان بهینه سازی است که باعث تسریع در آموزش مدل و مصرف حافظه کمتر میشود .
مدل VGG را در کراس با کلاس VGG بارگذاری میکنیم و لایه ی آخر را از مدل load شده حذف میکنیم زیرا این مدل برای پیش بینی طبقه بندی یک تصویر استفاده میشود .
keras ابزار هایی برای تغییر اندازه تصویر load شده دارد (مثلا 3 کانال 224 * 224 pixel image)
در زیر تابعی به اسم ()extract_features داریم با اسم دایرکتوری که میگیرد آن تصویر را load میکند ، آن را برای VGG آماده میکند ویژگی های پیش بینی شده از مدل VGG را جمع آوری میکند . ویژگی های تصویر یک بردار 1 بعدی 4096 تایی از عناصر است . این تابع یک دیکشنری از شناسه ی تصویر برای ویژگی های تصویر بر میگرداند .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# extract features from each photo in the directory def extract_features(directory): # load the model model = VGG16() # re-structure the model model.layers.pop() model = Model(inputs=model.inputs, outputs=model.layers[-1].output) # summarize print(model.summary()) # extract features from each photo features = dict() for name in listdir(directory): # load an image from file filename = directory + '/' + name image = load_img(filename, target_size=(224, 224)) # convert the image pixels to a numpy array image = img_to_array(image) # reshape data for the model image = image.reshape((1, image.shape[0], image.shape[1], image.shape[2])) # prepare the image for the VGG model image = preprocess_input(image) # get features feature = model.predict(image, verbose=0) # get image id image_id = name.split('.')[0] # store feature features[image_id] = feature print('>%s' % name) return features |
این تابع را میتوان برای تهیه داده های تصویر برای آزمایش مدل هایمان صدا بزنیم ، سپس دیکشنری تولید شده را با نام features.pkl ذخیره کنیم . کد بصورت کامل تر :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
from os import listdir from pickle import dump from keras.applications.vgg16 import VGG16 from keras.preprocessing.image import load_img from keras.preprocessing.image import img_to_array from keras.applications.vgg16 import preprocess_input from keras.models import Model # extract features from each photo in the directory def extract_features(directory): # load the model model = VGG16() # re-structure the model model.layers.pop() model = Model(inputs=model.inputs, outputs=model.layers[-1].output) # summarize print(model.summary()) # extract features from each photo features = dict() for name in listdir(directory): # load an image from file filename = directory + '/' + name image = load_img(filename, target_size=(224, 224)) # convert the image pixels to a numpy array image = img_to_array(image) # reshape data for the model image = image.reshape((1, image.shape[0], image.shape[1], image.shape[2])) # prepare the image for the VGG model image = preprocess_input(image) # get features feature = model.predict(image, verbose=0) # get image id image_id = name.split('.')[0] # store feature features[image_id] = feature print('>%s' % name) return features # extract features from all images directory = 'Flicker8k_Dataset' features = extract_features(directory) print('Extracted Features: %d' % len(features)) # save to file dump(features, open('features.pkl', 'wb')) |
این مجموعه داده شامل توضیحات متعدد برای هر تصویر است و متن توضیحات نیاز به مقداری تغییرات دارد . در ابتدا فایل شامل همه توصیفات را load میکنیم .
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# load doc into memory def load_doc(filename): # open the file as read only file = open(filename, 'r') # read all text text = file.read() # close the file file.close() return text filename = 'Flickr8k_text/Flickr8k.token.txt' # load descriptions doc = load_doc(filename) |
هر تصویر شناسه ی واحد مختص به خود دارد . این شناسه روی اسم فایل عکس و در فایل متنی توضیحات استفاده میشود .
سپس براساس لیست توضیحات عکس قدم برمیداریم . در زیر تابعی داریم به اسم ()load_descriptions تعریف شده است که یک دیکشنری از شناسه تصویر برای توضیحات برمیگرداند . هر شناسه تصویر به یک لیستی از یک یا چند توصیف متنی map میکند .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# extract descriptions for images def load_descriptions(doc): mapping = dict() # process lines for line in doc.split('\n'): # split line by white space tokens = line.split() if len(line) < 2: continue # take the first token as the image id, the rest as the description image_id, image_desc = tokens[0], tokens[1:] # remove filename from image id image_id = image_id.split('.')[0] # convert description tokens back to string image_desc = ' '.join(image_desc) # create the list if needed if image_id not in mapping: mapping[image_id] = list() # store description mapping[image_id].append(image_desc) return mapping # parse descriptions descriptions = load_descriptions(doc) print('Loaded: %d ' % len(descriptions)) |
سپس باید متن توصیف را تمیز کنیم . توصیفات همواره نشانه گذاری شده هستند و کار با آن ها آسان است .
متن را با شیوه های زیر تمیز میکنیم تا اندازه کلماتی که باید با آن ها کار شود را کاهش دهیم :
در زیر تابع ()clean_descriptions تعریف شده است که دیکشنری از شناسه ی تصاویر به توضیحات را میگیرد و با قدم برداشتن در طول هر توصیف آن را تمیز میکند .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import string def clean_descriptions(descriptions): # prepare translation table for removing punctuation table = str.maketrans('', '', string.punctuation) for key, desc_list in descriptions.items(): for i in range(len(desc_list)): desc = desc_list[i] # tokenize desc = desc.split() # convert to lower case desc = [word.lower() for word in desc] # remove punctuation from each token desc = [w.translate(table) for w in desc] # remove hanging 's' and 'a' desc = [word for word in desc if len(word)>1] # remove tokens with numbers in them desc = [word for word in desc if word.isalpha()] # store as string desc_list[i] = ' '.join(desc) # clean descriptions clean_descriptions(descriptions) |
بعد از تمیز کردن دیتاست متنی میتوان اندازه واژگان را خلاصه کنیم. بطور ایده آل ما میخواهیم یک واژه هم مشخص و هم تا حد ممکن کوچک باشد . واژگان کوچکتر در یک مدل کوچکتر سریعتر آموزش میبیند و نتیجه میدهد . ما میتوانیم توصیف های تمیز را داخل یک مجموعه ای قرار دهیم و اندازه آن را چاپ کنیم تا اندازه ایده آل برای واژگان دیتاست را بدست آوریم .
1 2 3 4 5 6 7 8 9 10 11 |
# convert the loaded descriptions into a vocabulary of words def to_vocabulary(descriptions): # build a list of all description strings all_desc = set() for key in descriptions.keys(): [all_desc.update(d.split()) for d in descriptions[key]] return all_desc # summarize vocabulary vocabulary = to_vocabulary(descriptions) print('Vocabulary Size: %d' % len(vocabulary)) |
در نهایت میتوان دیکشنری حاصل و توضیحات را در یک فایل جدید با نام descriptions.txt با یک شناسه تصویر و توضیحات در هر خط ذخیره کرد.
در زیر تابع ()save_descriptions را داریم که به عنوان ورودی یک دیکشنری حاوی mappinf شناسه ها به توضیحات و یک اسم فایل میگیرد و mappind را در آن فایل ذخیره میکند.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# save descriptions to file, one per line def save_descriptions(descriptions, filename): lines = list() for key, desc_list in descriptions.items(): for desc in desc_list: lines.append(key + ' ' + desc) data = '\n'.join(lines) file = open(filename, 'w') file.write(data) file.close() # save descriptions save_descriptions(descriptions, 'descriptions.txt') |
در این بخش مدل یادگیری عمیق تعریف میشود و آن را در مجموعه داده های fit ، train میکنیم . این بخش اینگونه تقسیم میشود:
ابتدا باید داده های آماده شده عکس و متن را بارگذاری کنیم تا بتوانیم از آن در fit کردن مدل استفاده کنیم . تصمیم گرفتیم داده را روی همه عکس ها و متن های داده train آموزش دهیم . در حین آموزش تصمیم داریم کارایی مدل روی داده توسعه یافته را مانیتور کنیم و از آن برای تصمیم گیری اینکه چه زمان مدل ها را در فایل ذخیره کنیم ، استفاده کنیم . داده train و توسعه یافته به ترتیب در فایل های Flickr_8k.trainImages.txt و Flickr_8k.devImages.txt ذخیره شده است که هردو حاوی لیستی از نام های فایل عکس هاست . از این اسم فایل ها میتوانیم شناسه های تصاویر را استخراج کنیم و از این شناسه ها برای فیلتر کردن تصاویر و توضیحات برای هر مجموعه استفاده کنیم.
تابع ()load_set یک مجموعه از شناسه های داده شده از اسم فایل مجموعه های train و توسعه یافته را بارگذاری میکند .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# load doc into memory def load_doc(filename): # open the file as read only file = open(filename, 'r') # read all text text = file.read() # close the file file.close() return text # load a pre-defined list of photo identifiers def load_set(filename): doc = load_doc(filename) dataset = list() # process line by line for line in doc.split('\n'): # skip empty lines if len(line) < 1: continue # get the image identifier identifier = line.split('.')[0] dataset.append(identifier) return set(dataset) |
در حال حاضر میتوانیم عکس ها و توصیفات را با استفاده از مجموعه از پیش تعریف شده شناسه های train یا توسعه یافته
در زیر تابع ()load_clean_description برای مجموعه ای از شناسه های داده شده توصیفات متن تمیز شده را از description.txt بارگذاری میکند و یک دیکشنری از شناسه ها به لیستی از توصیفات متنی برمیگرداند .
مدلی که ما توسعه خواهیم داد یک توصیف یا عنوان را برای عکس داده شده تولید میکند و توصیف در هر لحظه زمانی یک عنوان ایجاد میشود . دنباله ای از کلمات قبلا تولید شده به عنوان ورودی ارائه میشود . بنابراین نیاز به یک کلمه اول برای اولین بار از فرایند تولید و یک آخرین کلمه برای سیگنال پایان توصیف داریم . بدین منظور از رشته “startseq” و “endseq” استفاده میکنیم . این نشانه ها به توصیفات بارگذاری شده اضافه میشوند . مهم است که قبل از اینکه متن را encode کنیم این کار را انجام دهیم تا token ها به درستی encode شوند .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# load clean descriptions into memory def load_clean_descriptions(filename, dataset): # load document doc = load_doc(filename) descriptions = dict() for line in doc.split('\n'): # split line by white space tokens = line.split() # split id from description image_id, image_desc = tokens[0], tokens[1:] # skip images not in the set if image_id in dataset: # create list if image_id not in descriptions: descriptions[image_id] = list() # wrap description in tokens desc = 'startseq ' + ' '.join(image_desc) + ' endseq' # store descriptions[image_id].append(desc) return descriptions |
در زیر تابعی داریم به نام ()load_photo_features که تمام مجموعه توصیفات عکس را بارگذاری میکند و زیر مجموعه ای از interest را برای یک مجموعه شناسه های عکس داده شده برمیگرداند . این کار خیلی کارآمد نیست با این وجود کار را سریع و اجرا را سریع میکند .
1 2 3 4 5 6 7 |
# load photo features def load_photo_features(filename, dataset): # load all features all_features = load(open(filename, 'rb')) # filter features features = {k: all_features[k] for k in dataset} return features |
گام اول در رمزگذاری داده ها ، ایجاد یک mapping سازگار از کلمات مقادیر عدد صحیح منحصر به فرد است . keras کلاس tokenizer را فراهم میکند که میتواند این mapping را از داده ی توصیف بارگذاری شده ، یاد بگیرد .
در زیر تابع ()to_lines را تعریف میکنیم تا دیکشنری توصیفات را به یک لیستی از رشته ها تبدیل کنیم و تابع ()create_tokenizer هم یک tokenizer داده شده به متن توصیف عکس load شده را fit میکند .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# convert a dictionary of clean descriptions to a list of descriptions def to_lines(descriptions): all_desc = list() for key in descriptions.keys(): [all_desc.append(d) for d in descriptions[key]] return all_desc # fit a tokenizer given caption descriptions def create_tokenizer(descriptions): lines = to_lines(descriptions) tokenizer = Tokenizer() tokenizer.fit_on_texts(lines) return tokenizer # prepare tokenizer tokenizer = create_tokenizer(train_descriptions) vocab_size = len(tokenizer.word_index) + 1 print('Vocabulary Size: %d' % vocab_size) |
حالا میتوانیم متن را encode کنیم . هر توصیف به کلمات تقسیم میشود. مدل یک کلمه و عکس را میگیرد و کلمه بعدی را ایجاد میکند . سپس به عنوان ورودی اولین 2 کلمه از توصیف با یک عکس به مدل داده میشود برای تولید کلمه بعدی . نحوه آموزش مدل :
1 2 3 4 5 6 7 |
X1, X2 (text sequence), y (word) photo startseq, little photo startseq, little, girl photo startseq, little, girl, running photo startseq, little, girl, running, in photo startseq, little, girl, running, in, field photo startseq, little, girl, running, in, field, endseq |
زمانیکه این مدل برای تولید توصیفات استفاده میشود ، کلمات تولید شده پیوند داده میشوند و به عنوان ورودی برای تولید یک توصیف برای یک عکس بصورت بازگشتی ارائه میشود.
تابع زیر به اسم ()build_sequences با توجه به tokenizer ، حداکثر طول sequence و دیکشنری از همه ی توصیفات و عکس ها ، داده را به جفت داده های ورودی/خروجی از داده برای آموزش مدل منتقل میکند.
متن ورودی به عنوان عدد صحیح encode میشود که به یک لایه word embedding داده میشود . ویژگی های عکس بطور مستقیم به قسمت های دیگری از مدل تغذیه میشوند . این مدل پیش بینی میکند که توزیع احتمالی بیش از همه کلمات در واژگان خواهد بود.
داده خروجی یک بردار one-hot از هر کلمه است .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# create sequences of images, input sequences and output words for an image def create_sequences(tokenizer, max_length, descriptions, photos): X1, X2, y = list(), list(), list() # walk through each image identifier for key, desc_list in descriptions.items(): # walk through each description for the image for desc in desc_list: # encode the sequence seq = tokenizer.texts_to_sequences([desc])[0] # split one sequence into multiple X,y pairs for i in range(1, len(seq)): # split into input and output pair in_seq, out_seq = seq[:i], seq[i] # pad input sequence in_seq = pad_sequences([in_seq], maxlen=max_length)[0] # encode output sequence out_seq = to_categorical([out_seq], num_classes=vocab_size)[0] # store X1.append(photos[key][0]) X2.append(in_seq) y.append(out_seq) return array(X1), array(X2), array(y) |
ما باید حداکثر تعداد کلمات را در طولانی ترین caption محاسبه کنیم. بدین منظور تابع کمکی به نام ()max_length را تعریف میکنیم .
1 2 3 4 |
# calculate the length of the description with the most words def max_length(descriptions): lines = to_lines(descriptions) return max(len(d.split()) for d in lines) |
ما یک مدل یادگیری عمیق را بر اساس “مدل ادغام” توصیف میکنیم :
مدل را در 3 بخش تعریف میکنیم :
مدل استخراج ویژگی عکس ها انتظار دارد ویژگی های عکس ورودی یک وکتور 4096 عنصری باشد . اینها توسط یک لایه dense برای تولید 256 عنصر مورد استفاده قرار میگیرند .
مدل پردازنده sequence انتظار دارد sequence های ورودی با یک طول از قبل تعریف شده (34 کلمه) که به یک لایه embedding که یک mask برای نادیده گرفتن مقادیر padded استفاده میکند ، fed میشود . این توسط یک لایه LSTM با 256 واحد حافظه دنبال میشود .
هر دو مدل ورودی یک آرایه 256 عنصری تولید میکند . علاوه بر این هر دو مدل ورودی از Regularization بصورت 50% dropout استفاده میکند . این کار در جهت کاهش overfitting روی دیتاست training ، باعث یادگیری سریع مدل میشود .
مدل decoder وکتور هایی از هردو مدل های ورودی را با استفاده از یک عملیات اضافه ادغام میکند . سپس به یک لایه نورون 256 تایی dense و بعد از آن به یک لایه dense خروجی نهایی که یک پیش بینی softmax را در طول همه ی واژگان خروجی برای کلمه بعدی در جمله ایجاد میکند ، میفرستد .
تابع ()define_model بصورت زیر تعریف میشود و مدل را آماده برای fit شدن برمیگرداند .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# define the captioning model def define_model(vocab_size, max_length): # feature extractor model inputs1 = Input(shape=(4096,)) fe1 = Dropout(0.5)(inputs1) fe2 = Dense(256, activation='relu')(fe1) # sequence model inputs2 = Input(shape=(max_length,)) se1 = Embedding(vocab_size, 256, mask_zero=True)(inputs2) se2 = Dropout(0.5)(se1) se3 = LSTM(256)(se2) # decoder model decoder1 = add([fe2, se3]) decoder2 = Dense(256, activation='relu')(decoder1) outputs = Dense(vocab_size, activation='softmax')(decoder2) # tie it together [image, seq] [word] model = Model(inputs=[inputs1, inputs2], outputs=outputs) model.compile(loss='categorical_crossentropy', optimizer='adam') # summarize model print(model.summary()) plot_model(model, to_file='model.png', show_shapes=True) return model |
یک طرح برای مشاهده ساختار این شبکه برای درک بهتر در زیر آورده شده است :
این مدل overfit های داده training را سریع یاد میگیرد به همین دلیل ما مهارت مدل آموزش داده شده را روی مجموعه داده های توسعه monitor خواهیم کرد. وقتی مهارت مدل در مجموعه داده توسعه در پایان یک epoch بهبود یافت ، ما کل مدل را در فایلی ذخیره میکنیم .
در پایان اجرا ، ما میتوانیم از مدل ذخیره شده با بهترین مهارت روی دیتاست training به عنوان مدل نهایی استفاده کنیم .
ما میتوانیم این کار را با تعریف یک ModelCheckpoint در کراس انجام دهیم آن را مشخص کنیم تا مینیمم loss را روی دیتاست validation داشته باشیم و مدل را در یک فایلی که هم training loss و هم validation loss دارد ذخیره میکنیم .
1 2 3 |
# define checkpoint callback filepath = 'model-ep{epoch:03d}-loss{loss:.3f}-val_loss{val_loss:.3f}.h5' checkpoint = ModelCheckpoint(filepath, monitor='val_loss', verbose=1, save_best_only=True, mode='min') |
1 2 |
# fit model model.fit([X1train, X2train], ytrain, epochs=20, verbose=2, callbacks=[checkpoint], validation_data=([X1test, X2test], ytest)) |
ما گروهی از دانشجویان رشته کامپیوتر دانشگاه شهید رجایی هستیم که به راهنمایی و تدریس استاد علیرضا اخوانپور به دنیای یادگیری عمیق پا گذاشتیم و دوست داریم چیزهایی که در این مسیر یادمیگیریم رو به اشتراک بذاریم.
شبکه های اجتماعی