(转)OpenNLP进行中文命名实体识别(上:预处理及训练模型)
原文地址:http://blog.csdn.net/qdhy199148/article/details/51038637
OpenNLP是Apach下的Java自然语言处理API,功能齐全,但是网上似乎能找到的用于处理中文的资料很少。
正好前段时间面试遇到一个做命名实体识别的任务考题,这里来给大家介绍一下使用OpenNLP进行中文语料命名实体识别的过程。
首先是预处理工作,分词去听用词等等的就不啰嗦了,其实将分词的结果中间加上空格隔开就可以了,OpenNLP可以将这样形式的的语料照处理英文的方式处理,有些关于字符处理的注意点在后面会提到。
首先我们要准备各个命名实体类别所对应的词库,词库被存在文本文档中,文档名即是命名实体类别的TypeName,下面两个function分别是载入某类命名实体词库中的词和载入命名实体的类别。
- /**
- * 载入词库中的命名实体
- *
- * @param nameListFile
- * @return
- * @throws Exception
- */
- public static List<String> loadNameWords(File nameListFile)
- throws Exception {
- List<String> nameWords = new ArrayList<String>();
- if (!nameListFile.exists() || nameListFile.isDirectory()) {
- System.err.println("不存在那个文件");
- return null;
- }
- BufferedReader br = new BufferedReader(new FileReader(nameListFile));
- String line = null;
- while ((line = br.readLine()) != null) {
- nameWords.add(line);
- }
- br.close();
- return nameWords;
- }
- /**
- * 获取命名实体类型
- *
- * @param nameListFile
- * @return
- */
- public static String getNameType(File nameListFile) {
- String nameType = nameListFile.getName();
- return nameType.substring(0, nameType.lastIndexOf("."));
- }
因为OpenNLP要求的训练语料是这样子的:
- XXXXXX<START:Person>????<END>XXXXXXXXX<START:Action>????<END>XXXXXXX
很容易看出,被标注的命名实体被放在<START><END>范围中,并标出了实体的类别。
接下来是对命名实体识别模型的训练,先上代码:
- import java.io.File;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.StringReader;
- import java.util.Collections;
- import opennlp.tools.namefind.NameFinderME;
- import opennlp.tools.namefind.NameSample;
- import opennlp.tools.namefind.NameSampleDataStream;
- import opennlp.tools.namefind.TokenNameFinderModel;
- import opennlp.tools.util.ObjectStream;
- import opennlp.tools.util.PlainTextByLineStream;
- import opennlp.tools.util.featuregen.AggregatedFeatureGenerator;
- import opennlp.tools.util.featuregen.PreviousMapFeatureGenerator;
- import opennlp.tools.util.featuregen.TokenClassFeatureGenerator;
- import opennlp.tools.util.featuregen.TokenFeatureGenerator;
- import opennlp.tools.util.featuregen.WindowFeatureGenerator;
- /**
- * 中文命名实体识别模型训练组件
- *
- * @author ddlovehy
- *
- */
- public class NamedEntityMultiFindTrainer {
- // 默认参数
- private int iterations = 80;
- private int cutoff = 5;
- private String langCode = "general";
- private String type = "default";
- // 待设定的参数
- private String nameWordsPath; // 命名实体词库路径
- private String dataPath; // 训练集已分词语料路径
- private String modelPath; // 模型存储路径
- public NamedEntityMultiFindTrainer() {
- super();
- // TODO Auto-generated constructor stub
- }
- public NamedEntityMultiFindTrainer(String nameWordsPath, String dataPath,
- String modelPath) {
- super();
- this.nameWordsPath = nameWordsPath;
- this.dataPath = dataPath;
- this.modelPath = modelPath;
- }
- public NamedEntityMultiFindTrainer(int iterations, int cutoff,
- String langCode, String type, String nameWordsPath,
- String dataPath, String modelPath) {
- super();
- this.iterations = iterations;
- this.cutoff = cutoff;
- this.langCode = langCode;
- this.type = type;
- this.nameWordsPath = nameWordsPath;
- this.dataPath = dataPath;
- this.modelPath = modelPath;
- }
- /**
- * 生成定制特征
- *
- * @return
- */
- public AggregatedFeatureGenerator prodFeatureGenerators() {
- AggregatedFeatureGenerator featureGenerators = new AggregatedFeatureGenerator(
- new WindowFeatureGenerator(new TokenFeatureGenerator(), 2, 2),
- new WindowFeatureGenerator(new TokenClassFeatureGenerator(), 2,
- 2), new PreviousMapFeatureGenerator());
- return featureGenerators;
- }
- /**
- * 将模型写入磁盘
- *
- * @param model
- * @throws Exception
- */
- public void writeModelIntoDisk(TokenNameFinderModel model) throws Exception {
- File outModelFile = new File(this.getModelPath());
- FileOutputStream outModelStream = new FileOutputStream(outModelFile);
- model.serialize(outModelStream);
- }
- /**
- * 读出标注的训练语料
- *
- * @return
- * @throws Exception
- */
- public String getTrainCorpusDataStr() throws Exception {
- // TODO 考虑入持久化判断直接载入标注数据的情况 以及增量式训练
- String trainDataStr = null;
- trainDataStr = NameEntityTextFactory.prodNameFindTrainText(
- this.getNameWordsPath(), this.getDataPath(), null);
- return trainDataStr;
- }
- /**
- * 训练模型
- *
- * @param trainDataStr
- * 已标注的训练数据整体字符串
- * @return
- * @throws Exception
- */
- public TokenNameFinderModel trainNameEntitySamples(String trainDataStr)
- throws Exception {
- ObjectStream<NameSample> nameEntitySample = new NameSampleDataStream(
- new PlainTextByLineStream(new StringReader(trainDataStr)));
- System.out.println("**************************************");
- System.out.println(trainDataStr);
- TokenNameFinderModel nameFinderModel = NameFinderME.train(
- this.getLangCode(), this.getType(), nameEntitySample,
- this.prodFeatureGenerators(),
- Collections.<String, Object> emptyMap(), this.getIterations(),
- this.getCutoff());
- return nameFinderModel;
- }
- /**
- * 训练组件总调用方法
- *
- * @return
- */
- public boolean execNameFindTrainer() {
- try {
- String trainDataStr = this.getTrainCorpusDataStr();
- TokenNameFinderModel nameFinderModel = this
- .trainNameEntitySamples(trainDataStr);
- // System.out.println(nameFinderModel);
- this.writeModelIntoDisk(nameFinderModel);
- return true;
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- return false;
- }
- }
- }
有几个说明的地方,首先是参数,iterations是训练算法迭代的次数,太少了起不到训练的效果,太大了会造成过拟合,所以各位可以自己试试效果;cutoff是语言模型扫描窗口的大小,一般设成5就可以了,当然越大效果越好,时间可能会受不了;还有就是langCode语种代码和type实体类别,因为没有专门针对中文的代码,设成“普通”的即可,实体的类别因为我们想训练成能识别多种实体的模型,于是设置为“默认”。
代码中每个函数都配有注释,稍微有点编程基础的人肯定能看懂。需要说明一下的两个方法是:1.prodFeatureGenerators()方法用于生成个人订制的特征生成器,其意义在于选择什么样的n-gram语义模型,代码当中显示的是选择窗口大小为5,待测命名实体词前后各扫描两个词的范围计算特征(加上自己就是5个),或许有更深更准确的意义,请大家指正;2.就是训练模型的核心方法trainNameEntitySamples(),首先是将如上标注的训练语料字符串传入生成字符流,再通过NameFinderME的train()方法传入上面设定的各个参数,订制特征生成器等等,关于源实体映射对,就按默认传入空Map就好了。
返回训练得到的模型,可以写到磁盘上,形成二进制bin文件。
源代码开源在:https://github.com/Ailab403/ailab-mltk4j,test包里面对应有完整的调用demo,以及file文件夹里面的测试语料和已经训练好的模型。