基于 CPU 的图像多分类建模实践

· 18297字 · 37分钟

摘要:为节省成本,在服务器只有 CPU 的前提下,自建图像分类模型替代第三方技术。所有保险理赔图像共分为32个类别,各类别样本量极端不均衡,且数据标签源自第三方,不够准确,也缺少人力做人工标注。整体上,对大样本量的类别使用二阶段分类、深度学习模型融合 OCR 识别抽取特征、XGBoost作为分类器等建模,对小样本量的类别建设识别规则。经使用人工标记标签的结果验证,第三方整体准确率93%、自建模型91%,单看电子发票,第三方召回率98%、精确率99%,自建模型召回率96%、精确率97%。存在的局限是电子发票二维码检测的效率和准确率之间的两难问题没解决,部分类别的数据清洗实在有限,均限制了自建模型的上限,还需进一步优化。

关键词:仅有 CPU;保险理赔票据;图像多分类;二阶段分类

1.引言 🔗

1.1.业务背景 🔗

我司目前已引入第三方技术对保险理赔图像进行分类和识别,均按调用次数计费。为节省成本计,今尝试自建图像分类模型。以下是由第三方划分的图像类别,其中的“9.其它”是指,当图片没有识别出明确的分类时便落入“其它”之中。

类别 类别 类别 类别
1. 结算单 9. 其它 17. 手术记录 25. 事故证明
2. 清单 10. 病案首页 18. 理赔申请 26. 工伤证明
3. 住院发票 11. 护照 19. 出生证明 27. 第三方理赔结算单
4. 增值税发票 12. 报告单 20. 处方签 28. 伤残鉴定
5. 门诊发票 13. 化验单 21. 诊断证明 29. 死亡证明
6. 出院记录 14. 户口本 22. 授权书 30. 定额发票
7. 身份证 15. 入院记录 23. 结婚证 31. 存折
8. 银行卡 16. 门诊病历 24. 意外证明 32. 心电图

除了费用问题以外,使用第三方的技术还有两方面有待改善:其一,不完全贴合业务使用场景,存在固定样式的图片没有正确分类的情况,比如“受益人身份确认表”被归入“其它”,“税收居民身份声明”被归入“事故证明”;其二,也是固定样式的“理赔申请”(理赔申请书)精确率不到50%,大量混入“授权书”的问题得不到解决。

1.2.问题定义 🔗

以下对实际类别的数据量进行标记,其中:★ 代表累计历史数据不足以训练模型;★★ 代表一月数据不足,但累计历史数据足够训练模型;★★★ 代表一月数据就足够训练模型。

类别 类别 类别 类别 类别
1.结算单 (★★) 6.出院记录 (★★) 12.报告单 (★★) 20.处方签 (★★) 28.伤残鉴定 (★)
2.清单 (★★★) 7.身份证(正面) (★★) 13.化验单 (★★) 21.诊断证明 (★★) 29.死亡证明 (★)
3.住院发票-电子 (★★) 7.身份证(反面) (★★) 14.户口本 (★★) 22.授权书 (★★★) 30.定额发票 (★)
3.住院发票-非电子 (★) 7.身份证(双面) (★★) 15.入院记录 (★★) 23.结婚证 (★) 31.存折 (★)
4.增值税发票-电子 (★★) 8.银行卡 (★★) 16.门诊病历 (★★) 24.意外证明 (★) 32.心电图 (★)
4.增值税发票-非电子 (★) 9.其它 (★★★) 17.手术记录 (★★) 25.事故证明 (★) 税收居民身份声明 (★)
5.门诊发票-电子 (★★★) 10.病案首页 (★★) 18.理赔申请 (★★) 26.工伤证明 (★) 受益人身份确认表 (★)
5.门诊发票-非电子 (★★) 11.护照 (★) 19.出生证明 (★) 27.第三方理赔结算单 (★) -

总的来说,对这些图像进行分类,存在以下问题。

  1. 业务方面。
  • 需分类的类别数量较多。

  • 从样式是否固定来看,整体极为复杂。既存在样式固定的类别如身份证、授权书、理赔申请等,也存在制式相对固定但有差异的类别如三类电子发票,也存在制式差异极大的类别,比如不同医院的票据。

  • 不同类别分类结束的后续操作不尽相同,比如发票后续要调官方接口、医院相关票据后续做病历识别,不同类别的选择倾向不一致,比如对电子发票倾向于保障召回率、而对病历票据倾向于保障精确率。

  1. 数据方面。
  • 对用户上传图片暂时没有限制,有些图片较为模糊,有些图片文字扭曲严重,有些图片是多张票据叠放后拍照上传。

  • 第三方分类的标签并不是百分百准确,后续需要做大量的数据清洗,甚至人工确认。即便人工确认也无法保障百分百准确,因为许多票据就连核赔人员判断所属类别时也会存疑。

  • 各个类的数据量级差距悬殊,最少的一些类别每个月只有个位数,由于类别样本量不平衡,不适合把所有类一次性放到一个模型里

  1. 可用资源方面。
  • 写作本文时,服务器上暂无 GPU 可用。

1.3.本文结构 🔗

本文共分为五个章节。

第一章,引言,简述业务背景与问题定义。

第二章,基本概念,叙述本文使用的一些基本专业术语和观念,以及容易混淆的释义辨析。

第三章,数据与特征,叙述本文在数据清洗和构造特征方面使用的方法,以及对部分类别的具体实践。

第四章,建模方法,详细记录了建模过程,及个人思考和判断的内容,完整流程图见附录 B。

第五章,验证分析与下一步优化,展示三次用户验证的结果数据,记录判断的依据、分析的过程,以及下一步优化的几个方向。

2.基本概念 🔗

2.1.分类结构:从平行子类到二阶段分类 🔗

为便于理解多阶段分类的目的,这里假设用一个更贴近生活的事件来说明。假设已知一些人类样本的体检指标数据,需要对其身体年龄进行分类,从21到60岁每5岁落一个区间,共需划分8类。

  • 第一种方案如左图,所有类别是平行的。所有的平行子类都存在互相错判的风险。

  • 第二种方案如右图,先在第一阶段划分30以上、30及以下这两个类别,然后在第一阶段分类结果上继续第二阶段分类。这样能减少30以上的6类、30及以下的2类之间错判的风险。同时如果第一阶段判错,第二阶段会继续错而无法挽回。这里二阶段分类的本质是先分大类、再分小类,但具体操作上要将更接近、更相似的划分到一个大类里,使得二阶段分类的收益大于风险。

2.2.数据集的质量 🔗

准备数据集是机器学习建模最基础的步骤,且数据质量的上限决定了模型的上限。在给高质量数据集下定义之前,先结合本文的实践来探讨以下三个问题,论述数据质量如何影响分类效果。

  1. 可不可以只选最高清的、角度最正的、没叠放的图片来训练?

  2. 数据清洗和人工标记太麻烦,是否可以不做?

  3. 数据清洗和人工标记太麻烦,如果要做的话,是否可以缩减样本量?

机器学习建模是通过学习历史数据的特点来预测未来,以下三种情况都会导致模型分类能力不足。

其一,如果机器学习的历史数据里只放入最容易辨认的高清“好图”,把现实中会出现的模糊的、扭曲的、叠放的“坏图”全部剔除,这会造成训练数据和真实情况之间存在极大偏差,训练得到的模型将对真实情况存在“系统性偏见”,那么模型会因为没有学习过对“坏图”的辨认导致泛化能力不足。

其二,如果不做数据清洗,比如理赔申请书里面一半是混入的授权书,那么模型在学习理赔申请书的特点时,会容易把两者弄混,缺少足够准确的标签会导致模型欠拟合。

其三,如果做数据清洗偷工减料,比如只准备每类100张图片的历史数据就训练模型,在每类100张里面发掘到的“必然规律”,放大到每类1000张、2000张、4000张里去看很可能只是微小的偶然波动。这种情况下由于样本量太小,模型会过度学习小样本中的“必然规律”,而真实情况下要对10万张甚至100万张图片做分类时,模型面对真实数据里的规律(特征)无法稳定给出分类结果。再者说,大样本中还可能会存在相对低频的情况,只取小样本未必能覆盖这些。

总的来说,应从这三方面来判断数据集的质量好坏1:图片本身的采样情况是否与实际相符,偏差可控;数据标签的质量是否足够准确;是否有足够的样本量让特征表现稳定,并覆盖各种低频但真实存在的情况。

2.3.模型评估指标 🔗

先简单梳理一下二分类模型的评估指标,主要是准确率、精确率、召回率。我们把样本的实际结果定义为真(True)与假(False),把模型对样本预测的结果定义为正(Positive)与负(Negative),把预测结果与实际结果组合成一个混淆矩阵。

- 实际结果为真(True) 实际结果为假(False)
预测结果为正(Positive) TP(True Positive) FP(False Positive)
预测结果为负(Negative) TN(True Negative) FN(False Negative)

举一个有数据的栗子来计算,100个样本中,80个实际为真,20个实际为假,70个预测为正,30个预测为负,其中60个预测为正且实际也为真。

- T F 汇总
P 60 10 70
N 20 10 30
汇总 80 20 100

在这个矩阵的基础上计算评估指标。

  • 准确率(Accuracy):$\frac{TP + FN}{TP + TN + FP + FN} = \frac{60 + 10}{100}$

  • 精确率(Precision)

    • 预测为正的精确率:$\frac{TP}{TP + FP} = \frac{60}{70}$
    • 预测为负的精确率:$\frac{FN}{TN + FN} = \frac{10}{30}$
  • 召回率(Recall)

    • 实际为真的召回率:$\frac{TP}{TP + TN} = \frac{60}{80}$
    • 实际为假的召回率:$\frac{FN}{FP + FN} = \frac{10}{20}$

总的来说,准确率用于衡量总数中有多少正确分类,精确率衡量被预测的数据中有多少正确分类,召回率衡量实际数据中有多少正确分类。

下面再看三分类的情况,指标的计算方式与二分类一致。这里把混淆矩阵转置一下,因为混淆矩阵怎么摆不影响指标的计算结果。同理,若要建立更多类别的分类模型,这几个指标的计算方式也一样。

- 预测为双面 预测为正面 预测为反面 横向汇总 召回率
实际为双面 1000 4 5 1009 $\frac{1000}{1009}$
实际为正面 6 1200 7 1213 $\frac{1200}{1213}$
实际为反面 10 2 1100 1112 $\frac{1100}{1112}$
纵向汇总 1016 1206 1112 3334 -
精确率 $\frac{1000}{1016}$ $\frac{1200}{1206}$ $\frac{1100}{1112}$ - -
整体准确率 - - - - $\frac{1000 + 1200 + 1100}{3334}$

无论是二分类还是多分类,只要理解了基础三类评估指标,都可以在模型迭代中区分出效果是在变好还是变坏。而通过判断混淆矩阵中错分的数量,也可以根据具体错分的类别对,针对性分析出错的原因。这里假设一下,在以上三分类中{实际为双面,预测为正面}的数字是200,占实际为双面近20%,就很有问题,需要重点分析错判的原因然后改正。

2.4.阈值设定的两种倾向 🔗

在一个样本只划分一个类的情况下,二分类及多分类都会面临一个选择倾向的问题2。

下面还是先从二分类说起,一个样本数据输入模型,输出的是一个在0到1之间的连续取值,这里可以定一个阈值,阈值之上的作为预测真,阈值之下的作为预测假3。如下是一份实际真与假比例为16:84的数据:第一个混淆矩阵是将阈值定在前20%,那么模型输出的取值前20%作为预测真、后80%作为预测假;第二个混淆矩阵是将阈值定在前40%,那么模型输出的取值前40%作为预测真、后60%作为预测假。

- 预测为真 预测为假 横向汇总
实际为真 10 6 16
实际为假 10 74 84
纵向汇总 20 80 100
- 预测为真 预测为假 横向汇总
实际为真 13 3 16
实际为假 27 57 84
纵向汇总 40 60 100

根据两个混淆矩阵,可以计算得出同一个模型在不同阈值下的评估指标。很显然,当阈值从前20%扩大到前40%以后,“真”的精确率下降、召回率上升,代表着更多的“真”被召回,同时更多的“假”也被判为“真”。所以说,在二分类的情况下,考虑确定具体的阈值时,其实也是确立整体建模目标是倾向于精确率还是召回率。

  • 精确率

    • 阈值前20%,预测为真的精确率:$\frac{10}{20} = 50%$
    • 阈值前40%,预测为真的精确率:$\frac{13}{40} = 32.5%$
  • 召回率

    • 阈值前20%,实际为真的召回率:$\frac{10}{16} = 62.5%$
    • 阈值前40%,实际为真的召回率:$\frac{13}{16} = 81.25%$

再说多分类,相比于二分类的模型输出一个值,多分类的模型输出的是一个向量。比如某三分类模型的三个类别分别是正面、反面、双面,其中一个样本的模型输出结果是(0.5, 0.3, 0.2),表示这个样本有50%的概率属于“正面”、30%的概率属于“反面”、20%的概率属于“双面”,全部概率加起来等于100%。

其一,不设定阈值时,默认取最大的概率来进行分类。这种情况下,所有输入的样本都会被强行分一个类。

其二,可以对部分类别或全部类别分别设定阈值,只有超过阈值的才会被划分到对应的类。这种情况下,如果不存在超过阈值的,模型就会拒绝给出分类。

在理赔图像分类的应用场景里,建模型还是根据历史数据进行训练,随后用于预测新的图片类别。如果新预测的图片类别从未在历史中出现过,那么第一种情况会给错误分到某个类里,而第二种情况则会拒绝分类。同时,如果有些图片较为模糊,难以提取到准确的特征,在第一种情况仍然有可能分到正确的类,而第二种情况很可能被拒绝而无法落入正确类别。所以在多分类的情况下,同样需要在两种倾向里做出权衡取舍。

在本文的应用场景里,发票、医疗票据后续还要做识别,提出需求的用户无法给出具体的目标,第一次验证选择的倾向是对发票、医疗票据的子类设置阈值,尽量先保障精确率,第一次验证结束后用户认为应该召回足够多的电子发票,因此第二次验证的倾向作出修改,以降低精确率的代价来提高电子发票的召回率。

2.5.算法与模型的释义辨析 🔗

本文建模过程中多次涉及到不同的算法和模型,为避免混淆概念,这里提前做一番释义辨析。在本文作者的认知里,算法是理论,模型是应用。机器学习算法是指理论上的计算方法,比如建设多分类模型可选的分类算法有 SVM、XGBoost 等。而机器学习模型是指使用数据结合算法训练得到的文件,输入样本数据调用模型文件可得到预测结果,每个模型文件作为模型的实际预测能力都与所使用的数据质量息息相关。

在开源领域,关于图像特征抽取、图像分类、图像识别等方面都有现成训练好的模型,依据训练使用的数据量、算法复杂度不同而形成不同参数量的模型。本文使用的开源模型有两个。

其一,ResNet18。ResNet18模型是指 ResNet (2015年提出的深度卷积神经网络算法)在 ImageNet 数据集(人工标记了1400万以上的图片)训练得到的模型文件,该模型既可作为分类的基线模型,也可以用于提取图片特征。ResNet 家族还有更大参数量的模型,也有一些受其启发的衍生模型,为兼顾当前硬件资源的性能与模型运行效率,选择了该系列最轻量的ResNet18。

其二,Paddle(飞桨模型)。本文使用了百度飞桨 PaddleOCR 团队开源的PP-OCRv6 系列中最轻量的模型,官方并未公开训练使用的数据集,可知晓的是涵盖了手写体、印刷体及不同语言。

3.数据与特征 🔗

关于构造特征与数据清洗,这两项虽然目的不同,前者是将特征放入模型中,后者是运用特征来找出图片正确的标签,但都是要找出各类别图片不同于其他类别的特点。

虽然图像分类模型的特征也可以批量构造并筛选,但没法像普通的结构化数据那样更直接地探索分析。比如分析一个客户是否涉及欺诈,可以直接提取性别、年龄、行为等等特征,这些特征有直观的业务含义,也可以对多个指标批量探查趋势、分布、异常或缺失等情况,能够清楚掌握哪些特征最有作用。而对图像构造特征得先提取,特征的数量级更大,更缺少直观的含义去分析,即便批量构造出有用的特征,也很难搞清楚有用是具体怎么起作用的。因此,考虑到不同的图像类别各有不同的特点,确实还需要人工看才能搞清楚具体有什么特点、怎么构造特征,什么样的特征才是起到关键区分作用的。

而第三方分类的结果并不是百分百准确,数据标签的质量会限制模型的上限,因而还需要人工确认图片的正确标签(注:考虑到数据安全问题,不能让 AI 来辅助识别)。但是图像的历史数据量很大,相对来说人力却很有限,在不确定多大的样本量才算足够之前,全部用人工来标注正确标签显然不合理,也因此没法把数据清洗全做完了再建模,也没法把所有类别的数据清洗都做完。

在建模前期这两项就是同步进行的,先人工看少量图片确定各类别的基础特点,然后一边用这些特点作为特征做数据清洗、一边构造特征看对模型分类效果是否有用,然后根据两边的效果反馈进行反复调整。也因此真实的干活流程里,这两个步骤在人工探查数据的环节没有严格的先后顺序,且都是贯穿整个建模流程的。为使行文逻辑顺畅,先写使用的方法,再写不同类别的具体实践。

3.1.使用的方法 🔗

  1. 二维码检测

做二维码检测使用了现成的方法,对应的 Python 模块的名称分别是 OpenCV、pyzbar、zxing-cpp。在训练模型期间,为了保障整个模型最终运行效率达到每分钟200张,仅使用 OpenCV 和 pyzbar 做快速解码,但这样对检出二维码的准确率有损。而模型是对每张图片都划分类别,实际上也要对大量无码图片做检测,如果提取二维码相关特征时做多尺度(更多放大或缩小倍数)、多角度(更多旋转角度)、多窗口(裁剪更多窗口)的穷举,能够检出更多二维码,但同时会带来极大的计算开销,在提高检测准确率的同时降低检测效率(降到每张10秒)。

这部分是个有待突破的瓶颈。

  1. 图片文本提取

初步人工看过各类别的图片后,发觉最易区分的特点还是图片本身有不同的文本关键词,因此构建特征和数据清洗都需要用图片上的关键词作为基础特征。可用于训练模型的两台服务器都只有 CPU,在考虑硬件资源受限、保障分类效率的前提下,引入以下微型飞桨模型来做 OCR 识别。考虑到图片不全是正的方向,后续还可在这个基础上再加上[0, 90, 180, 270]等旋转角度。

from paddleocr import PaddleOCR

ocr = PaddleOCR(
    lang="ch",
    use_doc_orientation_classify=False,
    use_doc_unwarping=False,
    use_textline_orientation=False,
    text_det_limit_side_len=960,
    text_detection_model_name="PP-OCRv6_tiny_det",
    text_recognition_model_name="PP-OCRv6_tiny_rec",
    engine="onnxruntime",
    cpu_threads=4,
)

3.2.具体实践 🔗

在这几十个图像类别之中,可简略分为以下几种情况,构造特征的难度逐渐增加。

  1. 有固定样式、固定纹理,如身份证、银行卡、授权书、理赔申请书、受益人身份确认表、税收居民身份声明,还有出生证明、护照等。
  2. 样式不固定但有一致特点、有特殊可检测的特征(二维码),如住院、门诊、增值税等三类电子发票。
  3. 样式不固定,如医院出具的各类病历和单据,不同责任机关出具的死亡证明、工伤证明、事故证明等。

对于第3种,如果数据量很大,几乎无法用关键词识别的方法做初步的数据清洗;如果数据量较小,可以人工过一遍,总结用于识别的关键词,但应用时出现历史没见过的新的,还是很难正确区分。

3.2.1.发票 🔗

发票一共有三个大类:门诊发票、住院发票、增值税发票,每种发票还需要区分电子和非电子,用于区分的特点有以下这些。

其一,发票上面都会有监制章,财政部监制对应医疗收费票据,税务局监制对应增值税发票; 其二,电子发票会有“电子”字样,一般会有二维码,而非电子发票一般会有条形码; 其三,门诊发票会有“门诊收费票据”,住院发票会有“住院收费票据”,而增值税发票也会有“医疗服务(住院)”、“医疗服务(门诊)”等不同字样,也可能会有“住院号”或“门诊号”,因而不能仅用“门诊”或“住院”这样简短的关键词来区分; 其四,医疗收费票据会列出金额,而增值税发票会列出税额和税率; 其五,发票上通常都会盖红色的章,但也有黑白打印的,所以不适用红色的颜色占比来造特征。

类别 “电子” 二维码 其他关键词
住院发票-电子 有 一般会有 有住院收费票据,且有财政
住院发票-非电子 无 无 有住院收费票据,且有财政
门诊发票-电子 有 一般会有 有门诊收费票据,且有财政
门诊发票-非电子 无 无 有门诊收费票据,且有财政
增值税发票-电子 有 一般会有 有发票,且有税务、税率、税额之一
增值税发票-非电子 无 无 有增值税普通发票,且有税务、税率、税额之一

具体对六个子类做数据清洗时,最主要解决的是互混的问题,流程是先跑代码筛选,选出来的图片再人工确认正确类别。

  1. 非电子中混入电子,对非电子发票做二维码解码。
  2. 住院和门诊互混。
  • 门诊发票目录下,若 OCR 识别是含有住院医疗、住院费用、住院号其中之一,则筛选出来。
  • 住院发票目录下,若 OCR 识别是含有门诊字样,则筛选出来。

3.2.2.授权书 🔗

授权书这个类别的全名是个人信息使用授权书,本身是一个4页的 PDF 格式的文档,只有最后一部分需要留下客户的签名,也是唯一可以有不同的地方。

上传的图片大多是电子版,也有少量打印签字后拍照上传的。电子文档上传后会自动转成4张图片来存储,不知何故调用第三方识别后被大量错分到了理赔申请、病案首页这两个类别里。具体做数据清洗时,先基于图片文件本身的特点来筛选,剩下的再提取文本关键词判断,最后再人工确认一遍。

  1. 有些后缀是 jpg 的图片不能正常打开查看,发现其原本是 pdf 文件。根据这个特点找出扩展名是图片、文件头却是%PDF-的伪装文件,统一转换后归入正确类别。
  2. 由于授权书内容基本固定,转换成图片后文件字节大小基本保持一致。按照文件字节大小汇总计算文件数量,选出数量最多的抽样查看,发觉都是授权书,根据这个特点归入正确类别。

3.2.3.调整识别规则 🔗

数据清洗是在已经有第三方分类结果的基础上筛选出缺少关键词的图片,这样可以把不属于该类的图片挑出来。但是在正式应用时所有图片都是没有标签的,太过简短的关键词可能引起冲突,比如“个人信息使用授权书”里含有“授权书”关键词,而“理赔申请”中也有,如果把“授权书”作为前者的特征关键词,就会增大把后者错分到前者的概率。所以在训练过程中,需要根据混淆矩阵看错分的结果对来进行调整,尽量在反复验证后避开冲突。具体调整的方式有以下两种。

  1. 调整顺序。比如出生证明上面也有“护照”这个词语,如果识别顺序上护照在前,那么可能会增大把出生证明错分到护照这个类的概率,因此出生证明这个类要在护照这个类的前面。
  2. 删除过于简短的词语。比如“化验”、“检验”在化验单这个类别下出现会是“化验单”、“检验报表”,但在医疗票据上也会有“化验费”、“检验费”,因此不能只用“化验”或“检验”作为 OCR 识别的关键词。

4.建模方法 🔗

32个理赔图像类别按照数据量级大小分为前17类、后14类,没有分类结果的“其它”也视作1类。其中门诊发票、住院发票、增值税发票等三类又要区分电子、非电子,因此发票相关实际上有6个子类,而身份证又分为正面、反面、双面等3个子类,所以前17类一共有22个子类。

本章节共分为七个小节,对应的是实践过程中的进行顺序,其中前四个小节是围绕前22个子类训练模型,后三个小节主要是应用方面的建模决策。

其一,在前四个小节的建模过程中,均严格遵照这四个步骤:①导入数据+切分-②构造特征-③训练模型-④评估效果。

具体进行的思路是:先构造一个基线模型,然后从数据(数据清洗、扩大样本量)、特征、算法等三方面入手,每次只在上一版基础上改动一个步骤的细节,通过对比前后两版的评估指标的变化来做决策。

其二,在后三个小节的建模过程中,对模型效果的评估更加详细,增加的步骤是:④详细评估-⑤阈值扫描-⑥OOT验证-⑦最终模型,具体进行都是基于个人的分析与判断。

4.1.二阶段分类 🔗

四个步骤 第一版基线模型(baseline) 第二版层级模型
①导入数据+切分 22个子类作为平行分类,每个类抽样1000张图片,测试集比例固定为0.2,固定随机种子 改为二阶段分类
②构造特征 从形状/边缘、颜色、纹理等三个方向构造传统图像特征,添加发票单独业务特征 不变
③训练模型 SVM 不变
④评估效果 各类的精确率、召回率,混淆矩阵,错误对等 不变

第一版基线模型主要是搭建了一个跑得通的基本框架,先从传统方法做图像分类来入手,每个类暂时只抽1000张。

第二版层级模型主要是在第一版的基础上改为二阶段分类,下面是分别人工看了每个类少量图片后,具体划定的7个大类。这里二阶段分类相当于训练5个多分类模型,分别是 L1、发票、病历、身份证、其他单据。

由于此时属于建模初期,不必详细对比各类别指标,只对比22子类的整体准确率,显然层级模型更优。

- 说明 准确率
第一版基线模型(22类) 所有子类为平行类 0.6399
第二版层级模型(22类) 二阶段分类 0.7764

4.2.构造特征 🔗

构造特征的方法一共有以下三种。

  1. 传统方法构造特征,从图片的形状、颜色、纹理入手。
  2. 深度学习模型抽取特征,本项目使用的是经典的 ResNet18。
  3. 根据个人对图片的认识和理解,构造业务特征。
  • 二维码检测
  • OCR 关键词
  • 字段逻辑,比如入院记录是有住院号但无住院日期。

构造特征的步骤总是要反复打磨,因此也调整过许多次。在数据清洗的过程中对图片类别特点的认知加深,不断调整 OCR 关键词的文本以及顺序。不过考虑到改特征细节要重新缓存,后期样本量大了以后调整特征的时间成本升高,所以一般会多积累发现的细节再一块改。也并不是特征数量越多越好,过多冗余的特征反而降低分类效果,因此也要做特征重要性选择。

4.3.选择分类器 🔗

第一版基线模型直接用了 SVM,后续尝试从 RBF-SVM / LinearSVC / MLP / XGBoost 等4种分类器中选择。

在样本量没扩大之前,所对比的分别是5个模型各自训练4种分类器,以及5个模型用不同分类器混搭。由于训练分类器费时较少,所以前几版本迭代的时候也对比多种分类器的结果,入围最终评选的是 SVM 和 XGBoost。

在样本量扩大后,SVM 受限于自身特点无法高效处理大量样本,因此最后选择的分类器就是 XGBoost。

4.4.扩大样本量 🔗

考虑到 SVM 本身的特点(注:样本量大了以后会越来越慢),直接用 XGBoost 来判断是否需要扩大样本量。对比每类样本量从500增加到8000时5个模型的评估指标,当样本量逐渐扩大时,整体效果确实有提升。

以下是5个多分类模型的整体准确率。需要注意的是,由于4类病历、6类其他单据都没得到有效的数据清洗,因此测试的指标相比实际会有些“虚高”。

规模 500 1000 2000 3000 4000 5000 6000 7000 8000
L1(7大类) 93.90% 95.19% 96.27% 96.44% 96.57% 96.71% 96.77% 97.13% 97.06%
发票(6小类) 87.22% 89.40% 91.65% 91.98% 92.64% 92.52% 92.29% 92.85% 92.73%
病历(4小类) 96.25% 96.63% 96.90% 97.08% 97.30% 97.02% 97.38% 97.48% 97.42%
身份证(3小类) 97.46% 98.28% 98.90% 99.11% 99.28% 99.31% 99.31% 99.21% 99.38%
其他单据(6小类) 89.17% 89.82% 90.77% 91.22% 91.80% 92.27% 92.65% 92.65% 93.08%

以下是三类电子发票的召回率。

叶子类 500 1000 2000 3000 4000 5000 6000 7000 8000
住院发票-电子 89.40% 91.20% 95.50% 96.20% 96.70% 96.60% 97.10% 97.40% 97.60%
门诊发票-电子 86.40% 88.50% 91.50% 93.20% 94.30% 93.90% 94.30% 95.70% 95.20%
增值税发票-电子 93.50% 95.30% 96.70% 96.80% 96.80% 97.30% 97.30% 98.00% 98.20%

综合考虑多方因素,其一,服务器上只有 CPU,更多的样本量也会增加调优的时间成本;其二,各类别样本量不均衡,有些类别凑不出更多的样本量;其三,单人建模团队,投入做数据清洗的人力十分有限;其四,尽可能追求 L1分类器、三类电子发票有更好的指标。尽管扩大样本量到8000仍然有收益提升,最终选定每类样本量为6000。

4.5.构造拒识样本 🔗

前面几步做完后,针对前22类的图片分类模型基本成型,即二阶段分类 + 深度学习融合业务特征 + XGBoost + 扩大样本量。同时,后14类的数据清洗也结束,由于这些类别样本数量较少、数据标签质量不够好(注:心电图数据标签质量不行导致建模效果不佳),建模型很容易过拟合,再加上图片特征相对更简单,直接用 OCR 识别,类别上去掉4个(存折、定额发票、意外证明、第三方理赔结算单)、增加2个(受益人身份确认表、税收居民身份声明),最后类别数量改为12。

那么接下来就是要抉择顺序问题,前22类的模型分类和后12类的规则识别,哪个前哪个后,或者同时?

首先不能把规则识别放前面,因为命中关键词的方式容易把前22类错分,无法保证前22类的图片里面就没有后12类的那些关键词。其次也不适合同时,因为其实模型也要提取OCR特征,模型和规则同时执行影响效率。

考虑到前22类更重要且数据量更大,而之前的建模数据仅限于前22类,可是在正式应用时类别数量扩张,为了让模型适应正式环境,最后选择的方案是,用后12类和其他类构造拒识样本,将原来模型的 L1分类器扩张为8类,即原7大类加上拒识类,因此整体流程应如下:

正式运行时,第一顺序跑模型分类,第二顺序跑 OCR 识别规则,前两者都无分类结果的,落入其它。

构造拒识样本的目的是能够让模型更好地区分出“其它”,因而组成部分有两部分:其一,从后12类抽取,包括被去掉的4个类的数据(注:这4类本来以后就要并入其它);其二,针对有第三方分类标签的原“其它”数据,先过一遍所有类的 OCR 关键词,尽可能剔除掉那些能被正确分类的样本,然后从剩下真正没有类别的数据中抽取。值得一提的是,由于拒识类的作用是让模型将后12类和真其它都判定为拒识,而真其它没有具体特征,所以为了保障后12类的分类效果,其在拒识样本中的比例超过一半。

4.6.选择阈值 🔗

整个二阶段分类的模型其实可以设置三层阈值:

  1. 在 L1分类器设置全局阈值,低于此值就落入拒识类;
  2. 在发票、病历、身份证、其他单据等四个分类器设置全局阈值,低于此值就落入拒识类;
  3. 对每个子类设置阈值,低于此值就落入拒识类。

如果对 L1分类器设置全局阈值,这里是第一层,真拒识样本被误分进7类后续还可以被拒绝,但被误拒的真7类后续就捞不回来。对剩下四个分类器设置阈值,也是这个道理。结合时间外验证的效果,对比设阈值和不设阈值的差异,最后前两层都没有设阈值。

而对子类如三类电子发票单独设阈值,可以按照每0.02一个区间详细测算,结合用户的目标倾向(即更倾向召回率还是精确率)选择效果最好的阈值。

4.7.时间外验证及兜底规则 🔗

训练模型时将样本拆分训练集和测试集,是为了测试模型训练的效果。通常对比训练集和测试集的指标,能发现模型在训练过程中是否过拟合。但还需要用超过训练集、测试集的样本时间外,且与前者完全没有任何时间交集的新样本,来做时间外验证。如果训练好的模型在新的验证集上也没有过拟合,才可判断为泛化能力稳定。

又及,正式的图片分类流程是先过模型再过规则,其实时间外验证不仅仅可用于判断有无过拟合,还可以根据错误对的情况进一步分析,把规则扩充成更细致的兜底规则,尽可能纠正分类结果。兜底规则在验证阶段经历过以下多次调整。

  1. 调整后12类的顺序,主要是解决一些多种图片类别含有同一关键词的问题,比如出生证明先于护照、死亡证明先于户口本、手术记录先于心电图等。

  2. 用规则捞回前22类的关键词相比构造特征要更严格,因为构造特征时关键词只是用于区分7大类或各叶子类的几个小类,关键词之间几乎没有互斥,而这一环节里不清楚真其它都有些什么,所以只有更严格才能更准确捞回7类。比如要捞回发票,构造特征时不用每个特征都命中,但这个环节需要每组业务特征都命中才算。

  3. 在人工梳理各类别图片关键词的阶段,发现有社会保障卡、离婚证、光荣证、使用证、安葬证等具有确切特点的图片,因此识别的关键词中也可以包含这些,只要命中就落入其它。

  4. 由于使用的 OCR 模型只是微型模型,出现了把授权书分到理赔申请的问题,因此对所有模型判定为理赔申请的增加二次复核,若命中授权书的专属长条款关键词,则改判为授权书。

5.验证分析与下一步优化 🔗

由于建模阶段对病历(4类)、其他理赔票据(6类)、手术记录、心电图等类别的数据清洗十分有限,因此在时间外验证结束后,交付用户使用新一批样本测试模型的真实效果。在新的测试阶段,用户对比了第三方和自建模型的分类结果,针对两种结果有差异的部分(注,约占14%),全部使用人工确认真实标签。最后用人工确认标签作为最接近真实的标签来计算评估指标,以此来验证分析自建模型与第三方技术的具体差距。

5.1.三次数据验证 🔗

在同一批新样本上进行了三次验证。

5.1.1.第一次验证 🔗

第一次验证时,按照保障各类别精确率为倾向确定阈值,以下表格中命名为模型1。

这是全部类别的整体准确率。

类别 总体样本数 正确分类数 整体准确率
模型1 137495 125357 91.17%
第三方 137495 128288 93.30%

这是6类发票的汇总数,其中 TP 代表实际为真、预测为正,FP 代表实际为假、预测为正,TN 代表实际为真、预测为负。

子类类别 第三方-TP 第三方-FP 第三方-TN 模型1-TP 模型1-FP 模型1-TN
门诊发票-电子 32108 56 585 30734 547 1959
门诊发票-非电子 1496 477 87 1393 417 190
增值税发票-电子 5600 62 87 5222 63 465
增值税发票-非电子 0 4 10 0 0 10
住院发票-电子 3559 32 100 3221 111 438
住院发票-非电子 322 190 9 257 26 74

依据这些数据进一步计算,预测为正的精确率是$\frac{TP}{TP + FP}$,实际为正的召回率是$\frac{TP}{TP + TN}$。计算得到的三类电子发票精确率、召回率如下。

子类类别 精确率(第三方) 精确率(模型1) 召回率(第三方) 召回率(模型1)
门诊发票-电子 99.83% 98.25% 98.21% 94.01%
住院发票-电子 99.11% 96.67% 97.27% 88.03%
增值税发票-电子 98.90% 98.81% 98.47% 91.82%

5.1.2.第二次验证 🔗

第二次验证时,按照保障电子发票的召回率为倾向确定阈值,尽量让电子发票召回率与第三方对齐,可以降低精确率为代价,以下命名为模型2。

这是全部类别的整体准确率。

类别 总体样本数 正确分类数 整体准确率
模型2 137495 125422 91.21%
第三方 137495 128288 93.30%

计算得到,模型改变倾向后的三类电子发票精确率、召回率如下。

子类类别 模型2-TP 模型2-FP 模型2-TN 精确率(模型2) 召回率(模型2)
门诊发票-电子 31144 852 1549 97.34% 95.26%
住院发票-电子 3459 657 200 84.04% 94.53%
增值税发票-电子 5405 280 282 95.07% 95.04%

5.1.3.第三次验证 🔗

第三次验证,是在模型2的基础上增加是否二维码的判断(运行效率从每分钟200张下降到每张10秒、每分钟6张)。如果模型分类为非电子发票但是否二维码判定为是,那么也分为电子发票。

关于电子发票新的对比结果如下,其中模型、第三方的TP、FP、TN是先分别汇总三类电子发票的TP、FP、TN然后求和,而二维码只判断是否电子发票不区分子类,因此前者将三类之间互混视作判错而后者判对。

不同情况 TP FP TN 精确率 召回率
仅模型1 39177 721 2862 98.19% 93.19%
仅模型2 40008 1789 2031 95.72% 95.17%
仅二维码 36970 5688 5069 86.67% 87.94%
模型2+二维码 41726 6605 313 86.33% 99.26%
第三方 41267 150 772 99.63% 98.16%

5.2.结果分析 🔗

  1. 模型内二维码检测率 VS. 模型外二维码检测率

实际上,在模型内部也要提取二维码作特征,但建模时为顾及检测效率使得二维码检测率(召回率)仅维持在40%左右的水平,这样在特征重要性的筛选步骤里,未被选入对电子发票有用的特征,那么二维码特征其实在模型内基本没起作用。以下是模型内的二维码检测率数据。

子类类别 二维码检测率 条形码检测率
门诊发票-电子 37.7% 0.0%
住院发票-电子 36.5% 0.0%
增值税发票-电子 42.6% 0.0%
门诊发票-非电子 10.5% 14.0%
住院发票-非电子 16.0% 40.0%

其一,非电子发票里也能检测出二维码,说明历史数据标签的质量还不够好,可能存在不少非电子混入电子的情况,需要继续做数据清洗。

其二,观察第三次验证的结果,对比“仅模型2”和“模型2+二维码”,召回率从95.17%提升到了99.26%,而精确率从95.72%下降到了86.33%,说明提高二维码检测率可以提高电子发票的召回率,但把二维码检测单独放在模型外会牺牲精确率,这是因为模型内是几百个特征共同起作用,而模型外一个特征起作用,就会把很多有二维码但不是电子发票的也判为电子发票。

  1. 电子发票三类互混判错 VS. 判对

仅改变汇总统计的方式,把三类电子发票互混都判为结果正确,那么“仅模型2”的精确率从95.72%提升到97.14%、召回率从95.17%提升到96.58%,其实反过来可以说明三类电子发票的历史数据标签还存在互混的情况,需要进一步做数据清洗。

- 计算方式 TP FP TN 精确率 召回率
仅模型2 三类互混判错 40008 1789 2031 95.72% 95.17%
仅模型2 三类互混判对 40600 1197 1439 97.14% 96.58%
第三方 三类互混判错 41267 150 772 99.63% 98.16%
第三方 三类互混判对 41359 58 680 99.86% 98.38%

5.3.下一步优化 🔗

下一步优化从数据、特征、算法三个方面展开。

在数据方面:一是使用用户人工确认过标签的数据建模,看看更高质量的数据标签能带来多少收益;二是人工确认的数据量有限,依然存在类别数量不均衡的问题,好些类仅用人工确认数据无法完成从训练到时间外验证的需求,依然需要对历史数据做清洗,挖掘更多可使用的历史数据;三是能否将对数据质量的控制往前移,比如在客户上传图片时,较模糊的图片会上传失败需要重传。

在特征方面:一是尽量在当前硬件条件下,解决二维码检测的效率与准确率的两难问题4,增加模型内二维码特征的作用;二是当前建模使用微型飞桨(Paddle)模型提取图片中的文本,且参数上还有限制,还可继续尝试减少限制能否提高分类效果;三是当前使用 OCR 识别的关键词仅源于个人少量观察经验,还要进一步对没能正确分类的图片多分析看看,尽可能完善 OCR 识别的规则;四是当前深度学习提取特征的模型是 ResNet18,还可以尝试换其他模型,或者结合内部数据微调。

在算法方面:一是调整层级模型,可以改变当前的二阶段分类,由于判定是否电子发票比判定是哪类电子发票更容易,可以尝试将第二阶段的发票模型再扩充,先判断是否电子发票再判断具体子类,或者如果解决了二维码检测的两难问题,将二阶段改成三阶段,一阶段判断是否电子发票、二阶段判断图片大类、三阶段判断图片子类;二是确定阈值时与用户详细沟通选择的倾向,不仅是电子发票,还有其他分类后会调第三方接口的类别。

附录 🔗

附录 A 🔗

以下是用于构造特征的类别及关键词。

"结算单":   ["结算单"],
"清单":     ["清单", "祥单", "费用明细"],   # 已删裸词"明细": 发票有"项目明细"
"化验单":   ["化验单", "化验报告", "检验报告", "检验结果", "检验科", "临床检验"],
"报告单":   ["报告单", "检查报告", "影像", "放射", "病理", "超声", "CT", "核磁", "内镜"],
"处方签":   ["处方签", "处方"],
"诊断证明": ["诊断证明", "诊断书", "疾病证明", "诊断报告"],
"病案首页":   ["病案首页"],
"入院记录":   ["入院记录", "住院记录", "入院证", "出院日期"],
"出院记录":   ["出院记录", "出院证", "出院指导", "出院小结", "出院医嘱", "出院介绍信"],
"门诊病历":   ["门诊病历", "门(急)诊病历", "门(急)诊病历", "门急诊病历",
              "初诊综合病历", "复诊综合病历", "门诊复诊病历", "门诊暴露病历",
              "门诊电子病历", "门诊(复诊)病历", "门诊号", "病历内容",
              "急诊医生"],  
"银行卡":   ["银行", "银联", "UnionPay", "Bank", "ATM", "借记", "一卡通", "储蓄"],
"授权书":   ["个人信息使用授权书",
            "xxxx的第三方合作机构及必要合作伙伴",
            "我们如何收集、加工和使用您们的个人信息",
            "我们如何传输、提供、委托处理、公开您们的个人信息",
            "六、您们的权利", "信息共享授权条款"],
"理赔申请": ["理赔申请书", "理赔申请"],

以下是用于只做 OCR 识别的类别及关键词。

"出生证明":   ["出生医学证明", "出生证", "生医学证明", "出生孕周", "出生体重", "出生身长"],
"死亡证明":   ["户籍注销证明", "户口注销证明", "户口注销", "户籍注销",
              "注销户口证明", "注销户口", "遗体火化证明", "遗体火化", "火化证明",
              "火化证", "死亡证明", "死亡推断", "火化时间", "火化日期",
              "死亡调查记录", "死亡调查", "死亡医学证明", "死亡记录",
              "死亡注销证明", "死亡注销", "死亡原因"],
"手术记录":   ["手术记录", "手术及术后", "手术经过", "手术名称", "手术方式",
              "手术时间", "手术日期"],
"受益人身份确认表": ["受益人身份确认表"],
"结婚证":     ["结婚证", "结婚登记", "婚姻法", "婚结", "结婚", "婚姻登记"],
"伤残鉴定":   ["伤残鉴定", "司法鉴定", "伤残等级", "劳动能力鉴定", "伤残评估"],
"工伤证明":   ["工伤认定", "工伤证明", "工伤鉴定", "工伤职工"],  
"事故证明":   ["事故认定书", "当事人导致交通事故的过错及责任", "交通警察(签名)",
              "事故证明", "事故情况说明", "事故经过", "道路交通事故", "事故认定",
              "事故说明", "受伤经过", "外伤情况说明"],
"护照":       ["护照", "PASSPORT", "出入境"],
"户口本":     ["人口登记", "户主", "家庭户", "常住人口登记", "户别", "户号",
              "户口登记", "户口簿", "户口", "常住", "人口"],
"税收居民身份声明": ["税收居民身份声明"],
"心电图":     ["心电图报告", "窦性心律", "窦性心动", "房性心动", "房性心律",
              "最快心率", "最慢心率", "十二导联", "导联"],   

附录 B 🔗

以下是 AI 生成的大致流程图。

后记 🔗

这篇博客来源于前几个月的一项工作,起初我只是以记录工作日志的方式写下一些步骤和细节,后来把文档发给几个同事去看,有一位认真阅读后向我提出了一些问题并给出评价——“缺乏整体性”,且表达他理想中应如下。

首先说明下为什么做这个事情,整体工作流程与重要节点,然后说明各节点要做的什么,再具体讲下是如何做的,以及为什么要这么做。

“记录”和“汇报”确实是两个不同的方向,我后来想明白还是应该要用能让别人看得懂(至少看得下去)的方式来写,于是借了论文体裁来调整文章结构。但我毕竟是要写博客,不是写论文,所以行文风格还是偏口语。


  1. 这里作者想到一个更好理解的示例,以教学生读书然后让其去参加考试为例:1.如果只教什么是对的,而不教什么是错的,那么学生遇到错题将会无法辨别并改正;2.如果学生学习时,类似的题目、同一个解题方法,老师有时说是对的,有时又说是错的,这样学生很容易搞混,更难辨别什么是对、什么是错;3.如果学生将小学基础知识学得很好,但是考试遇到了初中那些根本没学过的超纲题,那么大概率答不出来。 ↩︎

  2. 注意,本节提到的两种倾向并不是零和博弈,不代表选了一个指标作为倾向,另一个指标就不管。而是在数据标签质量有限、硬件资源受限、提取的特征有限的情况下,优先选择保障提高其中之一。如果现实中没有任何限制,甚至用户上传理赔图像的时候加了硬性约束,清晰度不够的重传,处于非常理想的情况下,就不用选了。 ↩︎

  3. 定阈值的方式也可更加灵活,比如按十分位数赋予1-10个等级,或者定两个阈值划分成高中低三个等级,具体怎么操作看后续如何应用。 ↩︎

  4. 原先提取二维码特征仅使用了Pyzbar和OpenCV,二维码检出率仅40%左右,后来加上zxing,二维码检出率提高到80%左右。在检测效率方面,由于没有二维码的图片在检测时会多次调用zxing而耗时较多,对比限制最多调用次数和调用时间的方案,发现最多调用2次时能达到检测效率与准确率的双全。 ↩︎

R