网易首页 > 网易号 > 正文 申请入驻

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

0
分享至

选自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和labeldata, 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

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
中方投下赞成票

中方投下赞成票

政知新媒体
2026-06-24 09:57:23
詹姆斯前队友谈湖人顶薪签里夫斯:有点奇怪,防守端存在问题的人

詹姆斯前队友谈湖人顶薪签里夫斯:有点奇怪,防守端存在问题的人

好火子
2026-06-25 05:23:36
不休战!曝39岁梅西将出战第3轮:2场5球 有望连续2届夺世界杯金球

不休战!曝39岁梅西将出战第3轮:2场5球 有望连续2届夺世界杯金球

风过乡
2026-06-25 05:58:56
立陶宛新任总理出面,刚上台就对台当局放话,看来又是一个狠角色

立陶宛新任总理出面,刚上台就对台当局放话,看来又是一个狠角色

古史青云啊
2026-06-24 19:38:55
瞎打!运球17秒,媒体人怒批:他一人干废王俊杰、杨瀚森和徐昕

瞎打!运球17秒,媒体人怒批:他一人干废王俊杰、杨瀚森和徐昕

南海浪花
2026-06-24 06:54:30
54分惨败后再狂输54分!女篮一夜两大惨案:U21联赛真一边倒了?

54分惨败后再狂输54分!女篮一夜两大惨案:U21联赛真一边倒了?

篮球快餐车
2026-06-25 05:55:21
被问内马尔能出场多久,安帅:如果走着踢,我也能踢满90分钟

被问内马尔能出场多久,安帅:如果走着踢,我也能踢满90分钟

懂球帝
2026-06-24 11:39:30
上海交大走访7965名痴呆患者,调查发现:患痴呆的人,有7大共性

上海交大走访7965名痴呆患者,调查发现:患痴呆的人,有7大共性

健身狂人
2026-06-24 18:00:03
向太小儿子直播带货首秀!被怀疑有唐氏综合症,向太又遭质疑!

向太小儿子直播带货首秀!被怀疑有唐氏综合症,向太又遭质疑!

情感大头说说
2026-06-24 18:32:58
快扔掉,戴一天,辐射量相当于拍117次胸片

快扔掉,戴一天,辐射量相当于拍117次胸片

北青网-北京青年报
2026-06-22 11:00:34
6分不一定能出线?世界杯K组-L组末轮形势分析

6分不一定能出线?世界杯K组-L组末轮形势分析

世界BALL
2026-06-24 14:18:08
今夜,美股拉升!光通信,暴涨

今夜,美股拉升!光通信,暴涨

中国基金报
2026-06-25 03:10:36
哈佛研究发现:人生回报率最高的一件事,培养一个输出型爱好

哈佛研究发现:人生回报率最高的一件事,培养一个输出型爱好

心理观察局
2026-06-24 07:49:11
三大运营商终于作“死”了自己

三大运营商终于作“死”了自己

细雨中的呼喊
2026-06-10 23:49:50
你们再这么清醒下去,娱乐圈迟早要挂掉!

你们再这么清醒下去,娱乐圈迟早要挂掉!

走读新生
2026-06-23 17:32:04
1-2惨遭逆转!23岁郑钦文不敌世界第25:离谱12个双误 倒在第2轮

1-2惨遭逆转!23岁郑钦文不敌世界第25:离谱12个双误 倒在第2轮

风过乡
2026-06-24 21:25:36
图赫尔脸都被打肿了!英格兰 3 大球星被弃用 世界杯想破局都没人

图赫尔脸都被打肿了!英格兰 3 大球星被弃用 世界杯想破局都没人

澜归序
2026-06-25 02:18:39
央八终于要播了!36集传奇大剧,就冲这阵容,想不火都难!

央八终于要播了!36集传奇大剧,就冲这阵容,想不火都难!

情感大头说说
2026-06-25 00:22:50
新能源渗透率突破60%,中国车市却到了最危险的时刻?| 聚论

新能源渗透率突破60%,中国车市却到了最危险的时刻?| 聚论

车聚网
2026-06-24 21:57:30
秦海璐变卖房产,清空全部资产,凑出近亿身家,绝境兜底救下刘涛

秦海璐变卖房产,清空全部资产,凑出近亿身家,绝境兜底救下刘涛

秋别离
2026-06-13 15:50:00
2026-06-25 07:28:49
机器之心Pro incentive-icons
机器之心Pro
专业的人工智能媒体
13350文章数 142680关注度
往期回顾 全部

科技要闻

豆包专业版上线:定价68-500元每月

头条要闻

瑞士2-1加拿大 两队携手出线

头条要闻

瑞士2-1加拿大 两队携手出线

体育要闻

字母哥,会把凯尔特人拆了吗?

娱乐要闻

向佐向佑兄弟合体直播!母子终于和解

财经要闻

逃税23亿:审计署年报直指七家机构

汽车要闻

施鹏泽:为什么奥迪E7X强调座舱气味安全?

态度原创

游戏
数码
教育
健康
公开课

猎魂世界:实测分析这神奇的修罗密藏!为啥都在说爆率挺高?

数码要闻

三星电子公众号注销!家电业务已官宣退出中国大陆市场

教育要闻

最新:2026年全国各省市高考分数线最全汇总及报考分析

神经内科专家破解中风十大谣言

公开课

李玫瑾:为什么性格比能力更重要?

无障碍浏览 进入关怀版