Здравствуйте, дорогие друзья, хабрчане. Меня зовут Илья, я тут новенький, и сегодня, я расскажу вам, как я решил научить нейронную сеть различать пол человека по фотографии лица.
Первым делом нам понадобятся сам датасет. Датасетов в интернете огромное количество, но ни один мне не подошел. И по этому, я решил сделать свой датасет.
Находим источник для фото - у меня это был сайт, где генерируются лица людей, которые никогда не существовали и с каждым обновлением лицо меняется - 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, и функция потерь.
Надеюсь моя первая статья вам понравится, и новичкам она как-то поможет, всем удачи !
Комментарии (8)
eigrad
07.11.2022 22:04Все тоже самое кроме функции активации, теперь она не softmax а sigmoid, и функция потерь
И оптимизатор) короче вообще все другое, кроме "архитектуры") ой, в архитектуре тоже лишний Dense слой появился)
Чем был обеспечен выбор используемых оптимизаторов наверное нет смысла спрашивать?) Используй базовый SGD, он сильно быстрее и экономнее по памяти, ему нужно меньше параметров для подсчета градиентов.
dyadyaSerezha
08.11.2022 00:21И какова же точность распознавания? Хотя это не так важно. Лучше бы научил свою сеть определять симпатичных девушек и сразу определять их телефончики (по фото).
sergeim52b20
08.11.2022 09:15+1Автор научил не мужчин от женщин отличать, а сгенерированных мужчин от сгенерированных женщин. При работе на реальных фото неизвестно как измениться качество, возможно сильно просядет
MrKozelberg
09.11.2022 21:37В конце статьи предлагается новая архитектура нейронной сети и на этом внезапный конец. Какая из двух архитектура показала себя лучше в данной задаче?
Ksyushik
09.11.2022 21:37Ох, в некоторых странах подобные исследования опасно делать, так как там полов больше 2, а если это отвергать, то есть риск быть отмененным.
В следующий раз лучше используйте датасет с котанами и собакенами. Их ещё и любят больше, чем человеков!
eltardowut
«Гидхаб», лол.
sinefag
идем на "гидхаб" и там видим "for hubr".....