#! -*- coding: utf-8 -*-
# 通过积分梯度（Integrated Gradients）来给输入进行重要性排序
# 接 task_sentiment_albert.py
# 原始论文：https://arxiv.org/abs/1703.01365
# 博客介绍：https://kexue.fm/archives/7533
# 请读者务必先弄懂原理再看代码，下述代码仅是交互式演示代码，并非成品API

from task_sentiment_albert import *
from keras.layers import Layer, Input
from bert4keras.backend import K, batch_gather
from keras.models import Model
from bert4keras.snippets import uniout


class Gradient(Layer):
    """获取梯度的层
    """
    def __init__(self, **kwargs):
        super(Gradient, self).__init__(**kwargs)
        self.supports_masking = True

    def call(self, input):
        input, output, label = input
        output = batch_gather(output, label)
        return K.gradients(output, [input])[0] * input

    def compute_output_shape(self, input_shape):
        return input_shape[0]


label_in = Input(shape=(1,))  # 指定标签
input = model.get_layer('Embedding-Token').output
output = model.output
grads = Gradient()([input, output, label_in])
grad_model = Model(model.inputs + [label_in], grads)

# 获取原始embedding层
embeddings = model.get_layer('Embedding-Token').embeddings
values = K.eval(embeddings)

text = u'这家店真黑心'
text = u'图太乱了 有点看不懂重点  讲故事的时候很难让孩子集中'
text = u'这是一本很好看的书'
text = u'这是一本很糟糕的书'
token_ids, segment_ids = tokenizer.encode(text)
preds = model.predict([[token_ids], [segment_ids]])
label = np.argmax(preds[0])

pred_grads = []
n = 20
for i in range(n):
    # nlp任务中参照背景通常直接选零向量，所以这里
    # 让embedding层从零渐变到原始值，以实现路径变换。
    alpha = 1.0 * i / (n - 1)
    K.set_value(embeddings, alpha * values)
    pred_grad = grad_model.predict([[token_ids], [segment_ids], [[label]]])[0]
    pred_grads.append(pred_grad)

# 然后求平均
pred_grads = np.mean(pred_grads, 0)

# 这时候我们得到形状为(seq_len, hidden_dim)的矩阵，我们要将它变换成(seq_len,)
# 这时候有两种方案：1、直接求模长；2、取绝对值后再取最大。两者效果差不多。
scores = np.sqrt((pred_grads**2).sum(axis=1))
scores = (scores - scores.min()) / (scores.max() - scores.min())
scores = scores.round(4)
results = [(tokenizer.decode([t]), s) for t, s in zip(token_ids, scores)]
print(results[1:-1])

scores = np.abs(pred_grads).max(axis=1)
scores = (scores - scores.min()) / (scores.max() - scores.min())
scores = scores.round(4)
results = [(tokenizer.decode([t]), s) for t, s in zip(token_ids, scores)]
print(results[1:-1])
