ワードクラウドでは、初期設定では要らない品詞があったり、活用形のままであったり、言葉が途切れてしまいなかなか思い通りにいきません。ステップbyステップで、改善していきます。まずは大量の議事録を入れる前に1文で調整していきます。
最後にコード全体を載せています。
まず、ginzaとwordcloudをインストールします。たしかginzaのインストール時にspacyも一緒にインストールされます。
pip install -U ginza ja_ginza
pip install wordcloud
まずはモデルの読み込みをします。
import spacy
nlp = spacy.load("ja_ginza")
モデルを読み込んだら、解析を行います。
doc = nlp("小動物獣医療が進んで、動物の健康寿命が長くなった")
doc
小動物獣医療が進んで、動物の健康寿命が長くなった
なにも起きていないように見えますが、docの中にはいろいろな分析結果が入っています。
type(doc)
spacy.tokens.doc.Doc
これを以下のようにforで回すと、品詞ごとに分割されます。
for token in doc:
print(token)
小動物 獣 医療 が 進ん で 、 動物 の 健康 寿命 が 長く なっ た
このtokenは文字列ではありません。上のfor loopで一番最後に入れられたtokenを見てみます。
token
た
type(token)
spacy.tokens.token.Token
collectionsライブラリの中のConterを使って、出てきたtokenの数を数えます。そのためには、文字列にする必要があります。
token.text
'た'
type(token.text)
str
collectionsライブラリーのcounterを使って、tokenのカウントをします。
from collections import Counter
counter = Counter()
for token in doc:
counter.update([token.text])
counter
Counter({'が': 2, '小動物': 1, '獣': 1, '医療': 1, '進ん': 1, 'で': 1, '、': 1, '動物': 1, 'の': 1, '健康': 1, '寿命': 1, '長く': 1, 'なっ': 1, 'た': 1})
ではさっそくワードクラウドにしてみます。
import matplotlib.pyplot as plt
from wordcloud import WordCloud
from collections import Counter
wc = WordCloud(background_color="white",
font_path="C:/windows/fonts/meiryo.ttc")
wc.generate_from_frequencies(counter)
plt.axis("off")
plt.imshow(wc)
plt.show()
思っているのと違うのが出てきました。違う理由は以下3つだと思います。
- 「の」「が」はいりません。昔学校でならった国語を思い出すと、格助詞です。
- 「進ん」「なっ」という動詞の活用系の状態はいりません。「進む」とかにしてほしいです。
- 小動物獣医療は分かれてほしくないです。1語になってほしいです。
まずは、1のいらない品詞を無くすに取り組んでみたいと思います。
まずはfor loopの最後にtokenの入った「た」を使って調べてみたいと思います。
token
た
tokenのあとにtag_とつけると日本語の品詞,pos_とつけると全言語共通の品詞名が付けられます。
token.tag_
'助動詞'
token.pos_
'AUX'
このtag_の機能を使って、「が」を除くには格助詞を除けばいいのですが、他の品詞ももワードクラウドに入ったら邪魔なものがあるはずです。(思い出せないだけで)逆に、必要なのは動詞と名詞だとと考えて、ワードクラウドを作ればすっきりします。
for token in doc:
print(token.text,":",token.tag_,":", token.pos_)
小動物 : 名詞-普通名詞-一般 : NOUN 獣 : 接頭辞 : NOUN 医療 : 名詞-普通名詞-一般 : NOUN が : 助詞-格助詞 : ADP 進ん : 動詞-一般 : VERB で : 助詞-接続助詞 : SCONJ 、 : 補助記号-読点 : PUNCT 動物 : 名詞-普通名詞-一般 : NOUN の : 助詞-格助詞 : ADP 健康 : 名詞-普通名詞-形状詞可能 : NOUN 寿命 : 名詞-普通名詞-一般 : NOUN が : 助詞-格助詞 : ADP 長く : 形容詞-一般 : ADJ なっ : 動詞-非自立可能 : VERB た : 助動詞 : AUX
意外とtag_で出てくる種類が多くて、欲しいものだけ指定するのがめんどくさいです。pos_の方が種類が少なくて指定が楽です。pos_のverbとnounだけを使ったワードクラウドにします。
from collections import Counter
counter = Counter()
for token in doc:
if token.pos_ in ["NOUN","VERB"]:
counter.update([token.text])
counter
Counter({'小動物': 1, '獣': 1, '医療': 1, '進ん': 1, '動物': 1, '健康': 1, '寿命': 1, 'なっ': 1})
import matplotlib.pyplot as plt
from wordcloud import WordCloud
from collections import Counter
wc = WordCloud(background_color="white",
font_path="C:/windows/fonts/meiryo.ttc")
wc.generate_from_frequencies(counter)
plt.axis("off")
plt.imshow(wc)
plt.show()
だいぶ良くなりました。
今度は、2. 「進ん」「なっ」という動詞の活用系の状態はいりません。「進む」とかにしてほしいです。というのを実現します。
この「進ん」とかの形を活用形といいます。この原型「進む」を出すためには、token.lemma_とすれば原型を求められます。lemmatizeといいます。
for token in doc:
print(token.text,":",token.lemma_)
小動物 : 小動物 獣 : 獣 医療 : 医療 が : が 進ん : 進む で : で 、 : 、 動物 : 動物 の : の 健康 : 健康 寿命 : 寿命 が : が 長く : 長い なっ : なる た : た
1と2を組みあわせてワードクラウドを作ると以下のコードになります。
from collections import Counter
counter = Counter()
for token in doc:
if token.pos_ in ["NOUN","VERB"]:
counter.update([token.lemma_])
counter
Counter({'小動物': 1, '獣': 1, '医療': 1, '進む': 1, '動物': 1, '健康': 1, '寿命': 1, 'なる': 1})
import matplotlib.pyplot as plt
from wordcloud import WordCloud
from collections import Counter
wc = WordCloud(background_color="white",
font_path="C:/windows/fonts/meiryo.ttc")
wc.generate_from_frequencies(counter)
plt.axis("off")
plt.imshow(wc)
plt.show()
今度は、3. 小動物獣医療は分かれてほしくないです。を実現します。
このアプローチ方法は複数あります。私の知識では以下のような方法があると思っています。
- 解析する辞書に「獣医療」を追加する
- 解析するモデルを国語研長単位モデル(ja_gsdluw)に変更する
- 名詞と名詞がくっついているパターンを抽出する。
1はコマンドラインからexeを起動しないといけないパターンのやつです。パソコンにexeの実行制限がある場合無理です。 2はgithubからダウンロードします。(なぜかうまくいかなかった) 3は泥臭いですが、確実にできます。
3はspacyのmatcherという機能を使います。matcherにパターンを設定します。設定の仕方は以下のようにします。NOUNの2連続、3連続、4連続、動詞のパターンを設定します。
from spacy.matcher import Matcher
import spacy
npl = spacy.load("ja_ginza")
matcher = Matcher(nlp.vocab)
patterns = [
[{'POS':'NOUN'},{'POS':'NOUN'}],
[{'POS':'NOUN'},{'POS':'NOUN'},{'POS':'NOUN'}],
[{'POS':'NOUN'},{'POS':'NOUN'},{'POS':'NOUN'},{'POS':'NOUN'}],
[{'POS':'VERB'}],
]
for pattern in patterns:
name = f'noun_phrase_{len(pattern)}'
matcher.add(name,[pattern])
Matcherの使い方です。matcherをfor loopすることで、よくわからないもの、一致したものの開始位置、終わる位置が返ってきます。
counter = Counter()
doc = nlp("小動物獣医療が進んで、動物の健康寿命が長くなった")
for yokuwakaranai,begin,end in matcher(doc):
print(yokuwakaranai)
print(begin)
print(end)
18299346369723212617 0 2 3980228117911435732 0 3 18299346369723212617 1 3 1267925184705882228 4 5 18299346369723212617 9 11 1267925184705882228 13 14
開始位置と終了位置で文章をスライスします。
for yokuwakarania,begin,end in matcher(doc):
print(doc[begin:end].lemma_)
小動物獣 小動物獣医療 獣医療 進む 健康寿命 なる
少し問題があって、「小動物獣」と「小動物獣医療」と「獣医療」が出てきてしまっています。「小動物獣医療」だけでいいです。
この3つのbeginとendの位置の重なりで何ができるか考えてみます。
小動物獣 0 2 小動物獣医療 0 3 獣医療 1 3
前のendが後ろのbeginと重なっているところは、いったん保存を辞めておいて、重ならなくなった段階で、最初のstartとendでスライスした単語を登録すればいいということがわかりました。言葉での説明は難しいので、コードをご覧ください
mydict = dict()
mydict["start"] = [0,0]
for yokuwakaranai,begin,end in matcher(doc):
mydict[doc[begin:end].lemma_] = [begin,end]
mydict["last"] = [len(doc),len(doc)]
mydict
{'start': [0, 0], '小動物獣': [0, 2], '小動物獣医療': [0, 3], '獣医療': [1, 3], '進む': [4, 5], '健康寿命': [9, 11], 'なる': [13, 14], 'last': [15, 15]}
pre_begin = 0
pre_end = 0
for key,value in mydict.items():
if pre_end < value[0]:
print(doc[pre_begin:pre_end].lemma_)
pre_begin = value[0]
pre_end = value[1]
else:
pre_end = value[1]
小動物獣医療 進む 健康寿命 なる
うまくいきましたので、counterに入れたいと思います。
counter = Counter()
pre_begin = 0
pre_end = 0
for key,value in mydict.items():
if pre_end < value[0]:
counter.update([doc[pre_begin:pre_end].lemma_])
pre_begin = value[0]
pre_end = value[1]
else:
pre_end = value[1]
print(counter)
Counter({'小動物獣医療': 1, '進む': 1, '健康寿命': 1, 'なる': 1})
import matplotlib.pyplot as plt
from wordcloud import WordCloud
from collections import Counter
wc = WordCloud(background_color="white",
font_path="C:/windows/fonts/meiryo.ttc")
wc.generate_from_frequencies(counter)
plt.axis("off")
plt.imshow(wc)
plt.show()
だいぶいい感じになりました!
ここまでのコードをいったんまとめます。
from spacy.matcher import Matcher
import spacy
npl = spacy.load("ja_ginza")
matcher = Matcher(nlp.vocab)
patterns = [
[{'POS':'NOUN'},{'POS':'NOUN'}],
[{'POS':'NOUN'},{'POS':'NOUN'},{'POS':'NOUN'}],
[{'POS':'NOUN'},{'POS':'NOUN'},{'POS':'NOUN'},{'POS':'NOUN'}],
[{'POS':'VERB'}],
]
for pattern in patterns:
name = f'noun_phrase_{len(pattern)}'
matcher.add(name,[pattern])
counter = Counter()
doc = nlp("小動物獣医療が進んで、動物の健康寿命が長くなった")
mydict = dict()
mydict["start"] = [0,0]
for yokuwakaranai,begin,end in matcher(doc):
mydict[doc[begin:end].lemma_] = [begin,end]
mydict["last"] = [len(doc),len(doc)]
counter = Counter()
pre_begin = 0
pre_end = 0
for key,value in mydict.items():
if pre_end < value[0]:
counter.update([doc[pre_begin:pre_end].lemma_])
pre_begin = value[0]
pre_end = value[1]
else:
pre_end = value[1]
import matplotlib.pyplot as plt
from wordcloud import WordCloud
from collections import Counter
wc = WordCloud(background_color="white",
font_path="C:/windows/fonts/meiryo.ttc")
wc.generate_from_frequencies(counter)
plt.axis("off")
plt.imshow(wc)
plt.show()
このコードを使って大量の議事録を可視化してみたいと思います。
国会の子供に関連する委員会の議事録を引っ張ってきたいと思います。コピペします。
https://kokkai.ndl.go.jp/#/detail?minId=121105367X01720230705¤t=2
gijiroku = """
第211回国会 衆議院 地域活......
"""
そして先ほどのコードのdoc = nlp(“小動物獣医療が進んで、動物の健康寿命が長くなった”)のところをnlp(gijiroku)にすれば終わりかと思いきや
—> 18 doc = nlp(gijiroku)
Exception: Tokenization error: Input is too long, it can’t be more than 49149 bytes,
input is too longというエラーが出てしまいます。そのため議事録をある程度の長さに分割して実行する必要があります。バッチ処理ですね。chatGPTバッチ処理のコードを書いてもらいます。
num = len(gijiroku)
batch_size = 10000
# //は整数徐算数 %は余剰
batch_num = num // batch_size + (num % batch_size > 0)
print("There are",batch_num,"batches")
for i in range(batch_num):
start = i*batch_size
end = min((i+1)*batch_size,num)
#バッチの範囲を表示する
print(start,end)
There are 9 batches 0 10000 10000 20000 20000 30000 30000 40000 40000 50000 50000 60000 60000 70000 70000 80000 80000 89779
今までのを合わせると以下のようなコードになります。gijirokuという変数に議事録をコピペしたものを格納しておいてください。
from spacy.matcher import Matcher
import spacy
npl = spacy.load("ja_ginza")
matcher = Matcher(nlp.vocab)
patterns = [
[{'POS':'NOUN'},{'POS':'NOUN'}],
[{'POS':'NOUN'},{'POS':'NOUN'},{'POS':'NOUN'}],
[{'POS':'NOUN'},{'POS':'NOUN'},{'POS':'NOUN'},{'POS':'NOUN'}],
[{'POS':'VERB'}],
]
for pattern in patterns:
name = f'noun_phrase_{len(pattern)}'
matcher.add(name,[pattern])
num = len(gijiroku)
batch_size = 10000
# //は整数徐算数 %は余剰
batch_num = num // batch_size + (num % batch_size > 0)
num = len(gijiroku)
batch_size = 10000
# //は整数徐算数 %は余剰
batch_num = num // batch_size + (num % batch_size > 0)
counter = Counter()
for i in range(batch_num):
start = i*batch_size
end = min((i+1)*batch_size,num)
doc = nlp(gijiroku[start:end])
mydict = dict()
mydict["start"] = [0,0]
for _,begin,end in matcher(doc):
mydict[doc[begin:end].lemma_] = [begin,end]
mydict["last"] = [len(doc),len(doc)]
pre_begin = 0
pre_end = 0
for key,value in mydict.items():
if pre_end < value[0]:
counter.update([doc[pre_begin:pre_end].lemma_])
pre_begin = value[0]
pre_end = value[1]
else:
pre_end = value[1]
import matplotlib.pyplot as plt
from wordcloud import WordCloud
from collections import Counter
wc = WordCloud(background_color="white",
font_path="C:/windows/fonts/meiryo.ttc",
width=2400,
height=1600,
colormap="Set2" #色は他に Set1 Paster1 tab10 等
)
wc.generate_from_frequencies(counter)
wc.to_file("wordcloud2.png")
plt.axis("off")
plt.imshow(wc)
plt.show()
あと少しの微修正です。意味のない言葉を除きたいです。
del counter["おっしゃる"]
del counter["申し上げる"]
del counter["入る"]
del counter["行く"]
del counter["お考え"]
import matplotlib.pyplot as plt
from wordcloud import WordCloud
from collections import Counter
wc = WordCloud(background_color="white",
font_path="C:/windows/fonts/meiryo.ttc",
width=2400,
height=1600,
colormap="Set2" #色は他に Set1 Paster1 tab10 等
)
wc.generate_from_frequencies(counter)
wc.to_file("wordcloud3.png")
plt.axis("off")
plt.imshow(wc)
plt.show()