CCCMKホールディングス TECH Labの Tech Blog

TECH Labスタッフによる格闘記録やマーケティング界隈についての記事など

自然言語処理ライブラリspaCyを使ってレビュー文章を解析する方法を考えています。

こんにちは、技術開発の三浦です。

あっという間に10月になりました。昔の好きな曲の歌詞に「10月の夕暮れ」というフレーズが出てくるのですが、この時期になるとそのフレーズを思い出します。夕方になると、「今日の夕暮れはどんな感じだろう?」って気になる10月です。

インターネットショッピングなどで、他のユーザのレビューを参考にしながら商品を選んだという経験、けっこうあるのではないでしょうか。私はパソコン周辺機器やお菓子などを選ぶとき、レビューを参考にすることが多いです。レビューの文章の中には対象の商品に関する、そのユーザが考える特徴を表す表現が含まれています。この表現をレビューの中から上手く捉えることが出来れば、その商品の特徴をもっと効率的に知ることが出来そうです。

今回これを実現する方法として自然言語処理の形態素解析係り受け解析という手法が使えないか、と考えてみました。形態素解析は文章から文章の最小構成要素である形態素を見つけてその品詞(名詞や動詞など)を推計する手法で、係り受け解析は形態素間の関係性(主語や述語など)を推計する手法です。レビューを見ているとその商品に関する特徴は、評価を表す形容詞とそれに関連する機能を表す名詞で表現されていることが多いなど、ある程度の傾向があるようです。その特徴に該当する表現をレビューの文章から機械的に抽出出来ないか、というのが今回のテーマです。

形態素解析、係り受け解析といった自然言語処理に関する機能が搭載されたPythonのライブラリ「spaCy」を使って試してみました!

spaCy

spaCyはオープンソースのPythonの自然言語処理ライブラリです。

spacy.io

日本語を含めなんと66種類以上の言語に対応しているそうです。基本的な使い方はとてもシンプルで、解析対象のテキスト(str)をLanguageクラスの、一連の処理がまとまったpipelineに入力して形態素解析や係り受け解析を行い、その結果をDocクラスのオブジェクトとして得て、形態素の品詞や見出し語(辞書に登録されている基本形)、他の品詞との関係(係り受け)を確認することが出来るようになります。

Languageのpipelineで行われる処理の中には機械学習モデルによる推計処理が含まれます。そのため処理を行う前に学習済みのpipelineを選び、ダウンロードする必要があります。学習済みのpipelineはこちらで確認することが出来ます。

spacy.io

セットアップ

最初にspaCyのセットアップです。spaCyはpipコマンドを使ってインストールすることが出来ます。

pip install -U spacy

次に学習済みのpipelineをダウンロードします。例えばtransformer(BERT)をベースにした"cl-tohoku/bert-base-japanese-char-v2 (Inui Laboratory, Tohoku University)"のpipelineを使用する場合、以下のコマンドでダウンロードすることが出来ます。

!python -m spacy download ja_core_news_trf

簡単なテキストで結果を確認

それでは最初に簡単なテキストを使い、spaCyでどんな結果が得られるのかを見てみます。

import spacy
nlp = spacy.load('ja_core_news_trf')
text = 'このクッキーは、サクサクして美味しいです!'
doc = nlp(text)

spacy.load()でダウンロードしたpipelineを読み込んでLanguageクラスのnlpを生成しています。あとはnlpにテキスト(text)を入力することで、pipelineを通じて解析処理が行われ、結果がDocクラスで得られます。

解析結果であるdocの中にはトークン(形態素)が格納されています。このトークンに色々な解析結果が属性情報として登録されています。たとえば文章中での表記、辞書で登録されている表記(見出し語)、品詞、係り受け解析における親を確認してみます。

for token in doc:
  print(f'[text]:{token.text}, [lemma]:{token.lemma_},[tag]:{token.tag_},[head]:{token.head}')

結果は以下の様になりました。

[text]:この, [lemma]:この,[tag]:連体詞,[head]:クッキー
[text]:クッキー, [lemma]:クッキー,[tag]:名詞-普通名詞-一般,[head]:美味しい
[text]:は, [lemma]:は,[tag]:助詞-係助詞,[head]:クッキー
[text]:、, [lemma]:、,[tag]:補助記号-読点,[head]:クッキー
[text]:サクサク, [lemma]:サクサク,[tag]:副詞,[head]:美味しい
[text]:し, [lemma]:する,[tag]:動詞-非自立可能,[head]:サクサク
[text]:て, [lemma]:て,[tag]:助詞-接続助詞,[head]:サクサク
[text]:美味しい, [lemma]:美味しい,[tag]:形容詞-一般,[head]:美味しい
[text]:です, [lemma]:です,[tag]:助動詞,[head]:美味しい
[text]:!, [lemma]:!,[tag]:補助記号-句点,[head]:美味しい

3,4行の処理でここまで出力してくれるのか・・・とちょっとびっくりしました。

クッキーの特徴を表す表現

先ほどの文章はお菓子のクッキーのレビューですが、「サクサクして美味しいクッキー」であることが商品の特徴を表す表現であると言えます。解析結果の以下の部分で、今度は係り受け解析における関係性(dependency[dep])も出力してみました。

・・・
[text]:クッキー, [lemma]:クッキー,[tag]:名詞-普通名詞-一般,[head]:美味しい,[dep]:nsubj
・・・
[text]:サクサク, [lemma]:サクサク,[tag]:副詞,[head]:美味しい,[dep]:advcl
・・・
[text]:美味しい, [lemma]:美味しい,[tag]:形容詞-一般,[head]:美味しい,[dep]:ROOT

"サクサク"は"美味しい"を修飾(副詞節修飾語:advcl)、"クッキー"が"美味しい"の主語(nsubj)になっています。このパターンの表現は、何かを評価する時に共通して現れる表現だと思います。たとえば「このアプリは動作がとても軽い。」なども同様です。

spaCyには特定のルールにマッチする表現をテキスト全体から探すRule-based matchingの機能が搭載されています。テキストの内容だけでなく、トークン間の表現も検索対象のルールに含めることが出来ます。

特定のルールにマッチする表現を抽出

トークン間の係り受け関係に基づいて表現を抽出するにはspaCyのDependencyMatcherというクラスを使用します。DependencyMatcherに与えるルールは以下の様に記述します。

pattern = [
  [
    #美味しい
    {
      'RIGHT_ID':'ROOT',
      'RIGHT_ATTRS':{'TAG':{'REGEX':'^形容詞'}}
    },
    #クッキー
    {
      'RIGHT_ID':'NSUVJ',
      'RIGHT_ATTRS':{'DEP':'nsubj'},
      'LEFT_ID':'ROOT',
      'REL_OP':'>'
    },
    #サクサク
    {
      'RIGHT_ID':'ADVCL',
      'RIGHT_ATTRS': {'DEP':'advcl'},
      'LEFT_ID':'ROOT',
      'REL_OP':'>',
    }
  ]
]

最初に'RIGHT_ID':'ROOT'で係り受けで一番上の親になる"美味しい"に該当する条件を記述しています。"美味しい"は品詞が形容詞ですので、'RIGHT_ATTRS':{'TAG':{'REGEX':'^形容詞'}}で品詞タグ(TAG)が"形容詞"で始まるトークンであることを表しています。'^形容詞'は正規表現になっており、正規表現であることを表すために{'REGEX':'^形容詞'}と記述しています。

"美味しい"に依存する、例えば"クッキー"が該当するルールは2つ目の'RIGHT_ID':'NSUVJ'の辞書型で表しています。こちらは依存関係において主語であることを'RIGHT_ATTRS':{'DEP':'nsubj'}で、LEFT_IDに指定したROOTと従属関係であることを'REL_OP':'>'で表しています。"サクサク"も同様です。

このルールに従って、どのように表現が抽出出来るのか確認してみます。

from spacy.matcher import DependencyMatcher

matcher = DependencyMatcher(nlp.vocab)
matcher.add('review_rep',pattern)

text = 'このクッキーは、サクサクしてとても美味しいです!'
doc = nlp(text)
match_rep = matcher(doc)

print(match_rep)

出力結果は以下の様になります。

[(16440954905762048607, [8, 1, 4])]

ルールにマッチする表現がtupleのリストで返ってきました。今回はルールに該当する表現が1つだけなので、リストの要素数は1になります。tupleの要素は2つあり、1つ目はルール名review_repをハッシュ化した値です。2つ目のリストが条件に合致したトークンのインデックスのリストで、これを使ってインデックスに該当するトークンを抽出し、表現を再現することが出来ます。

for m in match_rep:
  print(' '.join([doc[i].lemma_ for i in m[1]]))

文章での使われ方によってトークンの表現がブレてしまうことを防ぐため、見出し語(lemma_)を取るようにしました。

結果は以下の様になりました。

美味しい クッキー サクサク

レビューの文章から欲しかった表現を取得することが出来ました。

まとめ

今回はspaCyを使って文章に形態素解析や係り受け解析を行い、依存関係などを条件に該当する表現を抽出する方法について調べてみました。DependencyMatcherには複数のルールを登録することが出来ますし、もっと色々なルールを記述することが出来るので色々な商品の特徴に関する表現を抽出することが出来ると思います。spaCyでは他にも自然言語関連の機能がたくさんあるようなので、色々と活用していきたいと思いました!