3

我想知道在 Perl/MySQL 中是否可以根据给定的单词构建一个变体单词列表,该单词可能会发生常见的 OCR 错误(即 8 而不是 b)?换句话说,如果我有一个单词列表,并且该列表中有单词“Alphabet”,那么有没有办法扩展或构建一个新列表以包含我的原始单词以及“Alphabet”的 OCR 错误变体?所以在我的输出中,我可能对 Alphabet 有以下变体:

Alphabet
A1phabet
Alpha8et
A1pha8et

当然,为 OCR 文本中出现的大多数(如果不是全部)常见错误编码将很有用。比如 8 代替 b,或 1 代替 l。我不想修复错误,因为在我的数据本身中我可能有 OCR 错误,但想根据我作为输入提供的单词列表创建一个单词的变体列表作为我的输出。所以在我的数据中,我可能有 Alpha8et,但如果我对 Alphabet 进行简单搜索,它不会发现这个明显的错误。

我快速而肮脏的 MySQL 方法

Select * from   
(SELECT Word
FROM words
union all
-- Rule 1 (8 instead of b)
SELECT 
case
    when Word regexp 'b|B' = 1 
        then replace(replace(Word, 'B','8'),'b','8')
    end as Word
FROM words
union all
-- Rule 2 (1 instead of l)
SELECT 
case
    when Word regexp 'l|L' = 1 
        then replace(replace(Word, 'L','1'),'l','1')
    end as Word
FROM words) qry
where qry.Word is not null
order by qry.Word;

我认为必须有一种更自动化和更清洁的方法

4

2 回答 2

0

实现此目的的一种有效方法是使用bitap 算法。Perl 有re::engine::TRE,一个与libtre的绑定,它实现了正则表达式中的模糊字符串匹配:

use strict;
use warnings qw(all);
use re::engine::TRE max_cost => 1;

# match "Perl"
if ("A pearl is a hard object produced..." =~ /\(Perl\)/i) {
    say $1; # find "pearl"
}

另外,还有一个agrep工具,它允许您从命令行使用 libtre:

$ agrep -i -E 1 peArl *
fork.pl:#!/usr/bin/env perl
geo.pl:#!/usr/bin/env perl
leak.pl:#!/usr/local/bin/perl

当您需要将多个单词与 OCR 化文本进行匹配时,有两种不同的方法。

如果足够小,您可以简单地使用整个字典构建一个正则表达式:

/(Arakanese|Nelumbium|additionary|archarios|corbeil|golee|layer|reinstill\)/

可以通过构建三元索引来优化大型字典查询。Perl 有一个String::Trigram用于在内存中执行此操作。一些 RDBMS 也有 trigram 索引扩展。PostgreSQL 风格的pg_trgm允许您编写这样的查询,即使对于非常大的字典也足够快:

SELECT DISTINCT street, similarity(street, word)
    FROM address_street
    JOIN (
        SELECT UNNEST(ARRAY['higienopolis','lapa','morumbi']) AS word
    ) AS t0 ON street % word;

(这个在一个有~150K行的表上花了~70ms)

于 2012-12-11T16:58:00.727 回答
0

如果您有扫描文本的示例,其中包含扫描(原始)版本和更正版本,则生成字符更正列表应该相对简单。从足够多的文本中收集这些数据,然后按频率对其进行排序。决定一个更正必须有多频繁才能成为“常见”,然后在列表中只留下常见的更正。

将列表变成以正确字母为键的地图;该值是该字母的常见错误扫描数组。使用递归函数获取一个单词并生成其所有变体。

这个例子,在 Ruby 中,展示了递归函数。收集可能的错误扫描取决于您:

VARIATIONS = {
  'l' => ['1'],
  'b' => ['8'],
}

def variations(word)
  return [''] if word.empty?
  first_character = word[0..0]
  remainder = word[1..-1]
  possible_first_characters =
    [first_character] | VARIATIONS.fetch(first_character, [])
  possible_remainders = variations(remainder)
  possible_first_characters.product(possible_remainders).map(&:join)
end

p variations('Alphabet')
# => ["Alphabet", "Alpha8et", "A1phabet", "A1pha8et"]

原始单词包含在变体列表中。如果您想要可能的错误扫描,请删除原始单词:

def misscans(word)
  variations(word) - [word]
end

p misscans('Alphabet') 
# => ["Alpha8et", "A1phabet", "A1pha8et"]

一个快速而肮脏(且未经测试)的命令行程序版本会将上述函数与这个“主”函数结合起来:

input_path, output_path = ARGV
File.open(input_path, 'r') do |infile|
  File.open(output_path, 'w') do |outfile|
    while word = infile.gets
      outfile.puts misscans(word)  
    end
  end
end
于 2012-12-11T17:15:37.510 回答