蛇ノ目の記

技術のことも。そうでないことも。

PyCon TW 2019に行ってきた話(だけど主に観光の話

PyCon JP 2019の疲れも取れぬまま、台北に飛んでPyCon TW 2019に参加してきた。という話をPyCon SG 2019の会場で書いている。

LCCはアラサーの体には堪える。なんなら4時半発なので羽田空港で夜を明かしてダメージ倍増。

tw.pycon.org

真面目な話はいずれ公開されると思うので、ここでは9/18の台北観光メインで書く。

実質、訪台中のツイートまとめ。

人生2度目の台湾。

上陸直後の台湾メシ。素麺みたいな細さのやつ。

nikkie氏と合流するため、地下鉄を乗り継いで桃園空港から松山空港へ。台北は地下鉄が発達してるので移動が割と楽。

桃園からのMRTの終点、台北車站にギリギリ隣接といえなくも無い距離で隣接している松山新店線の北門駅に移動して、南京復興駅へ。

南京復興駅から文湖線で松山機場へ。

nikkie氏と合流したついでにPyCon JP 2019座長の吉田さんと会えたので3人行動に切り替えて、台北市内にUberで移動した。

魯肉飯食いたいという気持ちで探したよさげな店のある龍山寺へ。美味いけど見た目が映えない。

ローカル感あるいい感じに雑感のある店だった。

四方阿九魯肉飯 (万華) 最新のレストランの口コ(2019年) - トリップアドバイザー

その後は龍山寺へ。

龍山寺でひいたおみくじ。後述するメイドカフェメイドさんにどんな内容か聞いてみたらだいぶいい感じらしい。上上って書いてあるし、納得感ある。

ここからは吉田さんと別れてnikkie氏と行動。台北オタク文化に触れるため、秋葉原と原宿を足して2で割った雰囲気の西門町へ。

ご当地キャラの垂れ幕があった。かわいい。林默娘っていうらしい。なんて読むのかは知らない。

台北アニメイトへ。翻訳されたラノベとか漫画がメインだけど、台湾独自のオタクコンテンツもあった。「食用系少女」って字面が危険な感じだけど、台湾グルメの擬人化なのでご安心。

こっちは鉄道むすめの台湾バージョンみたいな雰囲気。

@doi-tさんに教えてもらったマンゴーかき氷の店へ。一人で余裕で食べれる量やろ、と高をくくっていたら地獄を見た。ひたすら冷たい。美味いけど冷たい。

隣のテーブルに来た日本人の学生3人組は分けて食べるとのことだった。賢いと思ったら、彼らも一度は地獄を見ていたらしい。

暖かいものを飲んで冷えた体を温めようとメイドカフェへ。最初から行く予定だったけども。

台湾でのメイドカフェ探しは 台湾女僕珈琲(メイドカフェ)まとめ|もえ氏|note が役に立つ。

ここのメイドさんはカタコトながら日本語が話せる娘だったのでとても助かった。というか可愛くてカタコト日本語はポイント高い。チェキも撮った。

この日の夜は日本からのPyCon TW参加勢と合流して、青葉という台湾料理の店へ。

この翌日からPyCon TW 2019が始まるわけだけど、そちらの話は近日公開予定。

Uberはいいぞ

というかタクシー怖い。

PyCon TWの会場、中央研究院は南港展覧場駅からバスないしタクシーで行く距離にある。

カンファレンス初日、駅の前に停まってたタクシーを使おうとして運転手の爺さんにGoogle Mapで目的地を見せるも上手く伝わらず。どうやら字が小さいと言っているようだったので、テンパりながらも拡大しようとし続けててたら数十メートル走ったところで爺さんがキレて降ろされてしまった。タクシーはコミュニケーションが必要になるのでUberが絶対おすすめ。

Uberなら英語話せる若い人もいるしね。最終日、クラフトビールの店から台北車站まで乗ったUberの運転手の兄ちゃんはとてもいい人だった。パイナップルケーキのおすすめの店とか、東京に行ったことあるけど日本感なかったので京都がよかったかも、とかいろいろ話した。

PyCon JP 2019とビープラウド入社3年目突入

ども

から始まる山崎愛構文は「アイドルで理解するTF-IDF」ではないのでやらない。

PyCon JP 2019

9/16(月)から9/17(火)にかけてPyCon JP 2019が開催された。

pycon.jp

スタッフ3年目の今年は2018年に引き続き、コンテンツチームに所属してポスターセッション(コミュニティ)の担当をやっていた。

コミュニティは去年の5つから5つも増えて日本各地+海外から11のコミュニティに出展していただくことができた。ポスターセッションのコアタイムにはどのコミュニティの前にも人だかりができていて、コミュニティメンバーとの交流やコミュニティ間の情報交換が行われていた。年に一度、日本国内外からPythonistaが集まるイベントだからこそできる貴重な機会を作ることができて本当に良かった。

コミュニティブースはPythonコミュニティが活動を紹介する企画で、南はPyCon Kyushu、北は札幌Pythonまで日本各地から6つのコミュニティが参加した。

PyCon JP 2018とBP入社一周年 - 蛇ノ目の記

あと、気づいたらオープニング・クロージングの案内を任されていた。2年前のように全体的な司会ではないものの、200~300人はいそうな会場で喋るのはとにかく緊張する。今度、風花ちゃんに会ったら緊張しないコツとか教えてもらおう。英語の発音をいろいろな人から褒められたのは嬉しかった(話すのが早いという課題もあるけど)。なお、英語喋れてる風なだけで聞き取れないので会話は成立しない模様。

査読付きLT・飛び入りLTの司会も担当した。次の発表者までの繋ぎをやるのがとにかく難しくて、繋ぐことを諦めてしまったのが反省点。2日目のLTでは接続が上手くいかないことが何度かあったりした。ああいったトラブルの対応も難しい。

LTといえば、2日目飛び込みLT、 @mizzsuggar0425による「PyCon JP 2018で勇気をもらってPythonエンジニアになった話」が最高にいい話だった。PyCon JPで聞いたLTに刺激を受けてPythonエンジニアになりました、だけでなく今はまだ自信が無い人もきっとやれる、というメッセージがとにかくエモい。来年、@mizzsugar0425の後に続く人がLTするなんてことがあったらきっと感動にあまりにむせび泣く。

speakerdeck.com

そして特に面白かったトーク@_sin_tanakaによる「Pythonでライブをしよう!FoxDotを使った新時代のPython活用法 」

ライブコーディング(コードを書く方ではなくコードを書くことでライブ演奏する)を行えるPythonライブラリFoxDotの概要とDEMO。音楽わからない自分でも曲作れそうと思えたし、simple-spotifySpotifyの楽曲データAPIを叩いてコードやスケール、BPMを取得してくれば好きな曲と同じコードとスケール、BPMを素材にして演奏したりできるなと考えた。

speakerdeck.com

FoxDotを使ってLightningTalkならぬLightningLiveもできそう。今度stapyでやってみようかな。

ところで、来年も引き続きスタッフをやるかどうか実は悩んでいる。飽き性なので数ヶ月に渡る準備の間にモチベーションが下がってしまうことが多々あって、他のスタッフに迷惑を掛けてしまっているのではないかと思う。本格的にPyCon JP 2020が動き出すまでに、モチベーションをある程度維持できるように工夫するか当日スタッフだけやるかを決めておこう。文化祭をやっているみたいで楽しいのでスタッフをやらない選択肢は今のところなさそう。

ビープラウド入社3年目突入

ビープラウドに入社したのがPyCon JP 2017の直後だったのでPyCon JPの季節になると自然とビープラウド歴も更新される。

最近はタスクをバラしてスケジュールを立てることの苦手さやコードレビュー依頼の雑さに直面していて、特に何かできるようになったという感じは実はあまりない。

とはいえ、(自分にとって)新しいことを経験して、多少は知識も増えている。例えば直近ではUbuntu14.04から18.04, Python2.7から3.7, Django1.11から2.2という3つの移行をやっていることもあってUbuntu(というよりsystemdやjournald)の知識を新たに得たり、Djangoソースコードを読むようになったりしている。 移行案件が終わったら、イチから開発する案件をやってみたいなとか思いつつ、休みが明けたら移行済みテスト環境の画面テストで出たバグとの戦いに精を出すことになる。

このあたりでPyCon JP 2019のざっくりまとめとビープラウド3年目突入の話は終わり。

さて、PyCon TW 2019に遊びに行くために羽田空港にいて、早朝便まで寝れる場所を確保しないといけない。

アイドルで理解するTF-IDF TF-IDF編

ども

そろそろPyCon JP 2019が始まりますね

始まると一気にブログを書かなくなるので今のうちに書きます

PyCon JPのお祭り感が好きなんですよね

そのあとはPyCon TWに遊びに行きます

人生二度目の台湾です

TL; DR

  • TF は文書頻度。

    • 文書中の単語の重要度を表す指標
  • IDFは逆文書頻度

    • 各単語がどれだけの文書に出現するかを表す指標
  • TF-IDFはTFとIDFの積。それぞれの文書の中で重要な単語ほど大きい値になる

TFの計算式

文書d_iにおける単語t_jtfは以下のように表される。

tf_i=\frac{n_i,j}{\sum_k n_{k,j}}

n_{i,j}は文書d_iにおける単語t_jの出現回数。

{\sum_k n_{k,j}}は文書d_iにおけるすべての単語の出現回数の和。

tf-idf - Wikipedia

式で表しても(自分が)今ひとつわからないので実際の文書で考えてみる。

神﨑風花はかわいい

寺口夏花はかわいい

山崎愛はかわいい

みんなかわいい

という文書があって、単語は

  • 神﨑風花

  • 寺口夏花

  • 山崎愛

  • みんな

  • かわいい

と分けるとする。

文書中の単語はベクトルで表す。ここで単語は6つなのでベクトルの成分は6つになる。

神﨑風花はかわいい

の各単語のTFを求めてみる。

単語のベクトルはこのようになる。

神﨑風花 寺口夏花 山崎愛 みんな かわいい
1 0 0 0 1 1

「神﨑風花」という単語のtfは以下のように求められる。

{\sum_k n_{k,j}}は文書d_iにおけるすべての単語の出現回数の和なので、3になる。

tf_i=\frac{n_i,j}{\sum_k n_{k,j}}=\frac{1}{3}=0.3

同様に他の単語についてもtfを求めると、

神﨑風花はかわいい

の各単語のTFは以下のようになる。

神﨑風花 寺口夏花 山崎愛 みんな かわいい
0.3 0 0 0 0.3 0.3

IDF

文書d_iにおける単語t_jidf_iは以下のように表される。

|D|は文書の総数。

|\lbrace d:d \ni t_i \rbrace |は単語t_iを含む文書数。

tdf_i=\log\frac{|D|}{|\lbrace d:d \ni t_i \rbrace |}

tf-idf - Wikipedia

実際には単語tが出現しない文書があるとゼロ除算が発生するので、分子と分母にそれぞれ1を加える。

やっぱり式で表しても(自分が)今ひとつわからないので実際の文書で考えてみる。

神﨑風花はかわいい

寺口夏花はかわいい

山崎愛はかわいい

みんなかわいい

があって、単語のベクトルはそれぞれ以下のようになる。

神﨑風花 寺口夏花 山崎愛 みんな かわいい
1 0 0 0 1 1
0 1 0 0 1 1
0 0 1 0 1 1
0 0 0 1 0 1

単語それぞれのIDFを求めてみる。

  • 神﨑風花

idf_{神﨑風花}=\log_2\frac{4}{1}=\log_2 4=2.0

  • 寺口夏花

idf_{寺口夏花}=\log_2\frac{4}{1}=\log_2 4=2.0

  • 山崎愛

idf_{山崎愛}=\log_2\frac{4}{1}=\log_2 4=2.0

  • みんな

idf_{みんな}=\log_2\frac{4}{1}=\log_2 4=2.0

idf_{は}=\log_2\frac{4}{3}=0.4

※ 少数第二位で四捨五入してる

  • かわいい

idf_{かわいい}=\log_2\frac{4}{4}=0

神﨑風花 寺口夏花 山崎愛 みんな かわいい
2.0 2.0 2.0 2.0 0.4 0

TF-IDF

TF-IDFはtf_i,jidf_iの積。

「神﨑風花はかわいい」の場合

tf_{i,j}

神﨑風花 寺口夏花 山崎愛 みんな かわいい
0.3 0 0 0 0.3 0.3

idf_i

神﨑風花 寺口夏花 山崎愛 みんな かわいい
2.0 2.0 2.0 2.0 0.4 0

ifidf_{i,j}

神﨑風花 寺口夏花 山崎愛 みんな かわいい
0.6 0 0 0 0.12 0

となる。最も値が大きいのは「神﨑風花」なので、文書「神﨑風花はかわいい」の中で重要度が最も高いのは「神﨑風花」という単語ということになる。

Pythonでやってみる

scikit-learnを使えば簡単にできると「Pythonによるあたらしいデータ分析の教科書」という本に書いてあるけど、今回はNumPyを使って自力で実装してみる。

なお、TF-IDFのL2正規化はNumPyの力を借りた。

import typing

import numpy as np

def get_all_docs(in_csv: str) -> typing.List:
    all_docs = []
    with open(in_csv, encoding='utf-8') as fin:
        for line in fin:
            words = [word.strip() for word in line.split(',')[3:]]
            all_docs.append(words)
    return all_docs


def create_words_list(all_words: typing.List) -> typing.List:
    flatten_words = []
    for words in all_words:
        for word in words:
            flatten_words.append(word)
    words_list = []
    for word in flatten_words:
        if word not in words_list:
            words_list.append(word)
    return words_list


def get_word2int(words_list: typing.List) -> typing.Dict[str, int]:
    word2int = {}
    for idx, word in enumerate(words_list):
        word2int[word] = idx
    return word2int


def calc_tf(all_docs: typing.List, word2int: typing.Dict[str, int]) -> np.ndarray:
    tf_array = np.zeros((len(all_docs), len(word2int)))
    for doc_idx, doc in enumerate(all_docs):
        temp_array = np.zeros((1, len(word2int)))
        for word_idx, word in enumerate(word2int.keys()):
            if word in doc:
                temp_array[0, word_idx] += 1
        tf_array[doc_idx:] = temp_array / np.sum(temp_array)
    return tf_array


def calc_idf(all_docs: typing.List, word2int: typing.Dict[str, int]) -> np.ndarray:
    temp_array = np.zeros((1, len(word2int)))
    all_docs = all_docs
    for idx, word in enumerate(word2int.keys()):
        word_count = 0
        for doc in all_docs:
            if word in doc:
                word_count += 1
        temp_array[0, idx] = word_count
    idf_array = np.log2((len(all_docs) + 1 / temp_array + 1))
    return idf_array


def l2_normalize(tf_idf: np.ndarray) -> np.ndarray:
    l2_norm = np.linalg.norm(tf_idf, ord=2)
    return tf_idf / l2_norm

def tf_idf(in_csv: str):
    all_docs = get_all_docs(in_csv)   # in_csvは形態素解析して取り出した名詞のCSVファイル
    words_list = create_words_list(all_docs)
    word2int = get_word2int(words_list)
    tf_array = calc_tf(all_docs, word2int)
    idf_array = calc_idf(all_docs, word2int)
    tf_idf = tf_array * idf_array
    normalized_tf_idf = l2_normalize(tf_idf)

tf_idf_with_idol/tf_idf.py at master · NaoY-2501/tf_idf_with_idol · GitHubから抜粋。

次回は山崎愛ちゃんのブログに対して計算したTF-IDFと実際の記事を見比べて、特徴語が取り出せているかを検証したりする予定。

アイドルで理解するTF-IDF 形態素解析編

ども

昨日はstapy #49でしたね

LT聴いたくれた方ありがとうございます。

アイドルで理解するTF-IDF 形態素解析編です

TF-IDFまであと一歩です

別に記事を増やしたくて細かく分けてるわけじゃないです

なにごとも一歩ずつ進めていくのがいいと思うんです

知らんけど

TL;DR

  • NEologdを使うときはTaggerに辞書のパスを指定する

  • MeCab.Nodeオブジェクトはiterableじゃない

  • 形態素の詳細な情報はMeCab.Tagger.parseToNode.featureで取得できる

    • 次の形態素MeCab.Tagger.parseToNode.nextで取得する

形態素解析する

mecab-python3を使う。

pypi.org

今回のコードはこれだけ。

import csv

import MeCab


def analysis(in_csv: str, out_csv: str):
    tagger = MeCab.Tagger('-d /usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd')

    with open(out_csv, 'w', encoding='utf-8') as fout:
        with open(in_csv, encoding='utf-8') as fin:
            reader = csv.DictReader(fin)
            for row in reader:
                lemmas = []
                node = tagger.parseToNode(row['body'])
                while node:
                    result = node.feature.split(",")
                    pos = result[0]
                    lemma = result[-3]
                    if pos == '名詞':
                        lemmas.append(lemma)
                    node = node.next
                row = f"{row['date']},{row['url']},{row['title']},{','.join(lemmas)}\n"
                fout.write(row)

tf_idf_with_idol/morphological_analysis.py at master · NaoY-2501/tf_idf_with_idol · GitHub

MeCab: Yet Another Part-of-Speech and Morphological Analyzerスクリプト言語のバインディングにおおよその使い方が書いてある。

MeCab::Tagger というクラスのインスタンスを生成し, parse (もしくは parseToString) というメソッドを呼ぶことで, 解析結果が文字列として取得できます.

これはPerlの例で書いているけど、Pythonであれば「MeCab.Taggerというクラスのインスタンスを作成し〜」となる。 また、Taggerクラスの引数にオプションを指定できる。使用する辞書を指定するときは-dオプションの後に辞書のパスを書く。今回は前回、DockerイメージにインストールしたNEologdの辞書のパスを指定する。

表層系や形態素など詳しい情報を取りたい場合はMeCab.Tagger.parseToNode()メソッドを使う。 parseToNode()メソッドは文章を渡されると、'MeCab.Node'オブジェクトを返す。

MeCab::Node は, 双方向リストとして表現されており, next, prev というメン バ変数があります.

スクリプト言語のバインディング

Nodeオブジェクトはiterableではないので、for文で回そうとすると怒られる。

Traceback (most recent call last):
File "tf_idf_with_idol.py", line 13, in <module>
for n in node:
TypeError: 'MeCab.Node' object is not iterable

スクリプト言語のバインディング にあるようにwhile文を使って繰り返し処理しよう。

node<Swig Object of type 'MeCab::Node *' at 0x7fe6a4fae960>というようなオブジェクトになっている。 詳細な情報を取得するときはfeature属性を使う。 こんな感じの文字列が取得できる。

 名詞,一般,*,*,*,*,迷路,メイロ,メイロ

カンマ区切りの文字列なので、 split()メソッドを使ってリストにすると使いやすくなる。品詞は見てわかるように先頭にある。

なんとなくMeCabの使い方がわかったところで「広告の街」の歌詞の一部を形態素解析して終わりにする。

www.youtube.com

 BOS/EOS,*,*,*,*,*,*,*,*
 名詞,一般,*,*,*,*,迷路,メイロ,メイロ
 助詞,連体化,*,*,*,*,の,ノ,ノ
 名詞,非自立,助動詞語幹,*,*,*,様,ヨウ,ヨー
 助動詞,*,*,*,特殊・ダ,体言接続,だ,ナ,ナ
 名詞,一般,*,*,*,*,恋,コイ,コイ
 助詞,格助詞,一般,*,*,*,に,ニ,ニ
 動詞,自立,*,*,一段,連用形,落ちる,オチ,オチ
 助詞,接続助詞,*,*,*,*,て,テ,テ
 動詞,非自立,*,*,五段・カ行促音便,基本形,いく,イク,イク
 名詞,一般,*,*,*,*,心,ココロ,ココロ
 助詞,並立助詞,*,*,*,*,と,ト,ト
 名詞,一般,*,*,*,*,裏腹,ウラハラ,ウラハラ
 名詞,一般,*,*,*,*,ゲーム,ゲーム,ゲーム
 助詞,連体化,*,*,*,*,の,ノ,ノ
 名詞,非自立,助動詞語幹,*,*,*,様,ヨウ,ヨー
 助詞,副詞化,*,*,*,*,に,ニ,ニ
 名詞,サ変接続,*,*,*,*,レベルアップ,レベルアップ,レベルアップ
 形容詞,自立,*,*,形容詞・アウオ段,連用テ接続,うまい,ウマク,ウマク
 助詞,係助詞,*,*,*,*,は,ハ,ワ
 動詞,自立,*,*,五段・カ行促音便,未然形,いく,イカ,イカ
 助動詞,*,*,*,特殊・ナイ,基本形,ない,ナイ,ナイ
 名詞,サ変接続,*,*,*,*,検索,ケンサク,ケンサク
 名詞,副詞可能,*,*,*,*,結果,ケッカ,ケッカ
 動詞,自立,*,*,一段,連用形,並べる,ナラベ,ナラベ
 助詞,接続助詞,*,*,*,*,て,テ,テ
 助詞,係助詞,*,*,*,*,も,モ,モ
 動詞,自立,*,*,一段,未然形,探せる,サガセ,サガセ
 助動詞,*,*,*,特殊・ナイ,基本形,ない,ナイ,ナイ
 名詞,一般,*,*,*,*,気持ち,キモチ,キモチ
 助詞,係助詞,*,*,*,*,は,ハ,ワ
 名詞,一般,*,*,*,*,迷路,メイロ,メイロ
 助詞,連体化,*,*,*,*,の,ノ,ノ
 名詞,一般,*,*,*,*,奥,オク,オク
 助詞,格助詞,一般,*,*,*,に,ニ,ニ
 動詞,自立,*,*,一段,連用形,消える,キエ,キエ
 助詞,接続助詞,*,*,*,*,て,テ,テ
 動詞,非自立,*,*,五段・カ行促音便,基本形,いく,イク,イク
 名詞,代名詞,一般,*,*,*,あなた,アナタ,アナタ
 助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
 動詞,自立,*,*,五段・サ行,基本形,探す,サガス,サガス
 BOS/EOS,*,*,*,*,*,*,*,*

照井さんたちによる演奏動画もめちゃめちゃかっこいいので観てください。

www.youtube.com

次回はいよいよTF-IDF編の予定です。PyCon JPが始まる前に書かないと絶対フェードアウトするのでがんばるぞい。

アイドルで理解するTF-IDF 環境構築編

ども

台風やばかったですね

雨と風が強すぎてぜんぜん寝れませんでした

うちの近くに去年の台風で傾いた建物があるんですがそれはなんか無事でした

どうでもいいですね

はい

アイドルで理解するTF-IDF第2回です

まだTF-IDFまでたどり着きません

TL;DR

  • Dockerを使って形態素解析&TF-IDF計算の環境作った

  • Ubuntu 18.04 + Python3.7 + MeCab + NEologd

    • NEologdの辞書のPATHは必ずチェックしよう
  • docker-composeで形態素解析とTF-IDFの計算を実行するようにした

Dockerイメージを作る

Python3についてはPython3.7を使いたかったのでソースからビルドした。

MeCabとNEologdのインストールについてはMeCab用のDockerfile - sanshonokiの日記 を参考にDockerfileを書いた。

イメージの役割は形態素解析とTF-IDFの計算のみで、IPAフォントのインストールは不要と判断して削除した。Maplotlibで日本語入ったグラフを出すとかだったら入れないとダメ。

NEologdをMeCabのデフォルト辞書に設定する箇所について、参考にしたブログでは以下のようになっていた。

RUN sed -i 's/dicdir = \/var\/lib\/mecab\/dic\/debian/dicdir = \/usr\/lib\/mecab\/dic\/mecab-ipadic-neologd/' /etc/mecabrc

/etc/mecabrcdicdirをNEologdの辞書のパスに置き換えているわけだけど、この置き換え先のパスが今回の環境では異なっていて、/usr/lib/x86_64-linux-gnu/mecab/dic/mecab-ipadic-neologd となっていた。DockerイメージにNEologdをインストールしたら、一度そこでコンテナを実行して、NEologdの辞書のパスを確認するのがよさそう。

$ docker run -it イメージ名 /bin/bash
$ echo `mecab-config --dicdir`"/mecab-ipadic-neologd"

辞書のパス確認のコマンドはNEologdのREADMEに書かれている。

github.com

今になって思ったけど、sedを使って書き換えるではなく手元にdicdirを書き換えたmecabrcを用意してDockerイメージをビルドするときに置き換えるのもいいかもしれない。

$ docker run -it イメージ名 /bin/bash
# NEologdの辞書のパスを確認
$ echo `mecab-config --dicdir`"/mecab-ipadic-neologd"
# mecabrcを表示
$ cat /etc/mecabrc

ここで表示したmecabrcをコピーして手元で書き換える、とか。

とりあえず現状最新のDockerfile。

# ref. http://sanshonoki.hatenablog.com/entry/2018/10/09/231345
FROM ubuntu:18.04
RUN apt update \
    && apt install -y \
    build-essential \
    zlib1g-dev \
    libncurses5-dev \
    libgdbm-dev \
    libnss3-dev \
    libssl-dev \
    libreadline-dev \
    libffi-dev \
    wget \
    git \
    mecab \
    curl \
    libmecab-dev \
    mecab-ipadic-utf8\
    language-pack-ja \
    xz-utils \
    file \
    openssl \
    gawk \
    sudo \
    unzip \
    && apt clean \
    && update-locale LANG=ja_JP.UTF-8

# Set locale
ENV LANG ja_JP.UTF-8
ENV LANGUAGE ja_JP.UTF-8
ENV LC_ALL ja_JP.UTF-8

# Install Python3.7
# ref. https://linuxize.com/post/how-to-install-python-3-7-on-ubuntu-18-04/
WORKDIR /usr/local/src
RUN wget https://www.python.org/ftp/python/3.7.3/Python-3.7.3.tar.xz \
  && tar -xf Python-3.7.3.tar.xz \
  && cd Python-3.7.3 \
  && ./configure --enable-optimization \
  && make \
  && make altinstall

# Install mecab-ipadic-NEologd
WORKDIR /opt
RUN git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
WORKDIR /opt/mecab-ipadic-neologd
RUN ./bin/install-mecab-ipadic-neologd -n -y
RUN cd /opt
RUN rm -rf mecab-ipadic-neologd

# Set mecab-ipadic-NEologd as default
RUN sed -i 's/dicdir = \/var\/lib\/mecab\/dic\/debian/dicdir = \/usr\/lib\/x86_64-linux-gnu\/mecab\/dic\/mecab-ipadic-neologd/' /etc/mecabrc

# Make directory and Copy code
WORKDIR /home
RUN mkdir code
RUN mkdir output
WORKDIR /home/code
COPY code .

CMD python3.7 -V

mecab-pythonという名前をつけてDockerイメージをビルド。

docker build -t mecab-python .

docker-compose.ymlを作る

本当はDockerイメージをビルドする段階でパッケージをインストールしておきたかった。$ python3.7 -m pip install -r requirements_docker.txtでPython3.7のpipであることを明示的にしてインストールしたにもかかわらず、docker-composeでコンテナを開始するとそのパッケージが見つからなくなっていた(今回、原因まで調べられていない)。そのためコンテナ開始のタイミングでインストールしている。

version: '2'
services:
    mecab-python:
        build: .
        volumes:
            - ./code:/home/code
            - ./output:/home/output
        command: >
          /bin/bash -c
          "python3.7 -m pip install -U pip
          && python3.7 -m pip install -r requirements_docker.txt
          && python3.7 tf_idf_with_idol.py"

これで環境構築ができたので、次回は形態素解析とTF-IDFです。#stapyでのLTが終わってから書きます。

めっちゃどうでもいいし俺がやってもアレなだけなんだけど、このシリーズの冒頭は山崎愛ちゃん構文に寄せてます。

アイドルで理解するTF-IDF データ準備編 - アメブロを攻略する

ども。

一度もブログを書くことなく8月が過ぎてた。

もう9月ってやばくないですか。やばいです。

今回は9/11(水)のみんなのPython勉強会 #49でLTする予定の「アイドルで理解するTF-IDF」のデータ準備編。

TL;DR

今回やりたいこと

sora tob sakana official blogから記事のタイトル, 投稿日時, 記事本文をスクレイピングする。

スクレイピングした情報はCSVにまとめる。

ブログ記事の見た目はこんな感じ。

f:id:Nao_Y:20190907173221p:plain

※ 風花ちゃんのブログを例に出したけど今回は山崎愛ちゃんのブログ記事を対象にする。

ぱっと見、Requestsでレスポンスボディを取ってきてBeautifulSoupに喰わせてやればよさそうに見えるけど、実際にそれで取得できるのはタイトルと記事本文のみ。なぜ投稿日時がJavaScriptで描画されているのか。わからない。

山崎愛ちゃんのブログを選んだ理由

「基本的に1記事につき1つの話題」だから。TF-IDFで記事の特徴語を取ったときに、特徴語が記事のテーマと一致しているかの判断が簡単なように思えた。

あとオサカナのオタクとして、毎日更新されるのを楽しみにしてるから。愛ちゃんはここ数ヶ月毎日更新している。その原動力は知らんけど。

風花ちゃん最近割と更新が多いのでとても嬉しい。

ヘッドレスブラウザを導入する

ヘッドレスブラウザを使ってJavaScriptが実行された後のソースコードを使えばよさそう、ということでその方法を探す。今回はHeadless Chromeを使う。

ブラウザを実際に動かすということでまず必要になるのはSelenium。さくっと入れる。

$ pip install selenium

Chromeのドライバーのパスを指定してやる必要があるけど、chromedriver_binaryを使うとパスを通してくれる。便利かよ。

To use chromedriver just import chromedriver_binary. This will add the executable to your PATH so it will be found.

pypi.org

注意したいのは手元のChromeのバージョンに合ったchromedriver_binaryのバージョンをインストールする必要があるということ。

自分の手元では76.0.3809.132だったのでこれに合ったバージョンをインストールする。

$ pip install chromedriver_binary==76.0.3809.126.0

Chromeをヘッドレスモードで使うときはChromeのオプションに--headlessを指定する。

コードを書く

import chromedriver_binary # 手元の環境に合わせて76.0.3809.126.0を使う
from bs4 import BeautifulSoup as bs
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

def init_driver() -> webdriver.Chrome:
    options = Options()
    options.add_argument('--headless')
    driver = webdriver.Chrome(options=options)
    return driver


def get_soup(driver: webdriver.Chrome, url: str) -> bs:
    driver.get(url)
    html = driver.page_source
    return bs(html, 'html.parser')

def main():
    # 2019/09/06の山崎愛ちゃんの記事
    url = 'https://ameblo.jp/soratobsakana/entry-12521726090.html' 
    driver = init_driver()
    soup = get_soup(driver, url)

基本形としてはこれで以上。どうでもいいけど今回からType Hintingを導入してみた。

後はいい感じに前処理してCSVで保存する。

詳細は https://github.com/NaoY-2501/tf_idf_with_idol/blob/master/blog_scraper.py を参照のこと。

アイドルで理解するTF-IDFのリポジトリは以下。

github.com

↑を見られるとまだTF-IDFが実装できてないことがバレる。

次回は環境構築編を書く予定。

Notebookを見やすくするCSSを書いた

sora tob sakana東名阪ツアー大阪公演に遠征した@大阪

どういうわけかWiFiが入らない梅田のスタバで書いている。なにが悲しくてスタバでテザリングしないといけないのか…。

というのは置いておいて、近頃Colaboratoryが便利だなぁと思ってるんですが、どうにもフォントサイズが小さかったり行間が狭かったりしてコードが書きづらい、読みづらい。 そういうことを嘆いていたら、kashewさんからStylusを使うのがよさそうと助言を受けたのでChromeに入れてColaboratoryの見た目をいじることにした。kashew++。

chrome.google.com

というわけでCSS

gist.github.com

これを入れるとこうなるというのが以下のツイート。

ちなみにJupyter Notebookにも対応しているので、Colaboratory使ってないけどNotebookのコードが読みづらいなと感じてる人もこのCSSを試してみてくれよな。

iframeになっているアウトプット部分も調整しようと試行錯誤中。なにかいい方法あったら教えてください。