使用深度學(xué)習(xí)模型在 Java 中執(zhí)行文本情感分析
積極的? 消極的? 中性的? 使用斯坦福 CoreNLP 組件以及幾行代碼便可對句子進(jìn)行分析。
本文介紹如何使用集成到斯坦福 CoreNLP(一個(gè)用于自然語言處理的開源庫)中的情感工具在 Java 中實(shí)現(xiàn)此類任務(wù)。
斯坦福 CoreNLP 情感分類器
要執(zhí)行情感分析,您需要一個(gè)情感分類器,這是一種可以根據(jù)從訓(xùn)練數(shù)據(jù)集中學(xué)習(xí)的預(yù)測來識別情感信息的工具。
在斯坦福 CoreNLP 中,情感分類器建立在遞歸神經(jīng)網(wǎng)絡(luò) (RNN) 深度學(xué)習(xí)模型之上,該模型在斯坦福情感樹庫 (SST) 上進(jìn)行訓(xùn)練。
SST 數(shù)據(jù)集是一個(gè)帶有情感標(biāo)簽的語料庫,從數(shù)千個(gè)使用的句子中推導(dǎo)出每個(gè)句法上可能的短語,從而允許捕獲文本中情感的構(gòu)成效果。簡單來說,這允許模型根據(jù)單詞如何構(gòu)成短語的含義來識別情緒,而不僅僅是通過孤立地評估單詞。
為了更好地了解 SST 數(shù)據(jù)集的結(jié)構(gòu),您可從斯坦福 CoreNLP 情感分析頁面下載數(shù)據(jù)集文件。
在 Java 代碼中,Stanford CoreNLP 情感分類器使用如下。
首先,您通過添加執(zhí)行情感分析所需的注釋器(例如標(biāo)記化、拆分、解析和情感)來構(gòu)建文本處理管道。 就斯坦福 CoreNLP 而言,注釋器是一個(gè)對注釋對象進(jìn)行操作的接口,其中后者表示文檔中的一段文本。 例如,需要使用 ssplit 注釋器將標(biāo)記序列拆分為句子。
斯坦福 CoreNLP 以每個(gè)句子為基礎(chǔ)計(jì)算情緒。 因此,將文本分割成句子的過程始終遵循應(yīng)用情感注釋器。
一旦文本被分成句子,解析注釋器就會執(zhí)行句法依賴解析,為每個(gè)句子生成一個(gè)依賴表示。 然后,情感注釋器處理這些依賴表示,將它們與底層模型進(jìn)行比較,以構(gòu)建帶有每個(gè)句子的情感標(biāo)簽(注釋)的二值化樹。
簡單來說,樹的節(jié)點(diǎn)由輸入句子的標(biāo)記確定,并包含注釋,指示從句子導(dǎo)出的所有短語的從非常消極到非常積極的五個(gè)情感類別中的預(yù)測類別。 基于這些預(yù)測,情感注釋器計(jì)算整個(gè)句子的情感。
設(shè)置斯坦福 CoreNLP
在開始使用斯坦福 CoreNLP 之前,您需要進(jìn)行以下設(shè)置:
要運(yùn)行斯坦福 CoreNLP,您需要 Java 1.8 或更高版本。
下載 Stanford CoreNLP 包并將該包解壓縮到您機(jī)器上的本地文件夾中。
下載地址:
https://nlp.stanford.edu/software/stanford-corenlp-latest.zip
本文以將上述代碼解壓到如下目錄為例:
c:/softwareInstall/corenlp/stanford-corenlp-4.3.2
完成上述步驟后,您就可以創(chuàng)建運(yùn)行斯坦福 CoreNLP 管道來處理文本的 Java 程序了。
首先新建一個(gè)maven項(xiàng)目,并手動將stanford-corenlp-4.3.2添加到Libraries中:

在以下示例中,您將實(shí)現(xiàn)一個(gè)簡單的 Java 程序,該程序運(yùn)行斯坦福 CoreNLP 管道,以對包含多個(gè)句子的文本進(jìn)行情感分析。
首先,實(shí)現(xiàn)一個(gè)NlpPipeline類,該類提供初始化管道的方法和使用此管道將提交的文本拆分為句子然后對每個(gè)句子的情感進(jìn)行分類的方法。 下面是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 管道,它還初始化使用該情感工具所需的分詞器、依賴解析器和句子拆分器。 要初始化管道,請將帶有相應(yīng)注釋器列表的 Properties 對象傳遞給 StanfordCoreNLP() 構(gòu)造函數(shù)。 這將創(chuàng)建一個(gè)定制的管道,準(zhǔn)備好對文本執(zhí)行情感分析。
在NlpPipeline類的estimatingSentiment()方法中,調(diào)用之前創(chuàng)建的管道對象的process()方法,傳入文本進(jìn)行處理。 process() 方法返回一個(gè)注釋對象,該對象存儲對提交的文本的分析。
接下來,迭代注釋對象,在每次迭代中獲得一個(gè)句子級 CoreMap 對象。對于這些對象中的每一個(gè),獲取一個(gè)包含用于確定底層句子情緒的情緒注釋的 Tree 對象。
將 Tree 對象傳遞給 RNNCoreAnnotations 類的 getPredictedClass() 方法,以提取對應(yīng)句子的預(yù)測情緒的編號代碼。然后,獲取預(yù)測情緒的名稱并打印結(jié)果。
要測試上述功能,請使用調(diào)用 init() 方法的 main() 方法實(shí)現(xiàn)一個(gè)類,然后調(diào)用 nlpPipeline 類的 estimatingSentiment() 方法,將示例文本傳遞給后者。
在以下實(shí)現(xiàn)中,為了簡單起見,直接指定text文本。示例句子旨在涵蓋斯坦福 CoreNLP 可用的整個(gè)情緒評分范圍:非常積極、積極、中立、消極和非常消極。
- 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);
- }
- }
執(zhí)行結(jié)果:

分析在線客戶評論
正如您從前面的示例中了解到的,Stanford CoreNLP 可以返回句子的情緒。 然而,有許多用例需要分析多段文本的情緒,每段文本可能包含不止一個(gè)句子。 例如,您可能想要分析來自電子商務(wù)網(wǎng)站的推文或客戶評論的情緒。
要使用斯坦福 CoreNLP 計(jì)算多句文本樣本的情緒,您可能會使用幾種不同的技術(shù)。
在處理推文時(shí),您可能會分析推文中每個(gè)句子的情緒,如果有一些正面或負(fù)面的句子,您可以分別對整個(gè)推文進(jìn)行排名,忽略帶有中性情緒的句子。 如果推文中的所有(或幾乎所有)句子都是中性的,則該推文可以被列為中性。
然而,有時(shí)您甚至不必分析每個(gè)句子來估計(jì)整個(gè)文本的情緒。 例如,在分析客戶評論時(shí),您可以依賴他們的標(biāo)題,標(biāo)題通常由一個(gè)句子組成。
要完成以下示例,您需要一組客戶評論。 您可以使用本文隨附的 NlpBookReviews.csv 文件中的評論。 該文件包含在 Amazon Review Export 的幫助下從 Amazon 網(wǎng)頁下載的一組實(shí)際評論,這是一個(gè) Google Chrome 瀏覽器擴(kuò)展程序,允許您將產(chǎn)品評論及其標(biāo)題和評級下載到逗號分隔值 (CSV) 文件中 . (您可以使用該工具探索一組不同的評論以進(jìn)行分析。)
將下述代碼添加到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;
- }
您可能會注意到,上面的代碼類似于上一節(jié)中定義的 estimatingSentiment() 方法中的代碼。 唯一的顯著區(qū)別是這次您沒有迭代輸入文本中的句子。 相反,您只會得到第一句話,因?yàn)樵诖蠖鄶?shù)情況下,評論的標(biāo)題由一個(gè)句子組成。
下述代碼將從 CSV 文件中讀取評論并將它們傳遞給新創(chuàng)建的 findSentiment() 進(jìn)行處理,如下所示:
- 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();
- }
- }
執(zhí)行結(jié)果:

完整代碼:
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");
- }
- }