【自然言語処理】テキストデータを極性辞書で感情分析してみる

こんにちは。

今日はツイートデータを用いてテキストのネガティブ/ポジティブ分析に挑戦してみたいと思います!

極性辞書を用いた感情分析について

今回は、感情分析手法の中でもベーシックと思われる、事前作成済の極性辞書を用いた方法で進めていこうと思います。

極性辞書とは簡単に言えば、ある単語が一般的にネガティブなのか、ポジティブなのかを、-1(ネガティブ)から1(ポジティブ)までのスコアの形で表現したものになります。

例えば、”感染”という単語であれば、-0.99(かなりネガティブ)といった具合です。テキストデータを形態素解析で単語に分解した上で、各単語に辞書のスコアを割り当てていき、テキスト全体で合計し、そのテキストがポジティブ/ネガティブかを判定します。

一般に公開されている極性辞書としては、以下の2つが有名です。

今回は私は収録単語数がより多い単語感情極性対応表の方を利用しました。

辞書を用いた方法の注意点

・Qiitaのこちらの記事が大変わかりやすいのですが、上記のような極性辞書を用いた手法は、シンプルでコーディングも楽である一方、精度の点ではイマイチであるケース(精度を上げるためのチューニングが面倒)が多いようです。

・例えば、「悪くない」(悪い+ない)という単語に対して辞書からスコアを算出すると、悪い(ネガティブ)+ない(ネガティブ)でネガティブなワードとして算出されるでしょう。これは実際の感覚とは異なります。

・なので、例えば極性スコアの振られている単語の後に”ない”が続いた場合は、その単語のスコアを-1倍して反転させる、等の考慮が必要になってきます。

上記の課題を解消するための方法として、最近はWord2VecやFastTextといったDeep Learningを活用した手法がありますが、それはまた別の機会で実践してみたいと思います!

極性辞書を使った感情分析の実装

まず、実装にあたり、以下の準備が必要ですので、こちらは完了させておいてください。

実装の前提

・上記の極性辞書は事前にダウンロード済であること

・形態素解析に必要なライブラリ・辞書(MeCab、Neologdn)が使える状態であること。

*こちらは、過去の記事で紹介させていただいております。

・以下のようなテキストデータ(今回はTwitterから取得した”新型コロナ”を含む4000件程度のデータ)がデータフレーム(またはSeries)の形で取得できている状態

*テキストだけあれば問題ありません。
*取得方法については過去の記事でも説明がありますので、取得方法については割愛します。

想定しているデータセット

それでは、実装を進めていきます。

ステップ1:テキストデータの前処理

形態素解析を行う前に、事前にテキストのクリーニングを実施しておきます。

具体的には、英数字・記号を控除しておいたり、半角・全角の統一をしておいたり等の表現の正規化を実施しておきます。preProcessorという関数を自分で定義して、その中に必要な処理を実装しておくイメージです。

df_tweet["text_前処理後"] = df_tweet["text"].apply(lambda x: preProcessor(x))

ステップ2:テキストデータの形態素解析

形態素解析にあたっては、MeCab+Neologdn辞書を利用しています。また、分かち書きした単語は、表記統一のため標準形に変換しておくようにしています。

def textParser(text):
    sentense = []

    ## 分かち書きのみ
    tagger = MeCab.Tagger('-Owakati -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd')
    
    ## バージョン1:全単語取得
    #node = tagger.parse(text)
    node = tagger.parseToNode(text)
    while node:
        if node.feature.split(",")[0] != 'BOS/EOS':
            sentense.append(node.feature.split(",")[6])
        node = node.next
    return sentense

df_tweet["text_形態素解析後"] = df_tweet["text_前処理後"].apply(lambda x: textParser(x)).apply(lambda x: ",".join(x))
前処理+形態素解析後のデータイメージ

ステップ3:Bag-Of-Wordsで単語のベクトル化+出現頻度カウント

def Vectorization_BOW(df):
    vectorizer = CountVectorizer(token_pattern=u'(?u)\\b\\w+\\b',max_features=100000,stop_words=stop_words)
    vecs = vectorizer.fit_transform(df)
    header = vectorizer.get_feature_names()
    return vecs, header

vecs, header = Vectorization_BOW(df_tweet["text_形態素解析後"])
ベクトル化後のイメージ

ステップ4:感情値算出

さて、ここまでの前準備を経た後で、いよいよ感情値の算出に進みます。

以下、何をしているかと言うと、読み込んだ辞書で、単語、品詞、読みまで同じ単語がなぜかいくつかあるので、その重複を排除するのと、今回は簡易なトライアルなので、品詞・読みのパターンは考慮しないでやるということで、単語毎にスコアがユニークになるように控除しています。

## 辞書の読み込み
df_pn = pd.read_csv('単語感情極性対応表.dic',delimiter=":", encoding='cp932',names=["単語","読み","品詞","感情値"])
print(len(df_pn))
df_pn = df_pn.groupby(["単語","読み","品詞"],as_index=False).first().reset_index(drop=True)
print(len(df_pn))
df_pn = df_pn.groupby(["単語"],as_index=False).first().reset_index(drop=True)
print(len(df_pn))

辞書の整形が終わったところで、クリーニング済テキストデータに辞書をぶつけて感情値を算出していきます。

%%time
features = np.arange(len(df_tweet))

## 単語ベクトルに辞書のスコアを結合していく。辞書が突合しなかった単語は落とす。
df_tmp = pd.merge(pd.DataFrame(vecs.toarray(), columns=header).T.reset_index(),df_pn[["単語","感情値"]], left_on="index", right_on="単語", how="left").dropna()
print(df_tmp.shape)

## テキスト毎にスコアがふれた単語数とスコアの合計値を算出し、平均を算出
df_word_count = df_tmp[features].sum()
df_tmp[features] = df_tmp[features].apply(lambda x: x*df_tmp["感情値"])
df_word_score = df_tmp[features].sum()
df_score = pd.DataFrame(zip(df_word_score,df_word_count),columns=["感情スコア_合計値","感情スコア_対象単語数"])
df_score = pd.concat([df_tweet, df_score],axis=1)
df_score["感情スコア_平均値"] = df_score["感情スコア_合計値"]/df_score["感情スコア_対象単語数"]

以上でテキストデータ毎に感情スコアが算出できました。

結果を観察していきます。まずはスコア分布から。

これを見てみると、ほとんどのテキストデータがネガティブに寄っていることが分かります。

確認してみると、そもそものテキストの抽出条件キーワード自体にもスコアが振られていたからでした。

今回、「新型コロナ」を含むテキストデータに絞った分析をしていたわけですが、辞書にこれだけのスコア情報が登録されていました。

分析の目的が、特定キーワードを含むツイートに対して、ポジティブ/ネガティブなツイートを明らかすることであれば、抽出条件自体のスコアはカウントしないようにすべきなので、今回は辞書からこれらの単語を控除しておくように調整します。

その結果、今度は以下のようなスコア分布になりました。(加えて、.fillna(-2)を追加して、辞書に単語が1つもなくスコアが振られなかったテキストがあることに考慮しています)

最頻スコア帯が微妙にプラスよりに動きましたが、それでも依然ネガティブなツイートが多いと出ています。

これが妥当かどうか、実際のテキストデータで、ポジティブなものとネガティブなものを見てみます。

もっともネガティブだとスコアリングされたツイート上位5件

ポジティブだとスコアリングされたツイート上位5件

うーん、当たっているものもあれば、感覚と違うものもありますね。

  • 絵文字は「嬉し涙」と日本語に変換されていたりして、感情を正しく表す場合もあれば今回のツイートのように、ネガティブな意味合いで使われることもあり、扱いを考えた方が良いかも。笑も
  • 上の例では出てきてないですが、単語+否定形「ない」の場合に前の単語のスコアを反転させる考慮は必要。
  • 辞書に突合する単語がそもそも少ない。

今回は、極性辞書ベースでの簡単な感情分析を行ってみました。

次は、Word2VecやFastTextといったDeep Learningベースの感情分析手法を試してみようと思います。

この記事を気に入っていただけたらシェアをお願いします!

ABOUT US

Yuu113
初めまして。Yuu113と申します。 兵庫県出身、東京でシステムエンジニアをしております。現在は主にデータ分析、機械学習を活用してビジネスモデリングに取り組んでいます。日々学んだことや経験したことを整理していきたいと思い、ブログを始めました。旅行、カメラ、IT技術、江戸文化が大好きですので、これらについても記事にしていきたいと思っています。