Python 3でNatural Language Toolkit (NLTK)を使用して感情分析を行う方法
著者は「オープンインターネット/表現の自由基金」を、Write for DOnationsプログラムの一環として寄付を受け取るために選びました。
紹介
今日生成される大量のデータは非構造的であり、洞察を得るための処理が必要です。非構造的データの例としては、ニュース記事、ソーシャルメディアの投稿、検索履歴などがあります。自然言語を分析し、それを理解するプロセスは自然言語処理(NLP)の分野に属します。感情分析は一般的なNLPタスクであり、テキストやテキストの一部を事前に定義された感情に分類することを含みます。テキストデータを分析するために、Pythonで一般的に使用されるNLPライブラリであるNatural Language Toolkit(NLTK)を使用します。
このチュートリアルでは、異なるデータクリーニング方法を用いて、NLPのためにNLTKパッケージからのサンプルツイートのデータセットを準備します。データセットが処理の準備が整ったら、事前に分類されたツイートに基づいてモデルをトレーニングし、そのモデルを使用してサンプルツイートをネガティブとポジティブな感情に分類します。
この記事は、あなたがPythonの基本(私たちの「Python 3でのコーディング方法」シリーズを参照)に精通していることを前提としています。特に、データ構造、クラス、およびメソッドの使用に関してです。このチュートリアルは、あなたがNLPやnltkの背景を持っていないことを前提としていますが、少しでも知識があることはプラスになります。
前提条件
- このチュートリアルはPythonバージョン3.6.5に基づいています。Python 3がインストールされていない場合は、Python 3のローカルプログラミング環境をインストールして設定するためのガイドがあります。
- 言語データの取り扱いに関する慣れが推奨されます。NLTKを初めて使用する場合は、「Python 3における自然言語ツールキット(NLTK)を使った言語データの扱い方」ガイドを確認してください。
ステップ1 — NLTKのインストールとデータのダウンロード
このチュートリアルのすべてのNLPタスクには、PythonのNLTKパッケージを使用します。このステップでは、NLTKをインストールし、モデルのトレーニングとテストに使用するサンプルツイートをダウンロードします。
まず、pip パッケージマネージャーを使って NLTK パッケージをインストールします:
- pip install nltk==3.3
このチュートリアルでは、NLTKパッケージの一部であるサンプルツイートを使用します。まず、次のコマンドを実行してPythonのインタラクティブセッションを開始してください:
- python3
次に、Pythonインタープリタでnltkモジュールをインポートします。
- import nltk
NLTKパッケージからサンプルツイートをダウンロードします:
- nltk.download('twitter_samples')
このコマンドをPythonインタプリタから実行すると、ツイートがローカルにダウンロードされて保存されます。サンプルがダウンロードされると、それらはあなたの使用のために利用可能になります。
このチュートリアルの後半で感情分析のモデルを訓練するために、ネガティブおよびポジティブなツイートを使用します。感情を持たないツイートは、モデルをテストするために使用されます。
独自のデータセットを使用したい場合、Twitter APIを使用して特定の期間、ユーザー、またはハッシュタグからツイートを収集することができます。
NLTKをインポートし、サンプルツイートをダウンロードしたので、exit()と入力してインタラクティブセッションを終了してください。ツイートをインポートし、データの処理を開始する準備が整いました。
ステップ2 — データのトークン化
言語はその原形のままでは機械によって正確に処理することができないため、機械が理解しやすいように言語を処理する必要があります。データを理解するための最初のステップは、トークン化と呼ばれるプロセスを通じて、文字列をトークンと呼ばれる小さな部分に分割することです。
トークンは、テキスト内の文字のシーケンスであり、単位として機能します。トークンを作成する方法に基づいて、それらは単語、絵文字、ハッシュタグ、リンク、または個々の文字で構成される場合があります。言語をトークンに分割する基本的な方法は、テキストをホワイトスペースと句読点で分割することです。
始めるには、新しい.pyファイルを作成してスクリプトを保存します。このチュートリアルではnlp_test.pyを使用します:
- nano nlp_test.py
このファイルでは、最初にtwitter_samplesをインポートして、そのデータを扱うことができます:
from nltk.corpus import twitter_samples
これは、モデルの訓練とテストに使用するさまざまなツイートを含むNLTKからの3つのデータセットをインポートします:
negative_tweets.json: 5000件の否定的な感情を持ったツイートpositive_tweets.json: 5000件のツイートでポジティブな感情を持つものtweets.20150430-223406.json: 20000のツイートが感情なしで
次に、positive_tweets、negative_tweets、およびtextの変数を作成します:
from nltk.corpus import twitter_samples
positive_tweets = twitter_samples.strings('positive_tweets.json')
negative_tweets = twitter_samples.strings('negative_tweets.json')
text = twitter_samples.strings('tweets.20150430-223406.json')
twitter_samplesのstrings()メソッドは、データセット内のすべてのツイートを文字列として印刷します。異なるツイートコレクションを変数として設定すると、処理やテストが容易になります。
NLTKでトークン化を使用する前に、追加のリソースであるpunktをダウンロードする必要があります。punktモジュールは、単語や文をトークン化するのに役立つ事前にトレーニングされたモデルです。たとえば、このモデルは、名前にピリオドが含まれている可能性があること(「S. Daityari」のように)を理解しており、文中のこのピリオドが必ずしも文の終わりを意味するわけではありません。まず、Pythonのインタラクティブセッションを開始します:
- python3
セッションで次のコマンドを実行して、punktリソースをダウンロードします:
- import nltk
- nltk.download('punkt')
ダウンロードが完了したら、NLTKのトークナイザーを使用する準備が整いました。NLTKは、.tokenized()メソッドを使用してツイート用のデフォルトトークナイザーを提供します。positive_tweets.jsonデータセットをトークン化するオブジェクトを作成する行を追加してください:
from nltk.corpus import twitter_samples
positive_tweets = twitter_samples.strings('positive_tweets.json')
negative_tweets = twitter_samples.strings('negative_tweets.json')
text = twitter_samples.strings('tweets.20150430-223406.json')
tweet_tokens = twitter_samples.tokenized('positive_tweets.json')
このスクリプトをテストして、.tokenized メソッドがどのように動作するかを確認したい場合は、ハイライトされた内容をあなたの nlp_test.py スクリプトに追加してください。これにより、positive_tweets.json データセットからの単一のツイートがトークン化されます:
from nltk.corpus import twitter_samples
positive_tweets = twitter_samples.strings('positive_tweets.json')
negative_tweets = twitter_samples.strings('negative_tweets.json')
text = twitter_samples.strings('tweets.20150430-223406.json')
tweet_tokens = twitter_samples.tokenized('positive_tweets.json')[0]
print(tweet_tokens[0])
ファイルを保存して閉じ、スクリプトを実行します:
- python3 nlp_test.py
トークン化のプロセスには少し時間がかかります。なぜなら、単純に空白で分割するだけではないからです。処理が数瞬行われた後、次のような結果が表示されます:
['#FollowFriday',
'@France_Inte',
'@PKuchly57',
'@Milipol_Paris',
'for',
'being',
'top',
'engaged',
'members',
'in',
'my',
'community',
'this',
'week',
':)']
ここでは、.tokenized() メソッドが @ や _ のような特殊文字を返します。これらの文字は、このチュートリアルの後半で正規表現を通じて削除されます。
これで .tokenized() メソッドの使い方がわかったので、スクリプトの最後の行をコメントアウトするか削除して、トークン化されたツイートを印刷しないように、行の先頭に # を追加してください。
from nltk.corpus import twitter_samples
positive_tweets = twitter_samples.strings('positive_tweets.json')
negative_tweets = twitter_samples.strings('negative_tweets.json')
text = twitter_samples.strings('tweets.20150430-223406.json')
tweet_tokens = twitter_samples.tokenized('positive_tweets.json')[0]
#print(tweet_tokens[0])
あなたのスクリプトは現在、データをトークン化するように設定されています。次のステップでは、データを正規化するようにスクリプトを更新します。
ステップ3 — データの正規化
言葉には異なる形があり、例えば「ran」、「runs」、「running」はすべて同じ動詞「run」の異なる形です。分析の必要に応じて、これらのバージョンはすべて同じ形「run」に変換する必要がある場合があります。NLPにおける正規化は、単語をその標準形に変換するプロセスです。
正規化は、異なる形を持つが同じ意味の単語をグループ化するのに役立ちます。正規化がなければ、「ran」、「runs」、および「running」は異なる単語として扱われますが、同じ単語として扱いたい場合もあります。このセクションでは、正規化の2つの人気技術であるステミングとレmmatizationを探ります。
ステミングは単語から接頭辞や接尾辞を取り除くプロセスです。ステミングは単純な動詞形のみを扱い、単語の末尾を取り除くヒューリスティックプロセスです。
このチュートリアルでは、語彙の文脈とテキスト内の単語の形態解析を用いて単語を正規化するレマタイズのプロセスを使用します。レマタイズアルゴリズムは、単語の構造とその文脈を分析して、単語を正規化された形に変換します。したがって、速度のコストが伴います。ステミングとレマタイズの比較は、最終的に速度と精度のトレードオフに帰着します。
形態素解析を使用する前に、以下をPythonのインタラクティブセッションに入力して必要なリソースをダウンロードしてください:
- python3
セッションで以下のコマンドを実行してリソースをダウンロードしてください:
- import nltk
- nltk.download('wordnet')
- nltk.download('averaged_perceptron_tagger')
wordnetは英語のための語彙データベースで、スクリプトが基本語を特定するのに役立ちます。文中の単語の文脈を特定するためにはaveraged_perceptron_taggerリソースが必要です。
ダウンロードが完了すると、ほぼレマタイザーを使用する準備が整います。レマタイザーを実行する前に、テキスト内の各単語の文脈を特定する必要があります。これは、文の中での単語の相対的な位置を評価するタグ付けアルゴリズムによって実現されます。Pythonセッションでは、pos_tag関数をインポートし、トークンのリストを引数として提供してタグを取得します。これをPythonで試してみましょう:
- from nltk.tag import pos_tag
- from nltk.corpus import twitter_samples
-
- tweet_tokens = twitter_samples.tokenized('positive_tweets.json')
- print(pos_tag(tweet_tokens[0]))
ここにpos_tag関数の出力があります。
[('#FollowFriday', 'JJ'),
('@France_Inte', 'NNP'),
('@PKuchly57', 'NNP'),
('@Milipol_Paris', 'NNP'),
('for', 'IN'),
('being', 'VBG'),
('top', 'JJ'),
('engaged', 'VBN'),
('members', 'NNS'),
('in', 'IN'),
('my', 'PRP$'),
('community', 'NN'),
('this', 'DT'),
('week', 'NN'),
(':)', 'NN')]
タグのリストから、最も一般的なアイテムとその意味のリストはこちらです:
NNP: 名詞、固有名詞、単数形NN: 名詞、普通名詞、単数形または集合名詞中: 前置詞または接続詞、従属的VBG: 動詞、動名詞または現在分詞VBN: 動詞、過去分詞
ここにデータセットの完全なリストがあります。
一般に、タグがNNで始まる場合、その単語は名詞であり、VBで始まる場合、その単語は動詞です。タグを確認した後、exit()を入力してPythonセッションを終了します。
これを文を正規化する関数に組み込むには、まずテキスト内の各トークンに対してタグを生成し、次にそのタグを使用して各単語をレmmat化する必要があります。
nlp_test.pyファイルを以下の関数で更新して、文をレmmatizeしてください:
...
from nltk.tag import pos_tag
from nltk.stem.wordnet import WordNetLemmatizer
def lemmatize_sentence(tokens):
lemmatizer = WordNetLemmatizer()
lemmatized_sentence = []
for word, tag in pos_tag(tokens):
if tag.startswith('NN'):
pos = 'n'
elif tag.startswith('VB'):
pos = 'v'
else:
pos = 'a'
lemmatized_sentence.append(lemmatizer.lemmatize(word, pos))
return lemmatized_sentence
print(lemmatize_sentence(tweet_tokens[0]))
このコードはWordNetLemmatizerクラスをインポートし、それを変数lemmatizerに初期化します。
関数lemmatize_sentenceは、まずツイートの各トークンの位置タグを取得します。ifステートメント内で、タグがNNで始まる場合、そのトークンは名詞として割り当てられます。同様に、タグがVBで始まる場合、そのトークンは動詞として割り当てられます。
ファイルを保存して閉じ、スクリプトを実行します:
- python3 nlp_test.py
こちらが出力です:
['#FollowFriday',
'@France_Inte',
'@PKuchly57',
'@Milipol_Paris',
'for',
'be',
'top',
'engage',
'member',
'in',
'my',
'community',
'this',
'week',
':)']
動詞beingがその基本形であるbeに変わり、名詞membersがmemberに変わることに気づくでしょう。進む前に、スクリプトからサンプルツイートを表示する最後の行のコメントを外してください。
単語を正規化する関数を正常に作成したので、次はノイズを取り除く準備ができました。
ステップ4 — データからノイズを除去する
このステップでは、データセットからノイズを除去します。ノイズとは、データに意味や情報を追加しないテキストの部分を指します。
ノイズは各プロジェクトに特有のものであるため、あるプロジェクトでノイズと見なされるものが別のプロジェクトではそうでない場合があります。たとえば、ある言語で最も一般的な単語はストップワードと呼ばれます。ストップワードの例としては、「is」、「the」、「a」などがあります。特定のユースケースがそれらの含有を必要としない限り、言語処理の際には一般的に無関係です。
このチュートリアルでは、Pythonで正規表現を使用して、これらのアイテムを検索して削除します。
- ハイパーリンク - Twitterのすべてのハイパーリンクは、URL短縮サービスのt.coに変換されます。したがって、テキスト処理にそれらを保持しても、分析に価値を加えることはありません。
- 返信におけるTwitterユーザー名 - これらのTwitterユーザー名は
@シンボルによって前置きされており、意味を伝えるものではありません。 - 句読点と特殊文字 - これらはしばしばテキストデータに文脈を提供しますが、この文脈を処理することはしばしば難しいです。簡単のために、ツイートからすべての句読点と特殊文字を取り除きます。
ハイパーリンクを削除するには、まず http:// または https:// で始まるURLに一致する部分文字列を検索し、その後に文字、数字、または特殊文字が続く必要があります。パターンが一致したら、.sub() メソッドを使用して空の文字列に置き換えます。
私たちはremove_noise()関数内で単語の形式を正規化するので、スクリプトからlemmatize_sentence()関数をコメントアウトしても構いません。
データセットからノイズを削除するために、nlp_test.pyファイルに以下のコードを追加してください:
...
import re, string
def remove_noise(tweet_tokens, stop_words = ()):
cleaned_tokens = []
for token, tag in pos_tag(tweet_tokens):
token = re.sub('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+#]|[!*\(\),]|'\
'(?:%[0-9a-fA-F][0-9a-fA-F]))+','', token)
token = re.sub("(@[A-Za-z0-9_]+)","", token)
if tag.startswith("NN"):
pos = 'n'
elif tag.startswith('VB'):
pos = 'v'
else:
pos = 'a'
lemmatizer = WordNetLemmatizer()
token = lemmatizer.lemmatize(token, pos)
if len(token) > 0 and token not in string.punctuation and token.lower() not in stop_words:
cleaned_tokens.append(token.lower())
return cleaned_tokens
このコードは、ノイズを除去し、前のセクションで言及された正規化とレマタイゼーションを取り入れた remove_noise() 関数を作成します。このコードは、ツイートトークンとストップワードのタプルの2つの引数を取ります。
このコードは、データセットからノイズを削除するためにループを使用します。ハイパーリンクを削除するために、まずhttp://またはhttps://で始まり、文字、数字、または特殊文字が続くURLと一致する部分文字列を検索します。一度パターンが一致すると、.sub()メソッドがそれを空の文字列、または''に置き換えます。
同様に、@ メンションを削除するために、コードは正規表現を使用してテキストの関連部分を置き換えます。コードは re ライブラリを使用して、@ シンボルの後に数字、文字、または _ が続くものを検索し、それらを空の文字列で置き換えます。
最後に、ライブラリstringを使用して句読点を削除できます。
これに加えて、NLTKに組み込まれたストップワードのセットを使用してストップワードも削除しますが、これは別途ダウンロードする必要があります。
Pythonインタラクティブセッションから以下のコマンドを実行して、このリソースをダウンロードしてください:
- nltk.download('stopwords')
リソースがダウンロードされたら、インタラクティブセッションを終了してください。
英語のストップワードのリストを取得するには、.words() メソッドを使用できます。この関数をテストするために、サンプルツイートで実行してみましょう。nlp_test.py ファイルの最後に次の行を追加してください:
...
from nltk.corpus import stopwords
stop_words = stopwords.words('english')
print(remove_noise(tweet_tokens[0], stop_words))
ファイルを保存して閉じた後、スクリプトを再度実行して、以下に似た出力を受け取ります。
['#followfriday', 'top', 'engage', 'member', 'community', 'week', ':)']
この関数は、すべての@メンション、ストップワードを削除し、単語を小文字に変換することに注意してください。
次のステップでモデリングの演習に進む前に、remove_noise() 関数を使用してポジティブとネガティブのツイートをクリーンアップしてください。サンプルツイートに対する remove_noise() の出力を印刷する行をコメントアウトし、次の内容を nlp_test.py スクリプトに追加してください。
...
from nltk.corpus import stopwords
stop_words = stopwords.words('english')
#print(remove_noise(tweet_tokens[0], stop_words))
positive_tweet_tokens = twitter_samples.tokenized('positive_tweets.json')
negative_tweet_tokens = twitter_samples.tokenized('negative_tweets.json')
positive_cleaned_tokens_list = []
negative_cleaned_tokens_list = []
for tokens in positive_tweet_tokens:
positive_cleaned_tokens_list.append(remove_noise(tokens, stop_words))
for tokens in negative_tweet_tokens:
negative_cleaned_tokens_list.append(remove_noise(tokens, stop_words))
サンプルツイートをクリーンアップするコードを追加したので、元のトークンとクリーンアップされたトークンをサンプルツイートのために比較したいかもしれません。これをテストしたい場合は、ファイルに次のコードを追加して、リストの500番目のツイートの両方のバージョンを比較してみてください:
...
print(positive_tweet_tokens[500])
print(positive_cleaned_tokens_list[500])
ファイルを保存して閉じ、スクリプトを実行します。出力から、句読点とリンクが削除され、単語が小文字に変換されていることがわかります。
['Dang', 'that', 'is', 'some', 'rad', '@AbzuGame', '#fanart', '!', ':D', 'https://t.co/bI8k8tb9ht']
['dang', 'rad', '#fanart', ':d']
テキストの前処理中に発生する可能性のある特定の問題があります。たとえば、スペースのない単語(「iLoveYou」)は一つの単語として扱われ、そのような単語を分離するのが難しい場合があります。さらに、「Hi」、「Hii」、および「Hiiiii」は、特定の問題に対処するために何かを書くまで、スクリプトによって異なる扱いを受けます。特定のデータに合わせてノイズ除去プロセスを微調整することは一般的です。
「remove_noise()」関数の動作を見たので、スクリプトの最後の2行をコメントアウトするか削除して、さらに追加できるようにしてください。
...
#print(positive_tweet_tokens[500])
#print(positive_cleaned_tokens_list[500])
このステップでは、分析をより効果的にするためにデータからノイズを取り除きました。次のステップでは、サンプルデータセットで最も一般的な単語を見つけるためにデータを分析します。
ステップ5 — 語の密度を決定する
テキストデータに対する最も基本的な分析の形は、単語の頻度を抽出することです。単一のツイートは、単語の分布を見つけるにはあまりにも小さい単位であるため、単語の頻度の分析はすべてのポジティブなツイートに対して行われます。
次のスニペットは、get_all_wordsという名前のジェネレーター関数を定義しており、ツイートのリストを引数として受け取り、すべてのツイートトークンを結合した単語のリストを提供します。以下のコードをnlp_test.pyファイルに追加してください。
...
def get_all_words(cleaned_tokens_list):
for tokens in cleaned_tokens_list:
for token in tokens:
yield token
all_pos_words = get_all_words(positive_cleaned_tokens_list)
ツイートのサンプルに含まれるすべての単語をまとめたので、NLTKのFreqDistクラスを使って最も一般的な単語を見つけることができます。次のコードをnlp_test.pyファイルに追加してください:
from nltk import FreqDist
freq_dist_pos = FreqDist(all_pos_words)
print(freq_dist_pos.most_common(10))
.most_common() メソッドは、データ内で最も頻繁に出現する単語をリストアップします。これらの変更を行った後、ファイルを保存して閉じてください。
ファイルを今実行すると、データ内で最も一般的な用語が見つかります。
[(':)', 3691),
(':-)', 701),
(':d', 658),
('thanks', 388),
('follow', 357),
('love', 333),
('...', 290),
('good', 283),
('get', 263),
('thank', 253)]
このデータから、絵文字エンティティがポジティブなツイートの最も一般的な部分のいくつかを形成していることがわかります。次のステップに進む前に、上位10トークンを出力するスクリプトの最後の行をコメントアウトしてください。
要約すると、あなたは
次のステップでは、感情分析のためのデータを準備します。
ステップ6 — モデルのためのデータ準備
感情分析は、著者が書いているトピックに対する態度を特定するプロセスです。あなたはモデルを訓練するためのトレーニングデータセットを作成します。これは監視学習の機械学習プロセスであり、各データセットに「感情」を関連付ける必要があります。このチュートリアルでは、あなたのモデルは「ポジティブ」と「ネガティブ」の感情を使用します。
感情分析は、テキストをさまざまな感情に分類するために使用できます。簡便さとトレーニングデータセットの利用可能性のために、このチュートリアルでは、ポジティブとネガティブの2つのカテゴリのみでモデルをトレーニングする手助けをします。
モデルとは、ルールと方程式を用いてシステムを記述したものです。例えば、身長から体重を予測する方程式のように、非常に単純なものである場合もあります。あなたが構築する感情分析モデルは、ツイートにポジティブまたはネガティブな感情を関連づけるものです。データセットを二つの部分に分ける必要があります。最初の部分の目的はモデルを構築することであり、次の部分はモデルの性能をテストすることです。
データ準備のステップでは、トークンを辞書形式に変換し、トレーニングとテストの目的でデータを分割することによって、感情分析のためのデータを準備します。
トークンを辞書に変換する
まず、モデルに入力するデータを準備します。モデルを実行するためにNLTKのナイーブベイズ分類器を使用します。このモデルはツイート内の単語のリストだけでなく、単語をキー、Trueを値としたPythonの辞書を必要とします。以下の関数は、クリーンデータのフォーマットを変更するためのジェネレーター関数を作成します。
クリーンなトークンのリストからツイートを、トークンをキーとし、値としてTrueを持つ辞書に変換するために、以下のコードを追加します。対応する辞書はpositive_tokens_for_modelとnegative_tokens_for_modelに保存されます。
...
def get_tweets_for_model(cleaned_tokens_list):
for tweet_tokens in cleaned_tokens_list:
yield dict([token, True] for token in tweet_tokens)
positive_tokens_for_model = get_tweets_for_model(positive_cleaned_tokens_list)
negative_tokens_for_model = get_tweets_for_model(negative_cleaned_tokens_list)
モデルのトレーニングとテストのためのデータセットの分割
次に、NaiveBayesClassifier クラスのトレーニングのためにデータを準備する必要があります。データを準備するために次のコードをファイルに追加してください:
...
import random
positive_dataset = [(tweet_dict, "Positive")
for tweet_dict in positive_tokens_for_model]
negative_dataset = [(tweet_dict, "Negative")
for tweet_dict in negative_tokens_for_model]
dataset = positive_dataset + negative_dataset
random.shuffle(dataset)
train_data = dataset[:7000]
test_data = dataset[7000:]
このコードは、各ツイートにPositiveまたはNegativeラベルを付けます。そして、正のツイートと負のツイートを結合してdatasetを作成します。
デフォルトでは、データはすべての肯定的なツイートが続いた後にすべての否定的なツイートが順番に含まれています。モデルをトレーニングするときは、バイアスを含まないデータのサンプルを提供する必要があります。バイアスを避けるために、random の.shuffle() メソッドを使用してデータをランダムに配置するためのコードを追加しました。
最後に、コードはシャッフルされたデータをトレーニング用とテスト用にそれぞれ70:30の比率で分割します。ツイートの数が10000件であるため、シャッフルされたデータセットから最初の7000件のツイートをモデルのトレーニングに使用し、最後の3000件をモデルのテストに使用することができます。
このステップでは、クリーンされたトークンを辞書形式に変換し、データセットをランダムにシャッフルし、トレーニングデータとテストデータに分割しました。
ステップ7 — モデルの構築とテスト
最後に、NaiveBayesClassifierクラスを使用してモデルを構築できます。.train()メソッドを使用してモデルをトレーニングし、.accuracy()メソッドを使用してテストデータでモデルをテストします。
...
from nltk import classify
from nltk import NaiveBayesClassifier
classifier = NaiveBayesClassifier.train(train_data)
print("Accuracy is:", classify.accuracy(classifier, test_data))
print(classifier.show_most_informative_features(10))
コードを追加後、ファイルを保存して閉じ、実行してください。コードの出力は次のようになります:
Accuracy is: 0.9956666666666667
Most Informative Features
:( = True Negati : Positi = 2085.6 : 1.0
:) = True Positi : Negati = 986.0 : 1.0
welcome = True Positi : Negati = 37.2 : 1.0
arrive = True Positi : Negati = 31.3 : 1.0
sad = True Negati : Positi = 25.9 : 1.0
follower = True Positi : Negati = 21.1 : 1.0
bam = True Positi : Negati = 20.7 : 1.0
glad = True Positi : Negati = 18.1 : 1.0
x15 = True Negati : Positi = 15.9 : 1.0
community = True Positi : Negati = 14.1 : 1.0
精度は、モデルが感情を正しく予測できたテストデータセット内のツイートの割合として定義されます。テストセットでの99.5%の精度はかなり良いです。
最も情報量の多い特徴を示す表では、出力の各行がトレーニングデータセット内のポジティブおよびネガティブとしてタグ付けされたツイートにおけるトークンの出現比率を示しています。データの最初の行は、トークン :( を含むすべてのツイートで、ネガティブツイートとポジティブツイートの比率が 2085.6 対 1 であったことを示しています。興味深いことに、ポジティブデータセットには :( を含むトークンが1つ存在していたようです。テキスト内でのトップ2の識別項目は絵文字であることがわかります。さらに、sad のような言葉はネガティブな感情を引き起こすのに対し、welcome や glad はポジティブな感情と関連付けられています。
次に、モデルがTwitterのランダムなツイートでどのようにパフォーマンスを発揮するかを確認できます。このコードをファイルに追加してください:
...
from nltk.tokenize import word_tokenize
custom_tweet = "I ordered just once from TerribleCo, they screwed up, never used the app again."
custom_tokens = remove_noise(word_tokenize(custom_tweet))
print(classifier.classify(dict([token, True] for token in custom_tokens)))
このコードを使用すると、custom_tweet 変数に関連付けられた文字列を更新することで、カスタムツイートをテストすることができます。これらの変更を加えた後、ファイルを保存して閉じてください。
カスタムテキストを分析するためにスクリプトを実行します。こちらが例のカスタムテキストの出力です:
'Negative'
ポジティブなツイートを正しく特徴づけているかどうかも確認できます:
...
custom_tweet = 'Congrats #SportStar on your 7th best goal from last season winning goal of the year :) #Baller #Topbin #oneofmanyworldies'
こちらが出力です:
'Positive'
ポジティブな感情とネガティブな感情の両方をテストしたので、より複雑な感情であるサルカズムをテストするために変数を更新してください。
...
custom_tweet = 'Thank you for sending my baggage to CityX and flying me to CityY at the same time. Brilliant service. #thanksGenericAirline'
こちらが出力です:
'Positive'
このモデルはこの例をポジティブとして分類しました。これは、トレーニングデータが十分でなく、皮肉なツイートをネガティブとして分類できなかったからです。モデルに皮肉を予測させたい場合は、それに応じてトレーニングするための十分な量のトレーニングデータを提供する必要があります。
このステップでは、モデルを構築し、テストしました。また、特定の例で皮肉を検出できないなど、いくつかの制限についても調査しました。完成したコードにはチュートリアルに沿った余分な部分が残っているため、次のステップでは、コードをPythonのベストプラクティスに合わせる手順を案内します。
ステップ8 — コードの整理(オプション)
チュートリアルを完了したにもかかわらず、nlp_test.pyファイルのコードを再整理して、プログラミングのベストプラクティスに従うことをお勧めします。ベストプラクティスに沿って、あなたのコードは次の基準を満たすべきです:
- すべてのインポートはファイルの先頭に配置する必要があります。同じライブラリからのインポートは、1つのステートメントにまとめてグループ化するべきです。
- すべての関数はインポートの後に定義するべきです。
- ファイル内のすべての文は、
if __name__ == "__main__":条件の下に配置されるべきです。これにより、別のファイルでファイルの関数をインポートしている場合に文が実行されないことが保証されます。
チュートリアルに従ってコメントアウトされたコードと、レマタイズが新しい remove_noise 関数によって完了されるため、lemmatize_sentence 関数も削除します。
こちらが清掃されたnlp_test.pyのバージョンです:
from nltk.stem.wordnet import WordNetLemmatizer
from nltk.corpus import twitter_samples, stopwords
from nltk.tag import pos_tag
from nltk.tokenize import word_tokenize
from nltk import FreqDist, classify, NaiveBayesClassifier
import re, string, random
def remove_noise(tweet_tokens, stop_words = ()):
cleaned_tokens = []
for token, tag in pos_tag(tweet_tokens):
token = re.sub('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+#]|[!*\(\),]|'\
'(?:%[0-9a-fA-F][0-9a-fA-F]))+','', token)
token = re.sub("(@[A-Za-z0-9_]+)","", token)
if tag.startswith("NN"):
pos = 'n'
elif tag.startswith('VB'):
pos = 'v'
else:
pos = 'a'
lemmatizer = WordNetLemmatizer()
token = lemmatizer.lemmatize(token, pos)
if len(token) > 0 and token not in string.punctuation and token.lower() not in stop_words:
cleaned_tokens.append(token.lower())
return cleaned_tokens
def get_all_words(cleaned_tokens_list):
for tokens in cleaned_tokens_list:
for token in tokens:
yield token
def get_tweets_for_model(cleaned_tokens_list):
for tweet_tokens in cleaned_tokens_list:
yield dict([token, True] for token in tweet_tokens)
if __name__ == "__main__":
positive_tweets = twitter_samples.strings('positive_tweets.json')
negative_tweets = twitter_samples.strings('negative_tweets.json')
text = twitter_samples.strings('tweets.20150430-223406.json')
tweet_tokens = twitter_samples.tokenized('positive_tweets.json')[0]
stop_words = stopwords.words('english')
positive_tweet_tokens = twitter_samples.tokenized('positive_tweets.json')
negative_tweet_tokens = twitter_samples.tokenized('negative_tweets.json')
positive_cleaned_tokens_list = []
negative_cleaned_tokens_list = []
for tokens in positive_tweet_tokens:
positive_cleaned_tokens_list.append(remove_noise(tokens, stop_words))
for tokens in negative_tweet_tokens:
negative_cleaned_tokens_list.append(remove_noise(tokens, stop_words))
all_pos_words = get_all_words(positive_cleaned_tokens_list)
freq_dist_pos = FreqDist(all_pos_words)
print(freq_dist_pos.most_common(10))
positive_tokens_for_model = get_tweets_for_model(positive_cleaned_tokens_list)
negative_tokens_for_model = get_tweets_for_model(negative_cleaned_tokens_list)
positive_dataset = [(tweet_dict, "Positive")
for tweet_dict in positive_tokens_for_model]
negative_dataset = [(tweet_dict, "Negative")
for tweet_dict in negative_tokens_for_model]
dataset = positive_dataset + negative_dataset
random.shuffle(dataset)
train_data = dataset[:7000]
test_data = dataset[7000:]
classifier = NaiveBayesClassifier.train(train_data)
print("Accuracy is:", classify.accuracy(classifier, test_data))
print(classifier.show_most_informative_features(10))
custom_tweet = "I ordered just once from TerribleCo, they screwed up, never used the app again."
custom_tokens = remove_noise(word_tokenize(custom_tweet))
print(custom_tweet, classifier.classify(dict([token, True] for token in custom_tokens)))
結論
このチュートリアルでは、Python 3のnltkライブラリを使用した基本的な感情分析モデルについて紹介しました。まず、ツイートをトークン化し、単語を正規化し、ノイズを取り除くことでツイートの前処理を行いました。次に、データ内で頻繁に発生する項目を視覚化しました。最後に、ツイートを特定の感情に関連付けるモデルを構築しました。
教師あり学習モデルは、そのトレーニングデータに依存しています。モデルをさらに強化するために、興奮や怒りのような追加のカテゴリを考慮することができます。このチュートリアルでは、基本的なモデルを構築することで、ほんの表面をなぞったに過ぎません。ここでは、感情分析を行う際に注意すべきさまざまな考慮事項に関する詳細なガイドを示します。
