見出し画像

自然言語処理(NLP)について知りたくなったら~係り受け解析 コード実践編~

データテクノロジーラボです。

自然言語処理に関して連載の第3回目となります。

今回は「係り受け解析」を実際にコードを書いていきながら、どのように進めていけばよいのか見ていきます。

形態素解析までは詳しく書いている記事はあるんですが、係り受け解析から記事数が少ない気がしています。

今回は、「いや、形態素解析までは理解してるけど!」という方のお役に立てるように係り受け解析について解説できればと思います。

ハンズオン形式で前回と同様細かめの解説をしていく予定です。

では、行きましょう!(時間がない方は③からで大丈夫です!)

①係り受け解析のおさらい

形態素解析で、各形態素に分けたあと、形態素がどの形態素に係り受け(くっついているのか)を調べるのが係り受け解析(構文解析)でした。

詳しくは前回までの投稿から!

②係り受けのライブラリの種類

だいたい「python 係り受け解析 ライブラリ」で検索すれば
・GiNZA(ぎんざ)※spaCy互換日本語モデル
・CaboCha(かぼちゃ)
・NLTK

が良く出てきます。

形態素解析の回同様に、そこまで本格的な開発などを求めないのであれば、動かしやすいものから始めるのが一番良いと思っています。

importエラーが一番へこむので。。

・GiNZA(ぎんざ)

「spaCy(すぺーしー)」とよくセットで出てくると思いますが、spaCyについて先に説明すると、オープンソースの自然言語処理ライブラリです。

これだけだとなに?みたいなことになるので、補足すると、言語サポートが豊富で学習済みのモデルなども使えます。(つまり、いちいち自分でモデルの自作する手間を取りたくない!みたいな方にはうれしい。)

しかし、spaCyの学習済みモデルには日本語対応したものがなぜがなかったところに登場したのがGiNZAです。

一言でいえば、spaCyが日本語で使えるのがGiNZAくらいと思っておけば最初は大丈夫です。

もっと深く知りたい方はこちらがとても参考になります!

インストールの障壁も低く、pip install でドン!で基本的にすみます。(ほんとうれしい!)

・CaboCha

日本語の係り受け解析器です。

SVM(サポートベクターマシン 詳細は割愛しますが、機械学習のパターン認識モデル。分類など強い)などに基づいて固有表現も抜き出せるなど機能は豊富。

ただし、インストールにちょっと癖ありです。Linux系のOSをお使いなら比較的楽ですが、Windows OSの場合は癖ありです。。

・NLTK

そもそもの略称はNatural Language Toolkitです。(自分も調べて知りました。。)

※公式ドキュメントです。

日本語よりも英語の自然言語処理を強みとしているため、詳細な説明は割愛します。

③係り受けをコードで解説

今回はインストールしやすいGiNZAを使用して、どのように使っていくのかを見ていきます。

インストールの手順は以下二つが参考になりましたので、ここでの解説は割愛させていただきます。

それでは、見ていきましょう!

まず、注意点として今回Windows PCを使用していますが、Ubuntu(Ubuntu 20.04.2)を使用して稼働させています。

(余談ですが、自然言語処理に置いて、Unixの呪縛強めな感じ、個人的に感じています。。)

まずは、Ubuntuを開き、

(base) $ginza

を実行してginzaを起動させてみましょう。

そのあと、適当に文章を入力すると、以下のような結果が得られます。

画像1

お好きな文章で最初は試してもらえればと思います。

順番に何が書かれているのかというと、CoNLL-U formatと呼ばれる形式で出力されています。
・id(順番に割り振られます。1から始まります。)
・form(表層形)
・lemma(原形)
・upos tag(品詞)
・xpos tag(言語依存の品詞)
・feats(形態素の構成の仕組み)
・head(係り先)
・deprel(係り受けのラベル)
・deps(二次係り受け)
・ner(その他)

上記の深堀は今回の本質ではないので、詳しく知りたい方はこちらからどうぞ

今度はnotebookなどお使いのjupyterなどで動かしてみます。

解説①まずは書いてみる

全体から。

import spacy
#ginzaを使用するためのモデルを読み込み
nlp = spacy.load('ja_ginza')

#ginzaで解析を実行
doc = nlp('今日は夜が長いなと思ったら、遮光カーテンを開け忘れていました。')

#doc.sents で文章ごとの列が返って来ます。
for sent in doc.sents:
   for token in sent:
       info = [
           token.text,     # 表層
           token.lemma_,    # 基本形
           token.pos_,      # 品詞
           token.tag_,      # 品詞詳細
           token.head      #係り先
       ]
       print(info)

最初はライブラリのインポートとginzaをよみこむため、spacy.loadを実行します。

import spacy
#ginzaを使用するためのモデルを読み込み
nlp = spacy.load('ja_ginza')

次に、解析したい文章を実際に解析します。

doc = nlp('今日は夜が長いなと思ったら、遮光カーテンを開け忘れていました。')

このdocには解析された結果が格納されています。オブジェクト形式なので、さまざまなメソッドが用意されています。
・Doc.sents : 文章ごとに区切られて返されます。
・Doc.vector : 語彙のベクトルを返します
・Doc.noun_chunks :名詞句の列を返します
・Doc.similarity : コサイン類似を予測します

今回は一文ですが、基本的にdoc.sentsを利用し、それぞれをトークン化(意味あるかたまりに分割)しています。

for sent in doc.sents:
   for token in sent:
       info = [
           token.text,     # 表層
           token.lemma_,    # 基本形
           token.pos_,      # 品詞
           token.tag_,      # 品詞詳細
           token.head      #係り先
       ]
       print(info)

実行結果は以下です!

画像2

形態素解析よりも詳しく情報が取ってこれました。(遮光カーテンは分割されてますが。。)

ちなみに、もっとトークンの持つ情報を使いたい場合は、以下のサイトが参考になります。

解説②可視化で係り受けが見たいときは

次に係り受けにおいて、どこがどうかかっているのか視覚的に見てみたい!と思ったときどうすればよいのか書いていきます。

では、コード全体から。

import spacy
from spacy import displacy
#ginzaを使用するためのモデルを読み込み
nlp = spacy.load('ja_ginza')

sentences = ['虎杖は東堂のことを最初は怪しく思っていました。', '虎杖は戦っていくうちに、東堂をマイベストフレンドと思うようになりました。']

#ginzaで解析を実行
#nerの情報は今回は表示させない(必要な場合はdisable=~を削除でおけ)
docs = list(nlp.pipe(sentences, disable=['ner']))

#可視化!
displacy.render(docs, style="dep", options={"compact":True})

では、順を追って説明します。

まずは先ほどと同様にライブラリのインポート、ginzaの読み込みです。可視化の際に、spacyのなかのdisplay関数は個別で呼び出しておきます。

import spacy
from spacy import displacy
#ginzaを使用するためのモデルを読み込み
nlp = spacy.load('ja_ginza')

次に、先ほどは一文でしたが、複数文章を読み込みたいときは、リストに格納しておきます。

sentences = [
     '虎杖は東堂のことを最初は怪しく思っていました。',
     '虎杖は戦っていくうちに、東堂をマイベストフレンドと思うようになりました。'
 ]

準備したテキストを先ほどと同様にnlpに入れていきますが、文章が長くなれば長くなるほど処理に時間を要するため、内部でバッチ処理(一定量のデータを一括で処理)をしてくれるpipeメソッドを利用します。

今回、もっと高速に処理したい(わけではなかった)ので、ner(その他)の情報を無効化(読み込まない)disableを引数に指定します。全部読み込んでおいてほしい場合は、なにも引数に取らずそのまま入れましょう。

#ginzaで解析を実行
docs = list(nlp.pipe(sentences, disable=['ner']))

nlp.pipeはgenerator(前回の投稿でちらっと話しました)なので、listにしておきます(しなくてもいいです。)

最後に可視化です。

基本的にはdisplay.renderで行けます。
お決まりな感じなので、サクッと気にせず書いちゃう、くらいでいいと思います。
※表示の際にコンパクトに表示するためにoptionsを利用していますが、好みです

displacy.render(docs, style="dep", options={"compact":True})

実際の出力はこんな感じです!(見にくいですね。。)

画像3

解説③ベクトル化した文章や単語で類似判定や演算

係り受け解析をすると文章をベクトル化できる強みがあります。

「せっかくここまで来たのに数学かよ。。」みたいな方も、(たくさん)いると思うので、今回は数学的知識や数式は説明せず、文章での説明にとどめます(数学的知識はいずれ避けれなかったりするので、別個で開設予定。)

解説①で、文章同士がどのように似ているのか?を判定してるDoc.similarityというメソッドを紹介しました。似た者同士の文章を分類したりすることができるので、解析に非常に役立ちます。

単語・文章で、順番に類似度を見ていきましょう。

コード全体から。

import spacy
#ginzaを使用するためのモデルを読み込み
nlp = spacy.load('ja_ginza')

word1 = "少年"
word2 ="ジャンプ"
word3 = "週刊少年"

doc1 = nlp(word1)
doc2 = nlp(word2)
doc3 = nlp(word3)

print(f"{word1}{word2}の類似度は{doc1.similarity(doc2)}")
print(f"{word3}{word2}の類似度は{doc3.similarity(doc2)}")

書いている内容はシンプルなので、説明は省略して、出力結果をいきなり見てみましょう!

画像4

数字を見ても、よーわからん!ってなりますが、似ていると数値が大きくなる、くらいで大丈夫です。
(ちょっとだけ補足すると、一番小さい0という数値が出たら「全く似ていない」ことは言えますが、その単語同士が「対義語」であるというわけではないことに注意しましょう。)

実際にどの辺の数値が良い悪いという判断はしにくく、今回の例でいえば、「週刊少年」と「ジャンプ」のほうが「少年」と「ジャンプ」よりは似ている、くらいですね。

では、文章同士を見ていきましょう。

コード全体から。

import spacy
#ginzaを使用するためのモデルを読み込み
nlp = spacy.load('ja_ginza')

sentence1 = "石けんは、本来なら混じり合わない水と油を均一に混ぜ合わせる界面活性剤の作用で、油などの汚れを洗い落とします"
sentence2 = "皮脂層は乾燥を防ぐだけでなく、角質の中と毛穴にある常在菌のすみかを守る働きもあるという。"

doc1 = nlp(sentence1)
doc2 = nlp(sentence2)

print(f"類似度は{doc1.similarity(doc2)}")

こちらも先ほどまでの内容ですので、出力結果を見ていきましょう。

画像5

0.88というため、近しい文章といえます。

コサイン類似度などの数式をばっつりと省いた説明だったので、「ふ~ん」くらいかもしれませんが、今後解説していく予定です。

・おまけ①固有表現抽出

ちょっとだけ踏み込みたいあなたに、ここからおまけとして2つほどさらに解説します。

まずは「固有表現抽出」から。

業務の中で、固有表現(人、企業の名前など)を検出したいときに役立ちますが、ginzaではカンタンにパパっと抽出してくれます。

コードはこんな感じ
※おまけ的なパートなので、コードの詳細な解説は省きますがdisplay関数の引数のstyleをentに変更しているくらいです。

import spacy
#ginzaを使用するためのモデルを読み込み
nlp = spacy.load('ja_ginza')


#テキスト読み込み
filepath = "./text/sample2.txt"
#参考のテキスト元は
#https://news.yahoo.co.jp/articles/c64709262ffb43ca1c329f18681869752bd7e238
with open(filepath) as file:
   senteces = file.read()

#ここでいろんな処理が行われる
doc = nlp(senteces) 

#固有表現抽出の結果の描画
displacy.render(doc, style="ent", jupyter=True)

画像6

見にくいですが、めちゃくちゃきれいに抽出してくれます!

ちなみに、可視化でなく、単語を取り出したいだけという方は、Doc.entsで取得できます。

import spacy
#ginzaを使用するためのモデルを読み込み
nlp = spacy.load('ja_ginza')

doc = nlp('田中さんは最近、銀座で山田太郎さんとハンバーグ食べてたらしいよ')

# 固有表現抽出
for ent in doc.ents:
  print(
      ent.text+','+ # テキスト
      ent.label_+','+ # ラベル
      str(ent.start_char)+','+ # 開始位置
      str(ent.end_char)) # 終了位置

画像7

・おまけ②主成分分析を使って、次元圧縮し類似度を可視化

類似度のところで、視覚的に観たいけどな~と感じた方は、主成分分析で2次元まで圧縮し、プロットしてみるといいかもしれません。

コードはこちらを参考

import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
import spacy

#ginzaを使用するためのモデルを読み込み
nlp = spacy.load('ja_ginza')

#テキストの準備
text1 = "アキが子供時代にやりたかった「雪合戦」の場面で、現実の凄惨な戦闘シーンを代替している。"
text2 = "竹串などで、ハンバーグの真ん中にスポッと入れて、透明な汁がでたらできあがり。"
text3 = "データサイエンスはデータに関する総合的な学問及び学術分野といえます。"
text4 = "盛岡まで4時間かかる田舎出身で、都会へのあこがれが強いおしゃれな女の子です"
text5 = "残った汁に、ご飯を入れておじや風にして食べるのもおすすめ。"
text6 = "人工知能(AI)とは、人間の知的ふるまいの一部をソフトウェアを用いて人工的に再現したものです。"


#解析し、ベクトル化されたものを格納(100次元のベクトル)
vec1 = nlp(text1).vector
vec2 = nlp(text2).vector
vec3 = nlp(text3).vector
vec4 = nlp(text4).vector
vec5 = nlp(text5).vector
vec6 = nlp(text6).vector

#主成分分析
#列方向(axis=0)に連結し、6*100行列を作成
vectors = np.vstack((vec1, vec2, vec3, vec4, vec5, vec6))
#2次元に圧縮し学習
pca = PCA(n_components=2).fit(vectors)
trans = pca.fit_transform(vectors)
#寄与率を格納
pc_ratio = pca.explained_variance_ratio_

#plot
plt.figure()
plt.scatter(trans[:,0], trans[:,1])

i = 0
for txt in ['text1','text2','text3','text4','text5','text6']:
   #テキストを注釈として表示
   plt.text(trans[i,0]-0.2, trans[i,1]+0.1, txt)
   i += 1

plt.hlines(0, min(trans[:,0]), max(trans[:,0]), linestyle='dashed', linewidth=1)
plt.vlines(0, min(trans[:,1]), max(trans[:,1]), linestyle='dashed', linewidth=1)
plt.xlabel('PC1 ('+str(round(pc_ratio[0]*100,2))+'%)')
plt.ylabel('PC2 ('+str(round(pc_ratio[1]*100,2))+'%)')
plt.tight_layout()
plt.show()

画像8

そこまでいい結果とは言えませんね。。

工夫次第ではとても良きものが出てくると思いますので、ぜひチャレンジしてみてください!

・おわり

いかがでしたでしょうか?

意外と係り受け解析を体系的に学びたい!ってなったときに、なかなか見つからなかったので、同じような方に届けばうれしいです!

あと、めちゃくちゃながいので、一気に読むよりも、必要な情報だけ見ていくので、十分です。

さいごまでご覧いただきありがとうございます。

また次回お会いしましょう~!

ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー

・今回参考に参考にさせていただいたURL一覧(大変助かりました!!)

https://www.koi.mashykom.com/spacy_ginza.html#fd02
https://www.ogis-ri.co.jp/otc/hiroba/technical/similar-document-search/part4.html
https://qiita.com/youwht/items/b047225a6fc356fd56ee
https://spacy.io/usage/visualizers
https://tech-blog.optim.co.jp/entry/2020/08/05/000000
https://www.virment.com/ginza-dependency-parse/



この記事が気に入ったらサポートをしてみませんか?