作者 RYAN COMPTON时间2016.04.19 发表于ENGINEERING
我们上周在Clarifai正式宣布不安全的工作(NSFW)成人内容识别模型。本周,我们的一个数据科学家将指导您完成一个技术探索:他如何教电脑看到裸体的人。
警告和免责声明:这篇文章包含科学目的的裸露的可视化。我们要求你别再读下去如果你未满18岁,或者如果你会被裸体冒犯。
到现在20年以来,裸照发现的自动化一直是计算机视觉的一个核心问题,因为它的丰富的历史和简单直接的目标,一直是这个领域如何演化的一个很好的例子。在这篇文章中,我将使用探测裸照来说明现代卷积神经网络训练(回旋网)如何不同于过去的研究。
*警告:这篇文章包含可视化对应非常明确的裸照,小心行事!
在这个领域内的开创性工作是由Fleck等人巧妙命名的“发现裸体者”。它发表在90年代中期,并提供了一个很好的这种工作的先例,即计算机视觉研究人员优先做卷积神经网络接管。在论文的第二部分,他们总结了技巧:
算法:
-首先定位包含肤色区域的大部分面积的图像;
-其次,在这些区域中,找到细长的区域,用专门的包含了大量关于物体结构信息的分类器,把他们分组到可能的人体的四肢和相连的人体四肢组。
肤色检测是通过过滤颜色空间,分组肤色区域是通过给人体图建模,“作为近圆柱形零件的组装,两个人几何零件和零件之间的关系受几何骨架得到约束”(参看第二节)。为了更好地了解的构建一个这样的算法的工程,我们转向论文的图1,作者说明的几个手建的分组规则:
这篇文章报道“60%精度和52%召回测试集的138张不受控制的裸照。”他们也为真阳性和假阳性的例子提供可视化的用覆盖算法发现的特点:
手工构建特性的一个主要问题是其复杂性受研究人员的耐心和想象力的限制。在下一节中,我们将看到卷积神经网络训练如何来执行相同的任务却可以学习相同的数据的更复杂的表现。
深度学习研究者设计网络架构和数据集使得人工智能系统可以直接从数据学习表现,而不是发明正式规则来描述应该如何代表输入的数据。然而,由于深度学习研究者没有明确指明网络对于给定输入应该如何表现,一个新的问题出现了:一个人怎么能理解卷积网络的活动?
理解的卷积神经网络的操作需要解释在各层的特征活动。在本文剩下的部分里,我们将检查一个早期版本的NSFW模型,通过映射活动从顶层回到输入像素空间。这将使我们能够看到原始输入模式对给定的特征图造成了什么样的激活。(即,为什么一个图像被标记为“NSFW”)。
下图显示了丽娜的照片索德伯格后64 x64的3步的脚步滑动窗口,运用了NSFW模型于裁剪/阻挡版本的原始图像。
为了构建左边的热力图,我们发送每一个窗口到卷积神经网络,并在每个像素上平均NSFW模型的得分。当卷积神经网络遇到一个充满皮肤的物体时,它往往会趋向于预测“NSFW”,这会导致在丽娜的身体出现大块的红色区域。为了建立右边的热力图,我们系统地挡住部分原始图像并报告1 减去“NSFW”平均得分(即“SFW”分数)。当大部分NSFW地区被阻挡的时候,“SFW”分数增加,我们看到更高的热图中的值。需要澄清的是,下面这些图包含什么样的图片被送入卷积神经网络的例子,对于上面两个实验的每一个:
一个关于闭塞实验的好处是他们可能会展现当分类器是一个完全的黑箱。下面是通过我们的API重新生成这些结果的代码片段:
# NSFW occlusion experiment
fromStringIOimportStringIO
importmatplotlib.pyplotasplt
importnumpyasnp
fromPILimport Image, ImageDraw
importrequests
importscipy.sparseassp
fromclarifai.clientimportClarifaiApi
CLARIFAI_APP_ID ='…'
CLARIFAI_APP_SECRET ='…'
clarifai=ClarifaiApi(app_id=CLARIFAI_APP_ID,
app_secret=CLARIFAI_APP_SECRET,
base_url='https://api.clarifai.com')
defbatch_request(imgs, bboxes):
"""use the API to tag a batch of occulded images"""
assertlen(bboxes) <128
#convert to image bytes
stringios= []
forimginimgs:
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')
forresult,bboxinzip(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 output
defbuild_bboxes(img, boxsize=72, stride=25):
"""Generate all the bboxes used in the experiment"""
width=boxsize
height=boxsize
bboxes= []
for top inrange(0, img.size[1], stride):
for left inrange(0, img.size[0], stride):
bboxes.append((left, top, left+width, top+height))
returnbboxes
defdraw_occulsions(img, bboxes):
"""Overlay bboxes on the test image"""
images= []
forbboxinbboxes:
img2 =img.copy()
draw=ImageDraw.Draw(img2)
draw.rectangle(bbox, fill=True)
images.append(img2)
return images
defalpha_composite(img, heatmap):
"""Blend a PIL image and a numpy array corresponding to a heatmap in a nice way"""
ifimg.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))
returnImage.blend(img, rgba_img, 0.8)
defget_nsfw_occlude_mask(img, boxsize=64, stride=25):
"""generatebboxes 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
foriinrange(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= []
foridx, (nsfw_score, bbox) inenumerate(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)
returnalpha_composite(img, 80*np.transpose(heatmap)), np.stack(sparse_masks)
#Download full Lena image
r =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=64
stride=48
blended, masks =get_nsfw_occlude_mask(img, boxsize=boxsize, stride=stride)
#viz
blended.show()
尽管这些类型的实验提供了一个简单直接的展示分类器输出结果的方法,但是他们有一个缺点:生成的可视化图像非常模糊。这妨碍了我们获得有意义的洞察神经网络实际上在做什么以及理解可能在训练过程中出现的错误。
深度卷积网络Deconvolutional Networks
一旦我们在给定的数据集上训练成了一个网络,我们就能够取定一张图片和一个类,然后可以要求卷积神经网络做些事情,比如“我们应该如何改变这张图片以使它看起来更像给定的类?”对于这种情况,我们用深度卷积神经网络(deconvnet),请参考Zeiler和Fergus 2014年写的第二节:
深度卷积神经网络可以被看作用了相同组件但是相反的卷积神经网络,所以与把像素映射到特征上相反。为了检测到一个给定的卷积神经网络的激活作用,我们将所有层的激活设置为0,并把特征图作为输入传递给绑定的深度卷积神经网络层。然后我们成功地(i)unpool,(ii)纠正,(iii)过滤重构层中的活动,在它下面引发了选择好的激活。这会被一直重复直到输入像素空间被达到。
【……】
这个过程类似于返回一个强烈的激活的属性(而不是普通的梯度),即,用强烈激活计算 ,其中Xn 是特征映射的元素, 输入图像的位置。
这是用深度卷积神经网络想象我们如何修饰丽娜的图片使得看起来更像色情图片所得出的结果(此处所用的深度卷积神经网络需要一个正方形图使得运行正确-我们填补丽娜的完整图像以得到正确的比例):
芭芭拉是G级别版本的丽娜。根据我们的深度卷积神经网络,我们可以修饰芭芭拉给她的嘴唇添加红色使得看起来更像PG级别的:
这张Ursula Andress 在James Bond 的电影Dr. No中扮演Honey Rider 的图片被票选为“银幕史上最性感的100个时刻中的第一名”,根据2003年英国的一个调查:
上述实验的突出特征就是深度卷积神经网络学习到红嘴唇和肚脐作为“NSFW”模型的说明。这可能意味着我们没有在“SFW”的训练数据中包含足够的红嘴唇和肚脐的的图像。如果我们只通过检测精度/召回和ROC曲线(如下所示-测试集大小:428271)来评估我们的模型,我们将永远不会发现这个问题,因为我们的测试数据有相同的缺点。这突出了训练基于规则的分类器与现代人工智能研究的根本的区别。我们重新设计训练数据直到已发现的功能得到改善,而不是重新设计我们的功能。
最后,作为为了保证周全的检验,我们将深度卷积神经网络用于赤裸裸的情色图片以保证学习到的特征激活确实明显对应到NSFW模型的对象:
在这里,我们可以清楚地看到,事先正确学会了阴茎,肛门,阴户,乳头,屁股——我们的模型应该能标识出的物体。此外,发现的特征远比研究人员用手设计的详细和复杂,这帮助我们解释了用深度卷积神经网络识别NSFW图像得到的重大的改进。
翻译:曾庆福
原文连接:http://blog.clarifai.com/what-convolutional-neural-networks-see-at-when-they-see-nudity/#.Vxwpgn4rLIX
原创翻译文章,禁止转载。