转载

[译] 如何使用深度学习框架查找女優资源?

选自xolmon

机器之心编译

参与:Hitomi

情人节过了,单身狗也想飙一把车。在这篇技术博客(资源的真义)中,日本开发者使用深度学习框架实现了根据图片检索 AV 女优的功能。

[译] 如何使用深度学习框架查找女優资源?

开发环境:

  • PC: MacBook Air

  • CPU: 1.4 GHz Intel Core i5

  • 内存: 4GB

  • 普通的 MacBook Air 就可以实现这一程序,但是学习速度缓慢,由内存不足导致的各种 Crash 让开发工作变得非常痛苦。

0. 简单的流程

(1) 收集各女优的图片

(2) 使用 dlib 提取面部图像并调整为 96*96 的大小

(3) 使用数据扩张(Data Augmentation)将女优面部图像的数据扩张到 1000 张

(4) 将数据转换为 numpy 文件

(5) 使用 Chainer 进行面部图像的学习

(6) 在完成学习后的模型下,对任意图片进行预测

1. 收集女优图片

  • 这段有很多方法但是并不好写出来,所以请略过。只介绍下可以使用 Python 的 Beautiful Soup4 从网页上批量抓取数据。

  • 将取得的女优图片按名字建立存储目录。

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

2. 使用 dlib 提取面部图像

  • 说到图像识别,OpenCV 应该更加有名。不过在提取面部图像部分,dlib 程序库的误识别比较少,因此这里使用 dlib 可以更加精确。

  • 使用 OpenCV 和 dlib 进行人脸识别的比较可以参考这个视频:dlib vs OpenCV face detection (https://www.youtube.com/watch?v=LsK0hzcEyHI)(译者注:youtube 的视频,有必要的话可以搬过来。)

  • dlib 不仅可以提取面部图像,也有识别眼睛,鼻子,脸形等要素的机能。

importos
import sys
import glob
import cv2
from PIL import Image
import dlib
"""
INPUT_DIR是收集的女优图片所在的目录名
OUTPUT_DIR是提取后的图片存放的目录名(文件夹的构成与INPUT_DIR一样)
"""
detector = dlib . get_frontal_face_detector()
# 取得各女优的目录列表
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):
# 丢弃尺寸小于80的图像
if d . right() - d . left() < 80 or d . bottom() - d . top() < 80:
continue
image_file = image_file . replace(INPUT_DIR, OUTPUT_DIR)
# 如果一张图中提取了多个人脸,则进行重命名
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)

参考资料:dlib.net face_detect.py (http://dlib.net/face_detector.py.html)

3. 数据扩张 (Data augmentation)

在深度学习的过程中,如果数据量不够大,可以人工增加训练集的大小。通过平移, 翻转, 加噪声等方法从已有数据中创造出一批"新"的数据,这就是数据扩张 (Data augmentation)。

[译] 如何使用深度学习框架查找女優资源?

[译] 如何使用深度学习框架查找女優资源?

[译] 如何使用深度学习框架查找女優资源?

4. 将数据转换为 numpy 格式

import os
import sys
import glob
import random
import numpy as np
from scipy import misc
""" 从选择的目录里提取文件 """
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
""" 将数据储存为numpy格式 """
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):
# 生成10个分类的文件
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. 使用 Chainer 进行面部图像的学习

一开始打算使用 Tensorflow 做,不过由于自己想实现不少额外的机能,因此改用 Chainer 进行。

最初的学习,建立了一个 Cifar-10 (http://www.cs.toronto.edu/~kriz/cifar.html)(一般物品的 10 个分类)的学习方法,来对实际收集到的数据进行学习。

失败的地方:

  • 最初是打算使用多进程来构建程序,不过 Debug 非常的辛苦,觉得还是先构建一个更简单的程序比较好。

  • 如果一开始就读取所有图像,导入的图像会占用 1.7GB 的内存,导致死机。由于这个原因,创建了一个 BatchIterator 类,每 Batch 删除一次数据来释放内存,防止程序出现混乱。

// 每张图片的大小
96×96×3 = 27648(byte)
// 每类图片的大小
27648×1000 = 27648000(byte) = 26.4(MB)
// 所有图片 (66类) ... 可以计算么?
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

参考资料:

  • CNN 画像認識分野でのディープラーニングの研究動向 (http://ibisml.org/archive/ibis2013/pdfs/ibis2013-okatani.pdf)

    深層畳み込みニューラルネットワークを用いた画像スケーリング (http://postd.cc/image-scaling-using-deep-convolutional-neural-networks-part1/)

    CNNのチュートリアル (https://tech.d-itlab.co.jp/ml/666/)

  • Tensorflow

    TensorFlowでアニメゆるゆりの制作会社を識別する (http://kivantium.hateblo.jp/entry/2015/11/18/233834)

    TensorFlowによるディープラーニングで、アイドルの顔を識別する (http://d.hatena.ne.jp/sugyan/20160112/1452558576)

    Tensor Flow: How To (http://kzky.hatenablog.com/entry/2015/12/24/Tensor_Flow%3A_How_To)

  • Chainer

    GitHub - chainer/examples/imagenet/ (https://github.com/pfnet/chainer/tree/master/examples/imagenet)

    GitHub - mitmul/chainer-cifar10 (https://github.com/mitmul/chainer-cifar10)

    はじめてのアニメ顔認識 with Chainer (http://qiita.com/homulerdora/items/9a9af1481bf63470731a)

6. 使用学习后的模型对任意图像进行预测。

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


# 载入预测模型

model = set_model(model_name, model_path)
optimizer = set_optimizer(opt_name, opt_path, model)
detected_face_image = detect_face(input_image)

# 使用载入的模型进行预测

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

# 读取label (label在创建numpy形式的文件时做成)

categories = np.loadtxt(label_path, str , delimiter= "/n" )

# 按相似分高低重新排序

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 ))
   })

7. 使用 Keras 替代 Chainer 优化学习的方式。

前面的内容,建立了一个学习模型,它可以提取一张图片上的各种要素 (如眼睛,鼻子,脸型等) 并进行分类,从而判断这张图最像哪一位女优;本节则通过计算全结合层的特征向量的相似度,来进行相似图像的检索。

比起 Chainer,Keras 更加容易使用。因此借助 Keras,最终完全实现了根据图片检索 AV 女优的这一功能。

  • 数据扩张

Keras 可以使用 ImageDataGenerator 简单地进行数据的扩张。

不过,把随机的一张图像进行倾斜,变换后所得到的学习数据其实跟原图没有区别。这样的方法一般被认为会造成过度学习。

为降低输入数据冗余性,需要对数据进行 ZCA 白化。白化是降低输入数据冗余性的预处理过程,通过白化可以使得学习算法的输入具有如下性质:(i) 特征之间相关性较低;(ii) 所有特征具有相同的方差。

ZCA 白化的方法请参考以下文章:

データの白色化 - DEEPTONEWorks (http://deeptoneworks.com/2016/10/18/20161018020000/)

CIFAR-10 と ZCA whitening - まんぼう日記 (http://takatakamanbou.hatenablog.com/entry/2015/02/15/150430)

  • 脸部图像的正面化

第 2 节实现了使用 dlib 进行人脸检测,本次则更进一步,把脸部图像的特征点抽出,使用仿射变换,将眼和口的位置摆正。下面是实现本功能所使用的 openface 和 facenet,两者都已经实装。

facenet/src/align_dlib.py (https://github.com/davidsandberg/facenet/blob/master/src/align_dlib.py)_ _

openface/util/align-dlib.py (https://github.com/cmusatyalab/openface/blob/master/util/align-dlib.py)

from keras.preprocessing.image import ImageDataGenerator

# 读入何种data和label

data, label = load_data()

datagen = ImageDataGenerator(
zca_whitening= True ,
   rotation_range= 10 ,
   width_shift_range= 0.1 ,
   height_shift_range= 0.1 ,
   horizontal_flip= True )

datagen.fit(data)

# datagen.flow 代入模型的 fit_generator 函数,即可扩张动态数据
model.fit_generator(datagen.flow(data, label, batch_size= 32 ),
   samples_per_epoch=data.shape[ 0 ],
   nb_epoch= 100 )

  • 模型的构造

现阶段的学习数据还很少,模型和超参数的调整还没有完全,暂且使用的是下面这样的模型。

def conv_bn_relu (x, out_ch, name):
   x = Convolution2D(out_ch, 3 , 3 , border_mode= 'same' , name=name)(x)
   x = BatchNormalization(name= '{}_bn' . format (name))(x)
   x = Activation( 'relu' , name= '{}_relu' . format (name))(x)
    return x

def face_model (input_shape=( 3 , 224 , 224 ), nb_classes, weights_path= None ):

   inputs = Input(shape=input_shape, name= 'input' )

   x = conv_bn_relu(inputs, 64 , name= 'block1_conv1' )
   x = conv_bn_relu(x, 64 , name= 'block1_conv2' )
   x = MaxPooling2D(( 2 , 2 ), strides=( 2 , 2 ))(x)

   x = conv_bn_relu(x, 128 , name= 'block2_conv1' )
   x = conv_bn_relu(x, 128 , name= 'block2_conv2' )
   x = MaxPooling2D(( 2 , 2 ), strides=( 2 , 2 ))(x)

   x = conv_bn_relu(x, 256 , name= 'block3_conv1' )
   x = conv_bn_relu(x, 256 , name= 'block3_conv2' )
   x = conv_bn_relu(x, 256 , name= 'block3_conv3' )
   x = MaxPooling2D(( 2 , 2 ), strides=( 2 , 2 ))(x)

   x = conv_bn_relu(x, 512 , name= 'block4_conv1' )
   x = conv_bn_relu(x, 512 , name= 'block4_conv2' )
   x = conv_bn_relu(x, 512 , name= 'block4_conv3' )
   x = MaxPooling2D(( 2 , 2 ), strides=( 2 , 2 ))(x)

   x = Flatten()(x)
   x = Dense( 4096 , activation= 'relu' , name= 'fc1' )(x)
   x = Dense(nb_classes, activation= 'softmax' , name= 'predictions' )(x)

   model = Model( input =inputs, output=x)

  • 相似度的计算

本次的模型,是将最终层之前一层的全结合层特征向量提取出来,与各女优面部图像提取的特征向量计算余弦值,来判断与各女优的相似程度。

import numpy as np
from scipy.spatial.distance import cosine
from keras.models import Model, model_from_json
from keras.preprocessing import image
from keras.preprocessing.image import img_to_array

def calculate_similarity ():
    # 载入模型
   model_json = open ( 'face_model.json' ).read()
   base_model = model_from_json(model_json)
   base_model.load_weights( 'face_model.h5' )
    # 建立提取全结合层和 4096 维特征向量的模型
   model = Model( input =base_model. input , output=base_model.get_layer( 'fc1' ).output)

   image_file = 'image.jpg'
   img = image.load_img(image_file, target_size=( 96 , 96 ))
   x = img_to_array(img)
   x = np.expand_dims(x, axis= 0 )

   features = model.predict(x)
   features = features.flatten().tolist()


# actress_features 是之前计算好的各女优面部的特征向量
# 计算输入图片所提取的特征向量和各女优特征向量余弦值的相似度
   score = 1 - cosine(features, actress_features)

8. 建立服务器

这部分跟深度学习完全没有关系。一开始尝试使用了 Heroku 来建立网站,不过最终使用的是 Conoha。

dlib 和 chainer 在 Heroku 的服务器上安装非常困难。Conoha 的运行效率虽然有点问题,不过倒是很好的完成了 dlib 和 chainer 的安装。

樱花 VPS 也是一个不错的选择,不过樱花服务器没有初次使用的优惠,而 Conoha 则有针对新用户的免费方案。

原文来源:

1.chainerによるディープラーニングでAV 女優の類似画像検索サービスをつくったノウハウを公開する

2.KerasでAV 女優の類似画像検索機能を実装する 

最后

想直接要作者的实现?

[译] 如何使用深度学习框架查找女優资源?

©本文为机器之心编译, 转载请联系本公众号获得授权

✄------------------------------------------------

加入机器之心(全职记者/实习生):hr@jiqizhixin.com

投稿或寻求报道:editor@jiqizhixin.com

广告&商务合作:bd@jiqizhixin.com

原文  http://mp.weixin.qq.com/s/fyvqZGtSLqbkpDz5xVykyA
正文到此结束
Loading...