著名人工智能公司Clarifai近日推出了识别成人内容的模型和API NSFW,该模型能够很准确地识别含有裸体和半裸的图片和视频,在Clarifai的这篇博文中,作者用裸体检测问题来展示训练现代版的卷积神经网络模型 (convnets) 与过去的研究有何区别。
上周,我们在Clarifai上正式公布了 Not Safe for Work (NSFW) 成人内容识别模型。本周,我们一位数据科学家将带你探索计算机是如何学会分辨裸体人物的。
警告声明:本文内含有用于科研用途的裸体图片。如果你未满十八周岁或者不宜浏览裸体图片,请立即停止阅读。
过去二十年里,裸体图片的自动检测一直是计算机视觉领域的焦点问题,由于其丰富的研究历史和明确的目标,该问题是了解整个领域发展变化的极好例子。在本文中,我将用裸体检测问题来展示训练现代版的卷积神经网络模型 (convnets) 与过去的研究有何区别。
警告:本文内含有未打码的裸体图片,敬请注意!
早在1996年:
此领域的一项开创性工作是Fleck等人完成的一个项目,戏称为“寻找裸体人”。论文在90年代中期发表,它反映了计算机视觉研究者们在使用卷积神经网络之前所做的典型工作。在论文的第二段,他们概括了这种技术:
算法:
— 首先,筛选出含有大量肤色区域的图片;
— 其次,在这些区域内寻找长条状区域,按照人体结构的信息用专用组合器把它们组合成可能的肢体部位,并且连接这些肢体部位。
皮肤检测是通过对色彩空间过滤来实现,组合皮肤区域是通过对人体建模来实现,它将人体建模成“由相邻圆柱体组合而成的物体,每个独立部分的几何形状和各个部分之间的比例都符合人体骨架”(论文第二节)。为了更好地理解实现这类算法的工程技术,我们来看论文的图1,作者展示了一部分他们人为构造的组合规则:
图1. 左图:组合规则(箭头)说明了如何合并简单的组合(如躯干)形成复杂的组合(如四肢和身体的连接)。这些规则受限于它们在2维空间的相对位置,这是由于它们在3维空间有特定的组合分布。虚线表示了还未实现的组合规则。中图:组合器拒绝将大腿和脊椎(虚线表示的是盆骨)组合在一起,因为如果人在这种姿势下大腿会遮盖躯干,使得躯干不容易被检测到。右图:这个臀部连接也被拒绝了。髋骨连接的限制阻止了人的大腿如此摆放。
论文中提到“在138张无限制裸体照片的测试集上,准确率达到了60%,召回率达到52%”。他们还给出了一些真阳性和假阳性的图片例子,并标明算法检测到的相关特征:
图3. 成功检测出裸体人物的典型图片。皮肤过滤器的输出结果如图所示,脊椎用红线表示,肢体连接用蓝线表示,肢体与躯干连接用绿线表示。
图6. 错误地识别出裸体人物的典型对照图片。这些图片含有与皮肤色彩接近的材料(动物皮肤、木材、面包、褪色的墙面)和容易误判为脊椎或者腰带的结构。组合器经常将平行线组合搞混。
人工构造特征的一个主要问题是特征的复杂性受到了研究院的耐心和想象力的限制。在下一节里,我们将会看到如何训练卷积神经网络来完成同样的任务,更精细地表征相同的数据集。
到了2014年:
深度学习研究员不再发明各种规则来表征输入的数据,而是设计网络模型结构和数据集,使得人工智能系统能从数据中直接学到表征的方法。然而,由于深度学习研究员并没有明确指定网络模型该如何处理给定的数据集,新的问题就产生了:我们如何理解卷积神经网络的行为?
理解卷积神经网络模型的操作就需要解释各层网络的特征行为。我们将在本文的余下篇幅中介绍一个NSFW模型的早期版本,从模型顶层反推到原始输入的像素空间。这样能使我们明白原始输入的什么模式能够导致特征空间的某种特定激活(即为何一张图片被标记为“NSFW”)。
遮挡灵敏度
下图是我们在经过裁剪的Lena Soderberg图像上使用NSFW模型,用64×64的平滑窗口,3像素移窗处理而成。
我们把每个窗口覆盖的像素点传入卷积模型,计算每个像素点的平均“NSFW”得分,得到左侧的热度图。当卷积模型发现某一块裁剪的区域都属于皮肤,它就预测为“NSFW”,在图片中Lena身上相应的位置显示出一大片红色区域。生成右侧的热度图时,我们故意遮挡一部分原始图片,输出1减去平均“NSFW”得分之后的差值(即“SFW”得分)。当NSFW得分最高的区域被遮挡时,“SFW”得分随之上升,我们在热度图中看到了颜色更红的区域。下图的两个图片例子分别表示在上述两种实验中传入卷积模型的两类图片示例:
这些遮挡实验的一个优点就是当分类器完全是一个黑盒的情况下照样能够进行实验。下面是调用我们接口复现上述结果的代码片段:
# NSFW occlusion experimentfrom StringIO import StringIOimport matplotlib.pyplot as pltimport numpy as npfrom PIL import Image, ImageDrawimport requestsimport scipy.sparse as spfrom clarifai.client import ClarifaiApi
CLARIFAI_APP_ID = '...'CLARIFAI_APP_SECRET = '...'clarifai = ClarifaiApi(app_id=CLARIFAI_APP_ID,
app_secret=CLARIFAI_APP_SECRET,
base_url='https://api.clarifai.com')def batch_request(imgs, bboxes):
"""use the API to tag a batch of occulded images"""
assert len(bboxes) < 128
#convert to image bytes
stringios = [] for img in imgs:
stringio = StringIO()
img.save(stringio, format='JPEG')
stringios.append(stringio) #call api and parse response
output = []
response = clarifai.tag_images(stringios, model='nsfw-v1.0') for result,bbox in zip(response['results'], bboxes):
nsfw_idx = result['result']['tag']['classes'].index("sfw")
nsfw_score = result['result']['tag']['probs'][nsfw_idx]
output.append((nsfw_score, bbox)) return outputdef build_bboxes(img, boxsize=72, stride=25):
"""Generate all the bboxes used in the experiment"""
width = boxsize
height = boxsize
bboxes = [] for top in range(0, img.size[1], stride): for left in range(0, img.size[0], stride):
bboxes.append((left, top, left+width, top+height)) return bboxesdef draw_occulsions(img, bboxes):
"""Overlay bboxes on the test image"""
images = [] for bbox in bboxes:
img2 = img.copy()
draw = ImageDraw.Draw(img2)
draw.rectangle(bbox, fill=True)
images.append(img2) return imagesdef alpha_composite(img, heatmap):
"""Blend a PIL image and a numpy array corresponding to a heatmap in a nice way"""
if img.mode == 'RBG':
img.putalpha(100)
cmap = plt.get_cmap('jet')
rgba_img = cmap(heatmap)
rgba_img[:,:,:][:] = 0.7 #alpha overlay
rgba_img = Image.fromarray(np.uint8(cmap(heatmap)*255)) return Image.blend(img, rgba_img, 0.8)def get_nsfw_occlude_mask(img, boxsize=64, stride=25):
"""generate bboxes and occluded images, call the API, blend the results together"""
bboxes = build_bboxes(img, boxsize=boxsize, stride=stride) print 'api calls needed:{}'.format(len(bboxes))
scored_bboxes = []
batch_size = 125
for i in range(0, len(bboxes), batch_size):
bbox_batch = bboxes[i:i + batch_size]
occluded_images = draw_occulsions(img, bbox_batch)
results = batch_request(occluded_images, bbox_batch)
scored_bboxes.extend(results)
heatmap = np.zeros(img.size)
sparse_masks = [] for idx, (nsfw_score, bbox) in enumerate(scored_bboxes):
mask = np.zeros(img.size)
mask[bbox[0]:bbox[2], bbox[1]:bbox[3]] = nsfw_score
Asp = sp.csr_matrix(mask)
sparse_masks.append(Asp)
heatmap = heatmap + (mask - heatmap)/(idx+1)
return alpha_composite(img, 80*np.transpose(heatmap)), np.stack(sparse_masks)#Download full Lena imager = requests.get('https://clarifai-img.s3.amazonaws.com/blog/len_full.jpeg')
stringio = StringIO(r.content)
img = Image.open(stringio, 'r')
img.putalpha(1000)#set boxsize and stride (warning! a low stride will lead to thousands of API calls)boxsize= 64stride= 48blended, masks = get_nsfw_occlude_mask(img, boxsize=boxsize, stride=stride)#vizblended.show()
尽管这类实验以非常直接的方式呈现了分类器的输出结果,但缺点是输出的结果很模糊。这使得我们不能完全深入了解模型的工作状态以及发现训练过程中的错误。
去卷积网络模型
当我们用指定数据集训练得到一个模型后,往往希望给出一张图片和某个类别,然后想从模型中得到诸如“我们该如何改变这张图片使其看起来更像是属于那个类别的”之类的答案。针对这类问题,我们使用去卷积网络模型(deconvnet),Zeiler和Fergus014年论文的第二节:
下图展示的是我们用去卷积模型对Lena照片的处理结果,显示了我们该如何修饰Lena图片使其更像一张色情图片(作者注:这类使用的去卷积模型需要传入一张正方形的图片 —— 我们将Lena的完整图片填补成合适的尺寸):
Barbara 是Lena的G级(无限制级)版本。根据我们的去卷积模型,我们给她加上红唇后看起来更像PG级(可能不适宜儿童)图片。
这张图片是Ursula Andress 在电影《007之诺博士》中饰演Honey Rider的剧照,2003年被英国调查评选为“银幕上100个性感瞬间”的第一位:
上述实验的一个显著特点就是卷积模型学习到了红唇和肚脐是“NSFW”得分的指标。这似乎意味着我们“SFW”训练数据集中所包含的红唇和肚脐图片还不够。如果我们只是用准确率/召回率和ROC曲线(如下所示 – 测试集大小为428,271)来评价我们的模型,我们永远也无法发现这个问题,因为我们的测试数据集有着同样的缺陷。这里就是训练基于规则的分类器与现代人工智能研究的本质差别之一。相对于重新人工构造特征,我们重新设计训练数据集直到挖掘出更优质的特征。
最后,我们机智地在确定是色情图片的数据集上运行去卷积模型,确保模型学到的特征真的和明显的nsfw所对应:
这里,我们能清楚地看到卷积模型正确地学到了男女生殖器等器官 —— 我们模型应该标识的部位。而且,模型发现的特征远比研究员人工构造的特征更细化和复杂,有助于我们理解使用卷积模型识别NSFW图片的主要提升点。
来源:http://blog.clarifai.com/what-convolutional-neural-networks-see-at-when-they-see-nudity/#.Vy3EN4RcSkr
原文标题:WHAT CONVOLUTIONAL NEURAL NETWORKS LOOK AT WHEN THEY SEE NUDITY
作者:Ryan Compton
译者:赵屹华 审校:刘翔宇
欢迎加入本站公开兴趣群
商业智能与数据分析群
兴趣范围包括各种让数据产生价值的办法,实际应用案例分享与讨论,分析工具,ETL工具,数据仓库,数据挖掘工具,报表系统等全方位知识
QQ群:81035754