AiWin2021构建基于理赔文本语义的问答系统

摘要:

合集:AI案例-NLP-金融
赛题:AiWin2021文本视觉认知问答竞赛
主页:http://ailab.aiwin.org.cn/competitions/49
AI问题:识别文本中的实体并应用于问答系统的构建
数据集发布方:中国太平洋保险
数据集:保险文本视觉认知问答任务数据集,已使用PaddleOCR识别并转换的保险文本视觉认知问答任务数据集,可直接用于阅读理解模型的训练。
数据集价值:用于阅读理解模型的训练。
解决方案:PaddleOCR、PaddleNLP框架、使用Ernie模型中的问答任务类ErnieForQuestionAnswering进行抽取式阅读理解构建问答系统。

一、赛题简介

问题描述

在寿险、产险、健康险等保险的理赔流程和客户服务环节中,存在大量扫描文档,例如医疗票据、费用清单、病例等。对这些扫描文档进行文字检测与识别,并且提取出结构化信息,可以用于极速理赔、个人健康管理等业务场景。

赛题任务

本次赛题将提供面向保险场景的扫描图片数据集,参赛队利用OCR技术自动识别影像资料后,再通过AI智能判断所识别文字的内在逻辑,回答关于图片的自然语言问题。问题的答案是可以从图片中提取的任何文本/标记。

输入:保险场景的扫描文档(例如:医疗票据)+ 自然语言提问(例如:病人服用的药品清单有什么?)

输出:对应自然语言提问的事实性答案。

例如:

  • 提问:西药费的金额是多少? 回答:140.16
  • 提问:140.16元购买了什么药品? 回答:{甲}缘沙坦胶囊{基}
  • 提问:这是一份关于什么药品的说明? 回答:十三味疏肝胶囊
  • 提问:药品的有效期是多久? 回答:1.5年

二、数据集内容

原始数据集使用的扫描文件类型包括票据、说明、报告等20 多种。混合了印刷、打字和手写的内容。训练集有5000余张左右原始扫描文件及对应的 4万余个自然语言问答标注。提供的数据均已做了标注及脱敏。以下为使用PaddleOCR对图像识别后,转换成保险文本视觉认知问答任务的数据集的内容介绍。

train-utf8.csv

训练集用于模型训练,数据字段包括以下内容:

  • index:序号
  • question_id:问题的唯一id标识
  • filename:问题对应的唯一图片名称
  • question_text:问题描述
  • answer_text:问题对应的唯一答案

数据样例:(未展示文件名字段)

indexquestion_idquestion_textanswer_text
1Q00001这是什么药品?茶碱缓释片
2Q00002本说明书来源于哪里?黑龙江鼎恒升药业有限公司
3Q00003本品可通过什么屏障?胎盘
4Q00004说明书上方正中是什么字?茶碱缓释片
5Q00005左上角是什么字?说明书来源:黑龙江鼎恒升药业有限公司

test1-utf8.csv

测试集用于模型验证,需提交问题对应的答案,数据字段包括以下内容:

  • index:序号
  • question_id:问题的唯一id标识
  • filename:问题对应的唯一图片名称
  • question_text:问题描述
indexquestion_idfilenamequestion_text
1Q000012-603986_20190430_4_119.jpg证书有效期至什么时候?
2Q000022-603986_20190430_4_119.jpg此单据经什么部门审查,批准?
3Q000032-603986_20190430_4_119.jpg图片右上角的阿拉伯数字是什么?
4Q000042-603986_20190430_4_119.jpg图片正上方的文字是什么/
5Q000052-603986_20190430_4_119.jpg图片左下角的文字是什么内容?

train.json和dev.json

数据样例:

{
"data": [{
"paragraphs": [{
"context": "54020292北京市医疗网珍收费票据医保已世结發部监NO财16139-54-02实时结算:★医疗机构类型:交易流水号:2411000107180415993045社会保障卡号40096415918041502915城镇工男医保类型:单价数量单位业务流水号:性别:15380等级项目/规格姓名:金额有自作数量/单位鸡7500单价中成药贸6.2Y项目规格无自付:复方甲氧那明胶/48粒23.75001/瓶12.E200西药费收都联153.8000付甲酸左氧沙星/0.116.2G00无苏黄止咳囊/Q.45g2粒76.90002/津有效遣夫不北京市财政局印制·20172收费专用道172.32自付一17232000172.32起村金额17.750.G0衣饮医保范内金狮1332.51封顶金额0.00门诊大额支付0.0自付二0.累计医供内范金额190.07退体补充支付0.00年门诊大额票计支付0.白费个人支付金额陵军补财支付0.00190.070.09本饮支付后·个人账户余额单位补充险[原公疗]支付个人账户支付0.00基金支情2合计(大写收款人收款单位(章)",
"title": "c850b0d7018d127989d1b20d0f7118d66f50fbc2.png",
"qas": [{
"question": "本次医保范围支付多少钱?",
"id": "Q39447",
"answers": [{
"text": "172.32",
"answer_start": 288
}]
}, {
"question": "9260是什么的编号?",
"id": "Q39452",
"answers": [{
"text": "收款人",
"answer_start": 468
}]
}]
}]
},
{}
]
}

例如对于问题:本次医保范围支付多少钱?答案为:172.32元。答案在原文/context中的起始位置(以字符为单位)。

版权许可

版权许可:https://creativecommons.org/publicdomain/zero/1.0/deed.zh

SQuAD格式简介

SQuAD(Stanford Question Answering Dataset)是由斯坦福大学通过众包构建的一个机器阅读理解数据集,包含10万多个问题及其对应的原文和答案。

数据集结构

  • 训练集:用于模型的训练。
  • 开发集:用于模型的调整和优化。
  • 测试集:用于评估模型的最终性能,采用封闭评测方式。

数据格式

每个样本包含以下字段:

  • id:样本的唯一标识符。
  • paragraph_id:原文的唯一标识符。
  • question:用户提出的问题。
  • context:包含答案的原文段落。
  • answer_start:答案在原文/context中的起始位置(以字符为单位)。
  • answer_end:答案在原文/context中的结束位置(以字符为单位)。
  • is_impossible:布尔值,表示问题是否有答案。

示例

假设我们有一个段落(context)如下:

The university is located in a beautiful city, with a rich history and culture. It has a large campus, which includes modern teaching facilities and a library with a vast collection of books. 现在,我们有一个问题(question):“Where is the university located?”

对应的答案(answer)是:“in a beautiful city”。

在SQuAD数据集中,这个问答对可能会以如下的JSON格式表示:

{
"context": "The university is located in a beautiful city, with a rich history and culture. It has a large campus, which includes modern teaching facilities and a library with a vast collection of books.",
"question": "Where is the university located?",
"answers": [
  {
    "text": "in a beautiful city",
    "answer_start": 22
  }
]
}

在这个例子中,answer_start的值为22,这意味着答案“in a beautiful city”在段落中的起始位置是第22个字符(注意,这里的字符计数通常是从0或1开始的,但具体取决于数据集的标注方式。在上面的例子中,我们可以假设它是从1开始的,但实际情况可能因数据集而异)。

需要注意的是,answer_start字段的值是一个整数,它表示答案在段落中的起始位置(通常是字符索引)。这个字段对于训练能够精确定位答案位置的问答系统模型非常重要。同时,由于SQuAD数据集是用于评估模型阅读理解能力的基准数据集之一,因此answer_start字段的准确性对于模型的训练和评估也至关重要。

三、基线版解决方案

基线项目使用的是两阶段的处理方式:

  • 使用PaddleOCR框架识别图像中的文本信息。
  • 使用PaddleNLP框架根据保险理赔相关的文本信息和问题,进行抽取式阅读理解后输出问题对应的答案。

PaddleOCR是一个开源的OCR工具库,旨在提供高性能、易用、多语言支持的文本检测和识别功能。它支持多种OCR任务,包括文字检测、文字方向检测、多语种OCR、手写体OCR等,适用于文档扫描、票据识别、街景文字提取等多种场景。使用PaddleOCR对图像识别后,转换成保险文本视觉认知问答任务的数据集,该数据集可直接用于阅读理解模型的训练。本文只讨论第二阶段的解决方案。

PaddleNLP是百度飞桨深度学习框架中的自然语言处理库,集成了丰富的预训练模型、高效便捷的工具组件以及全面的数据集支持。它支持文本分类、文本匹配、序列标注、阅读理解、智能对话等多种自然语言处理/NLP任务,适用于信息抽取、语义检索、智能问答、情感分析等领域。抽取式阅读理解(Extractive Reading Comprehension) 是一种自然语言处理(NLP)任务,旨在从给定的文本(上下文)中直接抽取一段连续的文字片段作为问题的答案。与生成式阅读理解(生成新句子回答)不同,抽取式阅读理解要求答案必须来自原文,不进行改写或总结。ErnieForQuestionAnswering 是百度ERNIE模型针对问答任务优化的版本,其核心思想是通过预测答案在原文中的起始位置(start position)和结束位置(end position)来抽取答案。然后构建一个高精度的中文抽取式问答系统。

四、PaddleNLP问答系统样例

源码:paddle-nlp.py和utils.py

安装开发库

参考文章《安装深度学习框架PaddlePaddle》。

CPU版本

使用conda安装paddlepaddle==3.0.0b2;使用pip安装paddlenlp==3.0.0b0。

conda install paddlepaddle==3.0.0b2 -c paddle
pip install paddlenlp==3.0.0b0

检查:

(paddle3-cpu) C:\Users\86138>conda list paddle
# packages in environment at D:\App-Data\conda3\envs\paddle3-cpu:
#
# Name                   Version                   Build Channel
paddle2onnx               1.3.1                   pypi_0   pypi
paddlefsl                 1.1.0                   pypi_0   pypi
paddlenlp                 3.0.0b0                 pypi_0   pypi
paddlepaddle             3.0.0b2         py312_cpu_windows   paddle

GPU版本

基于CUDA12.3为例,使用conda安装paddlepaddle-gpu==3.0.0b2;使用pip安装 paddlenlp==3.0.0b0:

conda install paddlepaddle-gpu==3.0.0b2 paddlepaddle-cuda=12.3 -c paddle -c nvidia
pip install --upgrade paddlenlp==3.0.0b0

验证:

python -c "import paddlenlp;print(paddlenlp.__version__)"

输出:3.0.0b0

处理流程

1、导入开发库

import paddle
import paddlenlp as ppnlp

from functools import partial
from paddlenlp.data import Stack, Dict, Pad
from utils import prepare_train_features, prepare_validation_features, evaluate

2、加载问答任务模型

Ernie 1.0(Enhanced Representation through kNowledge IntEgration)是百度于2019年3月推出的知识增强语义表示模型,属于早期将知识图谱与预训练技术结合的创新性模型。ErnieForQuestionAnswering: 这是一个专门用于问答任务的 Ernie 模型类。

############参数配置###############
# 模型名称
MODEL_NAME = "ernie-1.0"

# 最大文本长度
max_seq_length = 512
# 文本滑动窗口步幅
doc_stride = 128

# 训练过程中的最大学习率
learning_rate = 3e-5
# 训练轮次
epochs = 1
# 数据批次大小
batch_size = 8
# 学习率预热比例
warmup_proportion = 0.1
# 权重衰减系数,类似模型正则项策略,避免模型过拟合
weight_decay = 0.01


#############模型################
# 加载模型
# ErnieForQuestionAnswering: 这是一个专门用于问答任务的 Ernie 模型类。
model = ppnlp.transformers.ErnieForQuestionAnswering.from_pretrained(MODEL_NAME)

# 加载 tokenizer
# Ernie 模型的分词器类, 从预训练模型中加载分词器的词汇表和配置。
tokenizer = ppnlp.transformers.ErnieTokenizer.from_pretrained(MODEL_NAME)

3、加载数据集

这段代码主要是用于使用 PaddleNLP 开发问答系统,具体实现了数据的加载、预处理、批量化和数据加载器的配置。

加载dureader_robust 数据集。dureader_robust 是从 DuReader 数据集中进一步加工而来的。其目标是提供一个更具挑战性和多样性的测试环境,以更好地检验和提升机器阅读理解系统的鲁棒性。数据组成:数据集包含训练集、验证集和测试集。每个样本通常由一段文本(文章)和一个或多个问题组成。对于每个问题,还提供了相应的答案,这些答案可能是文本中的某个片段或是直接的文本回答。

数据读取器train_batch_sampler使用DistributedBatchSampler对训练数据进行分批次采样,确保数据在分布式训练中能够正确分配,batch_size=batch_size表示每个批次的大小,shuffle=True表示每个epoch后打乱数据。train_batchify_fn是一个lambda函数,用于在每次加载数据时将样本填充和处理为指定格式:

  • input_ids:填充序列,使每个batch中的序列长度一致。
  • token_type_ids:与input_ids类似,用于表示句子类型。
  • start_positions 和 end_positions:这些是问答任务中的起始和结束位置,分别对应答案的起始位置和结束位置。Stack(dtype=”int64″)表示将这些位置打包成一个批次。Pad和Stack是PaddleNLP提供的用于批量处理数据的功能。

train_data_loader创建了一个DataLoader对象,用于将数据加载成批次。collate_fn=train_batchify_fn表示在加载数据时调用train_batchify_fn函数来处理数据。

验证集数据读取器:dev_batch_sampler使用BatchSampler对验证数据进行分批次采样,shuffle=False表示验证集数据不需要打乱。dev_batchify_fn与训练集的train_batchify_fn类似,对验证集数据进行填充处理。这里只处理input_ids和token_type_ids,不涉及start_positions和end_positions,因为验证集不需要标签。dev_data_loader与训练集的train_data_loader类似,创建一个DataLoader对象来加载验证集数据。

#############数据###############
# 加载数据集
train_ds = ppnlp.datasets.load_dataset('dureader_robust', data_files='data/data83268/train.json')
dev_ds = ppnlp.datasets.load_dataset('dureader_robust', data_files='data/data83268/dev.json')

# 数据滑窗处理
train_trans_func = partial(prepare_train_features,
                          max_seq_length=max_seq_length,
                          doc_stride=doc_stride,
                          tokenizer=tokenizer)

train_ds.map(train_trans_func, batched=True)

dev_trans_func = partial(prepare_validation_features,
                          max_seq_length=max_seq_length,
                          doc_stride=doc_stride,
                          tokenizer=tokenizer)
                         
dev_ds.map(dev_trans_func, batched=True)

# 数据读取器配置
train_batch_sampler = paddle.io.DistributedBatchSampler(
      train_ds, batch_size=batch_size, shuffle=True)

train_batchify_fn = lambda samples, fn=Dict({
  "input_ids": Pad(axis=0, pad_val=tokenizer.pad_token_id),
  "token_type_ids": Pad(axis=0, pad_val=tokenizer.pad_token_type_id),
  "start_positions": Stack(dtype="int64"),
  "end_positions": Stack(dtype="int64")
}): fn(samples)

train_data_loader = paddle.io.DataLoader(
  dataset=train_ds,
  batch_sampler=train_batch_sampler,
  collate_fn=train_batchify_fn,
  return_list=True)

dev_batch_sampler = paddle.io.BatchSampler(
  dev_ds, batch_size=batch_size, shuffle=False)

dev_batchify_fn = lambda samples, fn=Dict({
  "input_ids": Pad(axis=0, pad_val=tokenizer.pad_token_id),
  "token_type_ids": Pad(axis=0, pad_val=tokenizer.pad_token_type_id)
}): fn(samples)

dev_data_loader = paddle.io.DataLoader(
  dataset=dev_ds,
  batch_sampler=dev_batch_sampler,
  collate_fn=dev_batchify_fn,
  return_list=True)

4、定义优化器

这段代码是用于配置优化器的,特别是在训练深度学习模型时,优化器的设置对于模型的收敛速度和最终性能至关重要。这行代码创建了一个学习率调度器。ppnlp.transformers.LinearDecayWithWarmup 是 PaddleNLP 库中的一个类,用于实现带有热身(warmup)阶段的线性衰减学习率策略。这种策略可以帮助模型在训练初期更稳定地收敛,并在训练后期逐渐减小学习率以避免在最优解附近震荡。

  • learning_rate 是初始学习率。
  • num_training_steps 是总的训练步数,之前已经计算得到。
  • warmup_proportion 是热身阶段所占的比例,即在训练的前多少步中使用热身策略。

以上这段代码创建了一个优化器实例。这里使用的是 AdamW 优化器,它是 Adam 优化器的一个变种,特别适用于深度学习中的权重衰减。

  • learning_rate=lr_scheduler 指定了优化器使用的学习率调度器,这样学习率会在训练过程中根据预设策略动态调整。
  • parameters=model.parameters() 指定了优化器需要更新的参数,即模型的所有参数。
  • weight_decay=weight_decay 设置了权重衰减的系数,这是一个超参数,需要根据具体任务进行调整。
  • apply_decay_param_fun=lambda x: x in decay_params 是一个函数,用于决定哪些参数应该应用权重衰减。这里使用之前生成的 decay_params 列表来决定。
#############优化器配置#############
# 学习率策略
num_training_steps = len(train_data_loader) * epochs
lr_scheduler = ppnlp.transformers.LinearDecayWithWarmup(learning_rate, num_training_steps, warmup_proportion)

# Generate parameter names needed to perform weight decay.
# All bias and LayerNorm parameters are excluded.
decay_params = [
  p.name for n, p in model.named_parameters()
  if not any(nd in n for nd in ["bias", "norm"])
]

# 设置优化器
optimizer = paddle.optimizer.AdamW(
  learning_rate=lr_scheduler,
  parameters=model.parameters(),
  weight_decay=weight_decay,
  apply_decay_param_fun=lambda x: x in decay_params)

5、定义损失函数

这段代码定义了一个自定义的损失函数类 CrossEntropyLossForSQuAD,用于问答系统中的 SQuAD(Stanford Question Answering Dataset)任务。SQuAD 是一个流行的机器阅读理解数据集,目标是预测给定问题的答案在文本中的起始和结束位置。

#############损失函数################
class CrossEntropyLossForSQuAD(paddle.nn.Layer):
  def __init__(self):
      super(CrossEntropyLossForSQuAD, self).__init__()

  def forward(self, y, label):
      start_logits, end_logits = y   # both shape are [batch_size, seq_len]
      start_position, end_position = label
      start_position = paddle.unsqueeze(start_position, axis=-1)
      end_position = paddle.unsqueeze(end_position, axis=-1)
      start_loss = paddle.nn.functional.softmax_with_cross_entropy(
          logits=start_logits, label=start_position, soft_label=False)
      start_loss = paddle.mean(start_loss)
      end_loss = paddle.nn.functional.softmax_with_cross_entropy(
          logits=end_logits, label=end_position, soft_label=False)
      end_loss = paddle.mean(end_loss)

      loss = (start_loss + end_loss) / 2
      return loss

6、训练模型

这段代码实现了一个典型的深度学习模型训练流程,包括前向传播、损失计算、反向传播、参数更新和学习率调整。通过在每个周期结束后进行评估,可以监控模型的训练进度和性能。

#############模型训练################
# 实例化 loss
criterion = CrossEntropyLossForSQuAD()
global_step = 0

# 训练
for epoch in range(1, epochs + 1):
  for step, batch in enumerate(train_data_loader, start=1):
      global_step += 1
      input_ids, segment_ids, start_positions, end_positions = batch
      logits = model(input_ids=input_ids, token_type_ids=segment_ids)
      loss = criterion(logits, (start_positions, end_positions))

      if global_step % 100 == 0 :
          print("global step %d, epoch: %d, batch: %d, loss: %.5f" % (global_step, epoch, step, loss))
      loss.backward()
      optimizer.step()
      lr_scheduler.step()
      optimizer.clear_grad()

  evaluate(model=model, data_loader=dev_data_loader)

# 保存
model.save_pretrained('./checkpoint')
tokenizer.save_pretrained('./checkpoint')

utils.py

import paddle
import numpy as np

def prepare_train_features(data, tokenizer, max_seq_length=128, doc_stride=128):
  features = []
   
  # 遍历数据
  for item in data:
      context = item['context'] # 获取上下文文本
      question = item['question'] # 获取问题
       
      # 由于每个 item 中包含一个答案列表,假设只取第一个答案
      for answer, answer_start in zip(item['answers'], item['answer_starts']):
           
          # 使用 tokenizer 对答案文本进行编码
          label_encoding = tokenizer.encode(answer, add_special_tokens=False, truncation=True, max_length=max_seq_length)
           
          # 将问题和上下文拼接后送入 tokenizer
          encoding = tokenizer.encode_plus(
              question + " " + context, # 拼接问题和上下文
              add_special_tokens=True,
              max_length=max_seq_length,
              return_token_type_ids=True, # 显式请求返回 token_type_ids
              padding='max_length',
              truncation=True,
              return_attention_mask=True,
              return_tensors='pd'
          )
           
          # 提取 label_encoding 中的 input_ids(答案的 token ID)
          label_ids = label_encoding['input_ids']
           
          # 修正 start_positions 和 end_positions,确保它们不超出 max_seq_length - 1
          max_position = max_seq_length - 1
          start_position = min(answer_start, max_position)
          end_position = min(answer_start + len(label_ids) - 1, max_position)

          # 构造特征,添加到 features 列表
          features.append({
              'input_ids': encoding['input_ids'].flatten(),
              'attention_mask': encoding['attention_mask'].flatten(),
              'token_type_ids': encoding['token_type_ids'].flatten(), # 添加 token_type_ids
              'labels': paddle.to_tensor(label_ids, dtype='int64'), # 直接使用 label_ids
              'start_positions': paddle.to_tensor(start_position, dtype='int64'), # 使用修正后的 start_positions
              'end_positions': paddle.to_tensor(end_position, dtype='int64') # 使用修正后的 end_positions
          })
   
  return features

def prepare_validation_features(data, tokenizer, max_seq_length=128, doc_stride=128):
  return prepare_train_features(data, tokenizer, max_seq_length, doc_stride)

# #############
def evaluate(model, data_loader):
  model.eval()
  all_preds = []
  all_labels = []

  with paddle.no_grad():
      for batch in data_loader:
          # 检查返回的数据是否包含所有必需的字段
          if len(batch) == 5:
              # 正常的情况,包含所有必要字段
              input_ids, segment_ids, attention_mask, start_positions, end_positions = batch
          else:
              raise ValueError(f"Unexpected batch format. Expected 2 or 5 elements, got {len(batch)} elements.")

          # print(f"len(batch)={len(batch)}, input_ids: {input_ids}, segment_ids: {segment_ids}, attention_mask: {attention_mask}, start_positions: {start_positions}, end_positions: {end_positions}")
          # 使用模型进行预测
          logits = model(input_ids=input_ids, token_type_ids=segment_ids, attention_mask=attention_mask)
          start_logits, end_logits = logits

          # 获取预测的起始和结束位置
          start_preds = paddle.argmax(start_logits, axis=-1).numpy()
          end_preds = paddle.argmax(end_logits, axis=-1).numpy()

          # 如果有真实标签(start_positions,end_positions),则添加到all_labels中
          all_labels.extend(list(zip(start_positions.numpy(), end_positions.numpy())))

          # 保存预测结果
          all_preds.extend(list(zip(start_preds, end_preds)))

  # 计算精确匹配(EM)和F1分数
  def compute_metrics(p):
      preds, labels = p
      preds = np.array(preds)
      labels = np.array(labels)

      # 计算EM和F1(这里只是示例,真实计算应该更复杂)
      em = np.sum(preds == labels) / len(labels)
      f1 = 0 # 需要实现F1计算逻辑
      print(f"compute_metrics(), em: {em}, f1: {f1}")

      return {'em': em, 'f1': f1}

  metrics = compute_metrics((all_preds, all_labels))
  return metrics

运行结果

模型文件:./checkpoint/

global step 100, epoch: 1, batch: 100, loss: 5.33133
global step 200, epoch: 1, batch: 200, loss: 2.81528
...
Processing example: 1000
time per 1000: 11.201786994934082
Processing example: 2000
time per 1000: 11.235816478729248
Processing example: 3000
time per 1000: 10.834845066070557
Processing example: 4000
time per 1000: 11.04150128364563
Processing example: 5000
time per 1000: 11.004519701004028
Processing example: 6000
time per 1000: 11.003149509429932
Processing example: 7000
time per 1000: 11.149619340896606
{
"exact": 56.03663613655287,
"f1": 72.53400335174827,
"total": 1201,
"HasAns_exact": 56.03663613655287,
"HasAns_f1": 72.53400335174827,
"HasAns_total": 1201
}

问题: 本次医保范围支付多少钱?
原文: 54020292北京市医疗网珍收费票据医保已世结發部监NO财16139-54-02实时结算:★医疗机构类型:交易流水号:2411000107180415993045社会保障卡号40096415918041502915城镇工男医保类型:单价数量单位业务流水号:性别:15380等级项目/规格姓名:金额有自作数量/单位鸡7500单价中成药贸6.2Y项目规格无自付:复方甲氧那明胶/48粒23.75001/瓶12.E200西药费收都联153.8000付甲酸左氧沙星/0.116.2G00无苏黄止咳囊/Q.45g2粒76.90002/津有效遣夫不北京市财政局印制·20172收费专用道172.32自付一17232000172.32起村金额17.750.G0衣饮医保范内金狮1332.51封顶金额0.00门诊大额支付0.0自付二0.累计医供内范金额190.07退体补充支付0.00年门诊大额票计支付0.白费个人支付金额陵军补财支付0.00190.070.09本饮支付后·个人账户余额单位补充险[原公疗]支付个人账户支付0.00基金支情2合计(大写收款人收款单位(章)
答案: 172.32

问题: 9260是什么的编号?
原文: 54020292北京市医疗网珍收费票据医保已世结發部监NO财16139-54-02实时结算:★医疗机构类型:交易流水号:2411000107180415993045社会保障卡号40096415918041502915城镇工男医保类型:单价数量单位业务流水号:性别:15380等级项目/规格姓名:金额有自作数量/单位鸡7500单价中成药贸6.2Y项目规格无自付:复方甲氧那明胶/48粒23.75001/瓶12.E200西药费收都联153.8000付甲酸左氧沙星/0.116.2G00无苏黄止咳囊/Q.45g2粒76.90002/津有效遣夫不北京市财政局印制·20172收费专用道172.32自付一17232000172.32起村金额17.750.G0衣饮医保范内金狮1332.51封顶金额0.00门诊大额支付0.0自付二0.累计医供内范金额190.07退体补充支付0.00年门诊大额票计支付0.白费个人支付金额陵军补财支付0.00190.070.09本饮支付后·个人账户余额单位补充险[原公疗]支付个人账户支付0.00基金支情2合计(大写收款人收款单位(章)
答案: 收款单位
...

源码开源协议

Copyright (c) 2016 PaddlePaddle Authors. All Rights Reserved.

五、获取案例套装

需要登录后才允许下载文件包。登录 需要登录后才允许下载文件包。登录

发表评论