OpenCV ile Yüz Tanıma Bölüm 3-2
Önceki yazımda veri setimizi nasıl oluşturacağımızı anlatmıştım.
Bu kez veri setimizi Keras kullanarak eğiteceğiz. Sonra da yeni resimleri bu eğitilmiş veri setini kullanarak sınıflandıracağız.
3. ADIM – EĞİTİM
Projemize örnek olarak aldığımız çalışmada Adrian, Keras modellemesi için SmallerVGGNet sınıfını kullanıyor. Ben onun yerine daha basit bir model kullandım (vgg_like). Eğer isterseniz yazının sonundaki Referanslar bölümünde yer alan linkleri kullanarak SmallerVGGNet kodlarına ulaşabilirsiniz.
Lafı uzatmadan eğitim betiğimize geçelim. Betiğimizin adı: train.py
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 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
from keras.preprocessing.image import ImageDataGenerator from keras.optimizers import Adam from keras.preprocessing.image import img_to_array from sklearn.preprocessing import LabelBinarizer from sklearn.model_selection import train_test_split from smallervggnet import SmallerVGGNet from imutils import paths import numpy as np import random import pickle import cv2 import os import time from keras.models import Sequential from keras.layers.normalization import BatchNormalization from keras.layers.convolutional import Conv2D from keras.layers.convolutional 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 from keras.optimizers import SGD import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt def vgg_like(): model = Sequential() model.add(Conv2D(32, (3, 3), activation='relu', input_shape=(96, 96, 3))) model.add(Conv2D(32, (3, 3), activation='relu')) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Dropout(0.25)) model.add(Conv2D(64, (3, 3), activation='relu')) model.add(Conv2D(64, (3, 3), activation='relu')) model.add(MaxPooling2D(pool_size=(2, 2))) model.add(Dropout(0.25)) model.add(Flatten()) model.add(Dense(256, activation='relu')) model.add(Dropout(0.5)) model.add(Dense(3, activation='softmax')) return model t0=time.time() EPOCHS = 100 INIT_LR = 1e-3 BATCH_SIZE = 32 IMAGE_DIMS = (96, 96, 3) data = [] labels = [] print("Geçen süre: ",time.time()-t0) print("İmajlar yükleniyor...") imagePaths = sorted(list(paths.list_images("data"))) print(imagePaths) random.seed(42) random.shuffle(imagePaths) for imagePath in imagePaths: image = cv2.imread(imagePath) image = cv2.resize(image, (IMAGE_DIMS[1], IMAGE_DIMS[0])) image = img_to_array(image) data.append(image) # etiketler dosya adından oluşturuluyor label = imagePath.split(os.path.sep)[-2] labels.append(label) print(labels) # piksel değerleri [0, 1] olarak dönüştürülüyor data = np.array(data, dtype="float") / 255.0 labels = np.array(labels) print("matrix: {:.2f}MB".format( data.nbytes / (1024 * 1000.0))) # etiketler sayısallaştırılıyor lb = LabelBinarizer() labels = lb.fit_transform(labels) # veri eğitim ve test için ayrıştırılıyor (trainX, testX, trainY, testY) = train_test_split(data, labels, test_size=0.2, random_state=42) # veri çoğullama için imaj üreteci oluşturuluyor 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") print("Geçen süre: ",time.time()-t0) print("Model derleniyor...") # model = SmallerVGGNet.build(width=IMAGE_DIMS[1], height=IMAGE_DIMS[0], # depth=IMAGE_DIMS[2], classes=len(lb.classes_)) # SmallerVGGNet yerine daha basit bir model kullanıyorum model = vgg_like() print("Geçen süre: ",time.time()-t0) print("Model Özeti") print(model.summary()) with open("model_ozet.txt","w") as of: model.summary(print_fn=lambda x: of.write(x+'\n')) # opt = Adam(lr=INIT_LR, decay=INIT_LR / EPOCHS) opt = SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True) # daha iyi sonuç veriyor print("Geçen süre: ",time.time()-t0) model.compile(loss="categorical_crossentropy", optimizer=opt, metrics=["accuracy"]) # train the network print("Geçen süre: ",time.time()-t0) print("Ağ eğitiliyor...") H = model.fit_generator( aug.flow(trainX, trainY, batch_size=BATCH_SIZE), validation_data=(testX, testY), steps_per_epoch=len(trainX) // BATCH_SIZE, epochs=EPOCHS, verbose=1) print("Geçen süre: ",time.time()-t0) print("Model kaydediliyor...") model.save("yesilcam.model") print("Geçen süre: ",time.time()-t0) print("Etiketler kaydediliyor...") f = open("etiket.pickle", "wb") f.write(pickle.dumps(lb)) f.close() print("Geçen süre: ",time.time()-t0) print("Kayıp ve doğruluk grafikleri çiziliyor") # eğitimdeki kayıp ve doğruluğun çizimi 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.savefig("plot.png") print("Toplam süre: ",time.time()-t0) |
İşin içine eğitim girince bir sürü kütüphane ve modüle ihtiyaç duyuyoruz. Kullanacağımız kütüphaneler hakkında kabaca da olsa, bilgi sahibi olduğunuzu umuyorum. Yoksa bu yazıyı 1000-1500 kelimelik bir hacme sığdırmak olanaksız hale gelirdi.
İlk 27 satırımızda matplotlib, keras, sklearn, imutils, numpy, random, pickle, cv2 ve os kütüphanelerimizi içe aktarıyoruz. smallervggnet kütüphanesini de belki kullanırsınız diye listeden çıkarmadım.
Modeli daha basit tutmak amacıyla vgg_like() fonksiyonunu tanımladım. (VGGNet, 2014 yılındaki Imagenet Büyük Ölçekli Görsel Tanıma Yarışmasında çok iyi performans gösteren bir yapay sinir ağıdır. VGG (Visual Geometry Group) Oxford Üniversitesi Mühendislik Bilimleri Bölümünün bir alt birimidir.)
Eğitim için Doğrusal(Sequential) bir model kullanıyoruz – satır 30
Görsellerimiz 96×96 boyutlu ve 3 renk kanalına sahip.
Modelimize ilk önce bir Conv2D katmanı uyguluyoruz – satır 31
İkinci katmanımız yine Conv2D – satır 32
Üçüncü katmanda MaxPooling2D var – satır 33
34. satırdaki Dropout ile %25’lik giriş bilgisini devre dışı bırakıyoruz (overfitting engelleme).
Benzer işlemleri bir kez daha tekrarlıyoruz. Ama boyutlamalar biraz farklı. satır 36-39
Son aşamada modelimize önce Flatten(), sonra Dense() uyguluyoruz. satır 41-42
Dropout oranımız %50 satır 43
Son işlemimiz her zamanki gibi ‘softmax’ satır 44
45.satırda modelimizi geri döndürüyoruz.
47. satırda işlem başlangıç zamanını t0 olarak not ediyoruz. Bu değişkeni belirli aşamalarda geçen süreyi belirtmek için kullanacağız.
Satır 48-51’de sabitlerimizi tanımlıyoruz.
data ve labels boş birer listedir. satır 53-54
58. satırda görsel dosya yollarımızı bir listede topluyoruz (imagePaths).
imagePaths listesinin satırlarını karıştırırken, sonraki denemelerde de aynı rasgele değerleri elde edebilmek için seed değerini 42 olarak belirliyoruz. satır 60 (Bu değer herhangi bir başka sayı da olabilir. Önemli olan hep aynı başlatma değerini kullanmak.)
63. satırda görsel dosyalarını işlemeye başlıyoruz.
Dosyayı okuyoruz – satır 64
İmajı 96×96 olarak yeniden boyutlandırıyoruz – satır 65
İmajı bir dizi (array) haline dönüştürüyoruz – satır 66
Ve imaj dizisini data listesine ekliyoruz – satır 67
Dosya yolundan etiket bilgisini (sanatçı_kodu) ayrıştırıyoruz – satır 70
Etiket bilgisini labels listesine ekliyoruz – satır 71
data listesini elemanları 0 veya 1 olacak şekilde bir numpy dizisine çeviriyoruz – satır 75
Aynı şekilde labels listesini de bir numpy dizisi haline getiriyoruz – satır 76
Satır 81-82’de etiketleri sayısallaştırıyoruz.
Satır 85-86: data ve labels dizi elemanlarının %20’sini test, geri kalanını eğitim için ayırıyoruz.
Veri sayımız kısıtlı olduğu için data çoğullama (augmentation) işlemi yaptırıyoruz – satır 89-91 (Çoğullama sırasında döndürme, kaydırma, yükseklik değiştirme, kesme, büyütme ve yatay aynalama (flip) işlemleri yapılacak.)
Modelimizi vgg_like() fonksiyonu ile oluşturuyoruz – satır 99
model.summary(), model katmanlarımızın değişimini ayrıntılı bir şekilde açıklıyor. Bu özeti hem ekrana, hem de dosyaya kaydediyoruz. – satır 102-105
Optimizasyon işlemi için Adam da yaygın bir şekilde kullanılıyor. Benim yaptığım denemelerde SGD daha iyi sonuçlar almamı sağladı. Her ikisini de deneyebilirsiniz. Satır 108
110. satırda modelimizi derliyoruz.
Eğitim işlemleri (satır 116-120) için bir kaç dakika bekliyoruz.
Eğitim tamamlandığında, önce modelimizi (satır 124), sonra da etiketlerimizi (satır 128-130) kaydediyoruz.
Son aşama olarak, eğitim işlemlerimiz sırasında hesaplanan kayıp ve doğruluk değerlerini grafiğe döküyor ve plot.png adıyla kaydediyoruz. – satır 135-146
Artık sınıflandırma işlemlerimize geçebiliriz. Ama daha önce model özetimize kısaca bir göz atsak iyi olacak:
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 |
Model Özeti _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= conv2d_1 (Conv2D) (None, 94, 94, 32) 896 _________________________________________________________________ conv2d_2 (Conv2D) (None, 92, 92, 32) 9248 _________________________________________________________________ max_pooling2d_1 (MaxPooling2 (None, 46, 46, 32) 0 _________________________________________________________________ dropout_1 (Dropout) (None, 46, 46, 32) 0 _________________________________________________________________ conv2d_3 (Conv2D) (None, 44, 44, 64) 18496 _________________________________________________________________ conv2d_4 (Conv2D) (None, 42, 42, 64) 36928 _________________________________________________________________ max_pooling2d_2 (MaxPooling2 (None, 21, 21, 64) 0 _________________________________________________________________ dropout_2 (Dropout) (None, 21, 21, 64) 0 _________________________________________________________________ flatten_1 (Flatten) (None, 28224) 0 _________________________________________________________________ dense_1 (Dense) (None, 256) 7225600 _________________________________________________________________ dropout_3 (Dropout) (None, 256) 0 _________________________________________________________________ dense_2 (Dense) (None, 3) 771 ================================================================= Total params: 7,291,939 Trainable params: 7,291,939 Non-trainable params: 0 _________________________________________________________________ |
Conv2D katman çıkış boyutlarında 2 piksellik küçülme yaratıyor.
MaxPooling2D ise (2,2) oranları ile her iki yönde boyutları yarıya indiriyor.
Flatten işlemi katmanı tek boyutlu bir diziye dönüştürür.
Dropout input değerlerinden bir bölümünü işlem dışı bırakır. Böylece overfitting olasılığı azalır.
4. ADIM – SINIFLANDIRMA
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 |
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 # imajlar imajlar = ["c_arkin2.jpg","s_alisik6.jpg","t_soray2.jpg"] def check_image(image,output,imaj): image = cv2.resize(image, (96, 96)) image = image.astype("float") / 255.0 image = img_to_array(image) image = np.expand_dims(image, axis=0) print("Ağ yükleniyor...") model = load_model("yesilcam.model") lb = pickle.loads(open("etiket.pickle", "rb").read()) print("İmaj sınıflandırılıyor...") proba = model.predict(image)[0] idx = np.argmax(proba) label = lb.classes_[idx] filename = imaj[imaj.rfind(os.path.sep) + 1:] correct = "tamam" if filename.rfind(label) != -1 else "hata" mesaj = "{}: {:.2f}% ({})".format(label, proba[idx] * 100, correct) output = imutils.resize(output, width=400) cv2.putText(output, mesaj, (10, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) print("{}".format(label)) cv2.imshow("Sonuç", output) cv2.imwrite(imaj,output) cv2.waitKey(0) cv2.destroyAllWindows() if __name__ == "__main__": for imaj in imajlar: image = cv2.imread("kontrol/"+imaj) output = image.copy() check_image(image,output,imaj) |
Sınıflandırma işlemlerinde eğitim aşamasında kullanmadığımız görsellerden yararlanacağız. Her sanatçı için birer görsel seçtik ve bunları kontrol klasörünün altına kopyaladık.
İlk 7 satırda gerekli kütüphaneleri içeri aktarıyoruz.
imajlar listesi sınıflandıracağımız fotoğrafları tutuyor. satır 10
Sınıflandırma işlemlerini check_image() fonksiyonu ile yapacağız (satır 12). Her görsel ayrı ayrı işleme sokulacak.
Satır 13-16’da imajımızı 96×96 piksel boyutlarına küçültüyor ve modelimize uygun bir numpy dizisine dönüştürüyoruz.
Satır 19 ve 20’de eğitim sonunda kaydettiğimiz model ve etiket bilgilerini belleğe alıyoruz.
Modele göre bulunan tahmin ve olası doğruluk derecesini saptıyor; etiket değerini alıyoruz. satır 23-25
Sınıflandırma sonucunu genişliğini 400 pikselle sınırlandırdığımız imajımızın üzerine işliyoruz. satır 28-33
İşlenmiş imajı ekranda gösterip diske kaydettikten sonra bir tuşa basılmasını bekliyoruz. satır 35-38
Tuşa basıldığında belleği temizliyoruz: satır 39
check_image() fonksiyonumuzu her resim için ayrı ayrı çağıran döngümüz satır 42-45 arasında.
Elde ettiğim görüntüler aşağıda:
SmallerVGGNet kullanarak yaptığım denemelerde daha yüksek sayısal doğruluk oranlarına ulaştım ama, yapılan tahminlerin pek çoğu aslında hatalıydı. Bu, veri kümemizin çok dar olmasından kaynaklanmış olabilir.
Aynı eğitim setini yüz saptama fonksiyonlarıyla birlikte kullanarak grup fotoğraflarından çoklu yüz tanıma işlemleri gerçekleştirmek mümkün olabilir.
Açıklamalara böyle bir blog yazısında daha kapsamlı bir şekilde girmek mümkün olmuyor. Sorularınız varsa, yorum alanından iletebilirsiniz.
Bu tür çalışmalar çok geniş bir kapsama alanına sahip. Yapılan işlemler ve elde edilen sonuçlar deneysel nitelikte. Bu yüzden, bu tür konulara ilgi duyuyorsanız, siz de kendi modellerinizi geliştirin. Mümkünse veri kümeleri oluşturun. Deneyin. Yorumlayın.
Elbette hala öğrenecek çok şey var. Ama yapabileceğimiz şeyler de pek çok…
Önemli olan, hangi düzeyde olursak olalım, deneysel bakış açımızı yitirmemek ve hevesimizi kaybetmemek… Hata yapmak bizi korkutmasın. Hatalar, kullanmasını bilenler için en etkin yol göstericilerdir.
Yeni yazılarda buluşmak üzere…
Beni izlemeye devam edin.
Ahmet Aksoy
Referanslar:
https://github.com/kjaisingh/pokemon-classifier/blob/master/pyimagesearch/smallervggnet.py
https://www.pyimagesearch.com/2018/04/16/keras-and-convolutional-neural-networks-cnns/
https://hackernoon.com/learning-keras-by-implementing-vgg16-from-scratch-d036733f2d5
http://www.robots.ox.ac.uk/~vgg/research/very_deep/