您的位置:首页 > 其它

人物识别(2)

2015-08-20 16:34 369 查看
接上

然后对文本应用分类函数。

List<List<CoreLabel>>entityList = classifier.classify(sentence);


返回CoreLabel对象众多列表实例中的一个。返回对象是一个包含另一个列表的列表,被包含的列表是corelabel对象的一个列表实例,CorLabel类表示一个带有附加信息的词。“内部”表包含这些词的列表。在下述代码行中的for-each语句外,引用变量,internalList,表示文本中的一个句子。在每个for-each语句内展示了内部列表中的每个词。词汇函数返回单词,同时获取函数返回单词的类型。

然后显示单词及其类型:

for (List<CoreLabel> internalList: entityList) { 

for (CoreLabel coreLabel : internalList) { 

String word = coreLabel.word(); 

String category = coreLabel.get( 

CoreAnnotations.AnswerAnnotation.class); 

System.out.println(word + ":" + category); 

} 

}


部分输出如下。由于所有单词都需要显示因此有所删减,O代表“其他”类型:

Joe:PERSON

was:O

the:O

last:O

person:O

to:O

see:O

Fred:PERSON

.:O

He:O



look:O

for:O

Fred:PERSON

为了滤除不相关的单词,用以下语句代替println,这会剔除其他类型,只保留你想要的类型。

if (!"O".equals(category)) { 

System.out.println(word + ":" + category); 

}


现在输出简单多了:

Joe:PERSON

Fred:PERSON

Boston:LOCATION

McKenzie:PERSON

Joe:PERSON

Vermont:LOCATION

IBM:ORGANIZATION

Sally:PERSON

Fred:PERSON

使用用于LingPipe的NER

本章前部分—使用用于NER的正则表达式一节中论述了LingPipe中正则表达式的使用。这里,我们将论述命名实体模型和ExactDictionaryChunker类如何用作NER分析。

使用LingPipe的命名实体模型

LingPipe有一些可以和chunking一起使用的命名实体模型。这些模型文件由一个序列化对象组成。这个序列化对象能从一个文件中读出然后应用于文本。它们实现Chunker接口。chunking处理产生一系列能对感兴趣的实体进行识别的chunking对象。

下表列出了一组NER模型,这些模型能从http://alias-i.com/lingpipe/web/models.html下载:

[thead]
[/thead]
类型语料库文件
English NewsMUC-6ne-en-news-muc6.
English GenesGeneTagne-en-bio-genetag.HmmChunker
English GenomicsGENIAne-en-bio-genia.TokenShapeChunker
我们将使用ne-en-news-muc6中的模型

AbstractCharLmRescoringChunker文件解释了这个类的使用方法。

如下所示,我们以try-catch块开始来处理exceptions。打开这个文件并使用AbstractExternalizable类的静态readObject函数来创建一个Chunker类的实例。该方法能读取序列化模型:

try { 

File modelFile = new File(getModelDir(), 

"ne-en-news-muc6.AbstractCharLmRescoringChunker"); 

Chunker chunker = (Chunker) 

AbstractExternalizable.readObject(modelFile); 

... 

} catch (IOException | ClassNotFoundException ex) { 

// Handle exception 

}


Chunker和Chunking接口提供了与文本中的一组chunks共同工作的方法。它的chunk函数返回一个实现Chunking实例的对象。如下代码序列展示文本里每个句子中找到的chunks:

for (int i = 0; i < sentences.length; ++i) { 

Chunking chunking = chunker.chunk(sentences[i]); 

System.out.println("Chunking=" + chunking); 

}


这个代码序列输出如下:

Chunking=Joe was the last person to see Fred. : [0-3:PERSON@-Infinity, 

31-35:ORGANIZATION@-Infinity] 

Chunking=He saw him in Boston at McKenzie's pub at 3:00 where he paid 

$2.45 for an ale. : [14-20:LOCATION@-Infinity, 24-32:PERSON@-Infinity] 

Chunking=Joe wanted to go to Vermont for the day to visit a cousin who 

works at IBM, but Sally and he had to look for Fred : [0-3:PERSON@- 

Infinity, 20-27:ORGANIZATION@-Infinity, 71-74:ORGANIZATION@-Infinity, 

109-113:ORGANIZATION@-Infinity]


另外,我们能使用Chunk类中的函数从所示信息中提取特定的信息片段。用如下for-each语句代替前面的语句。本章前面—使用LingPipe的RegExChunker类,这一节中已经论述了这个displayChunkSet函数:

for (String sentence : sentences) { 

displayChunkSet(chunker, sentence); 

}


输出结果如下,然而输出类型并不总是和正确的实体类型相匹配。

Type: PERSON Entity: [Joe] Score: -Infinity

Type: ORGANIZATION Entity: [Fred] Score: -Infinity

Type: LOCATION Entity: [Boston] Score: -Infinity

Type: PERSON Entity: [McKenzie] Score: -Infinity

Type: PERSON Entity: [Joe] Score: -Infinity

Type: ORGANIZATION Entity: [Vermont] Score: -Infinity

Type: ORGANIZATION Entity: [IBM] Score: -Infinity

Type: ORGANIZATION Entity: [Fred] Score: -Infinity

使用ExactDictionaryChunker类

ExactDictionaryChunker类提供了一个简单方法来创建实体和实体类型的字典。这个字典能用于文本中寻找实体和实体类型。它使用一个MapDictionary对象来储存实体然后根据这个字典使用ExactDictionaryChunker类来抽取chunks。

AbstractDictionary接口支持实体、类别和分数的基本操作。这里的分数用作匹配处理。MapDictionary和TrieDictionary类是AbstractDictionary的两个接口,TrieDictionary类使用一个字符trie结构储存信息。这种方法占用更少内存,实例将使用MapDictionary类。

为了解释这个方法,我们以一个MapDictionary类的声明开头:

private MapDictionary dictionary;

这个字典包含了我们想找到的实体。使用initializeDictionary函数对模型进行初始化。这里使用的DictionaryEntry构造函数接受三类参数:

String:实体名

String:实体类型

Double:代表实体的分数

确定是否匹配时会用到分数,声明一部分实体然后添加到字典。

private static void initializeDictionary() { 

dictionary = new MapDictionary<String>(); 

dictionary.addEntry( 

new DictionaryEntry<String>("Joe","PERSON",1.0)); 

dictionary.addEntry( 

new DictionaryEntry<String>("Fred","PERSON",1.0)); 

dictionary.addEntry( 

new DictionaryEntry<String>("Boston","PLACE",1.0)); 

dictionary.addEntry( 

new DictionaryEntry<String>("pub","PLACE",1.0)); 

dictionary.addEntry( 

new DictionaryEntry<String>("Vermont","PLACE",1.0)); 

dictionary.addEntry( 

new DictionaryEntry<String>("IBM","ORGANIZATION",1.0)); 

dictionary.addEntry( 

new DictionaryEntry<String>("Sally","PERSON",1.0)); 

}


ExactDictionaryChunker实例将会使用这个字典。ExactDictionaryChunker类参数说明如下:

Dictionary:一个包含实体的字典

TokenizerFactory:chunker使用的一个tokenizer

boolean:若为真,chunker返回所有匹配的值

boolean:若为真,区分大小写的匹配

匹配允许重叠。例如,短语“第一国家银行”,银行实体能独立使用也能和短语中的其他成分配合使用,第三个参数决定是否返回所有的匹配值。

下列代码序列中,字典经过了初始化。然后我们使用印欧语系的分词器创建一个ExactDictionaryChunker类的实例,这里返回所有的匹配值并忽略词项:

initializeDictionary(); 

ExactDictionaryChunker dictionaryChunker 

= new ExactDictionaryChunker(dictionary, 

IndoEuropeanTokenizerFactory.INSTANCE, true, false);


逐句使用dictionaryChunker类。如下代码所示,我们将使用本章前面—使用LingPipe的RegExChunker类,这节所提到的displayChunkSet函数:

for (String sentence : sentences) { 

System.out.println("\nTEXT=" + sentence); 

displayChunkSet(dictionaryChunker, sentence); 

}


执行完,将得到以下输出:

TEXT=Joe was the last person to see Fred.

Type: PERSON Entity: [Joe] Score: 1.0

Type: PERSON Entity: [Fred] Score: 1.0

TEXT=He saw him in Boston at McKenzie’s pub at 3:00 where he paid $2.45

for an ale.

Type: PLACE Entity: [Boston] Score: 1.0

Type: PLACE Entity: [pub] Score: 1.0

TEXT=Joe wanted to go to Vermont for the day to visit a cousin who works

at IBM, but Sally and he had to look for Fred

Type: PERSON Entity: [Joe] Score: 1.0

Type: PLACE Entity: [Vermont] Score: 1.0

Type: ORGANIZATION Entity: [IBM] Score: 1.0

Type: PERSON Entity: [Sally] Score: 1.0

Type: PERSON Entity: [Fred] Score: 1.0

任务完成的很漂亮,但需要很大的精力创建包含大量词汇的字典。

训练模型

我们将使用OpenNLP来论述如何训练一个模型。训练文件必须满足以下要求:

 包含区分实体边界的标记

 每行一个句子

使用文件名为en-ner-person.train的模型文件:

<START:person> Joe <END> was the last person to see <START:person> 

Fred <END>. 

He saw him in Boston at McKenzie's pub at 3:00 where he paid $2.45 for 

an ale. 

<START:person> Joe <END> wanted to go to Vermont for the day to visit 

a cousin who works at IBM, but <START:person> Sally <END> and he had 

to look for <START:person> Fred <END>.


实例中的几个方法都能剔除异常信息。上面的这些语句将被如下的try-with-resource块代替,模型的输出流也是创建于此:

try (OutputStream modelOutputStream = new BufferedOutputStream( 

new FileOutputStream(new File("modelFile")));) { 

... 

} catch (IOException ex) { 

// Handle exception 

}


在try-with-resource块内,使用PlainTextByLineStream类创建OutputStream对象。这个类的构造函数调用FileInputStream实例并把每一行作为一个String对象返回。en-ner-person.train文件用作输入文件,像这里展示的一样,UTF-8指所使用的编码序列。

ObjectStream lineStream = new PlainTextByLineStream(

new FileInputStream(“en-ner-person.train”), “UTF-8”);

lineStream对象包含用标签标注的文本里描写的实体的数据流。这些需要转换为可训练模型的NameSample对象。这个转换由如下显示的NameSampleDataStream类完成。一个NameSample对象记录了文中已发现实体的名字:

ObjectStream<NameSample> sampleStream = 

new NameSampleDataStream(lineStream);


通过如下程序执行train函数:

TokenNameFinderModel model = NameFinderME.train( 

"en", "person", sampleStream, 

Collections.<String, Object>emptyMap(), 100, 5);


下表详细显示了这个函数的参数:

参数 含义

“en” 语言

“person” 实体类型

sampleStream 采样数据

null 资源

100 迭代的次数

5 截止条件

然后模型序列化到一个输出文件:

model.serialize(modelOutputStream);

输出如下。输出有所缩短以节省空间。关于模型创建的基本信息详细描述如下:

Indexing events using cutoff of 5

Computing event counts… done. 53 events

Indexing… done.

Sorting and merging events… done. Reduced 53 events to 46.

Done indexing.

Incorporating indexed data for training…

done.

Number of Event Tokens: 46

Number of Outcomes: 2

Number of Predicates: 34

…done.

Computing model parameters …

Performing 100 iterations.

1: … loglikelihood=-36.73680056967707 0.05660377358490566

2: … loglikelihood=-17.499660626361216 0.9433962264150944

3: … loglikelihood=-13.216835449617108 0.9433962264150944

4: … loglikelihood=-11.461783667999262 0.9433962264150944

5: … loglikelihood=-10.380239416084963 0.9433962264150944

6: … loglikelihood=-9.570622475692486 0.9433962264150944

7: … loglikelihood=-8.919945779143012 0.9433962264150944



99: … loglikelihood=-3.513810438211968 0.9622641509433962

100: … loglikelihood=-3.507213816708068 0.9622641509433962

评估模型

模型可以使用TokenNameFinderEvaluator类进行评估。评估使用标记的示例文本。对于这个简单的示例,先创建包含如下文本的en-ner-person.eval文件:

<START:person> Bill <END> went to the farm to see <START:person> Sally 

<END>. 

Unable to find <START:person> Sally <END> he went to town. 

There he saw <START:person> Fred <END> who had seen <START:person> 

Sally <END> at the book store with <START:person> Mary <END>.


接下来的代码用来实施评估。先前的模型用作TokenNameFinderEvaluator构造函数的参数。基于评估文件创建NameSampleDataStream实例。用TokenNameFinderEvaluator类的evaluate函数进行评估。

TokenNameFinderEvaluator evaluator = 

new TokenNameFinderEvaluator(new NameFinderME(model)); 

lineStream = new PlainTextByLineStream( 

new FileInputStream("en-ner-person.eval"), "UTF-8"); 

sampleStream = new NameSampleDataStream(lineStream); 

evaluator.evaluate(sampleStream);


为了判断模型在测试数据中表现,执行getFMeasure函数,结果如下:

FMeasure result = evaluator.getFMeasure();

System.out.println(result.toString());

如下的输出显示了准确度,查全率和F-测量结果。找到的实体有50%与测试数据精确匹配。查全率定义为语料库中相同位置找到的实体百分比。性能测量是统一两者的方式,定义为F1 = 2*精确度*查全率/(查全率+精确度)。

Precision: 0.5

Recall: 0.25

F-Measure: 0.3333333333333333

为创建一个更好的模型,数据和测试集应尽量大。注意这里只论述了训练和测试POS模型的基本方法。

总结

NER涉及实体检测和实体分类。一般的类别包括名字,地理位置和事物。许多应用都用它为搜索引擎提供技术支持、解决引用以及寻找文本的含义。这个过程经常用于下游任务。

我们探究了几个实现NER的技术。正则表达式是一个同时支持核心Java类和NLP APIs的方法。这个技术在很多应用中都有用,而且有很多可用的正则表达式库。

基于字典的方法在一些应用中也很有效。但有时需要大量精力完善字典。我们使用LingPipe的MapDictionary类来解释这个方法。

训练的模型也能被用来实现NER。我们测试了一些模型并论述了如何使用OpenNLP NameFinderME来训练模型。这个过程和之前训练过程相似。

下一章,我们将学习如何检测词性如名词、形容词和介词。

最后,想感谢z老师对我的信任与支持鼓励。

zerof,stay hungry stay foolish!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: