#はじめに
以前の記事で楽天トラベルサイトのレビューをスクレイピングし、勝手に多クラス分類する投稿をしました。
精度指標は良くなかったのですが、せっかくなら、各評価ごとのレビュー要約をしようじゃないか!って思い立ち、
ほんとはgensimのword2vecとかdoc2vecでも使おうかしら〜、なんて意気揚々としてましたが、ちょっと時間かかりそう..
なので、sumy使お!って思ったので、使いました。
#実行環境
前回のDocker imageから改良したものを使いますが、普通にpullするだけでできますので、気になさらずw
docker pull dockyupy/nlp-mecab-python:sumy
やぱ、OSそれぞれでエラーの出方が違ったりするのがしんどい時もあるので、Dockerは便利ですな。。
ちなみに、後々実行してみたのですが、Dockerfileに
pip install tinysegmenter
を入れ忘れたので、docker run後にコマンドを実行する必要があります。。
修正したDockerfileをpushしなおせばいいのですが、そんなに大変なコマンドでもないので、またDockerfile修正時にまとめてpushしておくとします。
データの準備
ここでは、前回スクレイピングで取得してきたホテルのCSVの一つを使います。
https://github.com/Yu0130fri/Scraping-NLP/tree/main/sample_csv
一応、ホテルレビューの内容はホテルごとに違うので、前回はマージして一つのDataFrameにしてましたが、今回は一つしか読み込まずに進めます。
import MeCab
import pandas as pd
from sumy.parsers.plaintext import PlaintextParser
from sumy.nlp.tokenizers import Tokenizer
from sumy.summarizers.lex_rank import LexRankSummarizer
# Mecabにて利用する辞書(mecab-ipadic-neologdなど)のURL
tagger = MeCab.Tagger("-d /usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd")
tagger.parse("")
data = pd.read_csv('sample.csv')
data['Comments'] = [word.strip() for word in data['Comments']]
下準備はこれだけ!!
爆速で要約
では、もういきなり始めるわけですが、関数で実は一撃。
# 文章要約メソッド
def summy_test(text):
key = tagger.parse(text)
corpus = []
for row in key.split("\n"):
word = row.split("\t")[0]
if word == "EOS":
break
else:
corpus.append(word)
# from_string(string, tokenizer) from builtins.type
parser = PlaintextParser.from_string(text, Tokenizer('japanese'))
summarizer = LexRankSummarizer() # LexRankSummarizer https://dl.acm.org/doi/10.5555/1622487.1622501
summarizer.stop_words = ['']
# sentences_countにて、sentence(文章の数)を選びます。
# If you choose 3 of sentences_count, you can get the 3 summarized sentences
summary = summarizer(document=parser.document, sentences_count=3)
b = []
for sentence in summary:
b.append(sentence.__str__())
return "\n".join(b) # print時にみやすくするために改行を入れているが、純粋にlistで使用したい時は'.join
もうこれだけなのです。
一応sumyに関してちょっと補足しておくと、要約には
・抽出型(Extractive)
・抽象型(Abstractive)
の2つのアプローチがありまして、今回のLexRankは、抽出型になります
つまり、必要っぽい単語を抜き取ってつなぎ合わせるような感じです。
では、使ってみましょ!
for rep in sorted(data.Reputations.unique()):
text = data.query('Reputations == @rep')['Comments'].tolist()
text =''.join(text)
print(f'Reputation: {rep}')
print(summy_test(text))
print('-'*10)
Reputation: 1
----------
Reputation: 2
それだけに接客がとても残念。
GWにしてはそれほど高くなかったので、仕方ないと言う気持ちもありますが、にしてもストリングスホテルなのにという無念さもあります。
朝食を期待していたのですが、価格に見合っていないと感じました。
----------
Reputation: 3
食事の量が、男性には少なめなのかなと思いましたが、スタッフの方の対応もよく、満足です!
到着時間15時の時点でホテルの廊下はまだ清掃中の状況.17時過ぎに再度出かけるときも同じような状況でした.チェックイン時間を過ぎているのにこのようなホテルは今までになかったため驚きました.夜泣きパスタがついている日だったためおいしくいただきました.今回はZeppに用事があったため,とても便利な立地です.名古屋駅などに用事がある場合は少し遠いです.コンビニやファミレスも少し歩けばあるので,食べるものには困りません.朝食はとてもおいしかったです.こちらは初めて宿泊しました。
ディナーは、レストランを別で予約して利用しましたが、とても美味しかったです。
----------
Reputation: 4
すぐ近くにコンビニがなかったり、駅から少し歩くのが少し不便ではあるかなぁと感じましたが、お部屋も施設もとても綺麗でオシャレまた利用したいと思いました!
(アメニティを置いてあるスペースが狭いため仕方ないとは思いますが、)朝食会場で通された席が隣と近くて、他の方に比べて明らかにテーブルが狭かったのだけ気になりました(すごく混雑している様には見えなかったしチャペル側の窓側のお席も空いているように見えました)とはいえ、お得な価格で宿泊できて満足でした!
部屋、接客はとても良かったと思います。
----------
Reputation: 5
とても綺麗な部屋で満足しています。
景色の良いお部屋をお願いしていたところ、チャペル側の最上階のお部屋を用意して頂きとても快適に過ごさせていただきました。
場所はZepp名古屋の目の前ですから名駅からは少し距離がありますが、車で行ったので特に問題ありませんでした。
----------
まるまる部分的に抜き出されているため、文章を機械的に要約したわけではないのが個人的に物足りなさを感じますが、大体どういう意見が各評価に対して代表しているのかを見つける手がかりになりそうですね!
(がっつりするなら、やはりgensim.........)
まとめ
今回はsumyについて簡単に触れてみて、一瞬で文章要約してみました。
コード量も少なく、すでにDockerとかでMeCab使えるようにしてたので、とても楽々でしたね!
参考文献やURL
https://izanagiblog.com/archives/1164
https://pypi.org/project/sumy/
https://ohke.hateblo.jp/entry/2018/11/17/230000