当前位置:   article > 正文

中文nlp处理常用工具(ltp、apache-nlp、spark-nlp等)_ltp工具

ltp工具

ltp是哈工大社会计算和信息检索研究中心(HIT-SCIR)开源的中文自然语言处理工具集,用户可以使用 ltp 对中文文本进行分词、词性标注、命名实体识别、语义角色标注、依存句法分析、语义依存分析等等工作。目前是我觉得比较好用的nlp工具、相比 apache-opennlp、spark-nlp 等工具对中文支持度更好。ltp是 pytorch 开发,项目上是整合到 elasticsearch 服务中使用。

GitHub - HIT-SCIR/ltp: Language Technology Platform

推理:

  1. package tool.deeplearning;
  2. import ai.onnxruntime.*;
  3. import org.opencv.core.Core;
  4. import java.io.BufferedReader;
  5. import java.io.File;
  6. import java.nio.charset.StandardCharsets;
  7. import java.nio.file.Files;
  8. import java.nio.file.Paths;
  9. import java.util.*;
  10. import java.util.concurrent.atomic.AtomicInteger;
  11. /**
  12. * @desc : 中文分词 lightltp 中文词法分析(分词、词性标注 => 命名实体识别)
  13. *
  14. * 推理参考:
  15. * https://github.com/zejunwang1/lightltp
  16. *
  17. * 参考推理文件:
  18. * test_onnxruntime_inference.py
  19. *
  20. * 哈工大参考文档:
  21. * http://ltp.ai/docs/quickstart.html#id8
  22. *
  23. * ltp:
  24. * https://github.com/HIT-SCIR/ltp
  25. * 是哈工大社会计算和信息检索研究中心(HIT-SCIR)开源的中文自然语言处理工具集,
  26. * 用户可以使用 ltp 对中文文本进行分词、词性标注、命名实体识别、语义角色标注、依存句法分析、语义依存分析等等工作。
  27. *
  28. * @auth : tyf
  29. * @date : 2022-05-17 09:21:23
  30. */
  31. public class chinese_segment_lightltp_2 {
  32. // 模型
  33. public static OrtEnvironment env1;
  34. public static OrtSession session1;
  35. // 模型
  36. public static OrtEnvironment env2;
  37. public static OrtSession session2;
  38. // 字典索引映射
  39. public static Map<String, Integer> token_id_map = new HashMap<>();
  40. public static Map<Integer, String> id_token_map = new HashMap<>();
  41. // 27种词性列表
  42. public static String[] pos_map_en = new String[]{
  43. "n", "v", "wp", "u", "d",
  44. "a", "m", "p", "r", "ns",
  45. "c", "q", "nt", "nh", "nd",
  46. "j", "i", "b", "ni", "nz",
  47. "nl", "z", "k", "ws", "o",
  48. "h", "e"};
  49. // 27种词性列表的中文
  50. public static String[] pos_map_ch = new String[]{
  51. "名词", "动词", "标点", "助词", "副词",
  52. "形容词", "数词", "介词", "代词", "地名",
  53. "连词", "量词", "机构团体", "人名", "方位词",
  54. "简称", "成语", "习用语", "其他名词", "专有名词",
  55. "其他专名", "字符", "后缀", "外文字符", "拟声词",
  56. "前缀", "错误字符"
  57. };
  58. // 命名实体分为位置标识和类型标识
  59. // B -> 实体开始词
  60. // I -> 实体中间词
  61. // E -> 实体结束词
  62. // S -> 单独构成实体
  63. // O -> 不构成实体
  64. // Nh->人名 Ni->机构名 Ns->地名
  65. public static String[] ner_map_en = new String[]{
  66. "O", "S-Ns", "S-Nh", "B-Ni", "E-Ni", "I-Ni", "S-Ni", "B-Ns", "E-Ns", "I-Ns", "B-Nh", "E-Nh", "I-Nh"
  67. };
  68. // 环境初始化
  69. public static void init1(String weight) throws Exception{
  70. // opencv 库
  71. System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
  72. env1 = OrtEnvironment.getEnvironment();
  73. session1 = env1.createSession(weight, new OrtSession.SessionOptions());
  74. // 打印模型信息,获取输入输出的shape以及类型:
  75. System.out.println("---------模型1输入-----------");
  76. session1.getInputInfo().entrySet().stream().forEach(n->{
  77. String inputName = n.getKey();
  78. NodeInfo inputInfo = n.getValue();
  79. long[] shape = ((TensorInfo)inputInfo.getInfo()).getShape();
  80. String javaType = ((TensorInfo)inputInfo.getInfo()).type.toString();
  81. System.out.println(inputName+" -> "+ Arrays.toString(shape)+" -> "+javaType);
  82. });
  83. System.out.println("---------模型1输出-----------");
  84. session1.getOutputInfo().entrySet().stream().forEach(n->{
  85. String outputName = n.getKey();
  86. NodeInfo outputInfo = n.getValue();
  87. long[] shape = ((TensorInfo)outputInfo.getInfo()).getShape();
  88. String javaType = ((TensorInfo)outputInfo.getInfo()).type.toString();
  89. System.out.println(outputName+" -> "+Arrays.toString(shape)+" -> "+javaType);
  90. });
  91. session1.getMetadata().getCustomMetadata().entrySet().forEach(n->{
  92. System.out.println("元数据:"+n.getKey()+","+n.getValue());
  93. });
  94. }
  95. // 环境初始化
  96. public static void init2(String weight) throws Exception{
  97. // opencv 库
  98. System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
  99. env2 = OrtEnvironment.getEnvironment();
  100. session2 = env2.createSession(weight, new OrtSession.SessionOptions());
  101. // 打印模型信息,获取输入输出的shape以及类型:
  102. System.out.println("---------模型2输入-----------");
  103. session2.getInputInfo().entrySet().stream().forEach(n->{
  104. String inputName = n.getKey();
  105. NodeInfo inputInfo = n.getValue();
  106. long[] shape = ((TensorInfo)inputInfo.getInfo()).getShape();
  107. String javaType = ((TensorInfo)inputInfo.getInfo()).type.toString();
  108. System.out.println(inputName+" -> "+ Arrays.toString(shape)+" -> "+javaType);
  109. });
  110. System.out.println("---------模型2输出-----------");
  111. session2.getOutputInfo().entrySet().stream().forEach(n->{
  112. String outputName = n.getKey();
  113. NodeInfo outputInfo = n.getValue();
  114. long[] shape = ((TensorInfo)outputInfo.getInfo()).getShape();
  115. String javaType = ((TensorInfo)outputInfo.getInfo()).type.toString();
  116. System.out.println(outputName+" -> "+Arrays.toString(shape)+" -> "+javaType);
  117. });
  118. session2.getMetadata().getCustomMetadata().entrySet().forEach(n->{
  119. System.out.println("元数据:"+n.getKey()+","+n.getValue());
  120. });
  121. }
  122. public static class TextObj{
  123. // 需要处理的文本
  124. String text;
  125. // 保存每个分割的单词
  126. ArrayList<String> words = new ArrayList<>();
  127. // 保存每个分割的单词的词性
  128. ArrayList<String> poss_en = new ArrayList<>();
  129. ArrayList<String> poss_ch = new ArrayList<>();
  130. // 保存 word_input 和 word_attention_mask 作为模型2的输入
  131. float[][][] word_input = null;
  132. public TextObj(String text) {
  133. this.text = text;
  134. }
  135. public static int getMaxIndex(float[] array) {
  136. int maxIndex = 0;
  137. float maxVal = array[0];
  138. for (int i = 1; i < array.length; i++) {
  139. if (array[i] > maxVal) {
  140. maxVal = array[i];
  141. maxIndex = i;
  142. }
  143. }
  144. return maxIndex;
  145. }
  146. // 模型推理
  147. public void inference1() throws Exception{
  148. System.out.println("----------------------------inference------------------------------");
  149. // 每个字符
  150. char[] chars = text.toCharArray();
  151. List<String> charSrt = new ArrayList<>();
  152. for (int i = 0; i < chars.length; i++) {
  153. charSrt.add(String.valueOf(chars[i]));
  154. }
  155. charSrt.add(0,"[CLS]");
  156. charSrt.add("[SEP]");
  157. // 文本转id索引
  158. List<Integer> ids = new ArrayList<>();
  159. for (int i = 0; i < charSrt.size(); i++) {
  160. String s = charSrt.get(i);
  161. Integer id = token_id_map.get(s.toLowerCase());
  162. // 可能存在字库中不存在的字,使用固定的id
  163. if(id!=null){
  164. ids.add(id);
  165. }else {
  166. ids.add(1);
  167. }
  168. }
  169. // 模型输入
  170. int count = ids.size();
  171. long[] inputIds = new long[count];
  172. long[] attentionMask = new long[count];
  173. long[] tokenTypeIds = new long[count];
  174. long[] shape = new long[]{1, count};
  175. // ---------模型输入-----------
  176. // input_ids -> [-1, -1] -> INT64
  177. // attention_mask -> [-1, -1] -> INT64
  178. // token_type_ids -> [-1, -1] -> INT64
  179. for(int i=0; i < ids.size(); i ++) {
  180. inputIds[i] = ids.get(i);
  181. attentionMask[i] = 1;
  182. tokenTypeIds[i] = 0;
  183. }
  184. // ---------模型输入-----------
  185. // input_ids -> [-1, -1] -> INT64
  186. // attention_mask -> [-1, -1] -> INT64
  187. // token_type_ids -> [-1, -1] -> INT64
  188. // 输入数组转为张量
  189. OnnxTensor input_ids = OnnxTensor.createTensor(env1, OrtUtil.reshape(inputIds, shape));
  190. OnnxTensor attention_mask = OnnxTensor.createTensor(env1, OrtUtil.reshape(attentionMask, shape));
  191. OnnxTensor token_type_ids = OnnxTensor.createTensor(env1, OrtUtil.reshape(tokenTypeIds, shape));
  192. Map<String,OnnxTensor> input = new HashMap<>();
  193. input.put("input_ids",input_ids);
  194. input.put("attention_mask",attention_mask);
  195. input.put("token_type_ids",token_type_ids);
  196. // 推理
  197. OrtSession.Result out = session1.run(input);
  198. // ---------模型输出-----------
  199. // seg_output -> [-1, -1, 2] -> FLOAT
  200. // pos_output -> [-1, -1, 27] -> FLOAT
  201. // char_input -> [-1, -1, 256] -> FLOAT
  202. OnnxValue onnxValue1 = out.get(0);
  203. OnnxValue onnxValue2 = out.get(1);
  204. OnnxValue onnxValue3 = out.get(2);
  205. // 1 * n * 2 其中n就是输入文本的长度,2代表当前字是否是一个新的词语的开始
  206. float[][][] seg_output = (float[][][]) onnxValue1.getValue();
  207. // 1 * n * 27 其中n就是输入文本的长度,27代表了27中词性的概率
  208. float[][][] pos_output = (float[][][]) onnxValue2.getValue();
  209. // 这个用于后续的命名实体识别
  210. float[][][] char_input = (float[][][]) onnxValue3.getValue();
  211. StringBuffer tmp = new StringBuffer();
  212. // 遍历每个字
  213. for(int i=0;i<seg_output[0].length;i++){
  214. // 2维,表示每个字是否属于单词的开始
  215. float seg_1 = seg_output[0][i][0];
  216. float seg_2 = seg_output[0][i][1];
  217. // 遇到了单词的开始
  218. if(seg_1<seg_2){
  219. // 保存上一个单词
  220. if(tmp.toString().length()>0){
  221. words.add(tmp.toString());
  222. }
  223. // 重新创建一个单词,并拼接当前字
  224. tmp = new StringBuffer();
  225. tmp.append(chars[i]);
  226. // 每次一个新开始的单词,取第一个字的词性作为单词词性
  227. // 27维,表示词性的概率,获取最大索引
  228. float[] pos = pos_output[0][i];
  229. int maxIndex = getMaxIndex(pos);
  230. // 根据最大概率索引从词性列表中取出词性字符串
  231. poss_en.add(pos_map_en[maxIndex]);
  232. poss_ch.add(pos_map_ch[maxIndex]);
  233. }else{
  234. tmp.append(chars[i]);
  235. }
  236. }
  237. // 保存最后一个单词
  238. if(tmp.toString().length()>0){
  239. words.add(tmp.toString());
  240. }
  241. // 保存 word_input 作为模型2的输入
  242. this.word_input = char_input;
  243. }
  244. // 命名实体识别
  245. public void inference2() throws Exception{
  246. // 字符个数
  247. int count = this.word_input[0].length;
  248. // 打印输入
  249. float[][][] word_input = this.word_input;
  250. long[][] word_attention_mask = new long[1][count];
  251. for(int i=0;i<count;i++){
  252. word_attention_mask[0][i] = 1;
  253. }
  254. // ---------模型2输入-----------
  255. // word_input -> [-1, -1, 256] -> FLOAT
  256. // word_attention_mask -> [-1, -1] -> INT64
  257. OnnxTensor word_input_tens = OnnxTensor.createTensor(env2,word_input);
  258. OnnxTensor word_attention_mask_tens = OnnxTensor.createTensor(env2,word_attention_mask);
  259. Map<String,OnnxTensor> input = new HashMap<>();
  260. input.put("word_input",word_input_tens);
  261. input.put("word_attention_mask",word_attention_mask_tens);
  262. // ---------模型2输出-----------
  263. // ner_output -> [-1, -1, 13] -> FLOAT 1 * n * 13 表示每个字属于哪一种命名实体的概率
  264. OrtSession.Result out = session2.run(input);
  265. OnnxValue onnxValue1 = out.get(0);
  266. float[][][] ner_output = (float[][][]) onnxValue1.getValue();
  267. // 依然是取每个单词开头的第一个字计算 13 中命名实体的概率作为每个单词的命名实体类型,所以要遍历前面已经得到的分析
  268. ArrayList<Integer> index = new ArrayList<>();
  269. Integer tmp = 0;
  270. // 单词个数
  271. int wordCount = words.size();
  272. for (int i = 0; i < wordCount; i++) {
  273. // 当前单词
  274. String now_word = words.get(i);
  275. index.add(tmp);
  276. tmp = tmp + now_word.length();
  277. }
  278. // 获取每个位置的概率数组
  279. ArrayList nerList = new ArrayList();
  280. for (int i = 0; i < ner_output[0].length; i++) {
  281. float[] gailv = ner_output[0][i];
  282. int in = getMaxIndex(gailv);
  283. String ner = ner_map_en[in];
  284. nerList.add(ner);
  285. }
  286. System.out.println("原始文本:");
  287. System.out.println(text);
  288. System.out.println("单词分割:");
  289. System.out.println(Arrays.toString(words.toArray()));
  290. System.out.println("命名实体:");
  291. System.out.println(Arrays.toString(nerList.toArray()));
  292. }
  293. // 打印结果
  294. public void show(){
  295. }
  296. }
  297. // 将字符和id索引进行保存
  298. public static void initVocab(String path) throws Exception{
  299. // 字符 => index
  300. try(BufferedReader br = Files.newBufferedReader(Paths.get(path), StandardCharsets.UTF_8)) {
  301. String line;
  302. int index = 0;
  303. while ((line = br.readLine()) != null) {
  304. token_id_map.put(line.trim().toLowerCase(), index);
  305. index ++;
  306. }
  307. }
  308. // index => 字符
  309. for (String key : token_id_map.keySet()) {
  310. id_token_map.put(token_id_map.get(key), key);
  311. }
  312. }
  313. public static void main(String[] args) throws Exception{
  314. // ---------模型输入-----------
  315. // input_ids -> [-1, -1] -> INT64
  316. // attention_mask -> [-1, -1] -> INT64
  317. // token_type_ids -> [-1, -1] -> INT64
  318. // ---------模型输出-----------
  319. // seg_output -> [-1, -1, 2] -> FLOAT
  320. // pos_output -> [-1, -1, 27] -> FLOAT
  321. // char_input -> [-1, -1, 256] -> FLOAT
  322. String n1 = "segpos_cpu_opt.onnx";
  323. // ---------模型2输入-----------
  324. // word_input -> [-1, -1, 256] -> FLOAT
  325. // word_attention_mask -> [-1, -1] -> INT64
  326. // ---------模型2输出-----------
  327. // ner_output -> [-1, -1, 13] -> FLOAT
  328. String n2 = "ner_cpu.onnx";
  329. // 模型1
  330. init1(new File("").getCanonicalPath()+
  331. "\\model\\deeplearning\\chinese_segment_lightltp\\"+n1);
  332. // 模型2
  333. init2(new File("").getCanonicalPath()+
  334. "\\model\\deeplearning\\chinese_segment_lightltp\\"+n2);
  335. // 加载字符库,每个字需要转为字符库中的索引
  336. initVocab(new File("").getCanonicalPath()+
  337. "\\model\\deeplearning\\chinese_segment_lightltp\\"+"vocab.txt");
  338. // 需要处理的文本
  339. String text = "四川省成都市金牛区一环路北二段,唐于凡,13388236293";
  340. TextObj textObj = new TextObj(text);
  341. // 模型1: 实现分词 + 词性标注
  342. textObj.inference1();
  343. // 模型2: 实现命名实体识别
  344. textObj.inference2();
  345. // 输出结果
  346. textObj.show();
  347. }
  348. }

测试结果:

另外 apache 下面的 open-nlp 文档和工具也比较齐全,官网:

https://opennlp.apache.org/download.html

opennlp 提供了 CLI 命令行操作、底层调用的推理或者训练等各个tool接口,可以打印包含的所有工具、打印每一种 tool 工具的 usage:

  1. // 打印所有 tool 工具
  2. new opennlp.tools.cmdline.CLI().main(new String[]{});
  3. // 可以获取单个 tool 工具使用示例
  4. System.out.println(new TokenizerTrainerTool().getHelp());
  5. System.out.println(new TokenNameFinderTrainerTool().getHelp());

主要就是模型推理和模型训练,官方文档有两部分:
前面 1~16是每一种任务的推理和训练说明、包含api 接口以及训练样本格式等等,17是 CLI 工具的说明,底层调用的 1~16 的接口。

1. Introduction
2. Language Detector
3. Sentence Detector
4. Tokenizer
5. Name Finder
6. Document Categorizer
7. Part-of-Speech Tagger
8. Lemmatizer
9. Chunker
10. Parser
11. Coreference Resolution
12. Extending OpenNLP
13. Corpora
14. Machine Learning
15. UIMA Integration
16. Morfologik Addon
17. The Command Line Interface
18. Evaluation Test Data

比如分词训练的 api 接口和样本格式:

对应的推理和训练代码:

  1. package org.tyf;
  2. import opennlp.tools.tokenize.*;
  3. import opennlp.tools.util.MarkableFileInputStreamFactory;
  4. import opennlp.tools.util.ObjectStream;
  5. import opennlp.tools.util.PlainTextByLineStream;
  6. import opennlp.tools.util.TrainingParameters;
  7. import java.io.BufferedOutputStream;
  8. import java.io.File;
  9. import java.io.FileOutputStream;
  10. import java.io.OutputStream;
  11. import java.nio.charset.StandardCharsets;
  12. import java.util.Arrays;
  13. public class ApacheNLPTokenizerME {
  14. public static void main(String[] args) throws Exception{
  15. // 模型保存路径
  16. String module = "C:\\Users\\tyf\\Desktop\\custom.bin";
  17. // 训练样本、一个句子用 <SPLIT> 隔开、句子内的单词用空格隔开:
  18. String data = "C:\\Users\\tyf\\Desktop\\data.txt";
  19. ObjectStream<String> lineStream = new PlainTextByLineStream(
  20. new MarkableFileInputStreamFactory(new File(data)),
  21. StandardCharsets.UTF_8);
  22. ObjectStream<TokenSample> sampleStream = new TokenSampleStream(lineStream);
  23. // 模型训练
  24. TokenizerModel model;
  25. try {
  26. model = TokenizerME.train(sampleStream, TokenizerFactory.create(
  27. null,
  28. "cmn",
  29. null,
  30. true,
  31. null
  32. ), TrainingParameters.defaultParams());
  33. }
  34. finally {
  35. sampleStream.close();
  36. }
  37. // 保存本地文件
  38. OutputStream modelOut = null;
  39. try {
  40. modelOut = new BufferedOutputStream(new FileOutputStream(module));
  41. model.serialize(modelOut);
  42. } finally {
  43. if (modelOut != null)
  44. modelOut.close();
  45. }
  46. // 加载模型推理
  47. TokenizerModel m = new TokenizerModel(new File(module));
  48. Tokenizer tokenizer = new TokenizerME(m);
  49. String text = "";
  50. String[] out = tokenizer.tokenize(text);
  51. System.out.println(Arrays.toString(out));
  52. }
  53. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/575437
推荐阅读
相关标签
  

闽ICP备14008679号