餘弦定理實現新聞自動分類算法

前言

餘弦定理,這個在初中課本中就出現過的公式,恐怕沒有人不知道的吧。可是另一個概念,可能不是不少的人會據說過,他叫空間向量,通常用e表示,高中課本中有專門講過這個東西,有了餘弦定理和向量空間,咱們就能夠作許多有意思的事情了,利用餘弦定理計算文本類似度的算法就是其中一個很典型的例子。固然這個話題太老,說的人太多,沒有什麼新意,恰巧週末閱讀了吳軍博士的<<數學之美>>這門書,書中講到了利用餘弦定理實現新聞分類,因而就索性完成這個算法的初步模型。感興趣的能夠繼續往下看。java

算法背景

在以往,若是對一則新聞進行歸類,通常使用的都是人工分類的辦法,大致上看一下標題和首尾兩段文字,就能知道新聞是屬於財經的,體育的又或者是健康類的。可是在當今信息爆炸的時代,這顯然是不可能完成的任務,因此咱們急切的相用機器本身幫咱們」分類「。最好的形式是我給計算機提供大量的已分類好的數據,等強大的計算機大腦訓練好了這個分類模型,後邊的事情就是他來完成了。看起來這好像很高深,很困難的樣子,可是其實咱們本身也能夠寫一個,只是效果可能不會那麼好。算法

分類器實現原理

新聞自動分類器實現的本質也是利用餘弦定理比較文本的類似度,因而這個問題的難點就在於這個特徵向量哪裏來,怎麼去得到。特徵向量,特徵向量,關鍵兩個字在於特徵,新聞的特徵就在於他的關鍵詞,個人簡單理解就是專業性的詞語,換句話說,就是屬於某類新聞特有的詞語,好比金融類的新聞,關鍵詞通常就是股票啊,公司啊,上市啊等等詞語。這些詞的尋找能夠經過統計詞頻的方式實現,最後統計出來的關鍵詞,進行降序排列,一個關鍵詞就表明一個新的維度。 那麼新的問題又來了,我要統計詞頻,那麼就得首先進行分詞,要把每一個新聞句子的主謂賓通通挖掘出來啊,好像這個工做比我整個算法還要複雜的樣子。OK,其實已經有人已經幫咱們把這個問題解決了,在這個算法中我使用的是中科大的ICTCLAS分詞系統,效果很是棒,舉個例子,下面是我原始的新聞內容:app

 

[java] view plain copyide

 print?工具

  1. 教育部副部長:教育公平是社會公平重要基礎  
  2. 7月23日,教育部黨組副書記、副部長杜玉波爲全國學聯全體表明做《教育綜合改革與青年學生成長成才》的專題報告。 中國青年網記者 張炎良 攝  
  3. 人民網北京7月24日電(記者 賀迎春 實習生 王斯慧  


通過分詞系統處理後的分詞效果:測試

 

 

[java] view plain copyui

 print?this

  1. 教育部/nt 副/b 部長/n :/wm 教育/v 公平/an 是/vshi 社會/n 公平/a 重要/a 基礎/n   
  2. 7月/t 23日/t ,/wd 教育部/nt 黨組/n 副/b 書記/n 、/wn 副/b 部長/n 杜玉波/nr 爲/p 全國學聯/nt 全體/n 表明做/n 《/wkz 教育/vn 綜合/vn 改革/vn 與/cc 青年/n 學生/n 成長/vi 成才/vi 》/wky 的/ude1 專題/n 報告/n 。/wj  中國/ns 青年/n 網/n 記者/n  張/q 炎/ng 良/d  攝/vg   
  3. 人民/n 網/n 北京/ns 7月/t 24日/t 電/n (/wkz 記者/n  賀/vg 迎春/n  實習生/n  王斯慧/nr )/wky 昨日/t ,/wd 教育部/nt 副/b 部長  


OK,有了這個分詞的結果以後,後面的事情就水到渠成了。編碼

 

算法的實現步驟

一、給定訓練的新聞數據集。spa

二、經過分詞系通通計詞頻的方式,統計詞頻最高的N位做爲特徵詞,即特徵向量

三、輸入測試數據,一樣統計詞頻,並於訓練數據的進行商的操做,獲得特徵向量值

四、最後利用餘弦定理計算類似度,並與最小閾值作比較。

算法的代碼實現

ICTCLAS工具類ICTCLAS.Java:

 

[java] view plain copy

 print?

  1. package NewsClassify;  
  2.   
  3. import java.io.File;  
  4. import java.io.FileOutputStream;  
  5. import java.io.InputStream;  
  6. import java.util.StringTokenizer;  
  7.   
  8. public class ICTCLAS50 {  
  9.     static {  
  10.         try {  
  11.             String libpath = System.getProperty("user.dir") + "\\lib";  
  12.             String path = null;  
  13.             StringTokenizer st = new StringTokenizer(libpath,  
  14.                     System.getProperty("path.separator"));  
  15.             if (st.hasMoreElements()) {  
  16.                 path = st.nextToken();  
  17.             }  
  18.   
  19.             // copy all dll files to java lib path  
  20.             File dllFile = null;  
  21.             InputStream inputStream = null;  
  22.             FileOutputStream outputStream = null;  
  23.             byte[] array = null;  
  24.   
  25.             dllFile = new File(new File(path), "ICTCLAS50.dll");  
  26.             if (!dllFile.exists()) {  
  27.                 inputStream = ICTCLAS50.class.getResource("/lib/ICTCLAS50.dll")  
  28.                         .openStream();  
  29.                 outputStream = new FileOutputStream(dllFile);  
  30.                 array = new byte[1024];  
  31.                 for (int i = inputStream.read(array); i != -1; i = inputStream  
  32.                         .read(array)) {  
  33.                     outputStream.write(array, 0, i);  
  34.                 }  
  35.                 outputStream.close();  
  36.             }  
  37.         } catch (Exception e) {  
  38.             e.printStackTrace();  
  39.         }  
  40.   
  41.         try {  
  42.             // load JniCall.dll  
  43.             System.loadLibrary("ICTCLAS50");  
  44.             System.out.println("4444");  
  45.         } catch (Error e) {  
  46.               
  47.             e.printStackTrace();  
  48.         }  
  49.     }  
  50.   
  51.     public native boolean ICTCLAS_Init(byte[] sPath);  
  52.   
  53.     public native boolean ICTCLAS_Exit();  
  54.   
  55.     public native int ICTCLAS_ImportUserDictFile(byte[] sPath, int eCodeType);  
  56.   
  57.     public native int ICTCLAS_SaveTheUsrDic();  
  58.   
  59.     public native int ICTCLAS_SetPOSmap(int nPOSmap);  
  60.   
  61.     public native boolean ICTCLAS_FileProcess(byte[] sSrcFilename,  
  62.             int eCodeType, int bPOSTagged, byte[] sDestFilename);  
  63.   
  64.     public native byte[] ICTCLAS_ParagraphProcess(byte[] sSrc, int eCodeType,  
  65.             int bPOSTagged);  
  66.   
  67.     public native byte[] nativeProcAPara(byte[] sSrc, int eCodeType,  
  68.             int bPOStagged);  
  69. }  

新聞實體類New.java

 

 

[java] view plain copy

 print?

  1. package NewsClassify;  
  2.   
  3. /** 
  4.  * 詞語實體類 
  5.  *  
  6.  * @author lyq 
  7.  *  
  8.  */  
  9. public class Word implements Comparable<Word> {  
  10.     // 詞語名稱  
  11.     String name;  
  12.     // 詞頻  
  13.     Integer count;  
  14.   
  15.     public Word(String name, Integer count) {  
  16.         this.name = name;  
  17.         this.count = count;  
  18.     }  
  19.   
  20.     @Override  
  21.     public int compareTo(Word o) {  
  22.         // TODO Auto-generated method stub  
  23.         return o.count.compareTo(this.count);  
  24.     }  
  25. }  

分類算法類NewsClassify.java:

 

 

[java] view plain copy

 print?

  1. package NewsClassify;  
  2.   
  3. import java.io.BufferedReader;  
  4. import java.io.File;  
  5. import java.io.FileReader;  
  6. import java.io.IOException;  
  7. import java.util.ArrayList;  
  8. import java.util.Collections;  
  9.   
  10. /** 
  11.  * 分類算法模型 
  12.  *  
  13.  * @author lyq 
  14.  *  
  15.  */  
  16. public class NewsClassifyTool {  
  17.     // 餘弦向量空間維數  
  18.     private int vectorNum;  
  19.     // 餘弦類似度最小知足閾值  
  20.     private double minSupportValue;  
  21.     // 當前訓練數據的新聞類別  
  22.     private String newsType;  
  23.     // 訓練新聞數據文件地址  
  24.     private ArrayList<String> trainDataPaths;  
  25.   
  26.     public NewsClassifyTool(ArrayList<String> trainDataPaths, String newsType,  
  27.             int vectorNum, double minSupportValue) {  
  28.         this.trainDataPaths = trainDataPaths;  
  29.         this.newsType = newsType;  
  30.         this.vectorNum = vectorNum;  
  31.         this.minSupportValue = minSupportValue;  
  32.     }  
  33.   
  34.     /** 
  35.      * 從文件中讀取數據 
  36.      */  
  37.     private String readDataFile(String filePath) {  
  38.         File file = new File(filePath);  
  39.         StringBuilder strBuilder = null;  
  40.   
  41.         try {  
  42.             BufferedReader in = new BufferedReader(new FileReader(file));  
  43.             String str;  
  44.             strBuilder = new StringBuilder();  
  45.             while ((str = in.readLine()) != null) {  
  46.                 strBuilder.append(str);  
  47.             }  
  48.             in.close();  
  49.         } catch (IOException e) {  
  50.             e.getStackTrace();  
  51.         }  
  52.   
  53.         return strBuilder.toString();  
  54.     }  
  55.   
  56.     /** 
  57.      * 計算測試數據的特徵向量 
  58.      */  
  59.     private double[] calCharacterVectors(String filePath) {  
  60.         int index;  
  61.         double[] vectorDimensions;  
  62.         double[] temp;  
  63.         News news;  
  64.         News testNews;  
  65.         String newsCotent;  
  66.         String testContent;  
  67.         String parseContent;  
  68.         // 高頻詞彙  
  69.         ArrayList<Word> frequentWords;  
  70.         ArrayList<Word> wordList;  
  71.   
  72.         testContent = readDataFile(filePath);  
  73.         testNews = new News(testContent);  
  74.         parseNewsContent(filePath);  
  75.   
  76.         index = filePath.indexOf('.');  
  77.         parseContent = readDataFile(filePath.substring(0, index) + "-split.txt");  
  78.         testNews.statWords(parseContent);  
  79.   
  80.         vectorDimensions = new double[vectorNum];  
  81.         // 計算訓練數據集的類別的特徵向量  
  82.         for (String path : this.trainDataPaths) {  
  83.             newsCotent = readDataFile(path);  
  84.             news = new News(newsCotent);  
  85.   
  86.             // 進行分詞操做  
  87.             index = path.indexOf('.');  
  88.             parseNewsContent(path);  
  89.             parseContent = readDataFile(path.substring(0, index) + "-split.txt");  
  90.             news.statWords(parseContent);  
  91.   
  92.             wordList = news.wordDatas;  
  93.             // 將詞頻統計結果降序排列  
  94.             Collections.sort(wordList);  
  95.   
  96.             frequentWords = new ArrayList<Word>();  
  97.             // 截取出前vectorDimens的詞語  
  98.             for (int i = 0; i < vectorNum; i++) {  
  99.                 frequentWords.add(wordList.get(i));  
  100.             }  
  101.   
  102.             temp = testNews.calVectorDimension(frequentWords);  
  103.             // 將特徵向量值進行累加  
  104.             for (int i = 0; i < vectorDimensions.length; i++) {  
  105.                 vectorDimensions[i] += temp[i];  
  106.             }  
  107.         }  
  108.   
  109.         // 最後取平均向量值做爲最終的特徵向量值  
  110.         for (int i = 0; i < vectorDimensions.length; i++) {  
  111.             vectorDimensions[i] /= trainDataPaths.size();  
  112.         }  
  113.   
  114.         return vectorDimensions;  
  115.     }  
  116.   
  117.     /** 
  118.      * 根據求得的向量空間計算餘弦類似度值 
  119.      *  
  120.      * @param vectorDimension 
  121.      *            已求得的測試數據的特徵向量值 
  122.      * @return 
  123.      */  
  124.     private double calCosValue(double[] vectorDimension) {  
  125.         double result;  
  126.         double num1;  
  127.         double num2;  
  128.         double temp1;  
  129.         double temp2;  
  130.         // 標準的特徵向量,每一個維度上都爲1  
  131.         double[] standardVector;  
  132.   
  133.         standardVector = new double[vectorNum];  
  134.         for (int i = 0; i < vectorNum; i++) {  
  135.             standardVector[i] = 1;  
  136.         }  
  137.   
  138.         temp1 = 0;  
  139.         temp2 = 0;  
  140.         num1 = 0;  
  141.   
  142.         for (int i = 0; i < vectorNum; i++) {  
  143.             // 累加分子的值  
  144.             num1 += vectorDimension[i] * standardVector[i];  
  145.   
  146.             // 累加分母的值  
  147.             temp1 += vectorDimension[i] * vectorDimension[i];  
  148.             temp2 += standardVector[i] * standardVector[i];  
  149.         }  
  150.   
  151.         num2 = Math.sqrt(temp1) * Math.sqrt(temp2);  
  152.         // 套用餘弦定理公式進行計算  
  153.         result = num1 / num2;  
  154.   
  155.         return result;  
  156.     }  
  157.   
  158.     /** 
  159.      * 進行新聞分類 
  160.      *  
  161.      * @param filePath 
  162.      *            測試新聞數據文件地址 
  163.      */  
  164.     public void newsClassify(String filePath) {  
  165.         double result;  
  166.         double[] vectorDimension;  
  167.   
  168.         vectorDimension = calCharacterVectors(filePath);  
  169.         result = calCosValue(vectorDimension);  
  170.   
  171.         // 若是餘弦類似度值知足最小閾值要求,則屬於目標分類  
  172.         if (result >= minSupportValue) {  
  173.             System.out.println(String.format("最終類似度結果爲%s,大於閾值%s,因此此新聞屬於%s類新聞",  
  174.                     result, minSupportValue, newsType));  
  175.         } else {  
  176.             System.out.println(String.format("最終類似度結果爲%s,小於閾值%s,因此此新聞不屬於%s類新聞",  
  177.                     result, minSupportValue, newsType));  
  178.         }  
  179.     }  
  180.   
  181.     /** 
  182.      * 利用分詞系統進行新聞內容的分詞 
  183.      *  
  184.      * @param srcPath 
  185.      *            新聞文件路徑 
  186.      */  
  187.     private void parseNewsContent(String srcPath) {  
  188.         // TODO Auto-generated method stub  
  189.         int index;  
  190.         String dirApi;  
  191.         String desPath;  
  192.   
  193.         dirApi = System.getProperty("user.dir") + "\\lib";  
  194.         // 組裝輸出路徑值  
  195.         index = srcPath.indexOf('.');  
  196.         desPath = srcPath.substring(0, index) + "-split.txt";  
  197.   
  198.         try {  
  199.             ICTCLAS50 testICTCLAS50 = new ICTCLAS50();  
  200.             // 分詞所需庫的路徑、初始化  
  201.             if (testICTCLAS50.ICTCLAS_Init(dirApi.getBytes("GB2312")) == false) {  
  202.                 System.out.println("Init Fail!");  
  203.                 return;  
  204.             }  
  205.             // 將文件名string類型轉爲byte類型  
  206.             byte[] Inputfilenameb = srcPath.getBytes();  
  207.   
  208.             // 分詞處理後輸出文件名、將文件名string類型轉爲byte類型  
  209.             byte[] Outputfilenameb = desPath.getBytes();  
  210.   
  211.             // 文件分詞(第一個參數爲輸入文件的名,第二個參數爲文件編碼類型,第三個參數爲是否標記詞性集1 yes,0  
  212.             // no,第四個參數爲輸出文件名)  
  213.             testICTCLAS50.ICTCLAS_FileProcess(Inputfilenameb, 0, 1,  
  214.                     Outputfilenameb);  
  215.             // 退出分詞器  
  216.             testICTCLAS50.ICTCLAS_Exit();  
  217.         } catch (Exception ex) {  
  218.             ex.printStackTrace();  
  219.         }  
  220.   
  221.     }  
  222. }  

場景測試了Client.java:

 

 

[java] view plain copy

 print?

  1. package NewsClassify;  
  2.   
  3. import java.util.ArrayList;  
  4.   
  5.   
  6. /** 
  7.  * 新聞分類算法測試類 
  8.  * @author lyq 
  9.  * 
  10.  */  
  11. public class Client {  
  12.     public static void main(String[] args){  
  13.         String testFilePath1;  
  14.         String testFilePath2;  
  15.         String testFilePath3;  
  16.         String path;  
  17.         String newsType;  
  18.         int vectorNum;  
  19.         double minSupportValue;  
  20.         ArrayList<String> trainDataPaths;  
  21.         NewsClassifyTool classifyTool;  
  22.           
  23.         //添加測試以及訓練集數據文件路徑  
  24.         testFilePath1 = "C:\\Users\\lyq\\Desktop\\icon\\test\\testNews1.txt";  
  25.         testFilePath2 = "C:\\Users\\lyq\\Desktop\\icon\\test\\testNews2.txt";  
  26.         testFilePath3 = "C:\\Users\\lyq\\Desktop\\icon\\test\\testNews3.txt";  
  27.         trainDataPaths = new ArrayList<String>();  
  28.         path = "C:\\Users\\lyq\\Desktop\\icon\\test\\trainNews1.txt";  
  29.         trainDataPaths.add(path);  
  30.         path = "C:\\Users\\lyq\\Desktop\\icon\\test\\trainNews2.txt";  
  31.         trainDataPaths.add(path);  
  32.           
  33.         newsType = "金融";  
  34.         vectorNum = 10;  
  35.         minSupportValue = 0.45;  
  36.           
  37.         classifyTool = new NewsClassifyTool(trainDataPaths, newsType, vectorNum, minSupportValue);  
  38.         classifyTool.newsClassify(testFilePath1);  
  39.         classifyTool.newsClassify(testFilePath2);  
  40.         classifyTool.newsClassify(testFilePath3);  
  41.     }  
  42.   
  43. }  

結果輸出:

 

 

[java] view plain copy

 print?

  1. 最終類似度結果爲0.39999999999999997,小於閾值0.45,因此此新聞不屬於金融類新聞  
  2. 最終類似度結果爲0.4635393084189425,大於閾值0.45,因此此新聞屬於金融類新聞  
  3. 最終類似度結果爲0.661835948543857,大於閾值0.45,因此此新聞屬於金融類新聞  
相關文章
相關標籤/搜索