1. 程式人生 > >資料探勘:基於樸素貝葉斯分類演算法的文字分類實踐

資料探勘:基於樸素貝葉斯分類演算法的文字分類實踐

前言:

  如果你想對一個陌生的文字進行分類處理,例如新聞、遊戲或是程式設計相關類別。那麼貝葉斯分類演算法應該正是你所要找的了。貝葉斯分類演算法是統計學中的一種分類方法,它利用概率論中的貝葉斯公式進行擴充套件。所以,這裡建議那些沒有概率功底或是對概率論已經忘記差不多的讀者可以先去學習或是溫習一下《概率論與數理統計》中的條件概率那一個章節。

  由於貝葉斯定理假設一個屬性值對給定類的影響獨立於其它屬性的值,而此假設在實際情況中經常是不成立的,因此其分類準確率可能會下降。為此,就衍生出許多降低獨立性假設的貝葉斯分類演算法,如TAN(tree augmented Bayes network)演算法。關於TAN演算法不在本文的敘述範圍之內,這裡我們不作討論。

  下面我們就針對樸素貝葉斯分類演算法,進行原理淺析和文字分類實踐(這裡筆者使用Java語言開發)。

                                                                 --轉載請註明出處

公式說明:

1.全概率公式:

2.貝葉斯公式:


上面的兩個公式是最簡單的兩個公式說明,旨在簡化理解。

思路分析:

  在開始理解貝葉斯演算法之前,獨立於本文之外。如果有人問你如何讓程式給一篇陌生的文章分類?你要怎麼做呢?

  我能想到的就是以關鍵詞來區分。比如分類為郵箱的類別中,我選取三個關鍵詞:郵箱、郵件和收發。然後使用這三個關鍵詞在文章中去依次查詢,統計出此三個關鍵詞總共出現了多少次,再與其他的類別進行比較。次數最多的即為這篇文章的分類。如果要再精確一些,可以採用不同權重的方式,上面說的方法,權重為1。如果採用權重的策略,那麼這裡就不是出現次數最多的類別了,而是以計分的方式,次數*權重並累加。最高分的類別即為本文的分類。

  當然,這是一種解決方法。這個其實跟貝葉斯分類演算法還是有一些類似的,有了這種想法,再去理解貝葉斯就容易得多了。

流程說明:

樸素貝葉斯分類的流程可以由下圖表示(圖片來源網路):


  針對上圖有一些需要說明的地方,首先這張流程圖的確可以很清楚地表達我們樸素貝葉斯模型的流程。需要注意的是,這裡如果P(x|yi)中的x如果是在訓練集中不存在的一個特徵值,我們是無法進行計算它的先驗概率的。不過還好,因為x在訓練中不存在,那麼我們就可以粗略認為,x是一個與yi無關的值,即概率為0。

程式碼展示:

1.準備階段:

  在準備階段有兩個步驟,確定特徵屬性和獲取樣本。確定特徵屬性這個會因個人對分類的理解以及需求不同而不同;而獲取樣本則是比較簡單的讀取檔案。如下:

/**
     * 讀取訓練文件中的訓練資料
     * 並進行封裝
     * 
     * @param filePath
     *          訓練文件的路徑
     * @return
     *          訓練資料集
     */
    public static ArrayList<ArrayList<String>> read(String filePath) {
        if (Tools.isEmptyString(filePath)) {
            return null;
        }
 
        ArrayList<ArrayList<String>> trainningSet = new  ArrayList<ArrayList<String>>();
        List<String> datas = readFile(filePath);
        ArrayList<String> singleTrainning = null;
        for (int i = 0; i < datas.size(); i++) {
            String[] characteristicValues = datas.get(i).split(" ");
            singleTrainning = new ArrayList<String>();
            for (int j = 0; j < characteristicValues.length; j++) {
                if (!Tools.isEmptyString(characteristicValues[j])) {
                    singleTrainning.add(characteristicValues[j]);
                }
            }
            
            trainningSet.add(singleTrainning);
        }
        
        return trainningSet;
    }

2.訓練階段:

  在訓練階段,我們就是預先計算出一些先驗概率,這些先驗概率是與待計算的特徵值x無關的。不關這個x是否在訓練集中存在,都是無關的,這個在前面已經說過了。那麼先驗概率主要有P(classify),P(key)和P(key|classify)。

  P(classify):

/**
     * 預先計算出每個分類出現的概率
     * 
     * @param map
     *          所有分類總的資料集
     * @param classifyProbablityMap
     *          每個分類classify的出現概率
     */
    public void preCalculateClassifyProbablity(Map<String, ArrayList<ArrayList<String>>> map, Map<String, Double> classifyProbablityMap) {
        if (map == null || classifyProbablityMap == null) {
            return;
        }
        
        Object[] classes = map.keySet().toArray();
        int totleClassifyCount = 0;
        for (int i = 0; i < classes.length; i++) {
            totleClassifyCount += map.get(classes[i].toString()).size();
        }
        
        if (totleClassifyCount == 0) {
            return;
        }
        
        for (int i = 0; i < classes.length; i++) {
            if (!classifyProbablityMap.containsKey(classes[i])) {
                classifyProbablityMap.put(classes[i].toString(), 1.0 * map.get(classes[i]).size() / totleClassifyCount);
            }
        }
    }
  P(key):
/**
     * 預先計算出每個關鍵字出現的概率
     * TODO
     * @param map
     *          所有分類總的資料集
     * @param keyProbablityMap
     *          每個特徵值key的出現概率
     */
    public void preCalculateKeyProbablity(Map<String, ArrayList<ArrayList<String>>> map, Map<String, Double> keyProbablityMap) {
        if (map == null || keyProbablityMap == null) {
            return;
        }
        
        Object[] classes = map.keySet().toArray();
        String key = "";
        int totleKeyCount = 0;
        for (int i = 0; i < map.size(); i++) {
            ArrayList<ArrayList<String>> classify = map.get(classes[i]);
            ArrayList<String> featureVector = null; // 分類中的某一特徵向量
            for (int j = 0; j < classify.size(); j++) {
                featureVector = classify.get(j);
                for (int k = 0; k < featureVector.size(); k++) {
                    key = featureVector.get(k);
                    totleKeyCount++;
                    if (keyProbablityMap.get(key) == null) {
                        keyProbablityMap.put(key, 1.0);
                    } else {
                        keyProbablityMap.replace(key, keyProbablityMap.get(key) + 1.0);
                    }
                }
            }
        }
        
        if (totleKeyCount == 0) {
            return;
        }
        
        Set<String> keys = keyProbablityMap.keySet();
        for (String string : keys) {
            keyProbablityMap.replace(string, keyProbablityMap.get(string) / totleKeyCount);
        }
    }
  P(key|classify):
/**
     * 計算先驗概率P(key|classify)
     * 
     * @param map
     *          所有分類總的資料集
     * @param keyClassifyMap
     *          先驗概率P(key|classify)的所有資料集
     */
    public void preCalculateKeyInClassifyProbablity(Map<String, ArrayList<ArrayList<String>>> map, Map<String, Map<String, Double>> keyClassifyMap) {
        if (map == null || keyClassifyMap == null) {
            return;
        }
        
        // 統計每種分類共有多少個特徵值
        Map<String, Double> keyCountMap = new HashMap<String, Double>();
        
        // 統計key|classify的個數
        Object[] classes = map.keySet().toArray();
        Map<String, Double> vector = null;
        for (int i = 0; i < map.size(); i++) {
            ArrayList<ArrayList<String>> classify = map.get(classes[i]);
            for (int j = 0; j < classify.size(); j++) {
                ArrayList<String> featureVector = classify.get(j);
                for (int k = 0; k < featureVector.size(); k++) {
                    // 統計特徵值
                    if (keyClassifyMap.containsKey(classes[i])) {
                        if (keyClassifyMap.get(classes[i]).containsKey(featureVector.get(k))) {
                            double lastValue = keyClassifyMap.get(classes[i]).get(featureVector.get(k));
                            vector = keyClassifyMap.get(classes[i]);
                            vector.put(featureVector.get(k), 1.0 + lastValue);
                            keyClassifyMap.replace(classes[i].toString(), vector);
                        } else {
                            vector = keyClassifyMap.get(classes[i]);
                            vector.put(featureVector.get(k), 1.0);
                            keyClassifyMap.put(classes[i].toString(), vector);
                        }
                    } else {
                        vector = new HashMap<String, Double>();
                        vector.put(featureVector.get(k), 1.0);
                        keyClassifyMap.put(classes[i].toString(), vector);
                    }
                    
                    // 統計每種分類共有多少個特徵值 keyCountMap
                    if (keyCountMap.containsKey(classes[i])) {
                        keyCountMap.put(classes[i].toString(), 1.0 + keyCountMap.get(classes[i]));
                    } else {
                        keyCountMap.put(classes[i].toString(), 1.0);
                    }
                }
            }
        }
        
        // 遍歷keyClassifyMap計算概率
        Map<String, Double> keyVector = null;
        Object[] keys = null;
        for (int i = 0; i < keyClassifyMap.size(); i++) {
            keyVector = keyClassifyMap.get(classes[i]);
            keys = keyVector.keySet().toArray();
            for (int j = 0; j < keyVector.size(); j++) {
                keyVector.put(keys[j].toString(), keyVector.get(keys[j]) / keyCountMap.get(classes[i]));
            }
            
            keyClassifyMap.put(classes[i].toString(), keyVector);
        }
    }

3.應用階段:

  對於貝葉斯的應用,即是針對上面的貝葉斯公式進行的。即計算P(classify|key)=?.

  也就是說,在特徵值為key時,分類為classify的概率為多少?這是我們所求的。這一步很簡單,只要我們拿到公式右邊的三個概率值,就可以計算出貝葉斯公式左邊的值:

/**
     * 計算在出現key的情況下,是分類classify的概率 [ P(Classify | key) ]
     * 
     * @param map
     *          所有分類的資料集
     * @param classify
     *          某一特定分類
     * @param key
     *          某一特定特徵
     * @return
     *          P(Classify | key)
     */
    private double calProbabilityClassificationInKey(Map<String, ArrayList<ArrayList<String>>> map, Map<String, Double> classPMap, Map<String, Double> keyPMap, Map<String, Map<String, Double>> keyClassifyMap, String classify, String key) {
        double pkc = (keyClassifyMap.get(classify).containsKey(key) ? keyClassifyMap.get(classify).get(key) : 0); // p(key|classify)
        double pc = classPMap.get(classify); // p(classify)
        double pk = keyPMap.get(key) == null ? 0 : keyPMap.get(key); // p(key)
        double pck = 0.0; // p(classify | key)
        
        if (pk == 0) {
            pck = 0;
        } else {
            pck = (pkc * pc / pk) * pk;
        }
        
        return pck;
    }
  以上就是本文關於貝葉斯分類演算法的全部內容。如有疑問可以留言,大家一起討論學習。

參考:

1.《概率論與數理統計》(第四版) 浙大版

2.《資料之美》

附件原始碼:

下面的程式碼是最初的一個版本,大家可以結合本文對程式碼進行修改。