在数学和计算机领域有很多重要的猜想,比如哥德巴赫猜想、黎曼猜想、ABC猜想,P&NP猜想等等,有些猜想是“简单的”,有些问题则已经困扰了全人类几百年。但是这些猜想里,大部分普通人都很难能看懂。
最近,有个新闻华裔数学家黄皓(Hao Huang)仅用两页纸就证明了一个近30年的计算机科学猜想——布尔函数敏感度猜想(Boolean Sensitivity ),同时这个证明是非常简洁可读的。其实关于敏感度,其实在深度学习里也有类似的考虑。虽然深度学习在大数据、强力GPU计算资源的加持下,在语音识别、图像分类、自然语言理解、强化学习等方向取得了巨大的进步,但研究人员发现,很多情况下,一个训练好,表现也非常优异的神经网络经常对于输入数据的扰动还是非常敏感的。
举个视觉的例子。假设我们训练好了一个目标分类的卷积神经网络模型M,它已经能够正确分类某一张图片为熊猫(哪怕置信度达到了99%),研究人员发现我们可以通过对这张图像进行人眼很难分辨的像素级别的微小修改,就可以使得模型M的预测结果发生改变,甚至变成任意指定的类别,比如苹果。
这些细微的不同可能是随机错误导致的,也可能是压缩带来的信息损失。当然伤害最大的可能还是hackers的定向hack,用来破解某些系统,例如语音识别和人脸识别等系统。为了预防hackers的破解,也为了深入理解一下相关内容,现请你针对一些神经网络进行分析,找出它们的薄弱点。
简单起见,我们弱化一个神经网络为最简单的两层全连接网络(不带偏置项),不需要任何相关知识就可以理解。
假设网络的输入是X,输出是Y,那么公式表达就是
Y=Softmax(Z), Z=W2*ReLU(A)), A=W1*X
其中X是一个N维的向量,W1是一个MxN的矩阵,ReLU(A)=max(A,0), W2是一个10xM的矩阵, Z是一个10维的向量。Softmax的操作定义为:
可以看到这时候所有Yi的求和为1,因此Yi可以代表数据X被分类到第i类的概率。为了数值稳定性,建议大家用上面第2个公式进行计算。
接下来请找出输入向量中敏感度最高的位置,假设输入数据X的每一维度上都只能是[-128,127]范围的整数,以下操作都只考虑只修改X的某一维的情况,不能同时修改多个维度的值:
1 如果存在X的某一维度(假设是第i维),当它的值修改为[-128,127]范围的某个整数后,网络输出的类别(即softmax后概率最高的位置)跟原始X的分类结果相比发生了改变,且使得新类别的预测概率最大,则i为敏感度最高的位置。
2 如果对于X的任意一个位置,把它的值进行[-128,127]范围的任意修改,网络输出的类别都不会发生改变,那么我们把可以使得网络输出概率可以降到最低的那个修改所对应位置称为敏感度最高的位置。
3 测试样例中,原始网络的输出中只会有一个类别概率最高,不会出现多个类别概率同时最高的情况,X中也不会出现存在多个敏感度最高位置。
Input Format
输入4行。
第一行是两个整数N,M。代表输入向量的维度为N,第一个隐藏层的节点数为M。
第二行有N个整数,以空格隔开。即为输入的向量。
第三行有M*N个浮点数,以空格隔开。即第一个全连接层的网络参数W1。其中第(i-1)*N+1到第i*N个浮点数是第一个全连接层第i个节点的权重参数。
第四行有10*M个浮点数,以空格隔开。即第二个全连接层的网络参数W2。其中第(i-1)*M+1到第i*M个浮点数是第二个全连接层第i个节点的权重参数。
Constraints
60%的测试样例满足如下条件:
第一行是一个整数N,取值范围:1<= N<= 100, 1 <= M <100
第二行的N个整数Ni,取值范围:-128 <= Ni < 128
第三行的浮点数fi,取值范围:-8.0 <= fi <= 8.0
第四行的浮点数fj,取值范围:-8.0 <= fj <= 8.0
剩下40%的测试样例满足如下条件:
第一行是一个整数N,取值范围:1<= N<= 1000, 1 <= M <100
第二行的N个整数Ni,取值范围:-128 <= Ni < 128
第三行的浮点数fi,取值范围:-8.0 <= fi <= 8.0
第四行的浮点数fj,取值范围:-8.0 <= fj <= 8.0
Output Format
输出2个整数P,V,以空格隔开。 其中P是[1,N]的某个整数,代表输入向量X中敏感度最高的位置。 V表示X的敏感度最高的位置的数字应该被修改成[-128,127]中的哪个数字,使得网络受影响最大(即:如果预测类别被改变了,怎么样改会概率最高;如果预测类别不变,怎么样改使得当前类别的预测概率最小)。
Sample Input 0
8 4
-4 -71 -56 -41 85 -19 -56 -3
0.00719 0.01590 -0.01121 -0.02345 0.00777 0.01680 0.01642 -0.01437 0.04963 -0.02698 -0.03168 -0.02930 0.00784 -0.03372 -0.01824 0.01997 -0.01687 -0.02018 -0.00434 -0.00647 -0.01860 -0.01780 -0.01345 0.03369 0.00142 -0.00109 -0.02072 0.00518 -0.02600 -0.01217 -0.00510 -0.00254
-0.00372 0.06219 0.00260 0.06550 -0.02418 -0.02375 0.00115 0.00132 0.00280 -0.01428 0.02612 -0.03527 -0.02926 -0.02194 -0.04160 0.03126 0.01071 0.02239 0.00883 0.03610 0.00117 0.00429 -0.05671 0.00374 0.03496 0.03749 0.03426 0.01259 0.01202 -0.00021 -0.04738 -0.02131 0.02525 0.04419 -0.01626 0.04310 -0.01328 -0.00932 -0.03152 0.06103
Sample Output 0
1 -77
题意:
大致的意思就是让我们自己手写一个脚本,来找到所给数字中的“特征数字”。
代码:
import math def solve(n, m, fc1, fc2, x): w1 = [[0 for _ in range(n)] for _ in range(m)] w2 = [[0 for _ in range(m)] for _ in range(10)] for i in range(m): for j in range(n): w1[i][j] = fc1[i * n + j] for i in range(10): for j in range(m): w2[i][j] = fc2[i * m + j] w1x = [0] * m for i in range(m): for j in range(n): w1x[i] += w1[i][j] * x[j] w1x_relu = [0] * m for i in range(m): w1x_relu[i] = max(0, w1x[i]) w2x = [0] * 10 for i in range(10): for j in range(m): w2x[i] += w2[i][j] * w1x_relu[j] z_max = max(w2x) org_class = w2x.index(z_max) w2x_exp = [0] * 10 for i in range(10): w2x_exp[i] = math.exp(w2x[i] - z_max) z_sum = sum(w2x_exp) w2x_softmax = [0] * 10 for i in range(10): w2x_softmax[i] = w2x_exp[i] / z_sum probilities = [0] * 10 probilities[org_class] = w2x_softmax[org_class] tmp_w1x_relu = [0] * m tmp_w2x = [0] * 10 tmp_w2x_exp = [0] * 10 tmp_w2x_softmax = [0] * 10 pv = [None] * 10 for i in range(n): tmp_w1x = w1x[:] delta = -129 - x[i] for k in range(m): tmp_w1x[k] += delta * w1[k][i] for val in range(-128, 128): for k in range(m): tmp_w1x[k] += w1[k][i] for k in range(m): tmp_w1x_relu[k] = max(tmp_w1x[k], 0) for i_ in range(10): tmp_w2x[i_] = 0 for j_ in range(m): tmp_w2x[i_] += w2[i_][j_] * tmp_w1x_relu[j_] tmp_z_max = max(tmp_w2x) tmp_class = tmp_w2x.index(tmp_z_max) for k in range(10): tmp_w2x_exp[k] = math.exp(tmp_w2x[k] - tmp_z_max) tmp_z_sum = sum(tmp_w2x_exp) for k in range(10): tmp_w2x_softmax[k] = tmp_w2x_exp[k] / tmp_z_sum if tmp_class == org_class: if tmp_w2x_softmax[tmp_class] <= probilities[tmp_class]: probilities[tmp_class] = tmp_w2x_softmax[tmp_class] pv[tmp_class] = (i + 1, val) else: if tmp_w2x_softmax[tmp_class] >= probilities[tmp_class]: probilities[tmp_class] = tmp_w2x_softmax[tmp_class] pv[tmp_class] = (i + 1, val) cnt = 0 for idx, val in enumerate(pv): if val is not None: cnt += 1 if cnt == 1: print('{} {}'.format(*pv[org_class])) else: ret = 0 prep = 0 for idx, val in enumerate(probilities): if idx == org_class: continue if val > prep: prep = val ret = idx print('{} {}'.format(*pv[ret])) if __name__ == '__main__': N, M = map(int, input().split()) X = list(map(int, input().split())) lc1 = list(map(float, input().split())) lc2 = list(map(float, input().split())) solve(N, M, lc1, lc2, X)
启发:
因为以前都是用C++来写一些比较常规的编程题目,这种类型的题目确实是第一次碰到,仔细看了一下别人的代码,发现代码所实现的就是把课本上的一些原理用代码来实现。之前学习数据科学导论做图像处理等操作的时候,都是直接导入一些第三方库,然后按照文档里面参数的含义来“调参”,但是从来没有关注过第三库是如何实现的?(现在想想也有点开始明白了,为什么有人会把大数据工程师叫做调参侠了)自己python是用的还是不太熟练。