欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Lingpipe中的spell模块-拼写纠错

程序员文章站 2022-03-12 23:40:18
...
基本模型
基本技术工作如下:搜索引擎提供可使用的稳定被用来索引和被训练成为一种语言模型。这种语言模型存储的是短语和短语统计的特征信息。当提交一个查询时,
类src/QuerySpellCheck.java 在模型中寻找与之匹配的字符编辑操作,诸如字符截取,插入,替换,转换和删除等等,这样使查询更好的适用于语言模型。如果
你提交一个查询"Gretski",模型中的数据来源为rec.sport.hockey,这个模型将会把'Gretzky' 推荐出来。

领域相关性
基于字典的拼写校正方案的最大优势是校正结果依赖于搜索索引数据。因此查询词"trt"在法律领域别校正为"tort",在厨餐领域被校正为"tart",在生物信息学
领域被校正为"TRt"。但是在Goole中,没有被纠正的推荐词,因为web域名"trt.com"分别是Thessaly Radio Television 和 Turkiye Radyo Televizyon的缩略形式。

上下文相关校正
Yahoo和Google采用的是上下文相关的校正。例如,查询"frod"(来自于德语的老英文单词,意思是智慧的、有经验的)被建议校正为"ford"(一家汽车公司);然而
查询词"frod baggins"被纠正为"frodo baggins"(一个20世纪编造的虚词)。以上是雅虎的校正结果。Google并不校正"frod baggins",页面返回有大约785条结果
满足它,而有820000条结果满足"frodo baggins"。而且Google也不校正"frdo and frdo baggins"。Amazon表现相似,但Bing纠正frd baggins为ford baggins比
frodo baggins要多。
Lingpipe的模型精确的支持这种上下文相关校正

运行的例子
下载并安装Lingpipe.然后修改路径如下:
> cd LING_PIPE/demos/tutorial/querySpellCheck



使用ant命令
ant querySpelcheck



运行程序在windows上
java
-cp "../../../lingpipe-4.0.0.jar;
     ../../lib/lucene-core-2.3.0.jar;
     querySpellCheck.jar"
QuerySpellCheck



下面的会话,用户输入的是斜体
PHASE I: TRAINING
     CONFIGURATION:
     Model File: SpellCheck.model
     N-gram Length: 5
     File=..\..\data\rec.sport.hockey\train/52550
     File=..\..\data\rec.sport.hockey\train/52551
...
     File=..\..\data\rec.sport.hockey\train/55022
Writing model to file=SpellCheck.model
Writing lucene index to =lucene
Reading model from file=SpellCheck.model

PHASE II: CORRECTION
     Constructing Spell Checker from File

Enter query <RETURN>.
     Use just <RETURN>b to exit.

>Wayn Gretsky
Found 0 document(s) that matched query 'Wayn Gretsky':
Found 43 document(s) matching best alt='Wayne Gretzky':

>Wayne Gretzky
Found 43 document(s) that matched query 'Wayne Gretzky':
 No spelling correction found.

>Stanley Cub Plaoofs
Found 90 document(s) that matched query 'Stanley Cub Plaoofs':
Found 208 document(s) matching best alt='Stanley Cup Playoffs':

>StanleyCup
Found 0 document(s) that matched query 'StanleyCup':
Found 143 document(s) matching best alt='Stanley Cup':

>Stan ley Cup
Found 132 document(s) that matched query 'Stan ley Cup':
Found 143 document(s) matching best alt='Stanley Cup':

>
     Detected empty line.
     Ending test.



通过拼写校正后我们发现查询索引文档,返回的文档数量增长的比较显著。尽管纠正的结果不是所有的都正确,但对一些模糊查询往往能得到比较好的结果。
lingpipe的拼写纠错模型与许多其他普通的拼写纠错模型的不同之处在于,它能对查询词进行分词,如查询"Stanleycup"时一个几个单词兼并的词,能被纠正为
"Stan ley Cup".

训练数据采用5-grams;编辑距离字符操作数限制在8到10;训练数据中校正需要250到1000倍左右的次的编辑

开始集成到搜索引擎中
类QuerySpellCheck.java 集成的拼写模块以及lucene搜索引擎
基本数据流:
1.搭建语言模型,拼写纠错模块和Lucene类
2.集成语言模型和Lucene索引
3.命令行上输入查询

搭建Spell Checker
需要几个相关类组合。首先我们需要TrainSpellChecker对象训练搜索引擎索引中的数据。这一部如下代码
FixedWeightEditDistance fixedEdit =
    new FixedWeightEditDistance(MATCH_WEIGHT,
                                DELETE_WEIGHT,
                                INSERT_WEIGHT,
                                SUBSTITUTE_WEIGHT,
                                TRANSPOSE_WEIGHT);

NGramProcessLM lm = new NGramProcessLM(NGRAM_LENGTH);
TokenizerFactory tokenizerFactory
    = new IndoEuropeanTokenizerFactory();
TrainSpellChecker sc
    = new TrainSpellChecker(lm,fixedEdit,
                            tokenizerFactory);



工作流自顶向下,通过创建FixedWeightEditDistance对象,它能设置像删除,插入等操作的权重。合理的值已经设置在demo中了,它只能在某种特定数据集中
的纠错效果较好。
下一步就是要创建NGramProcessLM ,搭建字符级别的语言模型在我们的demo中。这时我们需要知道数据集中所有的字符数量。越小的数据量。再者,你能评估
查询和查询纠正的质量
另外我们需要TokenizerFactory对象帮助查询的编辑,这个tokenizer和Lucene中的tokenizer是完全匹配的 ,即是org.apache.lucene.analysis.Analyzer接口
的实现,看下文
上面的对象在类TrainSpellChecker 中聚集到一起,做分词相关的训练

搭建Lucene搜素引擎
Lucene中的类IndexWriter确定了数据写索引的路径,数据分词器,以及新索引是否创建的布尔标志位等等。
static final File LUCENE_INDEX_DIR = new File("lucene");
    static final StandardAnalyzer ANALYZER = new StandardAnalyzer(Version.LUCENE_30);
    static final String TEXT_FIELD = "text";
...
        FSDirectory fsDir 
            = new SimpleFSDirectory(LUCENE_INDEX_DIR,
                                    new NativeFSLockFactory());
        IndexWriter luceneIndexWriter 
            = new IndexWriter(fsDir,
                              ANALYZER,
                              IndexWriter.MaxFieldLength.LIMITED);


...
例子中使用的是Lucene的StandardAnalyzer,它提供了一些标准的Filtersh和大小写转换

文档索引和训练拼写纠错模块
增加 的数据时非常简单,所有的数据需要分别进行语言模型的训练和索引过程
String[] filesToIndex = DATA.list();
for (int i = 0; i < filesToIndex.length; ++i) {
    System.out.println("     File="
                       + DATA + "/"
                       +filesToIndex[i]);
    File file = new File(DATA,filesToIndex[i]);
    String charSequence
        = Files.readFromFile(file);
    sc.handle(charSequence);
    Document luceneDoc = new Document();
    Field textField
        = new Field(TEXT_FIELD,charSequence,
                    Field.Store.YES,Field.Index.TOKENIZED);
    luceneDoc.add(textField);
    luceneIndexWriter.addDocument(luceneDoc);
}

System.out.println("Writing model to file="
                   + MODEL_FILE);
writeModel(sc,MODEL_FILE);

System.out.println("Writing lucene index to ="
                   + LUCENE_INDEX_DIR);
luceneIndexWriter.close();




一旦我们将文件转换为字符串,训练语言模型为代码sc.handle(charSequence)。训练后的数据加入到Lucene需要一些复杂过程,因为这些结构化的数据以一种
复杂的方式来支持所有的搜索引擎特征的。于是我们创建新的文本对象,添加数据导域名中(查询时需要的field名),然后添加文档到索引中。

下面我们简要阐述下spell checker序列化过程以及Lucne索引写到磁盘上,Lucene关闭和写索引以代码行luceneIndexWriter.close()为准

private static void writeModel(TrainSpellChecker sc,
                               File modelFile)
    throws IOException {

    FileOutputStream fileOut
        = new FileOutputStream(modelFile);
    BufferedOutputStream bufOut
        = new BufferedOutputStream(fileOut);
    ObjectOutputStream objOut
        = new ObjectOutputStream(bufOut);

    // write the spell checker to the file
    sc.compileTo(objOut);

    Streams.closeOutputStream(objOut);
    Streams.closeOutputStream(bufOut);
    Streams.closeOutputStream(fileOut);
}



从磁盘上读数据
一旦模型和索引写到磁盘上,我们能读它们,然后纠正查询。这种读模型方法显示了如何兼并相关的Java I/O 序列化连载类。我们通过请求产生纠错结果的
CompiledSpellChecker从而改变类。主要原因是编译的语言模型要比未编译版本快得多


// read compiled model from model file
System.out.println("Reading model from file="
                   + MODEL_FILE);
CompiledSpellChecker compiledSC
    = readModel(MODEL_FILE);
IndexSearcher luceneIndex
    = new IndexSearcher(fsDir);

System.out.print(TEST_INTRO);
testModelLoop(compiledSC,luceneIndex);


下载Lucene相似度改变了IndexSearcher的核心类,我们开始纠正查询了。上面最后一一行代码就是命令行的插查询纠错循环。

查询纠错循环

一个搜索引擎的基本部署需要比我们demo更多的功能,比如检索文档的排序。但是下面我们要尽量得到更好的拼写纠错模块,比想象中的部署要全面,
这个方法是一个无限循环命令行代码。流程是得到查询,流程是输入一个查询,运行Lucene并得到结果。
static void testModelLoop(SpellChecker sc,
                          IndexSearcher luceneIndex)
        throws IOException, ParseException {

    InputStreamReader isReader
        = new InputStreamReader(System.in);
    BufferedReader bufReader
        = new BufferedReader(isReader);

    QueryParser queryParser 
        = new QueryParser(Version.LUCENE_30,
                          TEXT_FIELD,
                          ANALYZER);

    while (true) {
        // collect query or end if null
        System.out.print(">");
        System.out.flush();
        String queryString = bufReader.readLine();
        if (queryString == null || queryString.length() == 0)
            break;

        Query query = queryParser.parse(queryString);
        TopDocs results = searcher.search(query,MAX_HITS);
        System.out.println("Found "
            + results.totalHits
            + " document(s) that matched query '"
            + query + "':");

        // compute alternative spelling
        String bestAlternative = sc.didYouMean(queryString);

        Query alternativeQuery
            = queryParser.parse(bestAlternative);
        TopDocs results2
            = luceneIndex.search(alternativeQuery,MAX_HITS);

        System.out.println("Found " + results2.totalHits
            + " document(s) matched best alternate '"
            + bestAlternative + "':");
    }
}



查阅更多相关代码,处理搞乱I/O和输入空查询时而破坏循环。首先我们使用Lucnene中的QueryParser产生查询。这样携带参数并保护查询串,然后Lucene索引的域被检索
(TEXT_FIELD),然后Analyzer对查询进行分词处理(UCENE_TOKENIZER),同样的采用相同的分词器对索引数据进行处理。

下一步我们收集匹配查询的文档Hits hits = luceneIndex.search(origQuery)。我们需要从Lucene中获取文档数量,以及Hits类提供的方法即及时每个文档与查询的相似度得分。

最后我们得到拼写纠错结果代码 String bestAlternative = sc.didYouMean(query);它采用了语言模型,我们通过有关查询的文本中的简单编辑距离发现更好的匹配。
如果纠错结果中没有更高得分的查询,那边系统就返回原始查询串

协调和部署
部署好的查询纠错模块需要比这个Demo系统更加完善,比如系统需要能兼容各种版本。协调模型有点复杂,另外模型训练数据量大小也是个问题。通常理想情况下,你需要
查询纠错系统评测集,结果好坏来自于实际不同使用者,你可以选择合适参数来协调系统。