Il y a environ un an écrit en Chainer C'est un programme pour classer les visages d'anime, mais cette fois je l'ai écrit dans Keras. Le programme est répertorié sur GitHub.
L'ensemble de données peut être obtenu à partir de animeface-character-dataset. Référence: j'ai essayé d'extraire les caractéristiques du visage animé avec Denoising AutoEncoder
Je l'ai amélioré un peu plus qu'avant. Redimensionner en données RVB 32x32 (forme = (3, 32, 32)). La différence avec la dernière fois est que cela fonctionne probablement même si vous ne supprimez pas le dossier vide qui ne contient aucune donnée. Exigences
Il est.
#! -*- coding: utf-8 -*-
import os
import six.moves.cPickle as pickle
import numpy as np
try:
import cv2 as cv
except:
pass
from progressbar import ProgressBar
class AnimeFaceDataset:
def __init__(self):
self.data_dir_path = u"./animeface-character-dataset/thumb/"
self.data = None
self.target = None
self.n_types_target = -1
self.dump_name = u'animedata'
self.image_size = 32
def get_dir_list(self):
tmp = os.listdir(self.data_dir_path)
if tmp is None:
return None
ret = []
for x in tmp:
if os.path.isdir(self.data_dir_path+x):
if len(os.listdir(self.data_dir_path+x)) >= 2:
ret.append(x)
return sorted(ret)
def get_class_id(self, fname):
dir_list = self.get_dir_list()
dir_name = filter(lambda x: x in fname, dir_list)
return dir_list.index(dir_name[0])
def get_class_name(self, id):
dir_list = self.get_dir_list()
return dir_list[id]
def load_data_target(self):
if os.path.exists(self.dump_name+".pkl"):
print "load from pickle"
self.load_dataset()
print "done"
else:
dir_list = self.get_dir_list()
ret = {}
self.target = []
self.data = []
print("now loading...")
pb = ProgressBar(min_value=0, max_value=len(dir_list)).start()
for i, dir_name in enumerate(dir_list):
pb.update(i)
file_list = os.listdir(self.data_dir_path+dir_name)
for file_name in file_list:
root, ext = os.path.splitext(file_name)
if ext == u'.png':
abs_name = self.data_dir_path+dir_name+'/'+file_name
# read class id i.e., target
class_id = self.get_class_id(abs_name)
self.target.append(class_id)
# read image i.e., data
image = cv.imread(abs_name)
image = cv.resize(image, (self.image_size, self.image_size))
image = image.transpose(2,0,1)
image = image/255.
self.data.append(image)
pb.finish()
print("done.")
self.data = np.array(self.data, np.float32)
self.target = np.array(self.target, np.int32)
self.dump_dataset()
def dump_dataset(self):
pickle.dump((self.data,self.target), open(self.dump_name+".pkl", 'wb'), -1)
def load_dataset(self):
self.data, self.target = pickle.load(open(self.dump_name+".pkl", 'rb'))
if __name__ == '__main__':
dataset = AnimeFaceDataset()
dataset.load_data_target()
Quand je l'ai lu
In [1]: from animeface import AnimeFaceDataset
In [2]: dataset = AnimeFaceDataset()
In [3]: dataset.load_data_target()
load from pickle
done
In [4]: x = dataset.data
In [5]: y = dataset.target
In [6]: print x.shape, y.shape
(14490, 3, 32, 32) (14490,)
Par conséquent, le nombre de données est de 14490 et le nombre de classes (nombre de caractères) est de 176. (Jusqu'à présent, c'est presque la même chose que la dernière fois.)
from keras.layers.convolutional import Convolution2D
from keras.layers.convolutional import MaxPooling2D
from keras.layers.core import Activation
from keras.layers.core import Dense
from keras.layers.core import Dropout
from keras.layers.core import Flatten
from keras.models import Sequential
def build_deep_cnn(num_classes=3):
model = Sequential()
model.add(Convolution2D(96, 3, 3, border_mode='same', input_shape=(3, 32, 32)))
model.add(Activation('relu'))
model.add(Convolution2D(128, 3, 3))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.5))
model.add(Convolution2D(256, 3, 3, border_mode='same'))
model.add(Activation('relu'))
model.add(Convolution2D(256, 3, 3))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.5))
model.add(Flatten())
model.add(Dense(1024))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes))
model.add(Activation('softmax'))
return model
Vous pouvez construire un réseau simplement en générant d'abord un modèle séquentiel avec model = Sequential ()
puis en y ajoutant Convolutional2D
et Dense
.
On a l'impression que «Convolutional2D» correspond à la couche de convolution et «Dense» correspond à la couche entièrement connectée.
Le tout premier,
Convolution2D(96, 3, 3, border_mode='same', input_shape=(3, 32, 32))
Il suffit de spécifier ʻinput_shape. Pour expliquer brièvement
Convolutional2D, le premier argument spécifie le nombre de noyaux convolutifs, et les deuxième et troisième arguments spécifient la taille du noyau convolutif. Il existe deux types de
border_mode,
sameet
valid, mais dans
same, le remplissage est la moitié de la taille du noyau, c'est-à-dire que la taille verticale et horizontale de la sortie ne change pas la taille verticale et horizontale de l'entrée. .. «valide» signifie qu'il n'y a pas de remplissage, c'est-à-dire que les dimensions verticale et horizontale de la sortie sont plus petites que les dimensions verticale et horizontale de l'entrée. En ce qui concerne le remplissage, [ici](https://github.com/vdumoulin/conv_arithmetic) est facile à comprendre. Cette fois, la "forme" du noyau de convolution avec "même" est "(96, 3, 3)", et la "forme" de l'entrée est "(3, 32, 32)", donc la "forme" de la sortie de cette couche est ". Cela devient (96, 32, 32) ». Dans le cas de «valide», la forme de sortie »est« (96, 32- (3-1), 32- (3-1)) = (96, 30, 30) ». Vous pouvez également définir
stride`, etc.
from keras.callbacks import EarlyStopping
from keras.callbacks import LearningRateScheduler
from keras.optimizers import Adam
from keras.optimizers import SGD
from animeface import AnimeFaceDataset
class Schedule(object):
def __init__(self, init=0.01):
self.init = init
def __call__(self, epoch):
lr = self.init
for i in xrange(1, epoch+1):
if i%5==0:
lr *= 0.5
return lr
def get_schedule_func(init):
return Schedule(init)
dataset = AnimeFaceDataset()
dataset.load_data_target()
x = dataset.data
y = dataset.target
n_class = len(set(y))
perm = np.random.permutation(len(y))
x = x[perm]
y = y[perm]
model = build_deep_cnn(n_class)
model.summary()
init_learning_rate = 1e-2
opt = SGD(lr=init_learning_rate, decay=0.0, momentum=0.9, nesterov=False)
model.compile(loss='sparse_categorical_crossentropy', optimizer=opt, metrics=["acc"])
early_stopping = EarlyStopping(monitor='val_loss', patience=3, verbose=0, mode='auto')
lrs = LearningRateScheduler(get_schedule_func(init_learning_rate))
hist = model.fit(x, y,
batch_size=128,
nb_epoch=50,
validation_split=0.1,
verbose=1,
callbacks=[early_stopping, lrs])
Pour les callbacks
de l'argument de la fonction fit
à apprendre, spécifiez ʻEarly Stopping, qui termine automatiquement l'apprentissage lorsque la convergence est déterminée, ou
LearningRateScheduler, qui peut ajuster le taux d'apprentissage pour chaque ʻepoch
. C'est pratique et peut être fait.
Pour expliquer brièvement «LearningRateScheduler», nous prenons «une fonction qui renvoie le taux d'apprentissage lorsque le nombre actuel de« epoch »est donné à l'argument (commençant par 0)» comme argument. Par exemple
class Schedule(object):
def __init__(self, init=0.01):
self.init = init
def __call__(self, epoch):
lr = self.init
for i in xrange(1, epoch+1):
if i%5==0:
lr *= 0.5
return lr
def get_schedule_func(init):
return Schedule(init)
lrs = LearningRateScheduler(get_schedule_fun(0.01))
Si vous faites comme cela, le taux d'apprentissage initial sera de 0,01 et le taux d'apprentissage sera divisé par deux toutes les 5 époques.
De plus, la fonction d'erreur pour chaque époque est enregistrée dans le ".history" de l'objet de valeur de retour de "fit" dans un type de dictionnaire. Pandas et Matplotlib peuvent être utilisés pour une visualisation pratique.
import pandas as pd
import matplotlib.pyplot as plt
plt.style.use("ggplot")
df = pd.DataFrame(hist.history)
df.index += 1
df.index.name = "epoch"
df[["acc", "val_acc"]].plot(linewidth=2)
plt.savefig("acc_history.pdf")
df[["loss", "val_loss"]].plot(linewidth=2)
plt.savefig("loss_history.pdf")
Les résultats sont les suivants, et le taux de réponse correcte pour la vérification était inférieur à 60%. En fait, si Adam est utilisé comme méthode d'optimisation, le taux de réponse correct pour la vérification dépasse 70%, alors essayez-le.
Recommended Posts