OpenNLP进行中文命名实体识别(上:预处理及训练模型)

http://blog.csdn.net/qdhy199148/article/details/51038637


OpenNLP是Apach下的Java自然语言处理API,功能齐全,但是网上似乎能找到的用于处理中文的资料很少。

正好前段时间面试遇到一个做命名实体识别的任务考题,这里来给大家介绍一下使用OpenNLP进行中文语料命名实体识别的过程。

首先是预处理工作,分词去听用词等等的就不啰嗦了,其实将分词的结果中间加上空格隔开就可以了,OpenNLP可以将这样形式的的语料照处理英文的方式处理,有些关于字符处理的注意点在后面会提到。

首先我们要准备各个命名实体类别所对应的词库,词库被存在文本文档中,文档名即是命名实体类别的TypeName,下面两个function分别是载入某类命名实体词库中的词和载入命名实体的类别。

[java]  view plain  copy
  1. /** 
  2.      * 载入词库中的命名实体 
  3.      *  
  4.      * @param nameListFile 
  5.      * @return 
  6.      * @throws Exception 
  7.      */  
  8.     public static List<String> loadNameWords(File nameListFile)  
  9.             throws Exception {  
  10.         List<String> nameWords = new ArrayList<String>();  
  11.   
  12.         if (!nameListFile.exists() || nameListFile.isDirectory()) {  
  13.             System.err.println("不存在那个文件");  
  14.             return null;  
  15.         }  
  16.   
  17.         BufferedReader br = new BufferedReader(new FileReader(nameListFile));  
  18.         String line = null;  
  19.         while ((line = br.readLine()) != null) {  
  20.             nameWords.add(line);  
  21.         }  
  22.   
  23.         br.close();  
  24.   
  25.         return nameWords;  
  26.     }  
  27.   
  28.     /** 
  29.      * 获取命名实体类型 
  30.      *  
  31.      * @param nameListFile 
  32.      * @return 
  33.      */  
  34.     public static String getNameType(File nameListFile) {  
  35.         String nameType = nameListFile.getName();  
  36.   
  37.         return nameType.substring(0, nameType.lastIndexOf("."));  
  38.     }  
因为OpenNLP要求的训练语料是这样子的:

[html]  view plain  copy
  1. XXXXXX<START:Person>????<END>XXXXXXXXX<START:Action>????<END>XXXXXXX  
很容易看出,被标注的命名实体被放在<START><END>范围中,并标出了实体的类别。

接下来是对命名实体识别模型的训练,先上代码:

[java]  view plain  copy
  1. import java.io.File;  
  2. import java.io.FileOutputStream;  
  3. import java.io.IOException;  
  4. import java.io.StringReader;  
  5. import java.util.Collections;  
  6.   
  7. import opennlp.tools.namefind.NameFinderME;  
  8. import opennlp.tools.namefind.NameSample;  
  9. import opennlp.tools.namefind.NameSampleDataStream;  
  10. import opennlp.tools.namefind.TokenNameFinderModel;  
  11. import opennlp.tools.util.ObjectStream;  
  12. import opennlp.tools.util.PlainTextByLineStream;  
  13. import opennlp.tools.util.featuregen.AggregatedFeatureGenerator;  
  14. import opennlp.tools.util.featuregen.PreviousMapFeatureGenerator;  
  15. import opennlp.tools.util.featuregen.TokenClassFeatureGenerator;  
  16. import opennlp.tools.util.featuregen.TokenFeatureGenerator;  
  17. import opennlp.tools.util.featuregen.WindowFeatureGenerator;  
  18.   
  19. /** 
  20.  * 中文命名实体识别模型训练组件 
  21.  *  
  22.  * @author ddlovehy 
  23.  * 
  24.  */  
  25. public class NamedEntityMultiFindTrainer {  
  26.   
  27.     // 默认参数  
  28.     private int iterations = 80;  
  29.     private int cutoff = 5;  
  30.     private String langCode = "general";  
  31.     private String type = "default";  
  32.   
  33.     // 待设定的参数  
  34.     private String nameWordsPath; // 命名实体词库路径  
  35.     private String dataPath; // 训练集已分词语料路径  
  36.     private String modelPath; // 模型存储路径  
  37.   
  38.     public NamedEntityMultiFindTrainer() {  
  39.         super();  
  40.         // TODO Auto-generated constructor stub  
  41.     }  
  42.   
  43.     public NamedEntityMultiFindTrainer(String nameWordsPath, String dataPath,  
  44.             String modelPath) {  
  45.         super();  
  46.         this.nameWordsPath = nameWordsPath;  
  47.         this.dataPath = dataPath;  
  48.         this.modelPath = modelPath;  
  49.     }  
  50.   
  51.     public NamedEntityMultiFindTrainer(int iterations, int cutoff,  
  52.             String langCode, String type, String nameWordsPath,  
  53.             String dataPath, String modelPath) {  
  54.         super();  
  55.         this.iterations = iterations;  
  56.         this.cutoff = cutoff;  
  57.         this.langCode = langCode;  
  58.         this.type = type;  
  59.         this.nameWordsPath = nameWordsPath;  
  60.         this.dataPath = dataPath;  
  61.         this.modelPath = modelPath;  
  62.     }  
  63.   
  64.     /** 
  65.      * 生成定制特征 
  66.      *  
  67.      * @return 
  68.      */  
  69.     public AggregatedFeatureGenerator prodFeatureGenerators() {  
  70.         AggregatedFeatureGenerator featureGenerators = new AggregatedFeatureGenerator(  
  71.                 new WindowFeatureGenerator(new TokenFeatureGenerator(), 22),  
  72.                 new WindowFeatureGenerator(new TokenClassFeatureGenerator(), 2,  
  73.                         2), new PreviousMapFeatureGenerator());  
  74.   
  75.         return featureGenerators;  
  76.     }  
  77.   
  78.     /** 
  79.      * 将模型写入磁盘 
  80.      *  
  81.      * @param model 
  82.      * @throws Exception 
  83.      */  
  84.     public void writeModelIntoDisk(TokenNameFinderModel model) throws Exception {  
  85.         File outModelFile = new File(this.getModelPath());  
  86.         FileOutputStream outModelStream = new FileOutputStream(outModelFile);  
  87.         model.serialize(outModelStream);  
  88.     }  
  89.   
  90.     /** 
  91.      * 读出标注的训练语料 
  92.      *  
  93.      * @return 
  94.      * @throws Exception 
  95.      */  
  96.     public String getTrainCorpusDataStr() throws Exception {  
  97.   
  98.         // TODO 考虑入持久化判断直接载入标注数据的情况 以及增量式训练  
  99.   
  100.         String trainDataStr = null;  
  101.         trainDataStr = NameEntityTextFactory.prodNameFindTrainText(  
  102.                 this.getNameWordsPath(), this.getDataPath(), null);  
  103.   
  104.         return trainDataStr;  
  105.     }  
  106.   
  107.     /** 
  108.      * 训练模型 
  109.      *  
  110.      * @param trainDataStr 
  111.      *            已标注的训练数据整体字符串 
  112.      * @return 
  113.      * @throws Exception 
  114.      */  
  115.     public TokenNameFinderModel trainNameEntitySamples(String trainDataStr)  
  116.             throws Exception {  
  117.         ObjectStream<NameSample> nameEntitySample = new NameSampleDataStream(  
  118.                 new PlainTextByLineStream(new StringReader(trainDataStr)));  
  119.           
  120.         System.out.println("**************************************");  
  121.         System.out.println(trainDataStr);  
  122.   
  123.         TokenNameFinderModel nameFinderModel = NameFinderME.train(  
  124.                 this.getLangCode(), this.getType(), nameEntitySample,  
  125.                 this.prodFeatureGenerators(),  
  126.                 Collections.<String, Object> emptyMap(), this.getIterations(),  
  127.                 this.getCutoff());  
  128.   
  129.         return nameFinderModel;  
  130.     }  
  131.   
  132.     /** 
  133.      * 训练组件总调用方法 
  134.      *  
  135.      * @return 
  136.      */  
  137.     public boolean execNameFindTrainer() {  
  138.   
  139.         try {  
  140.             String trainDataStr = this.getTrainCorpusDataStr();  
  141.             TokenNameFinderModel nameFinderModel = this  
  142.                     .trainNameEntitySamples(trainDataStr);  
  143.             // System.out.println(nameFinderModel);  
  144.             this.writeModelIntoDisk(nameFinderModel);  
  145.   
  146.             return true;  
  147.         } catch (Exception e) {  
  148.             // TODO Auto-generated catch block  
  149.             e.printStackTrace();  
  150.   
  151.             return false;  
  152.         }  
  153.     }  
  154. }  

有几个说明的地方,首先是参数,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文件夹里面的测试语料和已经训练好的模型。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值