🔎大家好,我是Sonhhxg_柒,希望你看完之后,能对你有所帮助,不足请指正!共同学习交流🔎
📝个人主页-Sonhhxg_柒的博客_CSDN博客 📃
🎁欢迎各位→点赞👍 + 收藏⭐️ + 留言📝
📣系列专栏 - 机器学习【ML】 自然语言处理【NLP】 深度学习【DL】
🖍foreword
✔说明⇢本人讲解主要包括Python、机器学习(ML)、深度学习(DL)、自然语言处理(NLP)等内容。
如果你对这个系列感兴趣的话,可以关注订阅哟👋
文章目录
到目前为止,我们已经处理了存储为文本数据的写作。但是,大部分写入的数据都存储为图像。要使用这些数据,我们需要将其转换为文本。这与我们的其他 NLP 问题不同。在这个问题上,我们的语言学知识不会那么有用。这与阅读不同。这只是字符识别。与说或听演讲相比,这是一种不那么有意的活动。幸运的是,书写系统往往是易于区分的字符,尤其是在印刷品中。这意味着图像识别技术应该适用于印刷文本的图像。
对象字符识别 (OCR)是获取书面语言(带有字符)的图像并将其转换为文本数据的任务。现代解决方案是基于神经网络的,本质上是将图像的各个部分分类为包含字符。然后将这些分类映射到文本数据中的一个字符或字符串。
让我们谈谈一些可能的输入。
OCR 任务的种类
有几种 OCR 任务。这些任务的不同之处在于输入的图像类型、图像中的文字类型以及模型的目标是什么。
打印文本和 PDF 到文本的图像
不幸的是,有许多系统将其文档导出为图像。有些会导出为 PDF,但由于可以通过多种方式将文档编码为 PDF,因此 PDF 可能并不比图像好。好消息是,在 PDF 中,字符的表示非常一致(字体和大小差异除外),背景对比度高。将这样的文档转换为文本数据是最简单的 OCR 任务。
如果图像实际上是文档的扫描,这可能会很复杂,这可能会引入以下错误:
打印错误 :打印机的打印头很脏,并在文本中产生斑点或留下线条。
纸张问题 :纸张陈旧、染色或有折痕。这会降低对比度并弄脏或扭曲图像的某些部分。
扫描问题 :纸张歪斜,这意味着文本不在行中。
手写文本到文本的图像
这种情况仍然有高对比度的背景,但字符的一致性要差得多。此外,如果文档有边注,则文本不成行的问题可能会更加困难。来自 MNIST 数据库的陈旧的手写数字数据集就是此任务的一个示例。
你需要一些方法来约束这个问题。例如,MNIST 数据集仅限于 10 个字符。一些电子笔软件通过学习一个人的笔迹来限制问题。试图为每个人的笔迹解决这个问题会更加困难。书写风格多种多样,以至于人类无法阅读陌生人的笔迹的情况并不少见。该模型在识别美国内战信件中所学到的知识对于识别医生的笔记或解析签名将毫无用处。
环境中的文本图像到文本
环境中文本图像的一个示例是识别街道图片中的标志所说的内容。通常,会打印此类文本,但字体和大小可能会有很大差异。也可能存在类似于我们扫描示例中的问题的失真。这种类型的图像中的倾斜文本问题比扫描时更困难。扫描时,一维是固定的,因此可以假设纸张平放在扫描床上。在环境中,文本可以以任何方式旋转。例如,如果您正在为自动驾驶汽车构建 OCR 模型,则某些文本将位于汽车的右侧,而某些文本将位于汽车上方。路上也会有文字。这意味着字母的形状将不一致。
这个问题也经常受到限制。在大多数司法管辖区,都有一些关于标牌的规定。例如,重要的说明是有限的并被发布。这意味着您无需将图像转换为文本,而是可以识别特定的标志。这将问题从 OCR 更改为对象识别。即使您想识别位置标志(例如地址和城镇名称),标志通常也会打印在特定的颜色上。这意味着您的模型可以分阶段学习。
Is this part of an image...
- a sign
- if so, is it a) instructions, or b) a place of interest
- if a) classify it
- if b) convert to text
目标文本图像
在某些情况下,我们可能想完全跳过文本。如果我们对扫描的文档进行分类,我们可以通过两种方式进行。首先,我们可以转换为文本,然后使用我们的文本分类技术。其次,我们可以简单地直接对图像进行分类。有几个权衡。
-
图像到文本到目标
- 优点:我们可以检查中间文本以识别问题
- 优点:我们可以分别重用图像到文本和文本到目标模型(如果一些输入是文本而一些是图像,则特别有价值)
- 缺点:当转换为文本时,我们可能会丢失图像中可以帮助我们分类的特征——例如,如果信头中有一张图像可以给我们一个很好的信号
-
要定位的图像
- 优点:这更简单——无需开发和组合两个单独的模型
- Pro:附加功能,如前所述
- 缺点:更难调试问题,如前所述
- 缺点:只能在类似的图像到目标问题上重用
最好从两部分方法开始,因为这可以让您探索您的数据。今天的大多数图像到文本模型都是神经网络,因此在项目后期添加一些层和重新训练应该不会太困难。
关于不同书写系统的注意事项
任务的难度与使用的书写系统有很大关系。如果你还记得,在第 2 章中我们定义了不同的书写系统系列。语标系统很困难,因为可能的字符数量要多得多。这很困难,原因有两个。首先,显而易见的原因是要预测的类别更多,因此参数也更多。其次,语标系统将有许多外观相似的字符,因为所有字符都是小盒子中的点、线和曲线。还有其他复杂性使书写系统变得困难。在印刷英语中,每个字符都有一个形式,但在草书英语中,字符最多可以有四种形式——孤立的、声母的、中间的和韵母。一些书写系统甚至在印刷文本中也有多种形式——例如,阿拉伯语。此外,如果一个书写系统大量使用变音符号,它会加剧污迹和歪斜等问题(见图 16-1)。对于大多数 abugidas,你会想要警惕这一点——例如,梵文和一些字母,如波兰语和越南语。
图 16-1。用 Ge'ez 写的阿姆哈拉语“Maksannyo”[星期二]
问题陈述和约束
在我们的示例中,我们将实现一个用于将图像转换为文本的 ETL 管道。该工具的用途非常普遍。此类工具的一个常见用途是将遗留系统中的文本图像转换为文本数据。我们的示例将使用(假)电子病历。我们将使用 Google 的 Tesseract。我们将使用 Spark 分散工作负载,以便我们可以并行处理。我们还将使用预训练的管道在存储文本之前对其进行处理。
- 我们试图解决的问题是什么?
我们将构建一个脚本,将图像转换为文本、处理文本并最终存储它。我们将分离功能,以便我们可以在未来增加或改进这些步骤。
- 有哪些限制条件?
我们将只使用印刷英文文本的图像。文档将只有一列文本。在我们的虚构场景中,我们也知道内容将与医学相关,但这不会影响此实现
- 我们如何解决约束问题?
我们想要一种可重复的方式来将图像转换为文本、处理文本并存储它。
计划项目
解决方案相对简单。首先,我们将编写一个脚本,允许我们将数据传递给 Tesseract,而不仅仅是文件。然后我们将编写一个 Python 脚本,该脚本将使用第一个脚本获取文本,然后使用 Spark NLP 处理文本。
实施解决方案
让我们从一个使用 Tesseract 的例子开始。让我们看一下程序的使用输出。
! tesseract -h
Usage:
tesseract --help | --help-extra | --version
tesseract --list-langs
tesseract imagename outputbase [options...] [configfile...]
OCR options:
-l LANG[+LANG] Specify language(s) used for OCR.
NOTE: These options must occur before any configfile.
Single options:
--help Show this help message.
--help-extra Show extra help for advanced users.
--version Show version information.
--list-langs List available languages for tesseract engine.
看起来我们只需要传递一个图像imagename
,和输出名称,outputbase
。让我们看一下图像中的文本。
CHIEF COMPLAINT
Ankle pain
HISTORY OF PRESENT ILLNESS:
The patient is 28 y/o man who tripped when hiking. He struggled back to his car, and immediately came in. Due to his severe ankle pain, he
thought the right ankle may be broken.
EXAMINATION:
An x-ray of right ankle ruled out fracture.
IMPRESSION:
The right ankle is sprained.
RECOMMENDATION:
- Take ibuprofen as needed
- Try to stay off right ankle for one week
让我们看看我们将要试验的图像(见图 16-2)。
现在,让我们尝试通过 Tesseract 传递图像
!esseract EHR\ example.PNG EHR_example
图 16-2。文本的 EHR 图像
现在让我们看看 Tesseract 提取了什么。
! cat EHR_example.txt
CHIEF COMPLAINT
Ankle pain
HISTORY OF PRESENT ILLNESS:
The patient is 28 y/o man who tripped when hiking. He struggled back to his car, and immediately came in. Due to his severe ankle pain, he
thought the right ankle may be broken.
EXAMINATION:
An x-ray of right ankle ruled out fracture.
IMPRESSION:
The right ankle is sprained.
RECOMMENDATION:
- Take ibuprofen as needed
- Try to stay off right ankle for one week
这非常有效。现在,让我们把我们的转换脚本放在一起。脚本的输入将是图像的类型,然后实际图像将被编码为 base64 字符串。我们创建一个临时图像文件并使用 Tesseract 提取文本。这也将创建一个临时文本文件,我们将流式传输到stdout
. 我们需要用特殊字符“~”替换新行,这样我们就可以知道哪些行来自哪个输入。
%%writefile img2txt.sh
#!/bin/bash
set -e
# 假设输入是 "image-type base64-encoded-image-data"
type=$1
data=$2
file="img.$type"
echo $data | base64 -d > $file
tesseract $file text
cat text.txt | tr '\n' '~'
让我们试试我们的脚本。
! ! ./img2txt.sh "png" $(base64 EHR\ example.PNG |\
tr -d '\n') |\
tr '~' '\n'
Tesseract Open Source OCR Engine v4.0.0-beta.1 with Leptonica
CHIEF COMPLAINT
Ankle pain
HISTORY OF PRESENT ILLNESS:
The patient is 28 y/o man who tripped when hiking. He struggled back to his car, and immediately came in. Due to his severe ankle pain, he
thought the right ankle may be broken.
EXAMINATION:
An x-ray of right ankle ruled out fracture.
IMPRESSION:
The right ankle is sprained.
RECOMMENDATION:
- Take ibuprofen when needed
- Try to stay off right ankle for one week
现在让我们处理完整的处理代码。首先,我们将获得一个预训练的管道。
import base64
import os
import sparknlp
from sparknlp.pretrained import PretrainedPipeline
spark = sparknlp.start()
pipeline = PretrainedPipeline('explain_document_ml')
explain_document_ml download started this may take some time. Approx size to download 9.4 MB [OK!]
现在让我们创建我们的测试输入数据。我们将把我们的图像复制一百次到EHRs
文件夹中。
! mkdir EHRs
for i in range(100):
! cp EHR\ example.PNG EHRs/EHR{i}.PNG
现在,我们将创建一个DataFrame
包含文件路径、图像类型和图像数据的三个字符串字段。
data = []
for file in os.listdir('EHRs') :
file = os.path.join('EHRs', file)
with open(file, 'rb') as image:
f = image.read()
b = bytearray(f)
image_b64 = base64.b64encode(b).decode('utf-8')
extension = os.path.splitext(file)[1][1:]
record = (file, extension, image_b64)
data.append(record)
data = spark.createDataFrame(data, ['file', 'type', 'image'])\
.repartition(4)
让我们定义一个函数,它将数据分区作为可迭代对象,并返回文件路径和文本的生成器。
def process_partition(partition):
for file, extension, image_b64 in partition:
text = sub.check_output(['./img2txt.sh', extension, image_b64])\
.decode('utf-8')
text.replace('~', '\n')
yield (file, text)
post_ocr = data.rdd.mapPartitions(process_partition)
post_ocr = spark.createDataFrame(post_ocr, ['file', 'text'])
processed = pipeline.transform(post_ocr)
processed.write.mode('overwrite').parquet('example_output.parquet/')
现在让我们把它放到一个脚本中。
%%writefile process_image_dir.py
#!/bin/python
import base64
import os
import subprocess as sub
import sys
import sparknlp
from sparknlp.pretrained import PretrainedPipeline
def process_partition(partition):
for file, extension, image_b64 in partition:
text = sub.check_output(['./img2txt.sh', extension, image_b64])\
.decode('utf-8')
text.replace('~', '\n')
yield (file, text)
if __name__ == '__main__':
spark = sparknlp.start()
pipeline = PretrainedPipeline('explain_document_ml')
data_dir = sys.argv[1]
output_file = sys.argv[2]
data = []
for file in os.listdir(data_dir) :
file = os.path.join(data_dir, file)
with open(file, 'rb') as image:
f = image.read()
b = bytearray(f)
image_b64 = base64.b64encode(b).decode('utf-8')
extension = os.path.splitext(file)[1][1:]
record = (file, extension, image_b64)
data.append(record)
data = spark.createDataFrame(data, ['file', 'type', 'image'])\
.repartition(4)
post_ocr = data.rdd.map(tuple).mapPartitions(process_partition)
post_ocr = spark.createDataFrame(post_ocr, ['file', 'text'])
processed = pipeline.transform(post_ocr)
processed.write.mode('overwrite').parquet(output_file)
现在我们有一个脚本,它将获取一个图像目录,它将生成一个从图像中提取的文本文件目录。
! python process_image_dir.py EHRs ehr.parquet
Ivy Default Cache set to: /home/alex/.ivy2/cache
The jars for the packages stored in: /home/alex/.ivy2/jars
:: loading settings :: url = jar:file:/home/alex/anaconda3/envs/...
JohnSnowLabs#spark-nlp added as a dependency
:: resolving dependencies :: org.apache.spark#spark-submit-parent...
confs: [default]
found JohnSnowLabs#spark-nlp;2.2.2 in spark-packages
...
Tesseract Open Source OCR Engine v4.0.0-beta.1 with Leptonica
Tesseract Open Source OCR Engine v4.0.0-beta.1 with Leptonica
Tesseract Open Source OCR Engine v4.0.0-beta.1 with Leptonica
Tesseract Open Source OCR Engine v4.0.0-beta.1 with Leptonica
Tesseract Open Source OCR Engine v4.0.0-beta.1 with Leptonica
Tesseract Open Source OCR Engine v4.0.0-beta.1 with Leptonica
Tesseract Open Source OCR Engine v4.0.0-beta.1 with Leptonica
Tesseract Open Source OCR Engine v4.0.0-beta.1 with Leptonica
以模型为中心的指标
我们可以通过字符和单词的准确性来衡量 OCR 模型的准确性。您可以通过计算预期文本和观察文本之间的 Levenshtein 距离然后除以文本大小来测量此字符错误率。
除了监控实际的模型错误率之外,您还可以捕获有关输出的统计信息。例如,监控单词的分布可以潜在地诊断问题。
审查
当您构建内部服务时,就像 OCR 工具一样,您将希望与需要它的团队一起审查工作。最终,您的应用程序的成功需要您的用户对技术正确性和可用支持感到满意。在某些组织中,尤其是较大的组织中,使用内部工具可能会有很大的压力。如果这些工具设计不当、文档不足或不受支持,其他团队将理所当然地尝试避免使用它们。这可能会产生不好的感觉,并导致重复工作和团队孤立。这就是为什么审查内部产品并尽早并经常寻求和接受反馈是一个好主意的原因。
结论
在本章中,我们研究了一个 NLP 应用程序,它不专注于从非结构化数据中提取结构化数据,而是专注于从一种类型的数据转换为另一种类型的数据。尽管这只是与语言学无关,但在实践中却非常重要。如果您正在构建一个使用来自历史悠久的行业的数据的应用程序,那么您很可能必须将图像转换为文本。
在本书的这一部分,我们讨论了构建简单的应用程序,这些应用程序应用了我们在第二部分中学到的一些技术。我们还讨论了可以帮助您成功构建 NLP 应用程序的特定和一般开发实践。回顾之前关于 Spark NLP 的观点,这个库的一个核心哲学原则是没有万能的。您将需要了解您的数据,并知道如何构建您的 NLP 应用程序。在下一部分中,我们将讨论部署应用程序的一些更通用的技巧和策略。