使用深度學習模型在 Java 中執行文字情感分析

語言: CN / TW / HK

積極的? 消極的? 中性的? 使用斯坦福 CoreNLP 元件以及幾行程式碼便可對句子進行分析。

本文介紹如何使用整合到斯坦福 CoreNLP(一個用於自然語言處理的開源庫)中的情感工具在 Java 中實現此類任務。

斯坦福 CoreNLP 情感分類器

要執行情感分析,您需要一個情感分類器,這是一種可以根據從訓練資料集中學習的預測來識別情感資訊的工具。

在斯坦福 CoreNLP 中,情感分類器建立在遞迴神經網路 (RNN) 深度學習模型之上,該模型在斯坦福情感樹庫 (SST) 上進行訓練。

SST 資料集是一個帶有情感標籤的語料庫,從數千個使用的句子中推匯出每個句法上可能的短語,從而允許捕獲文字中情感的構成效果。簡單來說,這允許模型根據單詞如何構成短語的含義來識別情緒,而不僅僅是通過孤立地評估單詞。

為了更好地瞭解 SST 資料集的結構,您可從斯坦福 CoreNLP 情感分析頁面下載資料集檔案。

在 Java 程式碼中,Stanford CoreNLP 情感分類器使用如下。

首先,您通過新增執行情感分析所需的註釋器(例如標記化、拆分、解析和情感)來構建文字處理管道。 就斯坦福 CoreNLP 而言,註釋器是一個對註釋物件進行操作的介面,其中後者表示文件中的一段文字。 例如,需要使用 ssplit 註釋器將標記序列拆分為句子。

斯坦福 CoreNLP 以每個句子為基礎計算情緒。 因此,將文字分割成句子的過程始終遵循應用情感註釋器。

一旦文字被分成句子,解析註釋器就會執行句法依賴解析,為每個句子生成一個依賴表示。 然後,情感註釋器處理這些依賴表示,將它們與底層模型進行比較,以構建帶有每個句子的情感標籤(註釋)的二值化樹。

簡單來說,樹的節點由輸入句子的標記確定,幷包含註釋,指示從句子匯出的所有短語的從非常消極到非常積極的五個情感類別中的預測類別。 基於這些預測,情感註釋器計算整個句子的情感。

設定斯坦福 CoreNLP

在開始使用斯坦福 CoreNLP 之前,您需要進行以下設定:

要執行斯坦福 CoreNLP,您需要 Java 1.8 或更高版本。

下載 Stanford CoreNLP 包並將該包解壓縮到您機器上的本地資料夾中。

下載地址:

https://nlp.stanford.edu/software/stanford-corenlp-latest.zip

本文以將上述程式碼解壓到如下目錄為例:

c:/softwareInstall/corenlp/stanford-corenlp-4.3.2

完成上述步驟後,您就可以建立執行斯坦福 CoreNLP 管道來處理文字的 Java 程式了。

首先新建一個maven專案,並手動將stanford-corenlp-4.3.2新增到Libraries中:

在以下示例中,您將實現一個簡單的 Java 程式,該程式執行斯坦福 CoreNLP 管道,以對包含多個句子的文字進行情感分析。

首先,實現一個NlpPipeline類,該類提供初始化管道的方法和使用此管道將提交的文字拆分為句子然後對每個句子的情感進行分類的方法。 下面是NlpPipeline類程式碼:

package com.zh.ch.corenlp; 
 
import edu.stanford.nlp.ling.CoreAnnotations; 
import edu.stanford.nlp.neural.rnn.RNNCoreAnnotations; 
import edu.stanford.nlp.pipeline.Annotation; 
import edu.stanford.nlp.pipeline.StanfordCoreNLP; 
import edu.stanford.nlp.sentiment.SentimentCoreAnnotations; 
import edu.stanford.nlp.trees.Tree; 
import edu.stanford.nlp.util.CoreMap; 
 
import java.util.Properties; 
 
public class NlpPipeline { 
    StanfordCoreNLP pipeline = null; 
    public  void init() 
    { 
        Properties props = new Properties(); 
        props.setProperty("annotators", "tokenize, ssplit, parse, sentiment"); 
        pipeline = new StanfordCoreNLP(props); 
    } 
    public void estimatingSentiment(String text) 
    { 
        int sentimentInt; 
        String sentimentName; 
        Annotation annotation = pipeline.process(text); 
        for(CoreMap sentence : annotation.get(CoreAnnotations.SentencesAnnotation.class)) 
        { 
            Tree tree = sentence.get(SentimentCoreAnnotations.SentimentAnnotatedTree.class); 
            sentimentInt = RNNCoreAnnotations.getPredictedClass(tree); 
            sentimentName = sentence.get(SentimentCoreAnnotations.SentimentClass.class); 
            System.out.println(sentimentName + "\t" + sentimentInt + "\t" + sentence); 
        } 
    } 
} 

init() 方法初始化StanfordCoreNLP 管道,它還初始化使用該情感工具所需的分詞器、依賴解析器和句子拆分器。 要初始化管道,請將帶有相應註釋器列表的 Properties 物件傳遞給 StanfordCoreNLP() 建構函式。 這將建立一個定製的管道,準備好對文字執行情感分析。

在NlpPipeline類的estimatingSentiment()方法中,呼叫之前建立的管道物件的process()方法,傳入文字進行處理。 process() 方法返回一個註釋物件,該物件儲存對提交的文字的分析。

接下來,迭代註釋物件,在每次迭代中獲得一個句子級 CoreMap 物件。對於這些物件中的每一個,獲取一個包含用於確定底層句子情緒的情緒註釋的 Tree 物件。

將 Tree 物件傳遞給 RNNCoreAnnotations 類的 getPredictedClass() 方法,以提取對應句子的預測情緒的編號程式碼。然後,獲取預測情緒的名稱並列印結果。

要測試上述功能,請使用呼叫 init() 方法的 main() 方法實現一個類,然後呼叫 nlpPipeline 類的 estimatingSentiment() 方法,將示例文字傳遞給後者。

在以下實現中,為了簡單起見,直接指定text文字。示例句子旨在涵蓋斯坦福 CoreNLP 可用的整個情緒評分範圍:非常積極、積極、中立、消極和非常消極。

package com.zh.ch.corenlp; 
 
import java.io.FileReader; 
import java.io.IOException; 
 
public class Main { 
     
    static NlpPipeline nlpPipeline = null; 
 
    public static void processText(String text) { 
        nlpPipeline.estimatingSentiment(text); 
    } 
 
    public static void main(String[] args) { 
        String text = "This is an excellent book. I enjoy reading it. I can read on Sundays. Today is only Tuesday. Can't wait for next Sunday. The working week is unbearably long. It's awful."; 
        nlpPipeline  = new NlpPipeline(); 
        nlpPipeline.init(); 
        processText(text); 
    } 
 
} 

執行結果:

分析線上客戶評論

正如您從前面的示例中瞭解到的,Stanford CoreNLP 可以返回句子的情緒。 然而,有許多用例需要分析多段文字的情緒,每段文字可能包含不止一個句子。 例如,您可能想要分析來自電子商務網站的推文或客戶評論的情緒。

要使用斯坦福 CoreNLP 計算多句文字樣本的情緒,您可能會使用幾種不同的技術。

在處理推文時,您可能會分析推文中每個句子的情緒,如果有一些正面或負面的句子,您可以分別對整個推文進行排名,忽略帶有中性情緒的句子。 如果推文中的所有(或幾乎所有)句子都是中性的,則該推文可以被列為中性。

然而,有時您甚至不必分析每個句子來估計整個文字的情緒。 例如,在分析客戶評論時,您可以依賴他們的標題,標題通常由一個句子組成。

要完成以下示例,您需要一組客戶評論。 您可以使用本文隨附的 NlpBookReviews.csv 檔案中的評論。 該檔案包含在 Amazon Review Export 的幫助下從 Amazon 網頁下載的一組實際評論,這是一個 Google Chrome 瀏覽器擴充套件程式,允許您將產品評論及其標題和評級下載到逗號分隔值 (CSV) 檔案中 . (您可以使用該工具探索一組不同的評論以進行分析。)

將下述程式碼新增到NlpPipeline中

public String findSentiment(String text) { 
        int sentimentInt = 2; 
        String sentimentName = "NULL"; 
        if (text != null && text.length() > 0) { 
            Annotation annotation = pipeline.process(text); 
            CoreMap sentence = annotation 
                    .get(CoreAnnotations.SentencesAnnotation.class).get(0); 
            Tree tree = sentence 
                    .get(SentimentCoreAnnotations.SentimentAnnotatedTree.class); 
            sentimentInt = RNNCoreAnnotations.getPredictedClass(tree); 
            sentimentName = sentence.get(SentimentCoreAnnotations.SentimentClass.class); 
        } 
        return sentimentName; 
    } 

您可能會注意到,上面的程式碼類似於上一節中定義的 estimatingSentiment() 方法中的程式碼。 唯一的顯著區別是這次您沒有迭代輸入文字中的句子。 相反,您只會得到第一句話,因為在大多數情況下,評論的標題由一個句子組成。

下述程式碼將從 CSV 檔案中讀取評論並將它們傳遞給新建立的 findSentiment() 進行處理,如下所示:

public static void processCsvComment(String csvCommentFilePath) { 
        try (CSVReader reader = new CSVReaderBuilder(new FileReader(csvCommentFilePath)).withSkipLines(1).build()) 
        { 
            String[] row; 
            while ((row = reader.readNext()) != null) { 
                System.out.println("Review: " + row[1] + "\t" + " Amazon rating: " + row[4] + "\t" + " Sentiment: " + nlpPipeline.findSentiment(row[1])); 
            } 
        } 
        catch (IOException | CsvValidationException e) { 
            e.printStackTrace(); 
        } 
    } 

執行結果:

完整程式碼:

NlpPipeline.java

package com.zh.ch.corenlp; 
 
import edu.stanford.nlp.ling.CoreAnnotations; 
import edu.stanford.nlp.neural.rnn.RNNCoreAnnotations; 
import edu.stanford.nlp.pipeline.Annotation; 
import edu.stanford.nlp.pipeline.StanfordCoreNLP; 
import edu.stanford.nlp.sentiment.SentimentCoreAnnotations; 
import edu.stanford.nlp.trees.Tree; 
import edu.stanford.nlp.util.CoreMap; 
 
import java.util.Properties; 
 
public class NlpPipeline { 
    StanfordCoreNLP pipeline = null; 
    public  void init() { 
        Properties props = new Properties(); 
        props.setProperty("annotators", "tokenize, ssplit, parse, sentiment"); 
        pipeline = new StanfordCoreNLP(props); 
    } 
    public void estimatingSentiment(String text) { 
        int sentimentInt; 
        String sentimentName; 
        Annotation annotation = pipeline.process(text); 
        for(CoreMap sentence : annotation.get(CoreAnnotations.SentencesAnnotation.class)) 
        { 
            Tree tree = sentence.get(SentimentCoreAnnotations.SentimentAnnotatedTree.class); 
            sentimentInt = RNNCoreAnnotations.getPredictedClass(tree); 
            sentimentName = sentence.get(SentimentCoreAnnotations.SentimentClass.class); 
            System.out.println(sentimentName + "\t" + sentimentInt + "\t" + sentence); 
        } 
    } 
 
    public String findSentiment(String text) { 
        int sentimentInt = 2; 
        String sentimentName = "NULL"; 
        if (text != null && text.length() > 0) { 
            Annotation annotation = pipeline.process(text); 
            CoreMap sentence = annotation 
                    .get(CoreAnnotations.SentencesAnnotation.class).get(0); 
            Tree tree = sentence 
                    .get(SentimentCoreAnnotations.SentimentAnnotatedTree.class); 
            sentimentInt = RNNCoreAnnotations.getPredictedClass(tree); 
            sentimentName = sentence.get(SentimentCoreAnnotations.SentimentClass.class); 
        } 
        return sentimentName; 
    } 
} 

Main.java

package com.zh.ch.corenlp; 
 
import com.opencsv.CSVReader; 
import com.opencsv.CSVReaderBuilder; 
import com.opencsv.exceptions.CsvValidationException; 
 
import java.io.FileReader; 
import java.io.IOException; 
 
public class Main { 
    static NlpPipeline nlpPipeline = null; 
 
    public static void processCsvComment(String csvCommentFilePath) { 
        try (CSVReader reader = new CSVReaderBuilder(new FileReader(csvCommentFilePath)).withSkipLines(1).build()) 
        { 
            String[] row; 
            while ((row = reader.readNext()) != null) { 
                System.out.println("Review: " + row[1] + "\t" + " Amazon rating: " + row[4] + "\t" + " Sentiment: " + nlpPipeline.findSentiment(row[1])); 
            } 
        } 
        catch (IOException | CsvValidationException e) { 
            e.printStackTrace(); 
        } 
    } 
 
    public static void processText(String text) { 
        nlpPipeline.estimatingSentiment(text); 
    } 
 
    public static void main(String[] args) { 
        String text = "This is an excellent book. I enjoy reading it. I can read on Sundays. Today is only Tuesday. Can't wait for next Sunday. The working week is unbearably long. It's awful."; 
        nlpPipeline  = new NlpPipeline(); 
        nlpPipeline.init(); 
//        processText(text); 
        processCsvComment("src/main/resources/NlpBookReviews.csv"); 
    } 
}