Здравствуйте, дорогие друзья, хабрчане. Меня зовут Илья, я тут новенький, и сегодня, я расскажу вам, как я решил научить нейронную сеть различать пол человека по фотографии лица.
Первым делом нам понадобятся сам датасет. Датасетов в интернете огромное количество, но ни один мне не подошел. И по этому, я решил сделать свой датасет.
Находим источник для фото - у меня это был сайт, где генерируются лица людей, которые никогда не существовали и с каждым обновлением лицо меняется - https://thispersondoesnotexist.com/
2)Теперь нужно закачать 2000 картинок с этого сайта, я буду для этого использовать следующий код:
import shutil import time import requests while (i < 1000): url = 'https://thispersondoesnotexist.com/image' response = requests.get(url, stream=True) with open(str(i)+'.jpg', 'wb') as out_file: shutil.copyfileobj(response.raw, out_file) del response i = i + 1 time.sleep(1)
Тут все просто, в любой папке создаем .py файл, добавляем туда этот код, и в консоли его вызываем, ждем 2000 сек. и гарантированно получаем 2к картинок.
Далее ручками(буквально) нужно набрать 500 картинок мужчин и женщин. Я делал следующим образом: открывал папку и выделял все фото мужчин через CTRL + клик, и вырезал в другую папку(конечно я отправлю ссылку на гидхаб где будут фото).
Теперь нам их нужно переименовать нормально: создаем функцию которая будет брать каждый файл из определенной папки и по номеру итерации цикла создает ему имя и переносит в другую папку, и так мы получаем файлы изобращений(0.jpg ... 499.jpg)
import os as s def renameFilesInNumbers(from_,to_, g=''): i = 0 name1 = s.listdir('./'+from_) while(i < len(name1)): # while(i < 101): s.rename('ваш путь до папки'+from_+'/' +name1[i],'./'+to_+'/'+ ''+str(i)+g+'.jpg') i = i + 1
Дальше нужно преобразовать эти фото в нужный формат: просто делаем их серыми и ставим разрешение 299*299.
Вот код:
import cv2 import os as s def ResizeImags(from_,to_): i = 0 name1 = s.listdir('ваш путь до папки'+from_) while( i < len(name1)): img = cv2.imread('ваш путь до папки'+from_+'/'+str(i)+'.jpg', cv2.IMREAD_UNCHANGED) gray_image = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) resized = cv2.resize(gray_image, (299, 299), interpolation = cv2.INTER_AREA) cv2.imwrite('./'+to_+'/'+str(i)+'.jpg',resized) i = i + 1
Далее мы создаем файл в google colab и пишем саму нейронку.
Архитектура максимально простая, была взята из примера про определение одежды:
model = Sequential() model.add(Conv2D( 2, 3, activation='relu', padding="same", input_shape=(299,299,1))) model.add(MaxPooling2D()) model.add(Conv2D( 2, 3, activation='relu', padding="same")) model.add(MaxPooling2D()) model.add(Conv2D( 2, 3, activation='relu', padding="same")) model.add(MaxPooling2D()) model.add(Flatten()) model.add(Dense(128,activation='relu')) model.add(Dense(64,activation='relu')) model.add(Dense(18,activation='relu')) model.add(Dense(2,activation='softmax')) model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
А теперь, вспомним о нашем датасете, как из папки картинок сделать дата сет, ну честно говоря, никакой датасет мы делать не будем, просто из каждой папки сделаем массив картинок.
Выглядеть это будет так:
i = 0 while (i < 500): # тут все понятно , 500 фоток == 500 этераций data.append( np.array(cv2.imread('./M/'+str(i)+'.jpg', cv2.IMREAD_UNCHANGED))/255 ) # при каждой итерации добаляем изображение мужчины data.append( np.array(cv2.imread('./W/'+str(i)+'.jpg', cv2.IMREAD_UNCHANGED))/255 ) # женщины data1.append( [0., 1.]) # при каждой итерации добаляем вектор значения мужчины data1.append( [1., 0.]) # женщины i = i + 1
Вектор значения - это мой собственный термин, обозначающий преобразование любого значения, любой определенной формы в векторный формат.
Уверен , что для того ,что я имею ввиду , есть свой термин , - я его просто не знаю.
Дальше просто указываю с какими параметрами будет компилироваться моя нейронная сеть, а именно: какая будет функция потерь(loss) - 'категориальная кросс энтропия', оптимизатор - 'adam', метрика которую будем видеть в момент обучения - точность.
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
Весь код:
from typing_extensions import Text import keras import numpy as np import cv2 from keras.models import Sequential from keras.layers import Dense, Dropout , Conv2D , Flatten, MaxPooling2D data = [] data1 = [] i = 0 while (i < 500): data.append( np.array(cv2.imread('./M/'+str(i)+'.jpg', cv2.IMREAD_UNCHANGED))/255 ) data.append( np.array(cv2.imread('./W/'+str(i)+'.jpg', cv2.IMREAD_UNCHANGED))/255 ) data1.append( [0., 1.]) data1.append( [1., 0.]) i = i + 1 model = Sequential() model.add(Conv2D( 2, 3, activation='relu', padding="same", input_shape=(299,299,1))) model.add(MaxPooling2D()) model.add(Conv2D( 2, 3, activation='relu', padding="same")) model.add(MaxPooling2D()) model.add(Flatten()) model.add(Dense(128,activation='relu')) model.add(Dense(64,activation='relu')) model.add(Dense(18,activation='relu')) model.add(Dense(2,activation='softmax')) model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy']) model.fit(np.array(data), np.array(data1) , epochs=7 ) print( model.predict( np.array( [np.array(cv2.imread('./M/'+str(519)+'.jpg', cv2.IMREAD_UNCHANGED))/255] ) ) ) print( model.predict( np.array( [np.array(cv2.imread('./M/'+str(541)+'.jpg', cv2.IMREAD_UNCHANGED))/255] ) ) )
Вот фото которые получились.




Казалось бы, миссия выполнена. Но можно по другому. Можно использовать другую архитектуру. В чем будет разница: так-как у нас всего 2 варианта, то можно вместо категориальной кросс энтропии использовать бинарную кросс энтропию.
Новый код:
import os import keras import numpy as np import cv2 from keras.models import Sequential from keras.layers import Dense, Dropout , Conv3D , Flatten, MaxPooling2D data = [] data1 = [] i = 0 while (i < 500): data.append( np.array(cv2.imread('./M/'+str(i)+'.jpg', cv2.IMREAD_UNCHANGED))/255 ) data.append( np.array(cv2.imread('./W/'+str(i)+'.jpg', cv2.IMREAD_UNCHANGED))/255 ) data1.append( [1.]) data1.append( [0.]) i = i + 1 model = Sequential() model.add(Conv2D( 2, 3, activation='relu', padding="same", input_shape=(299,299,1))) model.add(MaxPooling2D()) model.add(Conv2D( 2, 3, activation='relu', padding="same")) model.add(Flatten()) model.add(Dense(64, activation='relu')) model.add(Dense(64, activation='relu')) model.add(Dropout(0.5)) model.add(Dense(1, activation='sigmoid')) model.compile(loss='binary_crossentropy', optimizer='rmsprop', metrics=['accuracy']) model.fit(np.array(data), np.array(data1) , epochs=10 )
Все тоже самое кроме функции активации, теперь она не softmax а sigmoid, и функция потерь.
Надеюсь моя первая статья вам понравится, и новичкам она как-то поможет, всем удачи !
