1. 程式人生 > >【機器學習】使用Python的自然語言工具包(NLTK)對Reddit新聞標題進行情感分析

【機器學習】使用Python的自然語言工具包(NLTK)對Reddit新聞標題進行情感分析

讓我們使用Reddit API獲取新聞標題並執行情感分析

在我上一篇文章中,使用Python進行K-Means聚類,我們只是抓取了一些預編譯資料,但是對於這篇文章,我想更深入地瞭解一些實時資料。

使用Reddit API,我們可以從各種新聞subreddit獲得成千上萬的頭條新聞,並開始享受Sentiment Analysis的樂趣。

我們將使用NLTK的vader分析器,它可以計算識別文字並將其分為三種情緒:正面,負面或中立。

文章資源

    庫:pandas,numpy,nltk,matplotlib,seaborn

目錄

入門

NLTK

單詞分佈

積極的話語

否定詞

結論

入門

首先,一些匯入:

from IPython import display
import math
from pprint import pprint
import pandas as pd
import numpy as np
import nltk
import matplotlib.pyplot as plt
import seaborn as sns
sns.set(style='darkgrid', context='talk', palette='Dark2')

一旦使用,這些進口將被清除。現在值得一提的三個是pprint,它讓我們“漂亮地列印”JSON和列表,seaborn

它們將為matplotlib圖形新增樣式,以及iPython的display模組,它將讓我們控制清除迴圈內的列印輸出。更多關於以下內容。

NLTK

在開始收集資料之前,您需要安裝Natural Language Toolkit(NLTK)python包。要了解如何安裝NLTK,您可以訪問:http :// www 。nltk 。組織/ 安裝。HTML。您需要開啟Python命令列並執行nltk.download()以獲取NLTK的資料庫。

通過PRAW的Reddit API

對於本教程,我們將使用名為`praw`的Reddit API包裝器來遍歷/ r / politics subreddit標題。

要開始使用`praw`,您需要建立一個Reddit應用程式並獲取您的客戶端ID和客戶端金鑰。

製作Reddit應用

只需按以下步驟操作:

  1. 登入到您的帳戶
  2. 點選“ 你是開發者嗎?建立應用程式...... ” 按鈕
    1. 輸入名稱(使用者名稱有效)
    2. 選擇“指令碼”
    3. 使用http://localhost:8080 作為重定向URI
  3. 單擊“ 建立應用程式 ”後,您將看到客戶端ID和客戶端金鑰的位置。

Reddit API App

客戶ID和客戶端金鑰的位置

現在開始使用praw,我們需要先建立一個Reddit客戶端。

只需在以下行中替換您的詳細資訊(沒有插入符號<>):

import praw

reddit = praw.Reddit(client_id='<your_client_id>',
                     client_secret='<your_client_secret>',
                     user_agent='<your_user_name>')

讓我們為標題定義一個集合,這樣我們就不會在多次執行時獲得重複:

for submission in reddit.subreddit('politics').new(limit=None):
    headlines.add(submission.title)
    display.clear_output()
    print(len(headlines))
965

我們正在迭代/ r / politics中的“新”帖子,通過將限制設定為None,我們可以獲得最多1000個標題。這次我們只獲得了965個頭條新聞。

PRAW為我們做了很多工作。它允許我們使用一個非常簡單的介面,同時它在後臺處理許多工,如速率限制和組織JSON響應。

不幸的是,如果沒有一些更高階的技巧,我們無法超過1000個結果,因為Reddit在那時切斷了。我們可以多次執行此迴圈並繼續向我們的集合新增新標題,或者我們可以實現流式版本。還有一種方法可以利用Reddit的時間引數搜尋,但讓我們暫時轉到我們頭條新聞的情感分析。

標記我們的資料

NLTK的內建Vader情感分析器將使用正面和負面詞彙的詞彙將一段文字排名為正面,負面或中性。

我們可以通過首先建立一個情緒強度分析器(SIA)來分類我們的標題來利用這個工具,然後我們將使用該polarity_scores方法來獲得情緒。

我們將每個情緒詞典附加到結果列表中,我們將其轉換為資料幀:

from nltk.sentiment.vader import SentimentIntensityAnalyzer as SIA

sia = SIA()
results = []

for line in headlines:
    pol_score = sia.polarity_scores(line)
    pol_score['headline'] = line
    results.append(pol_score)

pprint(results[:3], width=100)
[{'compound': -0.5267,
  'headline': 'DOJ watchdog reportedly sends criminal referral for McCabe to federal prosecutor',
  'neg': 0.254,
  'neu': 0.746,
  'pos': 0.0},
 {'compound': 0.0,
  'headline': 'House Dems add five candidates to ‘Red to Blue’ program',
  'neg': 0.0,
  'neu': 1.0,
  'pos': 0.0},
 {'compound': 0.0,
  'headline': 'DeveloperTown co-founder launches independent bid for U.S. Senate',
  'neg': 0.0,
  'neu': 1.0,
  'pos': 0.0}]
df = pd.DataFrame.from_records(results)
df.head()

我們的資料幀由來自情緒得分四列:NeuNegPoscompound。前三個代表我們標題中每個類別的情緒分數百分比,以及compound評分情緒的單個數字。`compound`的範圍從-1(極端負)到1(極端正極)。

我們將考慮複合值大於0.2的帖子為正小於-0.2的帖子為負。選擇這些範圍需要進行一些測試和實驗,這裡需要進行權衡。如果您選擇更高的值,您可能會得到更緊湊的結果(更少的誤報和漏報),但結果的大小將顯著減少。

如果compound大於0.2,則建立一個正標籤,如果小於-0.2,則標籤為-1 compound。其他一切都將是0。

df['label'] = 0
df.loc[df['compound'] > 0.2, 'label'] = 1
df.loc[df['compound'] < -0.2, 'label'] = -1
df.head()

我們擁有需要儲存的所有資料,所以讓我們這樣做:

df2 = df[['headline', 'label']]
df2.to_csv('reddit_headlines_labels.csv', mode='a', encoding='utf-8', index=False)

我們現在可以繼續附加到此csv,但只要確保如果重新分配標題集,就可以獲得重複項。也許新增一個更高階的儲存功能,在儲存之前讀取和刪除重複項。

資料集資訊和統計

讓我們首先在一些積極和消極的頭條新聞中佔據一席之地:

print("Positive headlines:\n")
pprint(list(df[df['label'] == 1].headline)[:5], width=200)

print("\nNegative headlines:\n")
pprint(list(df[df['label'] == -1].headline)[:5], width=200)
Positive headlines:

['Japanese PM Praises Trump for North Korea Breakthrough',
 "Bernie Sanders Joins Cory Booker's Marijuana Justice Act to Federally Legalize Weed",
 'Trump Administration Seeks to Expand Sales of Armed Drones',
 'AP: Trump leaves open possibility of bailing on meeting with Kim, Trump supported by Japan',
 'Trump skews reasons behind his 2016 win']

Negative headlines:

['DOJ watchdog reportedly sends criminal referral for McCabe to federal prosecutor',
 'Beyer Statement On Syria Strikes',
 'Trump confidantes Bossie, Lewandowski urge against firing Mueller',
 'Mattis disputes report he wanted Congress to approve Syria strike',
 'Criminal charges recommended for fired FBI official Andrew McCabe']

現在讓我們檢查一下這個資料集中有多少正面和負面:

print(df.label.value_counts())

print(df.label.value_counts(normalize=True) * 100)
0    433
-1    332
 1    200
Name: label, dtype: int64

 0    44.870466
-1    34.404145
 1    20.725389
Name: label, dtype: float64

第一行給出了標籤的原始值計數,而第二行提供了normalize關鍵字的百分比。

為了好玩,讓我們繪製一個條形圖:

fig, ax = plt.subplots(figsize=(8, 8))

counts = df.label.value_counts(normalize=True) * 100

sns.barplot(x=counts.index, y=counts, ax=ax)

ax.set_xticklabels(['Negative', 'Neutral', 'Positive'])
ax.set_ylabel("Percentage")

plt.show()

pos neg neu reddit headline bar chart

大量中性頭條新聞主要有兩個原因:

  1. 我們之前做出的假設是複合值在0.2和-0.2之間的標題被認為是中性的。邊際越高,中性標題的數量越大。
  2. 我們使用通用詞典來分類政治新聞。更正確的方法是使用政治特定的詞典,但為此我們要麼需要人工手動標記資料,要麼我們需要找到已經制作的自定義詞典。

另一個有趣的觀察是負面新聞的數量,這可能歸因於媒體的行為,例如誇大clickbait的標題。另一種可能性是我們的分析儀產生了許多假陰性。

絕對值得探索改進的地方,但現在讓我們繼續前進。

標記符和停用詞

現在我們收集並標記了資料,讓我們來談談預處理資料的一些基礎知識,以幫助我們更清楚地瞭解我們的資料集。

首先,我們來談談tokenizer。標記是將文字流分解為稱為標記的有意義元素的過程。您可以將段落標記為句子,將句子標記為單詞等。

在我們的例子中,我們有標題,可以被認為是句子,所以我們將使用單詞標記器:

from nltk.tokenize import word_tokenize, RegexpTokenizer

example = "This is an example sentence! However, it isn't a very informative one"

print(word_tokenize(example, language='english'))
['This', 'is', 'an', 'example', 'sentence', '!', 'However', ',', 'it', 'is', "n't", 'a', 'very', 'informative', 'one']

如您所見,先前的標記生成器將標點符號視為單詞,但您可能希望擺脫標點符號以進一步規範化資料並減小特徵大小。如果是這種情況,您需要刪除標點符號,或者使用僅檢視單詞的其他標記器,例如:

tokenizer = RegexpTokenizer(r'\w+')
tokenizer.tokenize(example)
['This', 'is', 'an', 'example', 'sentence', 'However', 'it', 'isn', 't', 'a', 'very', 'informative', 'one']

有很多標記器,你可以在這裡檢視它們:http :// www 。nltk 。org / api / nltk 。令牌化。HTML。可能有一個比其他人更適合這個法案。這TweetTokenizer是一個很好的例子。

在上面的代幣中你還會注意到我們有很多單詞,比如','是','和','what'等與文字情緒有些無關,而且沒有提供任何有價值的資訊。這些被稱為停用詞

我們可以從NLTK中獲取一個簡單的停用詞列表:

from nltk.corpus import stopwords

stop_words = stopwords.words('english')
print(stop_words[:20])
['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', 'your', 'yours', 'yourself', 'yourselves', 'he', 'him', 'his', 'himself', 'she', 'her', 'hers']

這是一個簡單的英語禁用詞列表,其中包含大多數常見的填充詞,這些詞只會新增到我們的資料大小中,而無需其他資訊。更進一步,您很可能會使用更高階的禁用詞列表,這對您的用例非常理想,但NLTK是一個良好的開端。

單詞分佈

讓我們首先建立一個函式,該函式將讀取標題列表並執行小寫,標記化和刪除停用詞:

def process_text(headlines):
    tokens = []
    for line in headlines:
        toks = tokenizer.tokenize(line)
        toks = [t.lower() for t in toks if t.lower() not in stop_words]
        tokens.extend(toks)
    
    return tokens

積極的話語

我們可以從資料框中獲取所有正面標題標題,將它們交給我們的函式,然後呼叫NLTK的“FreqDist”函式來獲得正面標題中最常見的單詞:

pos_lines = list(df[df.label == 1].headline)

pos_tokens = process_text(pos_lines)
pos_freq = nltk.FreqDist(pos_tokens)

pos_freq.most_common(20)
[('trump', 80),
 ('says', 16),
 ('justice', 13),
 ('new', 13),
 ('senate', 12),
 ('york', 12),
 ('mueller', 11),
 ('comey', 11),
 ('support', 10),
 ('legal', 10),
 ('security', 9),
 ('white', 9),
 ('giuliani', 9),
 ('korea', 8),
 ('party', 8),
 ('donald', 8),
 ('cohen', 8),
 ('state', 8),
 ('like', 8),
 ('join', 8)]

現在,讓我們看看積極集中一些頂部單詞的頻率:

有趣的是,最正面的標題詞是“特朗普”!

看到其他一些最重要的積極話語與俄羅斯的調查有關,最有可能的情況是“特朗普”+“調查新聞”大多被認為是積極的,但正如我們將在負面詞部分看到的那樣,很多相同的單詞出現所以它不是確定的。

讓我們通過繪製頻率分佈來看更巨集觀的一面,並嘗試檢查單詞的模式,而不是具體地檢查每個單詞。

y_val = [x[1] for x in pos_freq.most_common()]

fig = plt.figure(figsize=(10,5))
plt.plot(y_val)

plt.xlabel("Words")
plt.ylabel("Frequency")
plt.title("Word Frequency Distribution (Positive)")
plt.show()

Word Frequency Positive

上圖顯示了頻率模式,其中y軸是單詞的頻率,x軸是按頻率排序的單詞。因此,最常說的一句話,這在我們的例子中是“王牌”,在繪製(1,74)(1,74)。

對於你們中的一些人來說,這個情節可能看起來有點熟悉。那是因為它似乎遵循了冪律分佈。因此,為了直觀地確認它,我們可以使用對數 - 對數圖:

y_final = []
for i, k, z, t in zip(y_val[0::4], y_val[1::4], y_val[2::4], y_val[3::4]):
    y_final.append(math.log(i + k + z + t))

x_val = [math.log(i + 1) for i in range(len(y_final))]

fig = plt.figure(figsize=(10,5))

plt.xlabel("Words (Log)")
plt.ylabel("Frequency (Log)")
plt.title("Word Frequency Distribution (Positive)")
plt.plot(x_val, y_final)
plt.show()

positive word freq distribution

正如所料,一條几乎是直線,尾巴很重(尾巴嘈雜)。這表明我們的資料符合Zipf定律。換句話說,上面的圖表顯示,在我們的單詞分佈中,大部分單詞出現的次數最多,而大多數單詞出現的次數較少。

否定詞

現在我們已經檢查了積極的話,現在是時候轉向消極的話了。讓我們獲取並處理負面文字資料:

neg_lines = list(df2[df2.label == -1].headline)

neg_tokens = process_text(neg_lines)
neg_freq = nltk.FreqDist(neg_tokens)

neg_freq.most_common(20)
[('trump', 125),
 ('mueller', 25),
 ('criminal', 21),
 ('judge', 20),
 ('mccabe', 19),
 ('court', 18),
 ('contempt', 17),
 ('police', 16),
 ('comey', 16),
 ('pittsburgh', 15),
 ('new', 15),
 ('kobach', 15),
 ('syria', 14),
 ('war', 14),
 ('senate', 13),
 ('u', 13),
 ('cohen', 13),
 ('case', 13),
 ('fires', 12),
 ('says', 12)]

好吧,總統再做一次。他也是最負面的消極詞。列表中有一個有趣的補充詞是“敘利亞”和“戰爭”。

這篇文章正在對敘利亞發生的第一次重大罷工時進行更新,所以看起來很明顯為什麼會被視為負面。

有趣的是,如上所述,我們看到一些相同的詞,如'comey'和'mueller',出現在正面集合中。需要進行更多的分析來確定差異,看看我們是否可以更準確地分離,但是現在讓我們繼續討論負面詞分佈的一些圖:

y_val = [x[1] for x in neg_freq.most_common()]

fig = plt.figure(figsize=(10,5))
plt.plot(y_val)

plt.xlabel("Words")
plt.ylabel("Frequency")
plt.title("Word Frequency Distribution (Negative)")
plt.show()

negative word distribution

y_final = []
for i, k, z in zip(y_val[0::3], y_val[1::3], y_val[2::3]):
    if i + k + z == 0:
        break
    y_final.append(math.log(i + k + z))

x_val = [math.log(i+1) for i in range(len(y_final))]

fig = plt.figure(figsize=(10,5))

plt.xlabel("Words (Log)")
plt.ylabel("Frequency (Log)")
plt.title("Word Frequency Distribution (Negative)")
plt.plot(x_val, y_final)
plt.show()

negative word distribution log

負分佈符合Zipf法則。斜坡有點平滑,但重尾絕對存在。這裡得出的結論與正分佈中顯示的前一個完全相同。

結論

正如您所看到的,Reddit API可以非常快速地編譯大量新聞資料。絕對值得花時間和精力來增強資料收集步驟,因為它可以很容易地獲得數千行政治標題用於進一步分析和預測。

在資料探勘方面仍然有很多可以設計,並且仍然有很多與檢索到的資料有關。在下一個教程中,我們將繼續通過資料集進行分析,以構建和訓練情感分類器。

原文: