[PYTHON] Divulguer le savoir-faire qui a créé un service de recherche d'images similaire pour les actrices audiovisuelles grâce à l'apprentissage profond par chainer

environnement

PC: MacBook Air CPU: 1.4 GHz Intel Core i5 Mémoire: 4 Go

Je l'ai fait avec un MacBook Air normal, mais c'était douloureux car l'apprentissage était lent et il est tombé en raison d'un manque de mémoire.

Écoulement brutal

  1. Collectez des images de chaque actrice.
  2. Découpez l'image du visage avec dlib et redimensionnez-la au format 96x96.
  3. Développez les données pour qu'il y ait 1000 images par personne.
  4. Convertissez les données en un fichier numpy.
  5. Apprenez les images de visage avec le chainer.
  6. Prédisez n'importe quelle image du modèle entraîné.

1. Collectez des images

Je ne peux pas écrire en détail ici, veuillez donc vous référer à l'article suivant.

[Python: raclage de sites Web avec BeautifulSoup4](http://momijiame.tumblr.com/post/114227737756/python-beautifulsoup4-%E3%82%92%E4%BD%BF%E3%81%A3 % E3% 81% A6-web-% E3% 82% B5% E3% 82% A4% E3% 83% 88% E3% 82% 92% E3% 82% B9% E3% 82% AF% E3% 83% AC% E3% 82% A4% E3% 83% 94% E3% 83% B3% E3% 82% B0% E3% 81% 99% E3% 82% 8B) Grattage avec Python et Beautiful Soup

Les images acquises sont enregistrées dans des répertoires séparés pour chaque actrice.

./folder
    |--- /actress1
    |        |--- image1.jpg
    |        |--- image2.jpg
    |        |--- image3.jpg
    |
    |--- /actress2
    |        .
    |        .
    |--- /actress3
    .
    .
    .
    

2. Découpez l'image du visage avec dlib

Je pense qu'OpenCV est célèbre en matière de reconnaissance d'image, mais si vous regardez dlib vs OpenCV face detection, la bibliothèque dlib est fausse quand il s'agit d'extraction de visage. J'ai utilisé dlib car il semble être bon avec peu de détections.

import os
import sys
import glob
import cv2
from PIL import Image
import dlib

"""
INPUT_DIR est(1.Collectez des images)Nom de répertoire de l'image obtenue dans
OUTPUT_DIR est le nom du répertoire de sortie(La structure du dossier est INPUT_Identique à DIR)
"""

detector = dlib.get_frontal_face_detector()

#Obtenez une liste de répertoires pour chaque actrice
dir_list = os.listdir(INPUT_DIR)

for i, dir_name in enumerate(dir_list):
    if not os.path.exists(os.path.join(OUTPUT_DIR, dir_name)):
        os.mkdir(os.path.join(OUTPUT_DIR, dir_name))
    image_files = glob.glob(os.path.join(INPUT_DIR, dir_name, "*.jpg "))

    for j, image_file in enumerate(image_files):
        img = cv2.imread(image_file)
        dets = detector(img, 1)
        open_img = Image.open(image_file)

        for k, d in enumerate(dets):
            #Ignorer les images inférieures à 80
            if d.right()-d.left() < 80 or d.bottom()-d.top() < 80:
                continue

            image_file = image_file.replace(INPUT_DIR, OUTPUT_DIR)
            #S'il y a plusieurs visages dans une image, le nom du fichier de sortie sera couvert, alors changez-le
            output_file = image_file.replace('.jpg', '_'+str(k)+'.jpg')

            cropped_img = open_img.crop((d.left(), d.top(), d.right(), d.bottom()))
            cropped_img.resize((96,96)).save(output_file, 'JPEG', quality=100, optimize=True)

En plus de l'extraction du visage, dlib a également la capacité de détecter des organes tels que les yeux, le nez et les contours du visage.

Matériel de référence

dlib.net face_detect.py

3. Augmentation des données

import os
import math
import random
import glob
import numpy as np
from scipy import misc
from PIL import Image
import cv2

#Retourner horizontalement
def flip_left_right(image):
    return image[:, -1::-1]

#Changer la luminosité
def random_brightness(image, max_delta=63, seed=None):
    img = np.array(image)
    delta = np.random.uniform(-max_delta, max_delta)
    image = Image.fromarray(np.uint8(img + delta))
    return image

#Changer le contraste
def random_contrast(image, lower, upper, seed=None):
    factor = np.random.uniform(-lower, upper)
    mean = (image[0] + image[1] + image[2]).astype(np.float32) / 3
    img = np.zeros(image.shape, np.float32)
    for i in range(0, 3):
        img[i] = (img[i] - mean) * factor + mean
    return img

#Recadrage d'image
def crop(image, name, crop_size, padding_size):
    (width, height) = image.shape
    cropped_images = []
    for i in xrange(0, width, padding_size):
        for j in xrange(0, height, padding_size):
            box = (i, j, i+crop_size, j+crop_size) #left, upper, right, lower
            cropped_name = name + '_' + str(i) + '_' + str(j) + '.jpg'
            cropped_image = image[i:i+crop_size, j:j+crop_size]
            resized_image = cv2.resize(cropped_image, (IMAGE_SIZE, IMAGE_SIZE))
            cropped_images.append(resized_image)

    return cropped_images

#Expansion des données
# data_"Inverser gauche et droite" "Changer la luminosité" "Changer le contraste" "Recadrer" jusqu'à ce que la valeur spécifiée dans num soit atteinte
def data_augmentation(image_files, data_num):
    image_list = []
    file_num = len(image_files)
        
    for image_file in image_files:
        image_list.append(misc.imread(image_file))

    if file_num >= data_num:
        return image_list

    # flip left right
    random.shuffle(image_list)
    for image in image_list:
        flipped_image = flip_left_right(image)
        image_list.append(flipped_image)
        if len(image_list) == data_num:
            return image_list

    # random brightness
    random.shuffle(image_list)
    for image in image_list:
        brightness_image = random_brightness(image)
        image_list.append(brightness_image)
        if len(image_list) == data_num:
            return image_list

    # random contrast
    random.shuffle(image_list)
    for image in image_list:
        contrast_image = random_contrast(image)
        image_list.append(contrast_image)
        if len(image_list) == data_num:
            return image_list

    # cropping
    random.shuffle(image_list)
    image_list.clear()
    cropped_size = int(IMAGE_SIZE * 0.75)
    padding_size = IMAGE_SIZE - cropped_size
    for image in image_list:
        cropped_image_list = crop(image, 'image', cropped_size, padding_size)
        for cropped_image in cropped_image_list:
            image_list.append(cropped_image)
            if len(image_list) == data_num:
                return image_list

    return image_list


dir_list = os.listdir(INPUT_DIR)

for dir in dir_list:
    image_files = glob.glob(os.path.join(input_dir, dir, "*.jpg "))
    if len(image_files) == 0:
        continue

     
    image_list = data_augmentation(image_files, 1000)

    for i, image in enumerate(image_list):
        image = whitening(image)
        misc.imsave(os.path.join(OUTPUT_DIR, dir, str(i) + '.jpg'), image)

Matériel de référence

Extension des données d'entraînement avec l'échantillon / blanchiment imagenet de Chainer

4. Convertissez les données au format numpy

import os
import sys
import glob
import random
import numpy as np
from scipy import misc

""" Get files from specified directory """
def load_data_from_dir(input_dir_name, input_dir_list, start_index, test_freq):
    train_list = []
    test_list = []

    for dir_index, dir_name in enumerate(input_dir_list):
        image_files = glob.glob(os.path.join(input_dir_name, dir_name, "*.jpg "))
        train_count = 0
        test_count = 0
        print('directory:{} index:{}'.format(dir_name, dir_index + start_index))

        for file_index, file_name in enumerate(image_files):
            image = misc.imread(file_name)
            label = np.int32(dir_index + start_index)
            if not file_index % test_freq == 0: # set train datq
                train_list.append((dir_name, image, label))
                train_count += 1
            else:
                test_list.append((dir_name, image, label))
                test_count += 1

        print("directory:{} total:{} train:{} test:{}".format(
               dir_name, train_count + test_count, train_count, test_count))

    return train_list, test_list

""" Save data in a numpy format """
def save_dataset_numpy(data_list, image_path, label_path):
    image_list = []
    label_list = []
    for _, image, label in data_list:
        image_list.append(image)
        label_list.append(label)

    image_data = np.array(image_list, dtype=np.float32)
    label_data = np.array(label_list, dtype=np.int32)

    np.save(image_path, image_data)
    np.save(label_path, label_data)

for i in xrange(0, len(DIR_LIST), 10):
    #Créez un fichier pour 10 classes
    train_list, test_list = load_data_from_dir(INPUT_DIR, dir_list[i:i+args.interval], i, 10)

    train_data_path = os.path.join(OUTPUT_DIR, 'train', 'data-{}.npy'.format(i+args.interval))
    train_label_path = os.path.join(OUTPUT_DIR, 'train', 'label-{}.npy'.format(i+args.interval))
    test_data_path = os.path.join(OUTPUT_DIR, 'test', 'data-{}.npy'.format(i+args.interval))
    test_label_path = os.path.join(OUTPUT_DIR, 'test', 'label-{}.npy'.format(i+args.interval))

    save_dataset_numpy(train_list, train_data_path, train_label_path)
    save_dataset_numpy(test_list, test_data_path, test_label_path)

5. Apprenez les images de visage avec le chainer

Au début, j'ai essayé de le faire avec Tensorflow, mais personnellement je suis passé à Chainer car il y avait de nombreuses fonctions supplémentaires.

Pour apprendre, faites d'abord quelque chose pour apprendre correctement avec CIFAR-10 (10 classifications d'objets généraux), puis apprenez les données réelles. Je l'ai fait.

Le réseau a utilisé AlexNet with Batch Normalization avec une légère modification.

Ce qui a échoué

//Taille par feuille
96×96×3 = 27648(byte)

//Par classe
27648×1000 = 27648000(byte) = 26.4(MB)

//L'ensemble(66 cours) ...Avez-vous un calcul?
26.4×66 = 1742.4(MB) = 1.7(GB)

"""
Batch iterator class

Usage:
batch_iter = BatchIter(DATA_DIR, 100)

for batch_data, batch_label in batch_iter:
    batch_start_time = time.time()
    x = np.asarray(batch_data, dtype=np.float32).transpose((0, 3, 1, 2))
    t = np.asarray(train_batch_label, dtype=np.int32)
    x = Variable(xp.asarray(x))
    t = Variable(xp.asarray(t))

    optimizer.update(model, x, t)
"""
class BatchIter(object):
    def __init__(self, data_dir, batch_size):
        self.index = 0
        self.batch_size = batch_size
        self.data_files = glob.glob(os.path.join(data_dir, 'data-*.npy'))
        self.label_files = glob.glob(os.path.join(data_dir, 'label-*.npy'))
        data_size = 0
        for data in self.data_files:
            loaded_data = np.load(data)
            data_size += loaded_data.shape[0]
            del loaded_data
        self.data_size = data_size

        assert len(self.data_files) == len(self.label_files), "Invalid data size."

    def __iter__(self):
        return self

    def next(self):
        if self.index >= self.data_size:
            raise StopIteration()

        data = np.zeros((self.batch_size, IMAGE_SIZE, IMAGE_SIZE, 3))
        label = np.zeros((self.batch_size))
        incremental_value = int(self.batch_size / len(self.data_files))

        count = 0
        for i in range(len(self.data_files)):
            loaded_data = np.load(self.data_files[i])
            loaded_label = np.load(self.label_files[i])
            assert loaded_data.shape[0] == loaded_label.shape[0], "Loaded data size is invalid."

            perm = np.random.permutation(loaded_data.shape[0])
            if i + 1 == len(self.data_files): # last item
                incremental_value = self.batch_size - count
                idx = perm[0:incremental_value]
            else:
                idx = perm[0:incremental_value]

            data[count:count+incremental_value] = loaded_data[idx]
            label[count:count+incremental_value] = loaded_label[idx]

            count += incremental_value
            del loaded_data
            del loaded_label

        self.index += self.batch_size
        return data, label

Matériel de référence

6. Prédire n'importe quelle image du modèle entraîné

def set_model(model_name, model_path):
    model_fn = os.path.basename('models/' + model_name + '.py')
    model = imp.load_source(model_fn.split('.')[0],
                            'models/' + model_name + '.py').model

    print('Load model from ', model_path)
    serializers.load_hdf5(model_path, model)

    return model

def set_optimizer(opt_name, opt_path, model):
    if opt_name == 'MomentumSGD':
        optimizer = optimizers.MomentumSGD(momentum=0.9)
    elif opt_name == 'Adam':
        optimizer = optimizers.Adam()
    elif opt_name == 'AdaGrad':
        optimizer = optimizers.AdaGrad()
    else:
        raise ValueError('Invalid architecture name')

    optimizer.setup(model)

    print('Load optimizer state from ', opt_path)
    serializers.load_hdf5(opt_path, optimizer)

    return optimizer

def detect_face(image_file):
    detector = dlib.get_frontal_face_detector()
    #img = cv2.imread(image_file)
    image = misc.imread(image_file)
    dets = detector(image, 1)
    d = dets[0]
    cropped_image = image[d.top():d.bottom(), d.left():d.right()]
    resized_image = misc.imresize(cropped_image, (96, 96))
    return resized_image


#Chargement du modèle
model = set_model(model_name, model_path)
optimizer = set_optimizer(opt_name, opt_path, model)
detected_face_image = detect_face(input_image)

#Prédire à partir du modèle chargé
x = np.asarray(detected_face_image, dtype=np.float32).transpose((0, 3, 1, 2))
x = Variable(np.asarray(x), volatile='on') 
pred = model.predict(x).data

#Lecture d'étiquettes(Créer un fichier d'étiquette lors de la création d'un fichier au format numpy)
categories = np.loadtxt(label_path, str, delimiter="\n")

#Trier par ordre décroissant de score
score = pred.reshape((pred.size,))
result = zip(score, categories)
result = sorted(result, reverse=True)

results = []
for i, (score, label) in enumerate(result[:10]):
    if i == 5: break
    print('num:{} score:{:.5f} label:{}'.format(i + 1, score * 100, label))
    results.append({
        'label': label,
        'score': str(round(score * 100, 2))
    })

Serveur de production

Cela n'a rien à voir avec l'apprentissage en profondeur, mais lorsque j'ai créé le site Web, j'ai d'abord essayé Heroku, mais j'ai finalement choisi Conoha. Lors de l'utilisation de dlib ou chainer, il était assez difficile de l'installer avec Heroku. Conoha semble avoir eu un problème avec le taux d'utilisation il y a quelque temps, mais il semble qu'il soit acceptable de le renouveler. J'étais perdu avec Sakura VPS, mais le facteur décisif était que Sakura VPS avait un coût initial, tandis que Conoha n'avait aucun coût initial.

J'ai également écrit un article sur l'utilisation de Chainer Trainer, qui contient un code abstrait pour la partie d'apprentissage, alors veuillez vous y référer si vous le souhaitez. J'ai essayé d'apprendre mon propre ensemble de données à l'aide de Chainer Trainer

Enfin, je crée un site qui recherche des images similaires d'actrices audiovisuelles utilisant CNN, alors jetez un œil si vous le souhaitez. Babelink - Service de recherche d'actrices audiovisuelles similaires

Recommended Posts

Divulguer le savoir-faire qui a créé un service de recherche d'images similaire pour les actrices audiovisuelles grâce à l'apprentissage profond par chainer
Créez un "bot qui vous informe des actrices audiovisuelles aux visages similaires" grâce à l'apprentissage en profondeur
Essayez une recherche similaire de recherche d'images à l'aide du SDK Python [Recherche]
Programme pour rechercher la même image
J'ai installé le framework Deep Learning Chainer
Création d'une image trompeuse pour le modèle de génération de légende
Introduction au Deep Learning pour la première fois (Chainer) Reconnaissance de caractères japonais Chapitre 3 [Reconnaissance de caractères à l'aide d'un modèle]
Recherche par image de la pellicule en utilisant Pythonista3
J'ai recherché une carte similaire de Hearthstone avec Deep Learning
Création d'un service qui vous permet de rechercher des données J-League
Résumé des pages utiles pour étudier le framework d'apprentissage profond Chainer
Introduction au Deep Learning pour la première fois (Chainer) Reconnaissance de caractères japonais Chapitre 2 [Génération de modèles par apprentissage automatique]
[AI] Apprentissage en profondeur pour le débruitage d'image
Mémo d'apprentissage Python pour l'apprentissage automatique par Chainer jusqu'à la fin du chapitre 2