Multi Label Classification چیست ؟
می دانیم در شبکه های کانولوشنی که دارای یک سری لایه ی میانی و لایه های ورودی و خروجی هستند ، در لایه های میانی اکثرا از Activation RELU و در لایه ی خروجی از Activation های Softmax و Sigmoid استفاده می شود.
Activation Sigmoid زمانی استفاده می شود که در لایه ی آخر 1 نورون داشته باشیم که نشان دهنده ی وجود دو کلاس است ، برای مثال زمانی که می خواهیم سگ را از گربه تشخیص دهیم از این تایع استفاده می کنیم که می توان 1 شدن آن را به منزله ی سگ بودن تصویر و 0 بودن آن را منزله ی گربه بودن تصویر در نظر گرفت .
Activation Softmax زمانی استفاده می شود که بخواهیم بیش از 2 کلاس را تشخیص دهیم ، برای مثال وقتی میخواهیم 5 میوه را به شبکه آموزش دهیم در لایه ی آخر Activation Softmax را استفاده میکنیم که در واقع تبدیل Exponential عدد خروجی لایه ی آخر که مثلا میتواند به صورت اعداد 12- ، 10 ، 5 ، 2 و 6- باشد گرفته شده و با هم جمع میگردد و سپس هرکدام از این اعداد به این حاصل جمع تقسیم می شود ، که اعداد به دست آمده در بازه ی 0 تا 1 خواهند بود . عددی که بیشترین مقدار را داشته بیشترین مقدار را در این بازه خواهد داشت و عددی که کمترین مقدار را داشته کمترین مقدار را در این بازه خواهد داشت ، مثلا عدد 10 به معادل 0.95 میرسد و این نشان دهنده ی برنده بودن کلاس مربوط به این نورون خواهد بود .
اشکال توابع Sigmoid و RELU به این شکل می باشد :


اما تمام آنچه گفته شد مربوط به زمانی است که ما بخواهیم وجود یک Object را تشخیص دهیم .
برای مثال در صورت داشتن یک عکس ماهواره ای که قصد داریم در آن تصویر وجود دریا و جنگل و … را بدانیم باید به شکل دیگری عمل کنیم ، و در واقع باید در لایه ی آخر نورون های مربوط به Object های موجود در تصویر True شده و نورون های مربوط به تصاویر غیر موجود در تصویر False شوند . به این نوع مسایل Multi Label Classification می گویند به عبات دیگر لایه ی اخر میتواند بیشتر از یک برنده داشته باشد و یا هیچ برنده ای نداشته باشد.
پیاده سازی Multi Label Classification در Keras :
برای پیاده سازی Multi Label Classification در Keras ابتدا کلاس های مورد نظر را import و پوشه ی دیتا ست مورد نظر را معرفی می کنیم.
|
# import the necessary packages from keras.models import Sequential from keras.layers.normalization import BatchNormalization from keras.layers.convolutional import Conv2D from keras.layers.<strong>convolutional</strong> import MaxPooling2D from keras.layers.core import Activation from keras.layers.core import Flatten from keras.layers.core import Dropout from keras.layers.core import Dense |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
import matplotlib matplotlib.use("Agg") # import the necessary packages from keras.preprocessing.image import ImageDataGenerator from keras.optimizers import Adam from keras.preprocessing.image import img_to_array from sklearn.preprocessing import MultiLabelBinarizer from sklearn.model_selection import train_test_split import matplotlib.pyplot as plt from imutils import paths import numpy as np import random import pickle import cv2 import os dataset = 'E:/dataset/multi-label/dataset' |
Dataset مورد استفاده برای مساله ی مورد بررسی دارای پوشه هایی بدین شکل است که داخل هر کدام فولدر های Test و Train وجود دارند .
- پوشه ی Black_jeans
- پوشه ی Blue_jeans
- پوشه ی Blue_shirt
- پوشه ی Red_shirt
بدین ترتیب شبکه ی ما باید به شکلی بتواند تشخیص دهد که آیا در تصویر ورودی Jeans یا shirt ویا رنگ آبی یا قرمز و یا مشکی وجود دارد یا خیر و چون در لایه ی اخر میتوانیم وجود بیش از یک Object را تشخیص دهیم ( آبی ، قرمز ، مشکی ، shirt و jeans ) این مساله ، مساله ی Multi Label Classification است . در واقع نورون های لایه ی اخر که هر کدام برای یکی از object های مورد نظر می باشند ، باید به یک Sigmoid متصل شوند تا در صورت تشخیص آن شی در تصویر Fire کنند.
مثلا در تصویر مقابل هم رنگ قرمز را داریم هم وجود Shirt پس باید در لایه ی اخر نورون های مربوط به رنگ قرمز و Shirt که هرکدام به یک Sigmoid متصل هستند True شده و بقیه False شوند .

و یا اگر تصویر مقابل را که هیچ کدام از Object های shirt و jeans و red و blue و black در آن نیست به شبکه بدهیم در لایه ی آخر هیچ کدام از Sigmoid ها نباید Fire کنند.

همانطور که در کد زیر مشاهده مینمایید بعد از تنظیم مقدار Epochs ، Learning Rate و Batch Size با استفاده از
|
((imagePaths = sorted(list(paths.list_images(dataset) |
میتوانیم تمام تصاویر موجود در دیتاست را در یک لیست به نام imagePaths نگه داریم . سپس آن را shuffle میکنیم تا عکس هایی که در یک پوشه بودند پشت هم قرار نگیرند.( دلیل اینکه از DataGenerator استفاده نمیکنیم این است که در Keras هر پوشه به منزله ی یک کلاس میباشد ولی در Multi Label Classification اینگونه نیست )
در مرحله ی بعد تمام عکس ها را خوانده و به سایز 96 در 96 ریسایز کرده و تابع mg_to_array را برایشان فراخانی می کنیم، سپس آن ها را در لیست Data میریزیم.
با استفاده از :
|
<span class="n">l</span> <span class="o">=</span> <span class="n">label</span> <span class="o">=</span> <span class="n">imagePath</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">sep</span><span class="p">)[</span><span class="o">-</span><span class="mi">2</span><span class="p">]</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">"_"</span><span class="p">)</span> <span class="n">labels</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">l</span><span class="p">)</span> |
از کل آدرس پوشه ی در دیتا ست قسمت نام آن را برمی داریم ، سپس دو کلمه ی آن را از محل “_” جدا می کنیم و در یک لیست به نام Labels میریزیم.
در مرحبه بعد دیتا ها را نرمال می کنیم.
اگر لیست Labels را چاپ کنیم خواهیم دید که لیستی از دوتایی ها خواهد بود که این دوتایی ها نام پوشه هایی هستند که از محل “_” جدا کردیم .
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
|
# batch size, and image dimensions EPOCHS = 50 INIT_LR = 1e-3 BS = 32 # grab the image paths and randomly shuffle them print("[INFO] loading images...") imagePaths = sorted(list(paths.list_images(dataset))) random.seed(42) random.shuffle(imagePaths) # initialize the data and labels data = [] labels = [] # loop over the input images for imagePath in imagePaths: # load the image, pre-process it, and store it in the data list image = cv2.imread(imagePath) image = cv2.resize(image, (96, 96)) image = img_to_array(image) data.append(image) # extract set of class labels from the image path and update the # labels list l = label = imagePath.split(os.path.sep)[-2].split("_") labels.append(l) # scale the raw pixel intensities to the range [0, 1] data = np.array(data, dtype="float") / 255.0 labels = np.array(labels) print("[INFO] data matrix: {} images ({:.2f}MB)".format( len(imagePaths), data.nbytes / (1024 * 1000.0))) labels |
بلاک کد بالا نتیجه ای بدین شکل خواهد داشت :

در قسمت بعد از کتابخانه ی scikit-learn استفاده کردیم و با استفاده از MultiLabelBinarizer هر کدام از دوتایی های لیست Labels را به حالت 0 , 1 درآوردیم تا بجای هر کلاسی که در آن بود مقدار 1 و به جای هر کلاسی که آن را وجود نداشت مقدار 0 را قرار دهیم ، به علاوه نام کلاس ها را چاپ کردیم:
|
# binarize the labels using scikit-learn's special multi-label # binarizer implementation print("[INFO] class labels:") mlb = MultiLabelBinarizer() labels = mlb.fit_transform(labels) # loop over each of the possible class labels and show them for (i, label) in enumerate(mlb.classes_): print("{}. {}".format(i + 1, label)) |
اگر لیست Labels را بعد از این عملیات چاپ کنیم آن را به این شکل مشاهده میکنیم :

سپس کلاس های موجود را چاپ میکنیم:

با استفاده از قطعه کد زیر از کل دیتا های موجود 80 درصد آن را برای Train و 20 درصد آن را برای Test برمیداریم :
|
# partition the data into training and testing splits using 80% of # the data for training and the remaining 20% for testing (trainX, testX, trainY, testY) = train_test_split(data, labels, test_size=0.2, random_state=42) |
سپس عملیات Augmentation را روی دیتا های خوانده شده انجام میدهیم :
|
# construct the image generator for data augmentation aug = ImageDataGenerator(rotation_range=25, width_shift_range=0.1, height_shift_range=0.1, shear_range=0.2, zoom_range=0.2, horizontal_flip=True, fill_mode="nearest") |
شبکه ای با معماری زیر میسازیم :
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
|
# initialize the model along with the input shape to be # "channels last" and the channels dimension itself model = Sequential() # CONV => RELU => POOL model.add(Conv2D(32, (3, 3), padding="same", input_shape=(96,96,3))) model.add(Activation("relu")) model.add(BatchNormalization()) model.add(MaxPooling2D(pool_size=(3, 3))) model.add(Dropout(0.25)) # (CONV => RELU) * 2 => POOL model.add(Conv2D(64, (3, 3), padding="same")) model.add(Activation("relu")) model.add(BatchNormalization()) model.add(Conv2D(64, (3, 3), padding="same")) model.add(Activation("relu")) model.add(BatchNormalization()) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Dropout(0.25)) # (CONV => RELU) * 2 => POOL model.add(Conv2D(128, (3, 3), padding="same")) model.add(Activation("relu")) model.add(BatchNormalization()) model.add(Conv2D(128, (3, 3), padding="same")) model.add(Activation("relu")) model.add(BatchNormalization()) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Dropout(0.25)) # first (and only) set of FC => RELU layers model.add(Flatten()) model.add(Dense(1024)) model.add(Activation("relu")) model.add(BatchNormalization()) model.add(Dropout(0.5)) # softmax classifier model.add(Dense(classes)) model.add(Activation("sigmoid")) |
مشاهده می شود که در لایه ی اخر به تعداد کلاس ها نورون قرار میدهیم و هر کدام را به یک Sigmoid متصل می کنیم .
سپس Optimizer Adam را با تنظیم کردن Learning Rate و کم کردن آن در هر epoch ( با تقسیم کردن بر شماره epoch ) ایجاد میکنیم :
|
opt = Adam(lr=INIT_LR, decay=INIT_LR / EPOCHS) |
در مرحله آخر مدل را Compile و نمودار های مربوط به Accuracy و Loss در Train Data و Test Dataرا رسم میکنیم میکنیم :
|
# compile the model using binary cross-entropy rather than # categorical cross-entropy -- this may seem counterintuitive for # multi-label classification, but keep in mind that the goal here # is to treat each output label as an independent Bernoulli # distribution model.compile(loss="binary_crossentropy", optimizer=opt, metrics=["accuracy"]) # train the network print("[INFO] training network...") H = model.fit_generator( aug.flow(trainX, trainY, batch_size=BS), validation_data=(testX, testY), steps_per_epoch=len(trainX) // BS, epochs=EPOCHS, verbose=1) |
|
%matplotlib inline # plot the training loss and accuracy plt.style.use("ggplot") plt.figure() N = EPOCHS plt.plot(np.arange(0, N), H.history["loss"], label="train_loss") plt.plot(np.arange(0, N), H.history["val_loss"], label="val_loss") plt.plot(np.arange(0, N), H.history["acc"], label="train_acc") plt.plot(np.arange(0, N), H.history["val_acc"], label="val_acc") plt.title("Training Loss and Accuracy") plt.xlabel("Epoch #") plt.ylabel("Loss/Accuracy") plt.legend(loc="upper left") plt.show() |

در آخر مدل را با پسوند h5 و Label ها را به صورت Pickle File ذخیره میکنیم .( اگر با فرمت Pickle.dumps در فایل بنویسیم یک آبجکت پایتونی را مستقیما در یک فایل میریزد و اگر Load آن را صدا بزنیم همان آبجکت پایتونی را Load میکند .)
|
# save the model to disk model.save("ml_fashion.h5") # save the multi-label binarizer to disk f = open('ml_fashion.pickle', "wb") f.write(pickle.dumps(mlb)) f.close() |
استفاده از مدل Save شده :
در ابتدا کتابخانه های مورد نیاز را Import میکنیم :
|
# import the necessary packages from keras.preprocessing.image import img_to_array from keras.models import load_model import numpy as np import imutils import pickle import cv2 import os import matplotlib.pyplot as plt %matplotlib inline |
سپس عکسی را از ورودی خوانده ، آن را resize میکنیم و نمایش میدهیم :
|
image_path = './images/example_mlc_01.jpg' # load the image image = cv2.imread(image_path) output = imutils.resize(image, width=400) plt.imshow(output[:,:,-1::-1]) |

سپس عکس را به سایز 96 در 96 resize و با تقسیم بر 255 آن را نرمال میکنیم ، به علاوه تابع img_to_array را فراخانی کرده و بعد با exapnd_dim یک dimension به آن اضافه میکنیم ( چون به صورت دیفالت با batch کار می کند و با اضافه کردن یک dimension ، انگار به آن یک batch میدهیم که در واقع در آن یک عکس بیشتر نداریم )
|
# pre-process the image for classification image = cv2.resize(image, (96, 96)) image = image.astype("float") / 255.0 image = img_to_array(image) image = np.expand_dims(image, axis=0) |
آدرس مدل و Label ها را که به صورت Pickle File ذخیره کرده بودیم ، مشخص میکنیم .
|
label_path = './ml_fashion.pickle' |
سپس مدل و Label ها را Load میکنیم :
|
# load the trained convolutional neural network and the multi-label # binarizer model = load_model(model_path) mlb = pickle.loads(open(label_path, "rb").read()) |
عکس مورد نظر را به مدل می دهیم و از بالاترین احتمالات دوتای اول آن را روی عکس چاپ میکنیم .
به علاوه تمام کلاس ها با احتمالاتشان را نمایش میدهیم :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
# classify the input image then find the indexes of the two class # labels with the *largest* probability proba = model.predict(image)[0] idxs = np.argsort(proba)[::-1][:2] # loop over the indexes of the high confidence class labels for (i, j) in enumerate(idxs): # build the label and draw the label on the image label = "{}: {:.2f}%".format(mlb.classes_[j], proba[j] * 100) cv2.putText(output, label, (10, (i * 30) + 25), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) # show the probabilities for each of the individual labels for (label, p) in zip(mlb.classes_, proba): print("{}: {:.2f}%".format(label, p * 100)) # show the output image plt.imshow(output[...,-1::-1]) cv2.imshow("Output", output) cv2.waitKey(0) |
که خروجی مطابق زیر خواهد داشت :


همانطور که مشاهده میکنید کلاس های red و shirt با بیشترین احتمالات درست تشخیص داده شده اند.
شبکه های اجتماعی