Java實現基於樸素貝葉斯的情感詞分析

樸素貝葉斯(Naive Bayesian)是一種基於貝葉斯定理和特徵條件獨立假設的分類方法,它是基於機率論的一種有監督學習方法,被普遍應用於天然語言處理,並在機器學習領域中佔據了很是重要的地位。在以前作過的一個項目中,就用到了樸素貝葉斯分類器,將它應用於情感詞的分析處理,並取得了不錯的效果,本文咱們就來介紹一下樸素貝葉斯分類的理論基礎和它的實際使用。java

在學習樸素貝葉斯分類以及正式開始情感詞分析以前,咱們首先須要瞭解一下貝葉斯定理的數學基礎。算法

貝葉斯定理

貝葉斯定理是關於隨機事件A和B的條件機率的定理,公式以下:shell

在上面的公式中,每一項表示的意義以下:app

  • P(A):先驗機率(prior probability),是在沒有任何條件限制下事件A發生的機率,也叫基礎機率,是對A事件機率的一個主觀判斷
  • P(A|B) :在B發生的狀況下A發生的可能性,也被稱爲A的後驗機率(posterior probability
  • P(B|A):似然性,也被稱爲條件似然(conditional likelihood
  • P(B):不論A是否發生,在全部狀況下B發生的機率,它被稱爲總體似然或歸一化常量(normalizing constant

按照上面的解釋,貝葉斯定理能夠表述爲:機器學習

後驗機率 = 先驗機率 * 似然性 / 歸一化常量函數

通俗的來講,能夠理解爲當咱們不能肯定某一個事件發生的機率時,能夠依靠與該事件本質屬性相關的事件發生的機率去推測該事件發生的機率。用數學語言來表達就是,支持某項屬性的事件發生得愈多,則該事件發生的的可能性就愈大,這個推理過程也被叫作貝葉斯推理。post

在查閱的一些文檔中,P(B|A)/P(B) 能夠被稱爲可能性函數,它做爲一個調整因子,表示新信息B對事件A帶來的調整,做用是將先驗機率(主觀判斷)調整到更接近真實的機率。那麼,貝葉斯定理也能夠理解爲:學習

新信息出現後A的機率 = A的先驗機率 * 新信息帶來的調整測試

舉一個例子,方便你們更直觀的理解這一過程。假設統計了一段時間內天氣和睦溫對於運動狀況的影響,以下所示:優化

天氣	氣溫		運動
晴天	很是高		游泳
晴天	高		足球		
陰天	中		釣魚
陰天	中		游泳
晴天	低		游泳		
陰天	低		釣魚

如今請計算在晴天,氣溫適中的狀況下,去游泳的機率是多少?根據貝葉斯定理,計算過程以下:

P(游泳|晴天,中溫)=P(晴天,中溫|游泳)*P(游泳)/P(晴天,中溫)
		=P(晴天|游泳)*P(中溫|游泳)*P(游泳)/[P(晴天)*P(中溫)]
		=2/3 * 1/3 *1/2 / (1/2 *1/3 )
		=2/3

最終得出去游泳的機率時2/3,上面就是基於貝葉斯定理,根據給定的特徵,計算事件發生機率大小的過程。

貝葉斯分析的思路對於由證據的積累來推測一個事物的發生的機率具備重大做用,當咱們要預測一個事物,首先會根據已有的經驗和知識推斷一個先驗機率,而後在新證據不斷的積累的狀況下調整這個機率。整個經過累積證據來獲得一個事件發生機率的過程咱們稱爲貝葉斯分析。這樣,貝葉斯底層的思想就能夠歸納爲,若是可以掌握一個事情的所有信息,就可以計算出它的一個客觀機率。

另外,在貝葉斯公式的基礎上進行變形,能夠獲得下面的公式:

其中B1,B2,…,Bj是一個完備事件組,上面的公式能夠表示在事件A已經發生的條件下,尋找致使A發生的各類「緣由」的Bi的機率。

樸素貝葉斯

在學習樸素貝葉斯以前,首先須要對貝葉斯分類進行一下了解,貝葉斯分類經過預測一個對象屬於某個類別的機率,經過比較不一樣類別機率的大小預測其最可能從屬的類別,是基於貝葉斯定理而構成出來的。在處理大規模數據集時,貝葉斯分類器表現出較高的分類準確性。

貝葉斯分類在處理一個未知類型的樣本X時,能夠先算出X屬於每個類別Ci的機率 P(Ci|X),而後選擇其中機率最大的類別。假設有兩個特徵變量x和y,而且存在兩個分類類別C1和C2,結合貝葉斯定理:

  • 若是P(C1|x,y) > P(C2|x,y),說明在x和y發生的條件下,C1比C2發生的機率要大,那麼它應該屬於類別C1
  • 反之若是P(C1|x,y) < P(C2|x,y),那麼它應該屬於類別C2

而樸素貝葉斯模型(Naive Bayesian Model)做爲一種強大的預測建模算法,它在貝葉斯定理的基礎上進行了簡化,假定了目標的特徵屬性之間相互獨立,這也是它被形容爲「樸素」的緣由。在實際狀況中若是屬性之間存在關聯,那麼分類準確率會下降,不過對於解決絕大部分的複雜問題很是有效。

設在樣本數據集D上,樣本數據的特徵屬性集爲X={x1,x2,…,xd},類變量可被分爲 ​Y={y1,y2,…,ym},即數據集D能夠被分爲ym個類別。咱們假設x1,x2,…,xd​相互獨立,那麼由貝葉斯定理可得:

對於相同的測試樣本,分母P(X)的大小是固定不變的,所以在比較後驗機率時,咱們能夠只比較分子的大小便可。

在這裏解釋一下貝葉斯定理、貝葉斯分類和樸素貝葉斯之間的區別,貝葉斯定理做爲理論基礎,解決了機率論中的逆機率問題,在這個基礎上人們設計出了貝葉斯分類器,而樸素貝葉斯是貝葉斯分類器中的一種,也是最簡單和經常使用的分類器,可使用下面的圖來表示它們之間的關係:

在實際應用中,樸素貝葉斯有普遍的應用,在文本分類、垃圾郵件過濾、情感預測及釣魚網站的檢測方面都可以起到良好的效果。爲了訓練樸素貝葉斯模型,咱們須要先在訓練集的基礎上對分類好的數據進行訓練,計算出先驗機率和每一個屬性的條件機率,計算完成後,機率模型就可使用貝葉斯原理對新數據進行預測。

貝葉斯推斷與人腦的工做機制很像,這也是它爲何可以成爲機器學習的基礎,大腦的決策過程就是先對事物進行主觀判斷,而後蒐集新的信息,優化主觀判斷,若是新的信息符合這個主觀判斷,那就提升主觀判斷的可信度,若是不符合,就下降主觀判斷的可信度。

代碼實現

在對理論有了基本的瞭解後,咱們開始分析怎樣將樸素貝葉斯應用於咱們文本處理的情感詞分析中。主要步驟以下:

  • 對訓練集和測試集完成文本分詞,並經過主觀判斷標註所屬的分類
  • 對訓練集進行訓練,統計每一個詞彙出如今分類下的次數,計算每一個類別在訓練樣本中的出現頻率、及每一個特徵屬性對每一個類別的條件機率(即似然機率)
  • 將訓練好的模型應用於測試集的樣本上,根據貝葉斯分類計算樣本在每一個分類下的機率大小
  • 比較在各個分類狀況下的機率大小,推測文本最可能屬於的情感分類

使用流程圖表示:

一、準備階段

首先準備數據集,這裏使用了對某酒店的評論數據,根據主觀態度將其分爲「好評」或「差評」這兩類待分類項,對每行分詞後的語句打好了情感標籤,而且已經提早對完整語句完成了對分詞,數據格式以下:

在每行的數據的頭部,是添加的「好評」或「差評」標籤,標籤與分詞采用tab分割,詞語之間使用空格分割。按照比例,將數據集的80%做爲訓練集,剩餘20%做爲測試集,分配過程儘可能保證隨機原則。

二、訓練階段

在訓練階段,主要完成詞頻的統計工做。讀取訓練集,統計出每一個詞屬於該分類下出現的次數,用於後續求解每一個詞出如今各個類別下的機率,即詞彙與主觀分類情感之間的關係:

private static void train(){
    Map<String,Integer> parameters = new HashMap<>();
    try(BufferedReader br = new BufferedReader(new FileReader(trainingData))){  //訓練集數據
        String sentence;
        while(null!=(sentence=br.readLine())){
            String[] content = sentence.split("\t| "); //以tab或空格分詞
            parameters.put(content[0],parameters.getOrDefault(content[0],0)+1);
            for (int i = 1; i < content.length; i++) {
                parameters.put(content[0]+"-"+content[i], parameters.getOrDefault(content[0]+"-"+content[i], 0)+1);
            }
        }
    }catch (IOException e){
        e.printStackTrace();
    }
    saveModel(parameters);
}

將訓練好的模型保存到文件中,能夠方便在下次使用時不用重複進行模型的訓練:

private static void saveModel(Map<String,Integer> parameters){
    try(BufferedWriter bw =new BufferedWriter(new FileWriter(modelFilePath))){
        parameters.keySet().stream().forEach(key->{
            try {
                bw.append(key+"\t"+parameters.get(key)+"\r\n");
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        bw.flush();
    }catch (IOException e){
        e.printStackTrace();
    }
}

查看保存好的模型,數據的格式以下:

好評-免費送	3
差評-真煩	1
好評-禮品	3
差評-髒亂差	6
好評-解決	15
差評-挨宰	1
……

這裏對訓練的模型進行保存,因此若是後續有一樣的分類任務時,能夠直接在訓練集的基礎上進行計算,對於分類速度要求較高的任務,可以有效的提升計算的速度。

三、加載模型

加載訓練好的模型:

private static HashMap<String, Integer> parameters = null;  //用於存放模型
private static Map<String, Double> catagory=null;
private static String[] labels = {"好評", "差評", "總數","priorGood","priorBad"};

private static void loadModel() throws IOException {
    parameters = new HashMap<>();
    List<String> parameterData = Files.readAllLines(Paths.get(modelFilePath));
    parameterData.stream().forEach(parameter -> {
        String[] split = parameter.split("\t");
        String key = split[0];
        int value = Integer.parseInt(split[1]);
        parameters.put(key, value);
    });

    calculateCatagory(); //分類
}

對詞進行分類,統計出好評及差評的詞頻總數,並基於它們先計算得出先驗機率:

//計算模型中類別的總數
public static void calculateCatagory() {
    catagory = new HashMap<>();
    double good = 0.0; //好評詞頻總數
    double bad = 0.0;   //差評的詞頻總數
    double total;   //總詞頻

    for (String key : parameters.keySet()) {
        Integer value = parameters.get(key);
        if (key.contains("好評-")) {
            good += value;
        } else if (key.contains("差評-")) {
            bad += value;
        }
    }
    total = good + bad;
    catagory.put(labels[0], good);
    catagory.put(labels[1], bad);
    catagory.put(labels[2], total);
    catagory.put(labels[3],good/total); //好評先驗機率
    catagory.put(labels[4],bad/total);	//差評先驗機率
}

查看執行完後的統計值:

「好評」對應的詞彙出現的總次數是46316個,「差評」對應的詞彙出現的總次數是77292個,訓練集詞頻總數爲123608個,並可基於它們計算出它們的先驗機率:

該文檔屬於某個類別的條件機率= 該類別的全部詞條詞頻總數 / 全部詞條的詞頻總數

四、測試階段

測試階段,加載咱們提早準備好的測試集,對每一行分詞後的評論語句進行主觀情感的預測:

private static void predictAll() {
    double accuracyCount = 0.;//準確個數
    int amount = 0;    //測試集數據總量

    try (BufferedWriter bw = new BufferedWriter(new FileWriter(outputFilePath))) {
        List<String> testData = Files.readAllLines(Paths.get(testFilePath));    //測試集數據
        for (String instance : testData) {
            String conclusion = instance.substring(0, instance.indexOf("\t"));  //已經打好的標籤
            String sentence = instance.substring(instance.indexOf("\t") + 1);
            String prediction = predict(sentence);  //預測結果

            bw.append(conclusion + " : " + prediction + "\r\n");
            if (conclusion.equals(prediction)) {
                accuracyCount += 1.;
            }
            amount += 1;
        }
        //計算準確率
        System.out.println("accuracyCount: " + accuracyCount / amount);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

在測試中,調用下面的predict方法進行分類判斷。在計算前,再來回顧一下上面的公式,在程序中進行簡化運算:

對於同一個預測樣本,分母相同,因此咱們能夠只比較分子的大小。對分子部分進行進一步簡化,對於連乘預算,咱們能夠對其進行對數操做,變成各部分相加:

這樣對於機率的大小比較,就能夠簡化爲比較 先驗機率和各個似然機率分別取對數後相加的和。先驗機率咱們在以前的步驟中已經計算完成並保存,因此這裏只計算各詞彙在分類條件下的似然機率便可。predict方法的實現以下:

private static String predict(String sentence) {
    String[] features = sentence.split(" ");
    String prediction;

    //分別預測好評和差評
    double good = likelihoodSum(labels[0], features) + Math.log(catagory.get(labels[3]));
    double bad = likelihoodSum(labels[1], features) + Math.log(catagory.get(labels[4]));
    return good >= bad?labels[0]:labels[1];
}

在其中調用likelihood方法計算似然機率的對數和:

//似然機率的計算
public static double likelihoodSum(String label, String[] features) {
    double p = 0.0;
    Double total = catagory.get(label) + 1;//分母平滑處理
    for (String word : features) {
        Integer count = parameters.getOrDefault(label + "-" + word, 0) + 1;//分子平滑處理
        //計算在該類別的狀況下是該詞的機率,用該詞的詞頻除以類別的總詞頻
        p += Math.log(count / total);
    }
    return p;
}

在計算似然機率的方法中,若是出如今訓練集中沒有包括的詞彙,那麼會出現它的似然機率爲0的狀況,爲了防止這種狀況,對分子分母進行了分別加一的平滑操做。

最後在主函數中調用上面的步驟,最終若是計算出基於樣本的好評機率大於等於差評機率,那麼將它分類劃入「好評」,反之劃入「差評」類別,到此就完成了訓練和測試的全過程:

public static void main(String[] args) throws IOException {
    train();
    loadModel();
    predictAll();
}

執行所有代碼,結果以下,能夠看到獲取了93.35%的準確率。

對比最後輸出的文檔中的標籤與預測結果,能夠看到,預測結果的準確度仍是很是高的。

五、總結

在上面的例子中,還有一些能夠進行改進的地方,例如能夠在前期創建情感詞庫,在特徵值提取的過程當中只提取情感詞,去除其他無用詞彙(如介詞等)對分類的影響,只選取關鍵的表明詞做爲特徵提取,達到更高的分類效率。另外,能夠在創建詞庫時,將測試集的情感詞也放入詞庫,避免出如今某個分類條件下似然機率爲零的狀況,簡化平滑步驟。

此外,樸素貝葉斯的分類對於追加訓練集的狀況有很好的應用,若是訓練集不斷的增長,能夠在現有訓練模型的基礎上添加新的樣本值、或對已有的樣本值的屬性進行修改,在此基礎上,能夠實現增量的模型修改。

若是文章對您有所幫助,歡迎關注公衆號 碼農參上

相關文章
相關標籤/搜索