中文自然語言處理(NLP)(五)應用HanLP分詞模塊進行分詞處理


在使用jieba分詞模塊進行分詞的處理之后,由於項目的需要,要寫一個java的分詞模塊。瀏覽了jieba的GitHub網頁之后發現:jieba的java部分已經是好久沒有更新過了,並且jieba的java版本功能非常不完善(甚至沒有按照詞性分詞的功能)。不過無可厚非就是了,畢竟jieba的口號是做最好的Python分詞。於是我就去網上查閱,發現另外一個評價非常高的分詞模塊——HanLP分詞。

代碼要實現的目標與之前的四次隨筆相同,鏈接:https://www.cnblogs.com/aLieb/

1.HanLP分詞簡介(摘自官網)

HanLP是由一系列模型與算法組成的工具包,目標是普及自然語言處理在生產環境中的應用。HanLP具備功能完善、性能高效、架構清晰、語料時新、可自定義的特點;提供詞法分析(中文分詞、詞性標注、命名實體識別)、句法分析、文本分類和情感分析等功能。

HanLp分詞的功能是非常強大的,在把幾個常用的java分詞模塊拿到一起比較之后(拿了數學課本上面的三段文本來進行分詞處理),HanLP分詞的分詞效果是其中最好的,甚至有根據語境區分詞性的功能,所以決定使用HanLP分詞來寫java版本的代碼。

2.涉及到的一些問題

2.1數據結構

在之前的python版本的代碼當中,分詞所得到的關鍵詞和其對應的三級知識點之間的數量關系(一張二維表)是存儲在二維的字典當中的,但是java當中並沒有這種數據結構(我的java學的又不是那么好)。所以找了半天發現使用java當中的HashMap就可以解決這個問題,二維HashMap,也就是HashMap<String,HashMap<String,Integer>>這樣的數據結構,通過其自帶的get()和put()方法就可以實現二維HashMap的初始化和值的更改。

2.2Excel文件的讀取

項目需要的分詞語料存儲在Excel表當中,我本以為使用java的poi工具就可以實現讀取的工作,在一開始做的小demo當中也沒有問題,但是當我把文件大小為27187kb的excel文件導入的時候,就出現了JVM內存不夠的問題,后來到網上一查發現如果Workbook使用XSSFWorkBook創建對象,導入的Excel大小又大於2M,就很容易出現OOM的問題,所以要使用poi里面的事件驅動的讀取方式來進行excel文件的讀取。

錯誤(不適用於大容量excel文件導入)的方式:(這個try-catch是eclipse自動插入的)

 1 Workbook wb_Workbook=null;//初始化一個workbook類型的對象
 2         try {
 3             wb_Workbook=new XSSFWorkbook(exceFile);
 4         } catch (InvalidFormatException e) {
 5             // TODO Auto-generated catch block
 6             e.printStackTrace();
 7         } catch (IOException e) {
 8             // TODO Auto-generated catch block
 9             e.printStackTrace();
10         }//獲取excel文件當中的workbook到wb當中,需要try-catch

正確(適合大容量Excel文件導入的方式):

1 FileInputStream in=new FileInputStream(new File(data_path));
2 Workbook wb1=null;
3 wb1=new StreamingReader(null).builder()
4           .rowCacheSize(100)
5           .bufferSize(4096)
6           .open(in);

以上內容參考:https://www.cnblogs.com/iliuyuet/p/4361201.html 

3.代碼及大致思路

3.1maven依賴部分

需要添加的依賴:

 1 <dependency>
 2       <groupId>com.hankcs</groupId>
 3       <artifactId>hanlp</artifactId>
 4       <version>portable-1.7.4</version>
 5     </dependency>
 6     <!-- 以下兩個為關於excel讀寫的依賴 -->
 7     <dependency>
 8       <groupId>org.apache.poi</groupId>
 9       <artifactId>poi</artifactId>
10       <version>3.15</version>
11     </dependency>
12     <dependency>
13       <groupId>org.apache.poi</groupId>
14       <artifactId>poi-ooxml</artifactId>
15       <version>3.15</version>
16     </dependency>
17     <dependency>
18        <groupId>com.monitorjbl</groupId>
19        <artifactId>xlsx-streamer</artifactId>
20        <version>1.2.0</version>
21     </dependency>
22     <dependency>
23         <groupId>org.slf4j</groupId>
24         <artifactId>slf4j-log4j12</artifactId>
25         <version>1.7.2</version>
26     </dependency>

3.2App.java部分

  1 package Big_Create.Word_Cut;
  2 
  3 import java.io.BufferedReader;
  4 import java.io.BufferedWriter;
  5 import java.io.File;
  6 import java.io.FileInputStream;
  7 import java.io.FileNotFoundException;
  8 import java.io.FileReader;
  9 import java.io.FileWriter;
 10 import java.io.IOException;
 11 import java.util.ArrayList;
 12 import java.util.HashMap;
 13 
 14 import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
 15 import org.apache.poi.ss.usermodel.Cell;
 16 import org.apache.poi.ss.usermodel.Row;
 17 import org.apache.poi.ss.usermodel.Workbook;
 18 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 19 
 20 import com.graphbuilder.curve.Point;
 21 import com.hankcs.hanlp.dictionary.CustomDictionary;
 22 import com.hankcs.hanlp.seg.common.Term;
 23 import com.hankcs.hanlp.tokenizer.StandardTokenizer;
 24 import com.monitorjbl.xlsx.StreamingReader;
 25 
 26 /**
 27  * Hello world!
 28  *
 29  */
 30 public class App 
 31 {
 32     
 33     //1.從excel文件當中導入詞匯的方法(是xlsx類型的文件)
 34     public static void add_words_from_excel(String excel_path,int cell_num) {
 35         File exceFile=new File(excel_path);//以傳進來的參數創建文件
 36         Workbook wb_Workbook=null;//初始化一個workbook類型的對象
 37         try {
 38             wb_Workbook=new XSSFWorkbook(exceFile);
 39         } catch (InvalidFormatException e) {
 40             // TODO Auto-generated catch block
 41             e.printStackTrace();
 42         } catch (IOException e) {
 43             // TODO Auto-generated catch block
 44             e.printStackTrace();
 45         }//獲取excel文件當中的workbook到wb當中,需要try-catch
 46         org.apache.poi.ss.usermodel.Sheet sheet_readSheet=wb_Workbook.getSheetAt(0);
 47         int firstRowIndex=sheet_readSheet.getFirstRowNum()+1;
 48         int lastRowIndex=sheet_readSheet.getLastRowNum();
 49         //獲取第一行和最后一行的行號
 50         for (int i = firstRowIndex; i < lastRowIndex; i++) {
 51             Row row=sheet_readSheet.getRow(i);
 52             if (row!=null) {
 53                 Cell cell=row.getCell(cell_num);
 54                 if (cell!=null) {
 55                     CustomDictionary.add(cell.getStringCellValue());
 56                     //測試用System.out.println(cell.toString());
 57                 }
 58             }
 59         }
 60     }
 61     
 62     //2.從txt文件當中導入停用詞表的方法,返回值為一個存儲字符串的arraylist
 63     //這里用的停用詞表是網上下載的哈工大的停用詞表
 64     public static ArrayList<String> get_stop_list(String txt_path) throws IOException {
 65         ArrayList<String> stop_list=new ArrayList<String>();
 66         //創建arraylist來存儲讀出的停用詞
 67         //現在已知停用詞表(txt格式)是一個詞一行存儲,詞的末尾有空格
 68         try {
 69             FileReader file_Reader=new FileReader(txt_path);
 70             BufferedReader buffer_BufferedReader=new BufferedReader(file_Reader);
 71             String read_line;
 72             while((read_line=buffer_BufferedReader.readLine()) != null) {
 73                 String out_str=delete_space(read_line);//去掉了空格之后的字符串,將其添加進輸出的arraylist里
 74                 stop_list.add(out_str);
 75             }
 76         } catch (FileNotFoundException e) {
 77             // TODO Auto-generated catch block
 78             e.printStackTrace();
 79         }
 80         return stop_list;
 81     }
 82     
 83     //3.用於去掉讀取的字符串中的空格的方法,返回為string
 84     public static String delete_space(String str_with_space) {
 85         String str_with_out_space = "";
 86         for (int i = 0; i < str_with_space.length(); i++) {
 87             char temp=str_with_space.charAt(i);
 88             if (temp!=' ') {
 89                 str_with_out_space=str_with_out_space+temp;
 90                 //因為這個情景下的文本不是很長,所以直接用字符串拼接即可
 91             }
 92         }
 93         return str_with_out_space;
 94     }
 95     
 96     //4.主體分詞方法,將所有的題干信息提取並且做分詞處理
 97     public static void striped(String data_path,String stop_path,String result_path1,String result_path2) throws IOException {
 98         //三個參數分別為:源數據路徑、停用詞表路徑、輸出結果路徑
 99         //設置變量部分:
100         int text_num=1;//題干所在的列號
101         int class_one_num=7;//一級知識點所在的列號
102         int knowledge_num=9;//三級知識點所在的列號
103         int knowledge_code_num=6;//三級知識點對應的知識點編碼所在的列號
104         
105         //設置數據結構部分:
106         ArrayList<String>knowledge_points=new ArrayList<String>();//存儲所有的三級單知識點
107         ArrayList<String>point_code=new ArrayList<String>();//存儲所有的三級知識點的知識點代碼
108         ArrayList<String>word_list=new ArrayList<String>();//存儲所有分出來的詞的索引
109         HashMap<String, String>point_to_code=new HashMap<String, String>();//存儲三級知識點和其代碼的對應關系
110         HashMap<String, HashMap<String, Integer>>word_to_point=new HashMap<String, HashMap<String,Integer>>();
111           //用於記錄詞在知識點當中出現次數的數據結構(相當於二維表,列為詞,行為知識點)
112         HashMap<String, HashMap<String, Integer>>point_to_word=new HashMap<String, HashMap<String,Integer>>();
113           //用於記錄知識點在詞當中出現次數的數據結構(相當於二維表,列為知識點,行為詞)
114           //以上兩個hashmap的初始化工作將在循環當中進行
115         
116         //獲取停用詞表:stop_list(ArrayList)
117         try {
118             ArrayList<String>stop_list=get_stop_list(stop_path);
119         } catch (IOException e) {
120             // TODO Auto-generated catch block
121             e.printStackTrace();
122         }
123         
124         File excel_file=new File(data_path);
125         ArrayList<String>all_textStrings=new ArrayList<String>();//創建arraylist來存儲所有的題干信息
126         
127         FileInputStream in=new FileInputStream(new File(data_path));
128         Workbook wb1=null;
129         wb1=new StreamingReader(null).builder()
130                 .rowCacheSize(100)
131                 .bufferSize(4096)
132                 .open(in);
133         
134         org.apache.poi.ss.usermodel.Sheet sheet1=wb1.getSheetAt(0);
135         //int firstRowIndex=sheet1.getFirstRowNum()+1;//獲取第一行的index
136         //int lastRowIndex=sheet1.getLastRowNum();//獲取最后一行的index
137         for (Row row:sheet1) {
138             //Row row=sheet1.getRow(i);
139             if (row!=null&&row.getRowNum()!=0) {
140                 Cell text_cell=row.getCell(text_num);//獲取存儲題干信息的單元格
141                 Cell point_cell=row.getCell(knowledge_num);//獲取存儲三級知識點信息的單元格
142                 Cell point_code_cell=row.getCell(knowledge_code_num);//獲取存儲知識點代碼信息的單元格
143                 String temp_text=text_cell.getStringCellValue();
144                 all_textStrings.add(temp_text);//將這個文本添加進arraylist里
145                 String pointString=point_cell.getStringCellValue();//知識點,這里還需要篩選單知識點
146                 String[]tempStrings=pointString.split("\\^\\.\\^");
147                 if (tempStrings.length==1) {
148                     //經過分割之后的長度為1,說明是單知識點,將其添加進arraylist當中
149                     String temp_point=tempStrings[0];
150                     if (knowledge_points.contains(temp_point)==false) {
151                         //若長度為1,並且在arraylist當中沒有出現過,就將其加入其中
152                         knowledge_points.add(temp_point);
153                         point_code.add(point_code_cell.getStringCellValue());
154                         point_to_code.put(temp_point, point_code_cell.getStringCellValue());
155                         //因為是單知識點,所以說可以同時將知識點代碼也添加進去
156                     }
157                 //獲取了無重復的知識點、知識點代碼索引
158                 java.util.List<Term>temp_List=StandardTokenizer.segment(temp_text);
159                     for (int j = 0; j < temp_List.size() ; j++) {
160                         String str_out=temp_List.get(j).toString();
161                         String[] str_splited=str_out.split("/");
162                         if ((word_list.contains(str_splited[0])==false)&&str_splited[1].equals("n")) {
163                             word_list.add(str_splited[0]);
164                         }
165                     }//獲得了所有的分詞得到的詞匯的無重復索引
166                 }
167             }//第一個for循環,獲取了詞匯、知識點、知識點代碼三個索引    
168         }
169         System.out.println("三級知識點總數為(不重復):"+knowledge_points.size());
170         System.out.println("分詞所得詞數為(不重復):"+word_list.size());
171         
172         for (int i = 0; i < word_list.size(); i++) {
173             HashMap<String, Integer>word_to_point_num=new HashMap<String, Integer>();
174             //這里要把HashMap的創建放在外面,否則會導致數據的覆蓋,出現null
175             for (int j = 0; j < knowledge_points.size(); j++) {
176                 word_to_point_num.put(knowledge_points.get(j), 0);
177             }
178             word_to_point.put(word_list.get(i), word_to_point_num);
179         }//至此完成了二維哈希圖的初始化工作,這時數據應當為一張二維表,值全部為0
180         
181         for (int i = 0; i < knowledge_points.size(); i++) {
182             HashMap <String, Integer>point_to_word_num=new HashMap<String, Integer>();
183             for (int j = 0; j < word_list.size(); j++) {
184                 point_to_word_num.put(word_list.get(j), 0);
185             }
186             point_to_word.put(knowledge_points.get(i), point_to_word_num);
187         }//初始化一張二維哈希圖,記錄知識點對應關鍵詞的出現次數
188         
189         //接下來開始再次遍歷excel表來獲取數據
190         File excel_file_again=new File(data_path);
191         FileInputStream in_again=new FileInputStream(excel_file_again);
192         Workbook data_Workbook=null;
193         data_Workbook=new StreamingReader(null).builder()
194                 .rowCacheSize(100)
195                 .bufferSize(4096)
196                 .open(in_again);
197         
198         org.apache.poi.ss.usermodel.Sheet data_Sheet=data_Workbook.getSheetAt(0);
199         for (Row row:data_Sheet) {
200             if(row!=null&&row.getRowNum()!=0) {
201                 //Row row=data_Sheet.getRow(i);
202                 Cell read_point_cell=row.getCell(knowledge_num);
203                 Cell read_text_cell=row.getCell(text_num);
204                 String point_to_check=read_point_cell.getStringCellValue();
205                 String[] temp_Strings=point_to_check.split("\\^\\.\\^");
206                 if (temp_Strings.length==1) {
207                     String temp_point=temp_Strings[0];
208                     String text_to_cut=read_text_cell.getStringCellValue();
209                     java.util.List<Term>temp_Terms=StandardTokenizer.segment(text_to_cut); 
210                     for (int j = 0; j < temp_Terms.size(); j++) {
211                         String strs_to_put=temp_Terms.get(j).toString();
212                         String[]str_striped=strs_to_put.split("/");
213                         String str_to_put=str_striped[0];
214                         if (word_list.contains(str_striped[0])&&str_striped[1].equals("n")) {
215                             //若word_list里存在這個詞,那么就將其出現次數+1
216                             Integer last_value=word_to_point.get(str_to_put).get(temp_point);
217                             Integer an_last_value=point_to_word.get(temp_point).get(str_to_put);
218                             //獲取之前的數值
219                             HashMap<String, Integer>temp_map=new HashMap<String, Integer>();
220                             temp_map=word_to_point.get(str_to_put);
221                             if (temp_map.get(temp_point)!=null) {
222                                 temp_map.put(temp_point, (last_value+1));
223                                 //word_to_point.get(str_to_put).put(temp_point, (last_value+1));
224                                 word_to_point.put(str_to_put, temp_map);
225                                 //將數值修改
226                             }
227                             HashMap<String, Integer>an_temp_map=new HashMap<String, Integer>();
228                             an_temp_map=point_to_word.get(temp_point);
229                             if (an_temp_map.get(str_to_put)!=null) {
230                                 an_temp_map.put(str_to_put, (an_last_value+1));
231                                 point_to_word.put(temp_point, an_temp_map);
232                             }//修改另外一個表的數值
233                             //System.out.println("成功修改:word:"+str_to_put+",在知識點:"+temp_point+" 當中的出現次數為:"+word_to_point.get(str_to_put).get(temp_point)+" 原始值為:"+last_value);
234                         }
235                     }
236                 }
237             }
238         }
239         //寫入txt文件的過程
240         File write_File=new File(result_path1);
241         BufferedWriter out_to_file=new BufferedWriter(new FileWriter(write_File));
242         for (int i = 0; i < word_list.size(); i++) {
243             out_to_file.write("word:"+word_list.get(i)+" 出現情況:");
244             //System.out.print("word:"+word_list.get(i)+" 出現情況:");
245             int appear_num=0;//統計詞頻用
246             for (int j = 0; j < knowledge_points.size(); j++) {
247                 appear_num=appear_num+word_to_point.get(word_list.get(i)).get(knowledge_points.get(j));
248             }
249             out_to_file.write("出現總數:"+appear_num);
250             //System.out.print("出現總數:"+appear_num);
251             if (appear_num!=0) {
252                 for (int k = 0; k < knowledge_points.size(); k++) {
253                     double f=(double)word_to_point.get(word_list.get(i)).get(knowledge_points.get(k))/appear_num;
254                     if (f!=0) {
255                         out_to_file.write("知識點:"+knowledge_points.get(k)+" 出現頻率:"+ f);
256                         //System.out.print("知識點:"+knowledge_points.get(k)+" 出現頻率:"+ f);
257                     }
258                 }
259             }
260             out_to_file.write("\r\n");
261             //System.out.print("\n");
262         }
263         out_to_file.flush();
264         out_to_file.close();
265         
266         File write_File2=new File(result_path2);
267         BufferedWriter out_to_file2=new BufferedWriter(new FileWriter(write_File2));
268         for (int i = 0; i < knowledge_points.size(); i++) {
269             out_to_file2.write("知識點:"+knowledge_points.get(i)+"包含詞語情況:");
270             int appear_num=0;
271             for (int j = 0; j < word_list.size(); j++) {
272                 appear_num=appear_num+point_to_word.get(knowledge_points.get(i)).get(word_list.get(j));
273             }
274             out_to_file2.write("出現總數:"+appear_num);
275             if (appear_num!=0) {
276                 for (int k = 0; k < word_list.size(); k++) {
277                     double f=(double)point_to_word.get(knowledge_points.get(i)).get(word_list.get(k))/appear_num;
278                     if (f!=0) {
279                         out_to_file2.write("詞語:"+word_list.get(k)+"頻率:"+f);
280                     }
281                 }
282             }
283             out_to_file2.write("\r\n");
284         }
285         out_to_file2.flush();
286         out_to_file2.close();
287     }
288     
289     //主方法
290     public static void main( String[] args ) throws IOException
291     {
292         String data_path="src/";//這里是要處理的exel文件名,將其改成你自己的路徑,或者直接放到maven項目的src下即可
293         String stop_path="src/stop_words1.txt";
294         String result_path="src/final_result.txt";
295         String point_to_word_path="src/point_to_word.txt";
296         striped(data_path, stop_path, result_path,point_to_word_path);
297     }
298 }
View Code

4.總結

跟之前的Python代碼思路大體無異,還是要先獲取所有詞語和三級知識點的索引,初始化二維表之后再進行一次遍歷,同時將數據記錄下來。

這次在小組成員的提醒之下,改正了平時編程時的一個壞習慣:總是喜歡使用絕對路徑,這樣把代碼發給別人的時候還要根據別的機子的路徑去修改才能跑通,會給別人帶來麻煩,所以說以后要注意使用項目內的相對路徑。學到了。

最后還是要贊美一下HanLP的開發者,分詞模塊做的真是好!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM