python3 extract_vectorize.py对应代码解读抽取式提取+生成式提取摘要代码解读------摘要代码解读2

由之前extract_convert.py打乱文本顺序的操作说起

之前最末尾的时候,对文本的id有一个打乱顺序的操作

if os.path.exists(data_random_order_json):
    idxs = json.load(open(data_random_order_json))
else:
    idxs = list(range(len(data)))
    np.random.shuffle(idxs)
    json.dump(idxs, open(data_random_order_json, 'w'))

data = [data[i] for i in idxs]
with open(data_extract_json, 'w', encoding='utf-8') as f:
    for d in data:
        f.write(json.dumps(d, ensure_ascii=False) + '\n')

这里的

np.random.shuffle(idxs)

对idxs进行打乱顺序,最后将打乱顺序之后的json内容写入到文件之中去

with open(data_extract_json, 'w', encoding='utf-8') as f:
    for d in data:
        f.write(json.dumps(d, ensure_ascii=False) + '\n')

回到当前的程序中去

当前的程序首先读取对应的json文件内容

data_extract_json = data_json[:-5]+'_extract.json'
data_extract_npy = data_json[:-5]+'_extract'

接着读取出对应的数据(这里读取的是之前打乱顺序的data_random_order_json的数据

data = load_data(data_extract_json)

这里取出对应的data,总共20条数据
接下来对data进行相应的转化

embeddings = convert(data)

这里重点观察convert函数

def convert(data):
    """转换所有样本
    """
    embeddings = []
    for texts in tqdm(data, desc=u'向量化'):
        outputs = predict(texts)
        embeddings.append(outputs)
    embeddings = sequence_padding(embeddings)
    return embeddings

这里的encoder为一个bert的对应网络层,加上一个平均池化层

# 加载bert模型,补充平均池化
encoder = build_transformer_model(
    config_path,
    checkpoint_path,
)
output = GlobalAveragePooling1D()(encoder.output)
#上面两步为bert+全局池化层
encoder = Model(encoder.inputs, output)
#下面一步重新定义了bert+全局池化层为对应的encoder网络层

接下来将依次预测(没有训练过程),

extract_vectorize.py中的convert函数内容


def convert(data):
    """转换所有样本
    """
    embeddings = []
    for texts in tqdm(data, desc=u'向量化'):
        outputs = predict(texts)
        #print('outputs shape = ')
        #print(np.array(outputs).shape)
        embeddings.append(outputs)
    embeddings = sequence_padding(embeddings)
    return embeddings

这里的第一波从循环data之中取出的texts为一个字符串数组

---texts = ---
['从龙伟培本人申请的大病求助及其他补助中结付现金,', '不知多少,', '如果钱到位,', '就分落下继承人:', '龙某1、龙某2、龙某3、龙某4。', '”', '该协议有龙某2、龙某1、龙某3、龙某4亲笔签名。', '原告认为,', 
............
, '或者人民法院认为审理案件需要的证据,', '人民法院应当调查收集。', '人民法院应当按照法定程序,', '全面地、客观地审查核实证据。']

也就是说这里的texts是从整个text中截取出来的残差不齐的
接下来predict预测之中还有一波代码的解读

extract_vectorize.py中的predict函数解读

for text in texts:
    token_ids, segment_ids = tokenizer.encode(text, maxlen=512)
    batch_token_ids.append(token_ids)
    batch_segment_ids.append(segment_ids)

这里的每一个text为上面

['从龙伟培本人申请的大病求助及其他补助中结付现金,', '不知多少,', '如果钱到位,', '就分落下继承人:', '龙某1、龙某2、龙某3、龙某4。', '”', '该协议有龙某2、龙某1、龙某3、龙某4亲笔签名。', '原告认为,', 
............
, '或者人民法院认为审理案件需要的证据,', '人民法院应当调查收集。', '人民法院应当按照法定程序,', '全面地、客观地审查核实证据。']

中对应的文本,然后batch_token_ids和batch_segment_ids依次放入编码之后的内容

batch_token_ids = 
[[101, 794, 7987, 836, 1824, 3315, 782, 4509, 6435, 4638, 1920, 4567, 3724, 1221, 1350, 1071, 800, 6133, 1221, 704, 5310, 802, 4385, 7032, 8024, 102], [101, 679, 4761, 1914, 2208, 8024, 102], [101, 1963, 3362, 7178, 1168, 855, 8024, 102]
............
[101, 782, 3696, 3791, 7368, 2418, 2496, 2902, 4212, 3791, 2137, 4923, 2415, 8024, 102], [101, 1059, 7481, 1765, 510, 2145, 6225, 1765, 2144, 3389, 3417, 2141, 6395, 2945, 511, 102]]

以及对应的batch_segment_ids内容

batch_segment_ids = 
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
............
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]

接下来调用extract_vectorize.py中的sequence_padding内容

batch_token_ids = sequence_padding(batch_token_ids)
batch_segment_ids = sequence_padding(batch_segment_ids)

sequence_padding之后,得到的几组文本的相应的结果

batch_token_ids = (256,80),batch_segment_ids = (256,80)
batch_token_ids = (117,128),batch_segment_ids = (117,128)
batch_token_ids = (191,62),batch_segment_ids = (191,62)

这里前面的256,117,191标记有几组对应的文本,而80,128,62标记这几组文本之中的最大的长度
接下来预测输出

outputs = encoder.predict([batch_token_ids,batch_segment_ids])

得到的outputs的对应结果

outputs = (256,768)
outputs = (117,768)
outputs = (191,768)

这里本质上是先调用bert模型,再对第一维度进行平均池化操作
首先我们查看一下模型的整体流程

# 加载bert模型,补充平均池化
encoder = build_transformer_model(
    config_path,
    checkpoint_path,
)
#encoder.output = (256,80,768)
output = GlobalAveragePooling1D()(encoder.output)
#output = (256,768)
encoder = Model(encoder.inputs, output)

可以看出越过bert之后的参数为(256,80,768),接下来使用全局池化层对第一个维度进行降维(因为这些句子虽然被用逗号切分开了,但是本质上是一体的),对第一个维度的80进行降维后得到(256,768),本质上是为了后面取出能够得到关键字的索引的值。
全局池化的操作

class GlobalAveragePooling1D(keras.layers.GlobalAveragePooling1D):
    """自定义全局池化
    """
    def call(self, inputs, mask=None):
        if mask is not None:
            mask = K.cast(mask, K.floatx())[:, :, None]
            return K.sum(inputs * mask, axis=1) / K.sum(mask, axis=1)
        else:
            return K.mean(inputs, axis=1)

关键语句

return K.mean(inputs,axis=1)

对第一维进行降维。
维度一致的思想:生出同样维度的第三维,再将第二维进行降维

解析带mask的平均池化层的计算过程

带mask的平均池化层中的调用过程

def call(self, inputs, mask=None):
    if mask is not None:
        mask = K.cast(mask, K.floatx())[:, :, None]
        return K.sum(inputs * mask, axis=1) / K.sum(mask, axis=1)
    else:
        return K.mean(inputs, axis=1)

这里我们选择分析一下

K.sum(inputs*mask,axis=1)/K.sum(mask,axis=1)

的过程,假设

inputs = [1,2,3,4,5],mask = [T,T,F,F,F]

则inputs*mask =

1*1+2*1 = 3

为加盖上掩码之后的对应值
(这个与mask作加法加上很小的值比如-1000的区别就在于,乘上False直接结果变为0,结果更直接)
而计算mask的总和时

K.sum(mask,axis=1)

这里对应的mask的内容为

mask = [T,T,F,F,F]

得到对应的K.sum(mask,axis=1)的结果为2
正好有效的部分内容为2
所以最后的结果为所有结果总和3/2 = 1.5
感觉mask使用乘法和使用加法的区别在于:使用乘法的时候每一次都需要进行操作,因为0产生梯度可能会影响最后的结果,使用加法的时候应该只用操作一次即可,因为数值足够大对最后结果影响甚微
加法的操作是:因为数值足够小,比如-1000,最后结果为比如0.几,所以几乎没影响,乘法的操作是,乘上一个0将中间的对应梯度截断。

回到extract_vectorize.py中的convert函数中去

def convert(data):
    """转换所有样本
    """
    embeddings = []
    for texts in tqdm(data, desc=u'向量化'):
        outputs = predict(texts)
        embeddings.append(outputs)
    embeddings = sequence_padding(embeddings)
    return embeddings

首先这里的embeddings.append(outputs)会压入各种各样不同维度的array的参数,比如[256,768],[117,768],总共有20个数据,所以这里embeddings长度为20
接下来使用sequence_padding将维度对齐第一维最长的那个,得到的embeddings的结果为[20,256,768]
最后将结果存入文件之中

np.save(data_extract_npy,embeddings)
## extract_vectorize.py之中的convert函数调用过程
```python
def convert(data):
    """转换所有样本
    """
    embeddings = []
    for texts in tqdm(data, desc=u'向量化'):
        outputs = predict(texts)
        embeddings.append(outputs)
    ......

这里的outputs分别压入

(256, 768),(117, 768)
......
(239, 768),(117, 768),(129, 768),(150, 768)

的内容,这也就是最后需要sequence_padding进行零填充的原因

embeddings = sequence_padding(embeddings)

填充完之后得到的内容

(20, 256, 768)

最后保存对应输出的文件的内容

np.save(data_extract_npy, embeddings)
print(u'输出路径:%s.npy' % data_extract_npy)

Guess you like

Origin blog.csdn.net/znevegiveup1/article/details/120870144