Python - 深度学习系列35 重塑实体识别2

说明

上一篇Python - 深度学习系列34 重塑实体识别介绍了如何进行训练,这篇讨论如何应用。

详细review了之后,发现其实早先的服务还是略有欠缺的。例如:

  • 1 最早的时候好像还没有pipeline,我用DataFrame并行处理,然后用模型的裸方法,可能不一定那么优雅。(意味着性能或信息有丢失)
python 复制代码
        minput = torch.cat(the_ss_codecs_values)

        if self.device == 'cuda':
            if self.model.device.type != 'cuda':
                self.model.to('cuda')
            with torch.no_grad():
                input_cuda = minput.to('cuda')
                outputs_cuda = self.model(input_cuda).logits

                predictions = torch.argmax(outputs_cuda, dim=2)
                predictions_list = list(predictions.to('cpu').numpy())
  • 2 在输出结果时丢掉了位置信息。最直观的问题是,如果产品需要进行高亮,那么是没办法的。
python 复制代码
    import numpy as np 
    @staticmethod
    def inv_convert_chinese_word_tag_list2BIO_v3(data_dict):
        data_list = data_dict['data_list']
        tag_list = data_dict['tag_list']

        res_data_list = []
        data_list1 = copy.deepcopy(data_list)

        # 如果tag_list都是无用词
        if set(tag_list) == {'O'}:
            return ','

        else:
            tag_arr = np.array(tag_list)
            ner_start_idx_list = list(np.where(tag_arr=='B')[0])
            
            for the_ner in ner_start_idx_list:

                tem_data = ''
                for i in range(198):

                    if the_ner+i == len(data_list1):
                        break
                    if the_ner+i >=198:
                        break
                    if tag_list[the_ner+i] != 'O':
                        tem_data += data_list1[the_ner+i]
                    else:
                        break
                    

                res_data_list.append(tem_data + ',')

            return ','.join(res_data_list)

内容

1 目标

本次迭代之后可以直接看到的效果应该是:

  • 1 模型的训练与调用方法更科学
  • 2 输出的结果更完整(按序输出)
  • 3 句子的分割更合理(仅以部分强分隔符切分句子)
  • 4 初步形成流水线方法

下一阶段的目标则是不断用更多的模型类型以及尺寸去调教一套实体识别模型。之后至少会使用Electra, Roberta,DistilBert几类模型;并且除了实体识别,还需要有情感分析和局对匹配等内容。

2 使用实例

载入模型,注意device选项

python 复制代码
from Basefuncs import * 
# 载入模型
import transformers 
import torch 
from transformers import AutoModelForMaskedLM, AutoTokenizer
xmodel_path = 'model01'
from transformers import pipeline

# 加载中文 Electra 模型和 tokenizer
ner = pipeline("ner", model=xmodel_path, tokenizer=xmodel_path, device=0)  

获取数据

python 复制代码
host = 'xxxxxx.cn'
port = 19000
database = 'model_train_datasets'
user = 'xxx'
password = 'xxx'
name = 'tem'
chc = CHClient(host = host, port = port , database = database, user = user, password = password, name = name )
the_sql = 'show tables'
chc._exe_sql(the_sql)
# 取数
data_tuple_list = chc.get_table('train_ner_news_title_org_20240517')
df = pd.DataFrame(data_tuple_list, columns = ['mid', 'x', 'y'])
data_list = list(df['x'])
# sub_list 
sub_list = data_list[:1000]

模型执行预测

python 复制代码
sentences = sub_list
entities = ner(sentences)

在这里,CPU和GPU执行有巨大的时间差别。使用 timeit测试,CPU对100条数据的识别大约是6.9秒,而GPU对1000条数据的执行约为8.11秒。其中GPU的负载观察下来只有25%,所以还可以继续并行。

CPU的信息如下,因为是虚拟机切分,所以单精度浮点算力约为0.4T

而GPU单精约为30T。

理论上GPU应该快60倍,实际上如果可以满载的话大约是40倍。中间有部分因为流程原因,如tokenize或者内存-显存搬运消耗掉了。所以这类任务应该进行一些并行化,大约到30倍左右就比较理想了。

解析

通过pipeline可以很快获得实体的结果,但是无法直接使用。

每一个sentence的解析结果是listofdict, 序列分类的任务结果是每个token都有一个分类。我们需要从这些结果中解析出实体以及对应的起始位置。考虑到数据增强,位置现在变得更关键,我希望结果可以通过labelstudio进行有效的展示和标注,然后形成质量更好的回流数据。

解析依赖的功能有以下几个:

  • 1 convert_entity_label 将LABEL_0,1,2转为最初的字符标签
  • 2 detokenize 反令牌化,将一些因为tokenize而改变的字还原回来(这里我也理解了为什么是按单个字拆分令牌,因为模型会加上##作为特殊的拓展)
  • 3 extract_bio_positions 抽出实体所在位置。这里将循环改为正则真的是灵感,快多了。
python 复制代码
from datasets import ClassLabel
# 定义标签列表
label_list = ['B', 'I', 'O']
# 创建 ClassLabel 对象
class_label = ClassLabel(names=label_list)
def convert_entity_label(x):
    x1 = int(x.split('_')[-1])
    return class_label.int2str(x1)

def detokenize(word_piece):
    """
    将 WordPiece 令牌还原为原始句子。
    """
    if word_piece.startswith('##'):
        x = word_piece[2:]
    else:
        x = word_piece
    return x
import re

def extract_bio_positions(bio_string):
    pattern = re.compile(r'B(I+)(O|$)')
    matches = pattern.finditer(bio_string)
    
    results = []
    for match in matches:
        start, end = match.span()
        results.append((start, end - 1))  # end-1 to include the last 'I'
    
    return results
# 示例字符串
bio_string = "BIIIIIIOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO"
# 提取位置
positions = extract_bio_positions(bio_string)
# 打印结果
for start, end in positions:
    print(f"Entity starts at {start} and ends at {end}")

对于每一个句子的结果处理,有两种思路:

  • 1 使用pandas + apply, 直观但是开销比较大
  • 2 使用 map, 不直观,但是开销小

返回都是 (ent, start, end) tuple list

pandas apply方法

python 复制代码
some_entity_list = entities[0]
def parse_ent_pos_pandas_apply(some_listofdict = None):
    some_entity_list = some_listofdict
    some_entity_df = pd.DataFrame(some_entity_list)
    some_entity_df['label'] = some_entity_df['entity'].apply(convert_entity_label)
    some_entity_df['ori_word'] = some_entity_df['word'].apply(detokenize)
    label_list = list(some_entity_df['label'])
    oriword_list = list(some_entity_df['ori_word'])
    label_str = ''.join(label_list)
    oriword_str = ''.join(oriword_list)
    pos_list = extract_bio_positions(label_str)
    part_ent_list = [(oriword_str[x[0]:x[1]] , *x) for x in pos_list]
    return part_ent_list

%%timeit
parse_ent_pos_pandas_apply(some_entity_list)
1.15 ms ± 2.12 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)

map方法: 快了10倍不止。

python 复制代码
def parse_ent_pos_map(some_listofdict = None):
    some_entity_list = some_listofdict
    word_list = [x['word'] for x in some_entity_list]
    label_list = [x['entity'] for x in some_entity_list]
    label_str =''.join(map(convert_entity_label,label_list))
    ori_word_str =''.join(map(detokenize,word_list))
    pos_list = extract_bio_positions(label_str)
    part_ent_list = [(ori_word_str[x[0]:x[1]] , *x) for x in pos_list]
    return part_ent_list
%%timeit
parse_ent_pos_map(some_entity_list)
76.5 µs ± 137 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

最后进行批量生成

python 复制代码
some_s = pd.Series(entities)
some_s2 =some_s.apply(parse_ent_pos_map)
res_df = pd.DataFrame()
res_df['clean_data'] = list(sub_list[:10])
res_df['ent_tuple_list0'] =list(some_s2)[:10]
res_df['ent_tuple_list'] = res_df['ent_tuple_list0'].apply(lambda x: [a + ('ORG',) for a in x])
res_df.head()

写到这里,暂时告一段落,先看看效果再进行下一步。

相关推荐
半盏茶香4 分钟前
在21世纪的我用C语言探寻世界本质 ——编译和链接(编译环境和运行环境)
c语言·开发语言·c++·算法
人机与认知实验室1 小时前
人、机、环境中各有其神经网络系统
人工智能·深度学习·神经网络·机器学习
Evand J1 小时前
LOS/NLOS环境建模与三维TOA定位,MATLAB仿真程序,可自定义锚点数量和轨迹点长度
开发语言·matlab
LucianaiB1 小时前
探索CSDN博客数据:使用Python爬虫技术
开发语言·爬虫·python
Ronin3051 小时前
11.vector的介绍及模拟实现
开发语言·c++
计算机学长大白2 小时前
C中设计不允许继承的类的实现方法是什么?
c语言·开发语言
PieroPc3 小时前
Python 写的 智慧记 进销存 辅助 程序 导入导出 excel 可打印
开发语言·python·excel
2401_857439696 小时前
SSM 架构下 Vue 电脑测评系统:为电脑性能评估赋能
开发语言·php
SoraLuna6 小时前
「Mac畅玩鸿蒙与硬件47」UI互动应用篇24 - 虚拟音乐控制台
开发语言·macos·ui·华为·harmonyos
xlsw_6 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis