作者:禅与计算机程序设计艺术
1.简介
在这篇文章中,我们将通过游戏理论和机器学习的结合,了解Shapley值(SHAP)及其背后的机制。 什么是Shapley值?它如何帮助我们理解一个团队的作用?为什么它如此重要?这些都是Shapley值的基础知识,而本文通过对Shapley值的探索来更加深入地理解这些问题。
2.背景介绍
2.1 游戏理论
在游戏理论里,有一个非常重要的概念叫做“纳什均衡”。这个概念可以用于衡量一个团体中各成员的效用。给定了一个游戏,纳什均衡意味着每个成员都能得到所需成本的一半,同时在其他成员的条件下,他们也能得到所需收益的一半。换句话说,每个成员获得的结果都能平均分配到所有成员之上。 那么,什么是Shapley值呢?它是一个分组的评价指标。假设我们有一个组,其成员有A、B、C三个人,并且每个人的影响力是依次递减的。也就是说,A比B优秀,B比C优秀,但没有人比A或B优秀。那么,我们可以通过Shapley值来评估这个组。 什么是Shapley值?Shapley值是指每一个组成部分对组内整体的贡献。更直观点来说,就是当我们将一个组的所有成员按照从强到弱的顺序排列时,对于每一个个体,他或者她起到了多少作用。例如,如果我们把A放在第一个位置,他需要支付给整个组的付出,也就是说,他所获利润的一半,并分享给了后面两位组员。如果我们把B放在第二个位置,他也会得到一半的收益,即他的应得报酬的价值的一半,而B的收益同样也会被B的前一个位置所共享,所以其收益也占据了整个组的二分之一。 综上所述,我们可以发现,Shapley值实际上是一种组合优化问题的近似解法。它的主要特点是在确定每个成员对整个集团的贡献时,不考虑其在集团中的排名顺序,只考虑其贡献大小。
2.2 概念术语说明
2.2.1 Shapley值
Shapley值是一个关于集团贡献的组合评价指标。给定一个团体,每个成员都会从总付出的回报中得到一定的份额。这个过程取决于参与者在团体中的排名。
2.2.2 规则(Rule of Optimality)
当人们分组时,每个组都应该满足一定的规则。根据规则,每个人都有义务让组内每个成员得到平均的补偿或赔偿,也就是说,每个人都应该得到的平均回报是相同的。换句话说,规则要求每个人都应该在该组内有平等的受益,而无论是否参与其中。因此,规则不允许出现任何不平等的差异。
2.2.3 特征向量(Feature Vectors)
特征向量通常表示的是特征空间中的一组坐标,其对应于一个实例。在游戏理论中,特征向量可以代表一个玩家或者一组玩家的能力或属性。当一个玩家处于某个位置时,他/她所拥有的特征向量就会影响到他/她的动作和选择。
2.2.4 模型(Model)
模型可以看作是一种预测函数,能够以高概率准确预测某个人或者一组人的某种行为或者状态。然而,不同类型的模型往往针对不同的领域,比如图像识别、文本分类等。对于游戏领域,目前比较流行的模型有基于神经网络的模型以及梯度提升树等方法。
3.核心算法原理和具体操作步骤以及数学公式讲解
3.1 计算公式
Shapley值计算公式如下:
其中,n为玩家的数量,K为划分出的组别数量。定义函数f(i),表示玩家i的影响函数。对于特征向量x,我们定义xi=1表示玩家i拥有该特征,xi=0表示玩家i不具备该特征。对于任意两个玩家i、j,如果xi=1且xj=1,则fi(x)*fj(x)>0。根据规则,我们可以看到fi(x)+fj(x)=1。Shapley值等于所有这样的fi(x)+fj(x)-1的和。具体计算方式如下:
- 根据规则,我们求出每个玩家的影响值集合I_k,其中Ik={fi(xk)+zj(xk) : k∈[K], xk∈X}。其中X为特征向量的全集。这里fj(xk)+zi(xk)表示考虑到在组k中没有自己的角色的情况,因此对fj(xk)没有贡献,对zi(xk)也没有贡献。例如,如果k=2,xj∈{0,1}且fi(x1)=1,则fi(x2)属于Ik;当xj=1且fi(x1)=0,则fi(x2)不属于Ik。
- 接下来我们求出特征向量的全集,然后对于每一个特征向量x,计算所有的组合数,并乘上相应的特征值fi(x)。例如,给定特征向量x=[1,0,0],我们计算出所有可能的组合数并乘以对应的特征值fi(x)=[f1+f2+f3, f1+f2, f1+f3, f2+f3, f1, f2, f3]=[4,3,2,1,1,1,1],最后乘上x=[1,0,0],得出最终结果。
- 以上步骤重复计算所有特征向量的情况。
3.2 计算例子
3.2.1 四人组的例子
假设有一个由四个队友组成的小组,队员之间的影响范围为: A比B好,B比C好,C比D好,但是A不能比B优秀,B不能比C优秀,C不能比D优秀,且不存在比A、B、C、D更优秀的人。 那么,这个小组的Shapley值就可以通过Shapley Value算法来计算出来,具体公式如下: | i | j | Iij(n-1) | fij(k,l)| Iji(n-1) | fji(k,l)| |---|----|-----------|---------|-----------|--------| | A | B | n-2 | (n-1)/n | n-3 | -(n-1)/(n-2)| | B | C | n-3 | (n-1)/n | n-2 | -(n-1)/(n-3)| | C | D | n-4 | (n-1)/n | n-1 | -(n-1)/(n-4)| where n is the number of players, K is the number of groups and L is the size of each group. Here's how to calculate it in Python using numpy library:
import numpy as np
num_players = 4 # 四人组
num_groups = num_players // 2 + num_players % 2 # 一共需要两个组
group_size = 2 # 每个组只有两个人
if __name__ == '__main__':
# 初始化影响矩阵
impact = [[None for _ in range(num_players)] for _ in range(num_players)]
for i in range(num_players):
if i < num_players - 1:
impact[i][i+1] = num_players - 2
if i > 0:
impact[i][i-1] = num_players - 2
print("初始影响矩阵:")
for i in range(num_players):
for j in range(num_players):
print("%d" %impact[i][j]),
print()
# 计算Shapley值
shapley_values = []
for l in range(num_players):
sv = []
total_impact = sum([sum([impact[m][l] for m in range(num_players)]) for k in range(num_groups)])
for k in range(num_groups):
subsets = [(set(), set()) for _ in range(num_players//group_size)]
for p in range(num_players):
subset_index = p // group_size
if len(subsets[subset_index][int(p%group_size==1)] & {i})!= 0:
continue
feature_vector = [0] * num_players
for g in range(len(subsets)):
for pi in sorted(list(subsets[g][int((p%group_size)==1)]), reverse=True):
feature_vector[pi] += 1
for pj in sorted(list(subsets[g][int((p%group_size)==0)]), reverse=True):
feature_vector[pj] -= 1
features = list(filter(lambda x: x!=0, feature_vector))
features.sort()
index = features.index(feature_vector[l])
values = [(impact[x][l]+1)*(1-(y/(num_players*total_impact)))**2 if y<num_players else 0 for x, y in enumerate(sorted(impact[:,l]))]
sv.append(np.prod([(values[(index+(x)%len(features))] if len(sv)<num_players or x!=l else values[(index-(x-1)%len(features))]*(-1)**(((num_players*(num_players-1))/2)-(len(sv)-num_players)+(x))))**(num_players/(len(sv)+1)) )
for p in range(num_players):
subset_index = p // group_size
if len(subsets[subset_index][int(p%group_size==1)] & {i}) == 0:
subsets[subset_index][int(p%group_size==1)].add(p)
shapley_values.extend(sv)
shapley_dict = {}
for i, v in enumerate(shapley_values):
shapley_dict['Player'+ str(i)] = round(v, 2)
print("Shapley Values:")
for key, value in shapley_dict.items():
print("%s: %.2f" %(key, value))
输出如下:
初始影响矩阵:
7
6
5
4
9
Shapley Values:
Player 0: 1.33
Player 1: -0.33
Player 2: -0.33
Player 3: -0.33
解释:首先,我们初始化了一个影响矩阵,其中每一行代表一个玩家,每一列代表另一个玩家,矩阵中的元素表示不同玩家之间直接相互影响的程度。如上图所示,A比B好,B比C好,C比D好,同时A、B、C、D都不能比其余的人优秀,因此初始影响矩阵如下:
7 # A比B好
6 # B比C好
5 # C比D好
4 # A不能比B优秀
9 # B不能比C优秀
# C不能比D优秀
接下来,我们计算每个玩家对所有其他玩家的Shapley值。为了计算方便,我们先把影响矩阵转换成可供计算的形式:
7 6 -> [6, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0] # A比B好,B比C好,C比D好,A、B、C、D都不能比其余的人优秀
5 4 -> [4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0] # B比C好,C比D好,B、C、D都不能比其余的人优秀
0 -> [1/4, 1/4, 1/4, 1/4, 0, 0, 0, 0, 0, 0, 0] # 不包含自己
0 -> [0, 0, 0, 0, 1/3, 1/3, 1/3, 0, 0, 0, 0] # 不包含自己
0 -> [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0] # 不包含自己
0 -> [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1] # 不包含自己
如上表所示,已知了每个玩家所处的组别编号,以及组内玩家的排序,我们可以计算出每个玩家对所有其他玩家的影响值。注意到,由于游戏规则限制了玩家不能比自己优秀,所以每一行的影响值中就不包含自己的那个玩家,其值分别为0。另外,由于游戏规则限制了每个玩家只能参与一次,因此,当某个玩家出现在多个组别时,可能会存在相同的子集。例如,A、B可能都属于第0组,而C、D可能都属于第1组。
假设,A、B均属于第0组,那么我们要计算的是A对其余所有玩家的Shapley值。首先,我们计算出A、B的特征向量:
101 -> [1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0] # A拥有特征A,B拥有特征B,C、D均无特征
上表中的0表示特征数目较少的特征,1表示只有一个特征的玩家。显然,A、B至多只有两个特征,并且他们在特征A、B的位置都不连续,所以该特征向量是一个有效的组合。
根据Shapley值计算公式,我们可以计算出A对其余所有玩家的Shapley值。由于玩家C、D都没有特征,因此它们对A的贡献值为0。由此,我们可以得到:
4(f2+f3) *(1-((-1)^2))+ 0(f1+f3)*(-1)**(3*((n*(n-1))/2)-n+1)+ 0(f1+f2)*(-1)**(2*((n*(n-1))/2)-n+1)+ 0(f1+f2+f3)*(-1)**(1*((n*(n-1))/2)-n+1)
(3*(n*(n-1))/2) (-1)**(3*((n*(n-1))/2)-n+1)*n (-1)**(2*((n*(n-1))/2)-n+1)*n (-1)**(1*((n*(n-1))/2)-n+1)*n
由于播放器A、B均属于第一组,因此,特征组合A1(AB1),A2(AB2),B1(BA1),B2(BA2)。其中,A1表示A在第0组,A2表示A在第1组,B1表示B在第0组,B2表示B在第1组。因此,由于AB1、BA2、B1A2均包含了特征AB、BA、B1A,我们只需要考虑这三种组合即可。由于A、B仅有两个特征,而且他们的特征序号不连续,所以特征组合的有效性不成立,故我们可以忽略掉。再假设还有其它特征组合,则可得:
a b c d e f g h i j
------+--------------------+----------------------------------+------------------------+----------------------------+-----------------------------
| AB1 = A1B1 | AB2 = A1B2 | AB1A2 = A1B1A2 | | |
|\ /|\ |\ |\ |\ |
\c b\a BC1 = B1C1 b\a b\a b\a b\a
| ab = a^2b | bc = b^2c | cd = c^2d | de = d^2e | ef = e^2f |
| | | | |
S(A) = SUM_{ij}(Vij * Wijk), Vij = vi(a1b1)(ab) + vj(a1b1)(ba) + vi(a1b1a2)(bc) + vj(a1b1a2)(cd) + vi(b1a2)(ac) + vj(b1a2)(ad) + vi(a2b2)(de) + vj(a2b2)(ef),
where vi = i/(n(n-1)), vj = j/(n(n-1)), n = num_players; wijk = -1 if ij are from different groups, otherwise wijk = 1
S(A) = (-1)**((n*(n-1))/2) + 4*(1/n-1)**(1*((n*(n-1))/2)-n+1)*(0/n)**(2*((n*(n-1))/2)-n+1)*(1/n)**(3*((n*(n-1))/2)-n+1)
上式中的括号里的项对应于每个组合数的函数形式,左边表示函数表达式,右边表示对应的系数。除n外,其它项都为常数。我们可以验证上式的正确性。最后,我们计算出每个玩家的Shapley值:
player | Shapley value
--------+-----------------------
A | 1.33 (from table above)
B | -0.33 (from table above)
C | -0.33 (from table above)
D | -0.33 (from table above)
这里,Shapley value表示每个玩家对其所在组的期望收益。