barefoot

技術のことを書いていく

janomeとmecabのいいとこどりラッパーを作っている話

こちらの記事はFringe81 Advent Calendar 20197日目の記事です。前日は期待の新星@lilpacy[Elm] PortをTaskで定義する思考実験でした。

僕はFringeのデータ分析チームでUniposの分析、開発を行っているtenajimaといいます。この記事ではUniposの分析を行っていく上で僕が作ったmecabのラッパーを紹介したいと思います。

きっかけ

なら自分でつくろう
となりました。

その名もwakame!

github.com

pypi.org

思想

janomeの「me」の部分とmecabの派生からwakameにしました。僕がわかめ好きだからという理由ではありません。

wakameにできること

まずimportします

from wakame.tokenizer import Tokenizer
from wakame.analyzer import Analyzer
from wakame.charfilter import *
from wakame.tokenfilter import *

tokenizerはjanomeと同様に使い回すので、最初に1回だけインスタンス化します。今回はNEologdを使うようにします。

tokenizer = Tokenizer(use_neologd=True)

特定の品詞をとってくる

「私は阪神の糸原選手が大好きです」という文章があったとします。この単語から「阪神」や「近本」のように名詞は重要だけど「私」のような代名詞は取ってきたくないとすると以下のようにできます。

text = "私は阪神の糸原選手が大好きです"
token_filters = [POSKeepFilter(['名詞']),POSStopFilter(['名詞,代名詞'])]
analyzer = Analyzer(tokenizer, token_filters=token_filters)
for token in analyzer.analyze(text):
    print(token)
阪神  名詞,固有名詞,組織,*,*,*,阪神,ハンシン,ハンシン
糸原  名詞,固有名詞,人名,姓,*,*,糸原,イトハラ,イトハラ
選手  名詞,一般,*,*,*,*,選手,センシュ,センシュ
大好き   名詞,形容動詞語幹,*,*,*,*,大好き,ダイスキ,ダイスキ

surface部分のみ分かち書きもできます

print(analyzer.analyze(text, wakati=True))
['阪神', '糸原', '選手', '大好き']

情報をDataFrameで取得したい

普段pandasのデータフレームを扱うことが多い場合はDataFrameに変換するメソッドも用意しています。

print(analyzer.analyze_with_dataframe(text))
  surface part_of_speech infl_type infl_form base_form reading phonetic
0      阪神   名詞,固有名詞,組織,*         *         *        阪神    ハンシン     ハンシン
1      糸原   名詞,固有名詞,人名,姓         *         *        糸原    イトハラ     イトハラ
2      選手      名詞,一般,*,*         *         *        選手    センシュ     センシュ
3     大好き  名詞,形容動詞語幹,*,*         *         *       大好き    ダイスキ     ダイスキ

特定の品詞を変換したい

文章から特定の品詞を置き換えることもできます。これは名前などをマスクしてあげるときに便利です。

text = "阪神の鳥谷選手とヤクルトの青木選手は大学の同級生です。"
token_filters = [
    POSReplaceFilter(["名詞,固有名詞,組織"], "某チーム"),
    POSReplaceFilter(["名詞,固有名詞,人名"], "名無しさん"),
]
analyzer = Analyzer(tokenizer, token_filters=token_filters)
print("".join(analyzer.analyze(text, wakati=True)))
某チームの名無しさん選手と某チームの名無しさん選手は大学の同級生です。

その他にも...

janomeに実装されているFilterや、URLを取り除く(置き換える)Filterなども作っています。次はハッシュタグを取り除くFilterとか作ろうかなと思っています。

最後に僕の2019年のツイートでワードクラウドを作ってみる

細かいインストール方法などはここでは触れずに、どんな感じで使えるかというのを2019年の僕のツイートを題材にして見ていこうと思います。

twitterでデータリクエストするとどかっとデータがもらえるのでそれを使います。

import matplotlib.pyplot as plt
import pandas as pd
from IPython.display import Image, display_png
from sklearn.feature_extraction.text import TfidfVectorizer
from wakame.tokenizer import Tokenizer
from wakame.analyzer import Analyzer
from wakame.tokenfilter import POSKeepFilter, POSStopFilter, LowerCaseFilter
from wakame.charfilter import URLReplaceFilter
from wordcloud import WordCloud
tweet = pd.read_json("./twitter/tweet.js")

# 時間とテキストデータだけ取得する
tweet = tweet[["created_at", "full_text"]]
# UTC時刻になっているので日本時間にする
tweet["created_at"] = tweet["created_at"].map(lambda utc: utc.tz_convert("Asia/Tokyo"))
# 2019年のデータだけとってくる
tweet = tweet[tweet['created_at'] > '2019-01-01'].sort_values('created_at').reset_index(drop=True)
print(tweet.head(1))
                 created_at                                          full_text
0 2019-01-01 00:15:18+09:00  あけましておめでとうございます\n今年も今年とて好き勝手楽しんでまいりますのでよろしくお願い...

ちゃんとあけましておめでとうございますと言えているので良さそうですね

print(tweet.shape[0], "ツイート")
483 ツイート

今年は12月7日時点で483ツイートしてるので1日1ツイート以上していますね。

それではここからwakameの出番です。

tokenizer = Tokenizer(use_neologd=True)
char_filters = [URLReplaceFilter("")]
token_filters = [
    LowerCaseFilter(),
    POSKeepFilter(["名詞"]),
    POSStopFilter(
        # ここの指定は割と泥臭くやっています...
        ["名詞,接尾,人名", "名詞,非自立,一般", "名詞,非自立,一般", "名詞,副詞可能", "名詞,代名詞", "名詞,非自立,形容動詞語幹"]
    ),
]
analyzer = Analyzer(
    tokenizer=tokenizer, char_filters=char_filters, token_filters=token_filters
)

単語のスペース区切りの文字列を用意します

tweet["word"] = tweet["full_text"].map(
    lambda text: " ".join([token.base_form for token in analyzer.analyze(text)])
)

tf-idfを使って重要単語に重み付けを行います

vectorizer = TfidfVectorizer(min_df=3, stop_words=["retweet"])
vectorizer.fit(tweet["word"])
TfidfVectorizer(analyzer='word', binary=False, decode_error='strict',
                dtype=<class 'numpy.float64'>, encoding='utf-8',
                input='content', lowercase=True, max_df=1.0, max_features=None,
                min_df=3, ngram_range=(1, 1), norm='l2', preprocessor=None,
                smooth_idf=True, stop_words=['retweet'], strip_accents=None,
                sublinear_tf=False, token_pattern='(?u)\\b\\w\\w+\\b',
                tokenizer=None, use_idf=True, vocabulary=None)
vec = vectorizer.transform(tweet["word"])
importance = pd.DataFrame(vec.todense(), columns=vectorizer.get_feature_names()).sum()
wordcloud = WordCloud(
    width=480,
    height=320,
    background_color="white",
    font_path="/Users/tenajima/fonts/ipaexg.ttf",
    stopwords=set(stopwords),
)

wordcloud.fit_words(importance.to_dict())
wordcloud.to_file('wordcloud.png')
display_png(Image('./wordcloud.png'))

f:id:tenajima:20191207132355p:plain

こうやって振り返ってみると今年は確かに阪神最高でしたね。最後のCS滑り込みは感動でした。選手の名前もちょろちょろと入っていますね。「セクシー」とか入ってるのは某外国人助っ人のことを表しています(虎党にしか伝わらない...) 来シーズンが待ち遠しいです。 他にはPyConが目立ちますね。Pythonもあってよかった。

最後に

wakameはまだまだ至らぬところが多いですが、OSSとして公開していると「改善しよう」という気概が出るし、我が子のようにかわいいので作ってよかったなと感じています。また、修正や要望をすぐに反映できるフットワークの軽さがいいです。
まだドキュメントが整備されてないなどver1.0には遠いのですが、今後もかわいがって育てていこうと思っています。

さて、今回僕はこのへんで次は我らが巨匠ことkaz3284にバトンをつなぎます。
引き続きFringe81 Advent Calendar 2019をお楽しみください!