مجموعه داده
مجموعه داده گربه در مقابل سگ برای این مثال استفاده شده است. این مجموعه داده در چالش بینایی کامپیوتر اواخر سال ۲۰۱۳ توسط سایت Kaggle.com در دسترس عموم قرار گرفت. مجموعه داده اصلی دارای ۲۵۰۰۰ تصویر (۱۲۵۰۰ از هر کلاس) است که میتوانید آن را از اینجا دریافت کنید.

ما بعد از دانلود تصاویر، آن ها را در ساختار جدیدی مرتب کردیم. ۱۰۰۰ عکس برای آموزش، ۵۰۰ عکس برای ارزیابی و ۵۰۰ عکس برای آزمون از هر کلاس (گربه و سگ) جدا کردیم. این فرایند توسط کد زیر انجام شده و تصاویر مرتب شده از اینجا در دسترس است.
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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
|
import os, shutil # The path to the directory where the original # dataset was uncompressed original_dataset_dir = 'E:/dataset/catDog/train' # The directory where we will # store our smaller dataset base_dir = 'E:/dataset/catDog/catVsdog' os.mkdir(base_dir) # Directories for our training, # validation and test splits train_dir = os.path.join(base_dir, 'train') os.mkdir(train_dir) validation_dir = os.path.join(base_dir, 'validation') os.mkdir(validation_dir) test_dir = os.path.join(base_dir, 'test') os.mkdir(test_dir) # Directory with our training cat pictures train_cats_dir = os.path.join(train_dir, 'cats') os.mkdir(train_cats_dir) # Directory with our training dog pictures train_dogs_dir = os.path.join(train_dir, 'dogs') os.mkdir(train_dogs_dir) # Directory with our validation cat pictures validation_cats_dir = os.path.join(validation_dir, 'cats') os.mkdir(validation_cats_dir) # Directory with our validation dog pictures validation_dogs_dir = os.path.join(validation_dir, 'dogs') os.mkdir(validation_dogs_dir) # Directory with our validation cat pictures test_cats_dir = os.path.join(test_dir, 'cats') os.mkdir(test_cats_dir) # Directory with our validation dog pictures test_dogs_dir = os.path.join(test_dir, 'dogs') os.mkdir(test_dogs_dir) # Copy first 1000 cat images to train_cats_dir fnames = ['cat.{}.jpg'.format(i) for i in range(1000)] for fname in fnames: src = os.path.join(original_dataset_dir, fname) dst = os.path.join(train_cats_dir, fname) shutil.copyfile(src, dst) # Copy next 500 cat images to validation_cats_dir fnames = ['cat.{}.jpg'.format(i) for i in range(1000, 1500)] for fname in fnames: src = os.path.join(original_dataset_dir, fname) dst = os.path.join(validation_cats_dir, fname) shutil.copyfile(src, dst) # Copy next 500 cat images to test_cats_dir fnames = ['cat.{}.jpg'.format(i) for i in range(1500, 2000)] for fname in fnames: src = os.path.join(original_dataset_dir, fname) dst = os.path.join(test_cats_dir, fname) shutil.copyfile(src, dst) # Copy first 1000 dog images to train_dogs_dir fnames = ['dog.{}.jpg'.format(i) for i in range(1000)] for fname in fnames: src = os.path.join(original_dataset_dir, fname) dst = os.path.join(train_dogs_dir, fname) shutil.copyfile(src, dst) # Copy next 500 dog images to validation_dogs_dir fnames = ['dog.{}.jpg'.format(i) for i in range(1000, 1500)] for fname in fnames: src = os.path.join(original_dataset_dir, fname) dst = os.path.join(validation_dogs_dir, fname) shutil.copyfile(src, dst) # Copy next 500 dog images to test_dogs_dir fnames = ['dog.{}.jpg'.format(i) for i in range(1500, 2000)] for fname in fnames: src = os.path.join(original_dataset_dir, fname) dst = os.path.join(test_dogs_dir, fname) shutil.copyfile(src, dst) |
برای بررسی صحت انجام کار، تعداد تصاویر آموزشی، ارزیابی و آزمون را بررسی میکنیم.
|
print('total training cat images:', len(os.listdir(train_cats_dir))) print('total training dog images:', len(os.listdir(train_dogs_dir))) print('total validation cat images:', len(os.listdir(validation_cats_dir))) print('total validation dog images:', len(os.listdir(validation_dogs_dir))) print('total test cat images:', len(os.listdir(test_cats_dir))) print('total test dog images:', len(os.listdir(test_dogs_dir))) |
|
total training cat images: 1000 total training dog images: 1000 total validation cat images: 500 total validation dog images: 500 total test cat images: 500 total test dog images: 500 |
تعریف معماری مدل (model architecture)
ما از یک مدل کانولوشنالی برای حل این مساله استفاده میکنیم. از قبل از سال ۲۰۱۳ استفاده از شبکه های کانولوشنالی خیلی رایج نبود ولی بعد از آن تقریبا در همه مدل های بینایی ماشین با روش یادگیری عمیق، از این شبکه ها استفاده میشود.شبکه ما از ۴ لایه ی کانولوشنالی و پولینگ بعد از هر کدام، یک لایه متراکم (Dense) پنهان و یک لایه متراکم برای لایه خروجی که دارای یک نورون است استفاده میکنیم.تابع فعال سازی لایه آخر sigmoid است. در پیاده سازی مدل برای تشخیص اعداد دست نویس دیتاست هدی، ما در لایه آخر از تابع softmax استفاده کردیم. با تابع softmax خروجی نورون های لایه آخر به صورتی میشود که مقدار هر کدام عددی بین ۰ و ۱ است و مجموع همه برابر یک خواهد بود. این تابع برای لایه آخر مسائل طبقه بندی به کار میرود.در این مسال مابه جای استفاده از دو نورون با تابع softmax از یک نورون با تابع sigmoid استفاده میکنیم. این تابع عددی بین ۰ و ۱ برمیگرداند. چون تنها دو کلاس داریم، مقدار یکی از نورون ها برابر اختلاف دیگری با عدد ۱ است. پس میتوانیم عدد ۰ را متناظر با یکی از کلاس ها (مثلا گربه) و عدد ۱ را متناظر با کلاس دیگر (سگ) قرار دهیم. در این صورت به جای دو نورون در لایه خروجی، با یک نورون همان کار را انجام میدهیم.
|
from keras import layers from keras import models model = models.Sequential() model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(150, 150, 3))) model.add(layers.MaxPooling2D((2, 2))) model.add(layers.Conv2D(64, (3, 3), activation='relu')) model.add(layers.MaxPooling2D((2, 2))) model.add(layers.Conv2D(128, (3, 3), activation='relu')) model.add(layers.MaxPooling2D((2, 2))) model.add(layers.Conv2D(128, (3, 3), activation='relu')) model.add(layers.MaxPooling2D((2, 2))) model.add(layers.Flatten()) model.add(layers.Dense(512, activation='relu')) model.add(layers.Dense(1, activation='sigmoid')) |
نگاهی به ابعاد و تعداد اطلاعات در هر یک از لایه های شبکه میاندازیم.نگاهی به ابعاد و تعداد اطلاعات در هر یک از لایه های شبکه میاندازیم.
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
|
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= conv2d_1 (Conv2D) (None, 148, 148, 32) 896 _________________________________________________________________ max_pooling2d_1 (MaxPooling2 (None, 74, 74, 32) 0 _________________________________________________________________ conv2d_2 (Conv2D) (None, 72, 72, 64) 18496 _________________________________________________________________ max_pooling2d_2 (MaxPooling2 (None, 36, 36, 64) 0 _________________________________________________________________ conv2d_3 (Conv2D) (None, 34, 34, 128) 73856 _________________________________________________________________ max_pooling2d_3 (MaxPooling2 (None, 17, 17, 128) 0 _________________________________________________________________ conv2d_4 (Conv2D) (None, 15, 15, 128) 147584 _________________________________________________________________ max_pooling2d_4 (MaxPooling2 (None, 7, 7, 128) 0 _________________________________________________________________ flatten_1 (Flatten) (None, 6272) 0 _________________________________________________________________ dense_1 (Dense) (None, 512) 3211776 _________________________________________________________________ dense_2 (Dense) (None, 1) 513 ================================================================= Total params: 3,453,121 Trainable params: 3,453,121 Non-trainable params: 0 _________________________________________________________________ |
چون این مساله تنها دو کلاس داریم، از تابع binary_crossentropy برای محاسبه میزان خطا استفاده میکنیم. با توجه به اینکه تعداد تصاویر یکسانی از سگ و گربه برداشته ایم معیار Accuracy معیارمناسبی برای ارزیابی خواهد بود.
|
from keras import optimizers model.compile(loss='binary_crossentropy', optimizer=optimizers.RMSprop(lr=1e-4), metrics=['acc']) |
پیش پردازش داده (Data preprocessing)
مراحل آماده سازی تصاویر برای شروع عملیات یادگیری:
- خواندن تصویر
- تبدیل فرمت jpeg به پیکسل های RGB
- تبدیل آن به tensor (آرایه چند بعدی) از اعداد حقیقی
- نرمال کردن اعداد (تبدیل بازه ۰ تا ۲۵۵ به بازه ۰ تا ۱)
به لطف ابزار های کتابخانه کراس، تمام این مراحل به راحتی انجام میشود. کافی است کلاس ImageDataGenerator را از keras.preprocessing.image ایمپورت کرده، آدرس فولدر ها، ابعاد تصاویر ورودی، نوع طبقه بندی و اندازه batch را مشخص کنیم و از کلاس های ایجاد شده برای یادگیری و ارزیابی مدل استفاده کنیم.
اطلاعات بیشتر در مستندات Keras :
https://keras.io/preprocessing/image/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
from keras.preprocessing.image import ImageDataGenerator # All images will be rescaled by 1./255 train_datagen = ImageDataGenerator(rescale=1./255) test_datagen = ImageDataGenerator(rescale=1./255) train_generator = train_datagen.flow_from_directory( # This is the target directory train_dir, # All images will be resized to 150x150 target_size=(150, 150), batch_size=20, # Since we use binary_crossentropy loss, we need binary labels class_mode='binary') validation_generator = test_datagen.flow_from_directory( validation_dir, target_size=(150, 150), batch_size=20, class_mode='binary') |
|
Found 2000 images belonging to 2 classes. Found 1000 images belonging to 2 classes. |
اگر بخواهیم نگاهی به خروجی کلاس های generator بیاندازیم خواهیم دید در هر گام پیمایش، آن ها یک دسته(batch) از تصاویر رنگی ۱۵۰ در ۱۵۰ پیکسل (3, 150, 150, 20) و به تعداد تصاویر، عنوان آن ها را (۰ برای گربه و ۱ برای سگ) برمیگرداند. با ادامه پیمایش این روند به تعداد نامحدود تکرار میشود.
|
for data_batch, labels_batch in train_generator: print('data batch shape:', data_batch.shape) print('labels batch shape:', labels_batch.shape) break |
|
data batch shape: (20, 150, 150, 3) labels batch shape: (20,) |
آموزش مدل (fitting model)
حالا نوبت آموزش مدل میشود. برای این کار به جای تابع fit از تابع fit_generator استفاده میکنیم که همان کار را انجام میدهد ولی با generator ها کار میکند. این تابع برای اولی ورودی یک generator مانند آنچه ساختیم میگیرد و با آن مدل را تغذیه میکند. چون generator ها بدون اطلاع از تعداد تصاویر، مدام خروجی ها را برمیگردانند، باید به گونه ای تعداد گام های پیمایش را به ازای هر دوره(epoch) مشخص کنیم. پس علاوه بر generator باید مقدار ورودی steps_per_epoch را هم به تابع بدهیم. چون تعداد تصاویر ۲۰۰۰ تا و اندازه هر تکه ۲۰ است، ما برای هر دوره به ۱۰۰۰ گام برای پیمایش نیاز داریم.
مقدار validation_steps با توجه به نسبت تصاویر آموزشی و ارزیابی مشخص میشود. چون در این مساله ما ۱۰۰۰ عکس آموزشی و ۵۰۰ عکس برای ارزیابی داریم، به ازای هر دو گام آموزش، یک بار ارزیابی مدل صورت میگیرد. پس تعداد گام های ارزیابی در هر دوره، نصف گام های آموزش است. پس این مقدار را برابر ۵۰ قرار میدهیم.
|
history = model.fit_generator( train_generator, steps_per_epoch=100, epochs=30, validation_data=validation_generator, validation_steps=50) |
حالا نمودار میزان خطا و میزان دقت را در طول آموزش رسم میکنیم.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
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(len(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.legend() plt.figure() plt.plot(epochs, loss, 'bo', label='Training loss') plt.plot(epochs, val_loss, 'b', label='Validation loss') plt.title('Training and validation loss') plt.legend() plt.show() |

این تصاویر نشان دهنده وقوع overfitting است. دقت اطلاعات آموزشی ما به نزدیک ۱۰۰٪ رسیده ولی دقت اطلاعات ارزیابی بعد از ۵ دوره به حدود ۷۰٪ رسیده و بعد از آن تغییری نکرده. چون تعداد تصاویر ما کم است و شبکه اطلاعات را حفظ کرده و قدرت عمومیت بخشی ندارد.
شبکه با Dropout
برای جلوگیری از این اتفاق روش Dropout را امتحان میکنیم.
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
|
model = models.Sequential() model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(150, 150, 3))) model.add(layers.MaxPooling2D((2, 2))) model.add(layers.Conv2D(64, (3, 3), activation='relu')) model.add(layers.MaxPooling2D((2, 2))) model.add(layers.Conv2D(128, (3, 3), activation='relu')) model.add(layers.MaxPooling2D((2, 2))) model.add(layers.Conv2D(128, (3, 3), activation='relu')) model.add(layers.MaxPooling2D((2, 2))) model.add(layers.Flatten()) model.add(layers.Dropout(0.5)) model.add(layers.Dense(512, activation='relu')) model.add(layers.Dense(1, activation='sigmoid')) model.compile(loss='binary_crossentropy', optimizer=optimizers.RMSprop(lr=1e-4), metrics=['acc']) history = model.fit_generator( train_generator, steps_per_epoch=100, epochs=100, validation_data=validation_generator, validation_steps=50) |
حالا دوباره نمودار های دقت و خطا را بررسی میکنیم.

همان طور که تصاویر مشخص است این روش تاثیر اندکی روی سیستم گذاشته و شبکه را کمی بهبود داده ولی هنوز هم مشکل overfitting برطرف نشده. برای مقابله با این مشکل باید از روش data augmentation استفاده کنیم که در آینده با آن آشنا خواهیم شد.
در آخر مدل را برای استفاده های بعدی ذخیره میکنیم.
|
model.save('cats_and_dogs_small_1.h5') |
شبکه های اجتماعی