Здравствуйте, дорогие друзья, хабрчане. Меня зовут Илья, я тут новенький, и сегодня, я расскажу вам, как я решил научить нейронную сеть различать пол человека по фотографии лица.

Первым делом нам понадобятся сам датасет. Датасетов в интернете огромное количество, но ни один мне не подошел. И по этому, я решил сделать свой датасет.

  1. Находим источник для фото - у меня это был сайт, где генерируются лица людей, которые никогда не существовали и с каждым обновлением лицо меняется - 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, и функция потерь.

Надеюсь моя первая статья вам понравится, и новичкам она как-то поможет, всем удачи !

git: https://github.com/paradiseMaestro

Комментарии (8)


  1. eltardowut
    07.11.2022 14:39

    «Гидхаб», лол.


    1. sinefag
      07.11.2022 19:47
      -1

      идем на "гидхаб" и там видим "for hubr".....


  1. densss2
    07.11.2022 19:56

    Автор статьи проконсультировался со своим адвокатом?))))


  1. eigrad
    07.11.2022 22:04

    Все тоже самое кроме функции активации, теперь она не softmax а sigmoid, и функция потерь

    И оптимизатор) короче вообще все другое, кроме "архитектуры") ой, в архитектуре тоже лишний Dense слой появился)

    Чем был обеспечен выбор используемых оптимизаторов наверное нет смысла спрашивать?) Используй базовый SGD, он сильно быстрее и экономнее по памяти, ему нужно меньше параметров для подсчета градиентов.


  1. dyadyaSerezha
    08.11.2022 00:21

    И какова же точность распознавания? Хотя это не так важно. Лучше бы научил свою сеть определять симпатичных девушек и сразу определять их телефончики (по фото).


  1. sergeim52b20
    08.11.2022 09:15
    +1

    Автор научил не мужчин от женщин отличать, а сгенерированных мужчин от сгенерированных женщин. При работе на реальных фото неизвестно как измениться качество, возможно сильно просядет


  1. MrKozelberg
    09.11.2022 21:37

    В конце статьи предлагается новая архитектура нейронной сети и на этом внезапный конец. Какая из двух архитектура показала себя лучше в данной задаче?


  1. Ksyushik
    09.11.2022 21:37

    Ох, в некоторых странах подобные исследования опасно делать, так как там полов больше 2, а если это отвергать, то есть риск быть отмененным.

    В следующий раз лучше используйте датасет с котанами и собакенами. Их ещё и любят больше, чем человеков!