专栏名称: 机器之心
专业的人工智能媒体和产业服务平台
目录
相关文章推荐
爱可可-爱生活  ·  [LG]《Stick-breaking ... ·  2 天前  
爱可可-爱生活  ·  【Agent.exe:开源的 ... ·  3 天前  
宝玉xp  ·  //@西雅图黄都督://@程序员邹欣://@ ... ·  3 天前  
宝玉xp  ·  转发微博-20241023224936 ·  3 天前  
爱可可-爱生活  ·  【Whispo:一款AI驱动的语音听写工具, ... ·  5 天前  
51好读  ›  专栏  ›  机器之心

资源 | 情人节后先飙车:如何使用深度学习框架查找女優资源?

机器之心  · 公众号  · AI  · 2017-02-15 12:08

正文

选自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 不仅可以提取面部图像,也有识别眼睛,鼻子,脸形等要素的机能。


import os
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. 使用学习后的模型对任意图像进行预测。


defset_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

defset_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:
       raiseValueError('Invalid architecture name')

   optimizer.setup(model)

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

   return optimizer

defdetect_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 女優の類似画像検索機能を実装する 

 

最后


想直接要作者的实现?





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

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

加入机器之心(全职记者/实习生):[email protected]

投稿或寻求报道:[email protected]

广告&商务合作:[email protected]