pipでライブラリをアップデートする

pipの使い方メモです。

まず、インストール済みのパッケージについての情報は pip list で確認できます。
更新版があるパッケージのみ出力するオプションは -o または --outdatedです。


$ pip list --outdate
Package Version Latest Type
------------------ --------- ---------- -----
alabaster 0.7.11 0.7.12 wheel
astroid 2.0.4 2.1.0 wheel
astropy 3.0.4 3.1.1 wheel
beautifulsoup4 4.6.3 4.7.1 wheel
bleach 2.1.4 3.1.0 wheel
bokeh 0.13.0 1.0.4 sdist
certifi 2018.8.24 2018.11.29 wheel
click 6.7 7.0 wheel
~~~ 以下略 ~~~

アップデートしたいパッケージを決めたら、
pip install に、
 -U か --upgrade のどちらかのオプションをつけてパッケージを指定し実行するとアップデートできます。

例:


$ pip install --upgrade scikit-learn

SNSシェアボタンを追加しました

数日前からこのブログの各記事にSNSへのシェアボタンを追加しました。

ボタンの追加はWordpressの、AddToAny Share Buttons というプラグインを使っています。

とりあえずほとんどデフォルト設定のままで、変更したのはLineへのシェアボタンを表示した点だけです。

requestsのレスポンスが文字化けする場合に文字コードを修正する

非常に手軽にhttpアクセスができるrequestsですが、日本語の文書を取得する時に文字コードが正常に取れないケースがあります。

たとえば、今回は青空文庫の羅生門のページで発生しました。


import requests
url = "https://www.aozora.gr.jp/cards/000879/files/127_15260.html"
response = requests.get(url)
html = response.text

これで取得したhtml変数の中身を見るとひどいことに。

~略~
<div class="main_text"><br/>\r\n\x81@\x82\xa0\x82é\x93ú\x82Ì\x95é\x95û\x82Ì\x8e\x96\x82Å\x82\xa0\x82é\x81B\x88ê\x90l\x82Ì<ruby><rb>\x89º\x90l</rb><rp>\x81i</rp><rt>
~略~

問題は文字コードを正常に取れていないことのようです。
サイトのメタタグでは Shift_JIS が指定されていますが、
print(response.encoding)
を実行すると、
ISO-8859-1
が戻ってきます。

このような時は、apparent_encodingを使います。
ドキュメントを見る限りでは他のライブラリの機能を取り込んでるようですね。

response.apparent_encoding に、正しい文字コードである SHIFT_JISが格納されているので、
これをencodingにセットしてあげれば大丈夫です。


import requests
url = "https://www.aozora.gr.jp/cards/000879/files/127_15260.html"
response = requests.get(url)
# この下の行を追加
response.encoding = response.apparent_encoding
html = response.text

これで、htmlに文字化けしていないテキストが入りました。

requestsを使って、Webサイトのソースコードを取得する

今回はとりあえず単純に httpで getするだけのコードを紹介します。
サンプルとして、yahooのトップページのHTMLを取得します。

利用するのは、 requests というpythonのライブラリです。
ドキュメントにある通り、超手軽に使えます。

こちらのコードで、htmlという変数に結果が入ります。


import requests
url = "https://www.yahoo.co.jp/"
response = requests.get(url)
html = response.text

scikit-learnでテキストをBoWやtfidfに変換する時に一文字の単語も学習対象に含める

本当に初めて自然言語処理をやった頃のメモから記事化。

テキストを分かち書きしたあと、BoW(Bag of Words) や tfidfに変換するとき、
scikit-learnを使うと便利です。
sklearn.feature_extraction.text に次の二つのクラスが定義されていて、
それぞれ語彙の学習と BoW /tfidfへの変換を行ってくれます。

CountVectorizer
TfidfVectorizer

ただ、これらのクラスはデフォルトパラメーターに少し癖があり注意していないと一文字の単語を拾ってくれません。
TfidfVectorizer の方を例にやってみましょう。


from sklearn.feature_extraction.text import TfidfVectorizer

model = TfidfVectorizer()
text_list = [
    "すもも も もも も もも の うち",
    "隣 の 客 は よく 柿 食う 客 だ",
]
model.fit(text_list)
print(model.vocabulary_)

これの出力結果が、下記になります。
これらが学習した単語です。
{‘すもも’: 1, ‘もも’: 2, ‘うち’: 0, ‘よく’: 3, ‘食う’: 4}

“も” や “の” が入ってないのはまだ許容範囲としても、
“隣”や”客”がvocabulary_に含まれないのは困ります。

これらは TfidfVectorizer のインスタンスをデフォルトのパラメーターで作ったことに起因します。

modelの内容を表示してみましょう。(jupyterで model とだけ入力して実行した結果)


>>> model
TfidfVectorizer(analyzer='word', binary=False, decode_error='strict',
        dtype=, encoding='utf-8', input='content',
        lowercase=True, max_df=1.0, max_features=None, min_df=1,
        ngram_range=(1, 1), norm='l2', preprocessor=None, smooth_idf=True,
        stop_words=None, strip_accents=None, sublinear_tf=False,
        token_pattern='(?u)\\b\\w\\w+\\b', tokenizer=None, use_idf=True,
        vocabulary=None)

注目するべきはここです。
token_pattern='(?u)\\b\\w\\w+\\b’
\\ は \をエスケープしたものなので、以下の説明中では\\ は \と書きます。
正規表現で、\b は単語の境界 、 \wは、単語構成文字、\w+ は1文字以上の単語構成文字の連続 を意味します。
そのため、 \w\w+ は2文字以上の単語構成文字の連続を意味し、
\b\w\w+\b は “単語の境界、2文字以上の単語構成文字、単語の境界” と続く文字列を単語のパターンとして採用するという指定になります。
そのため、 一文字の単語は学習対象から抜けています。

英語なら a や I が抜けるだけですが日本語では多くの漢字が抜けてしまうので、これは困りますね。

ということで、最初に TfidfVectorizer のオブジェクトを作る時には token_pattern を指定しましょう。
1文字以上を含めたいだけなので、 ‘(?u)\\b\\w+\\b’ にすれば大丈夫です。


from sklearn.feature_extraction.text import TfidfVectorizer

model = TfidfVectorizer(token_pattern='(?u)\\b\\w+\\b')
text_list = [
    "すもも も もも も もも の うち",
    "隣 の 客 は よく 柿 食う 客 だ",
]
model.fit(text_list)
print(model.vocabulary_)

これで出力は下記のようりなり、”隣”も”客”も含まれます。
{‘すもも’: 1, ‘も’: 5, ‘もも’: 6, ‘の’: 3, ‘うち’: 0, ‘隣’: 10, ‘客’: 8, ‘は’: 4, ‘よく’: 7, ‘柿’: 9, ‘食う’: 11, ‘だ’: 2}

これ以外にも “-” (ハイフン) などが単語の境界として設定されていて想定外のところで切られたり、
デフォルトでアルファベットを小文字に統一する設定になっていたり(lowercase=True)と、注意する時に気をつけないといけないことが、結構あります。

norm,smooth_idf,sublinear_tf などの影響でよくある自然言語処理の教科書に載っている数式と実装が違うのも注意ですね。
この辺はまた別の機会にまとめようと思います。

WP Mail SMTPを設定してLightsailのwordpressからメール送信できるようにする

WordPressには、コメント等があったときにメール通知する機能があるようなのですが、
メールサーバーが設定されておらず、これまでメールが送信できないようになっていました。

今後使えた方が便利なので、yahooメールを使ってメール送信するように設定します。
まず、
WP Mail SMTP というプラグインを有効化します。
Lightsailのwordpressの場合、最初からインストールされていて、無効状態で存在ます。

有効化したら設定です。

送信元アドレス: 自分のyahooメールのアドレス。
If you using an email provider (Gmail, Yahoo, Outlook.com, etc) this should be your email address for that account.
とある通り、普段使用しているえメアドを使う必要があります。
送信者名: ここはおそらくなんでもいいはず。僕はブログタイトルにしておきました。

メーラー: Other SMTP
SMTPホスト: smtp.mail.yahoo.co.jp
Yahooメールのヘルプページで指定されています

暗号化: SSL
SMTPポート: 465
Auto TLS : ON
認証 : ON
SMTP Username : yahooメールのユーザー名 (@より前の部分)
SMTO Password : yahooメールのパスワード

これでプラグインの設定完了です。

次に一般設定に移ります。

メールアドレス、をデフォルトから自分のメールアドレスに変更すると、
メールアドレスの確認メールが届き、SMTPの設定がうまくいったことが確認できます。
また、その確認メール内のリンクをクリックすると今後の通知がそのアドレスに届くようになります。

Data Driven Developer Meetup #4 に参加しました

2019/1/22 に 開催された、 Data Driven Developer Meetup #4 に参加しました。
セッションの内容や資料は上のリンク先の connpass のページにもありますが、一応ここにも書いておきます。

スポンサーセッション

  • 『機械学習を使ったサポート問い合わせ予測』 by freee 株式会社 Asaki Iwata 様 (@AsakiIwata)

メインセッション (20 min x 2)

LT (5 min x 2)

以下、発表中のまとめです。
個人の感想が多々入っていますし、ところどころ勘違いなどあるかもしれませんがご容赦ください。

『機械学習を使ったサポート問い合わせ予測』

感想として、単に機械学習で予想してみようというのではなく事前に目的のすり合わせや傾向の確認など、
ビジネス的に役立てるための準備をきちんとされているのを感じました。
例えば下記の点。
– そもそも問い合わせ数を予測することの目的はアルバイトを何人雇っておくべきかなどの人員計画にあること。
– 予測の許容誤差はビジネスサイドと事前に確認。
– 個人顧客と法人顧客で傾向が違い、今回は法人顧客を対象とした。
– 予測が高すぎた時と低すぎた時はそれぞれデメリットが異なる。(過剰な人件費の発生/サポートの品質低下による解約)

少し意外だったのは評価方法として絶対誤差を使われていたのと、
Gradient Boosting や XGBoost を使われていたこと。
自分だったら二乗誤差で評価し、モデルはLSTMなどのRNN系を前提に考えるかなと思いましたが、
それらも検証した上で、XGBoostなどを選ばれているようでした。
最終的に、評価指標が一番良かったモデルではなく、
評価は2番目だが、予測が上振れ傾向にあって顧客満足度を保てそうなモデルが採用されたということでした。
ビジネスサイドとよく連携して、機械学習を活用されているような印象を受けました。

『機械学習を⽤いた⽇経電⼦版Proのユーザ分析』

法人向けに、日経電子版Proというサービスがあるそうです。
そのサービスについて、無料トライアルの顧客が本契約してくれそうかどうかを機械学習でモデリングされたそうです。

日経新聞社にはデータ道場というデータドリブンを加速する教育制度があるそうです。
僕のチームではまだまだ個人個人の自己啓発や、メンバー間のナレッジ共有に頼りっきりなので、
このような制度を回せているは非常に羨ましいですね。

今回のモデルが予測する本契約率は売り上げ直結の重要指標で、
契約してくれる可能性が低い顧客に対して対策を打ったりするのに活用されるそうです。
内省されたというリアルタイムデータ処理基盤Atlraは結構羨ましい。

探索的データ分析として
最初に取得したデータの特徴や、欠損値の有無などを確認することの重要性を話されていました。
これは僕も常々重要だと思っていて、BIツールやモデルにデータを突っ込む前に必ずやるべきプロセスだと思っています。
(発表ではなく僕らの話ですが、Tableau等にデータを突っ込むと、大抵の場合は綺麗に可視化できてしまうので、
事前にしっかり確認しておかないと致命的な見落としが発生するものです。
なかなか、最初のデータの確認を定着させるのが難しいのですが、教育体制の差でしょうか。)
ビジネスでは(kaggleと違って)解決するべき問題を立てることが重要というのもその通り。
Leakageに注意することも大切ですね。自分も何度かやらかしています。
手法は再びGradient Boosting。
僕はこれまであまり使ってない方法なのですが、もっと試したくなりました。

『医療用語に注目した文書の類似度計算』

元々今回のイベントは、これを聞くために参加を決めました。
「文書の類似度」って難しいんですよね。
単語ならword2vecやfasttextなどが使えるのですが、
文章はtdidf化してcos類似度したりしてもどうも今ひとつな結果しか得られないし、
word2vecの(idfで重みづけた)線型結合等を試してもそこまで良くはならない。
Doc2vecにもがっかりしてきましたが、これは発表中でもディスられてました。

目的はサービスを横断してのキーワードからの記事のレコメンドだそうです。
問題の課題点に上がれらている3項目は似たような課題に直面した経験があるのでよくわかります。

まず今回参考になったのは、単純に自然言語処理的な手法だけでなく、
ユーザーのクリックなどをもとにしてアイテム間の類似度を協調フィルタリングで求めて使っていることです。
研究者が見るテキスト現場の医師が見るテキストが全然違うので、
テキストを一切考慮しない要素を加えているのだとか。
(お医者さんにとってオプシーボが肺がんの薬であることをは当然なので、
 オプシーボは肺がんの薬だとかいう説明が出てくることはなく、
 word2vecではこれらの単語が結びつかないなど。)

次にキーワードのマッチによる類似度。

テキストの分散表現はSCDV
これ、前々から存在は知っているのですがまだ実装して動かすってことをやってないです。
今回の発表聞いていて自分も早急にSCDVを試さなきゃと思いました。

これらの特徴量をつかって、XGBoostでモデルを作られたとのこと。

また、予測時には学習時と違ってクリックの情報は使わないそうです。
新しいアイテムをいち早く取り上げるためだそうで、これもなるほどと思いました。

このほか、fasttextの話もありました。日本語だとこれも結構上手く行くらしいので、
今後もっと試すようにしたいです。(いつもword2vecの方を使っているので。)

『Bokehではじめるデータビジュアライゼーション』

このLTからお酒も入り、気軽な感じで進みました。

Bokehというのは、インタラクティブビジュアライゼーションライブラリだそうです。
見てて、結構面白そうなライブラリだと思いました。

今の所、さっと作れるグラフなら matplotilibで可視化していますし、
ちょっと複雑なことをしたいときやインタラクティブなことをやりたいときは Tableauを使っているので
なかなか使う機会がなかったライブラリです。
でも、機械学習をWebアプリ風に設定変えて学習から可視化まででっきるのは便利そうです。
次、Tablauの代わりにこれを試してみようと思います。

『SlackへのKPI通知Botを作ったら いろいろ捗った話』

(GASは使いませんが)KPIのSlack通知はちょうど今うちの職場でもやろうとしていることです。
目的もほとんど同じなのですが、上手くいっているという話を聞けてとても励みになりました。
僕がどういうことを始めたいのか、どうやったらメンバーに伝わるか考えていたのですが、
とりあえずこの資料を共有しよう。

まとめ

元々の目的だったテーマ以外にも非常に勉強になったり励みになったりする発表が続き非常に有意義な時間でした。
主催者や登壇者のみなさん、会場を提供してくださいましたfreee株式会社さん、本当にありがとうございました。

matplotlibのデフォルトのフォントを変更する

前の記事でmatplotlibで日本語を表示できるフォントをインストールしましたので、
この記事では実際にそのフォントを使う方法を書いておきます。

最初に、デフォルトのフォントのままだと、グラフがどのように表示されるのかを見ておきましょう。


import matplotlib.pyplot as plt
# デフォルトの設定を確認
print(plt.rcParams["font.family"]) # => ['sans-serif']
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(range(10),range(10), label="データ1")
ax.plot(range(10),range(0,20,2), label="データ2")
ax.set_title("タイトル")
ax.set_xlabel("x軸のラベル")
ax.set_ylabel("y軸のラベル")
ax.legend()
plt.show()

こちらのコードを実行した結果がこの画像です。日本語文字が豆腐のようになります。

これを回避する方法の一つは実行するたびにフォントを指定することです。
このように、font.familyを指定することで、日本語の文字も豆腐にならず表示されます。


import matplotlib.pyplot as plt
plt.rcParams["font.family"] = "IPAexGothic"

このように指定したあとに、上と同じプログラムを実行すると、下図のように正しく日本語が表示されます。

ただし、これは毎回書くのは結構面倒です。たった1行なのに。
そこで、対応としてmatplotlibの設定ファイルでフォントを指定します。

最初に下記のコードを実行して、設定ファイルの場所を確認します。


>>> import matplotlib
>>> print(matplotlib.matplotlib_fname()) 

環境によって結果は変わりますが、自分の場合は下記の場所にありました。
/Users/<ユーザー名>/.pyenv/versions/anaconda3-5.3.1/lib/python3.7/site-packages/matplotlib/mpl-data/matplotlibrc

このmatplotlibrcのバックアップを取って編集します。
ファイル内に下記の記載があるのでコメントアウトを解除してIPAexGothicを指定します。
元の記述
#font.family : sans-serif
修正後
font.family : IPAexGothic

これで次回以降はフォントの指定をしなくてもmatplotlibで日本語が使えます。

もしうまく表示されない場合はキャッシュファイルを一度削除する必要があります。
matplotlib.get_cachedir()
でキャッシュのディレクトリがわかるので、ここにあるファイルを消して試してみてください。

MacにIPAフォントをインストールする

MacにIPAフォントをインストールする手順のメモです。
データサイエンティストがフォントをインストールする目的はおそらく一つしかないと思うのですが、
この作業の目的は、pythonのグラフ描画ライブラリである、matplotlibで作成したグラフ中で日本語を使うためです。

もともとAppleGothicというのが入っているので、それを使ってもいいのですが、
「ー」や「数」など、かなり使用頻度の高い文字が対応していなかったりするので、IPAのフォントをインストールします。

配布元サイト
ダウンロードページ
インストール方法

それでは順番にやっていきましょう。

最初にダウンロードページからフォントファイルをダウンロードします。
ゴシックの方だけあれば十分ですが、せっかくなので
2書体パック(IPAex明朝(Ver.003.01)、IPAexゴシック(Ver.003.01))
を選びました。

ダウンロードしたzipファイルを解答すると中に次の2ファイルと、ライセンス関係のテキストファイルがあります。
ipaexg.ttf
ipaexm.ttf

この2ファイルを
/Library/Fonts
にコピーしたらインストール完了です。

Mac には、Font Book というアプリケーションがあり、インストールされているフォントを確認できますが、
この中に
IPAexゴシックと、IPAex明朝 というのが登場していれば成功です。
フォントを選択して、[i]マークを押すと、詳細な情報が見れます。
この中の、PostScript名 に入っている
IPAexGothic や、 IPAexMincho が、matplotlibに設定するときの名前なのでメモしておきましょう。

MeCab.Tagger()はかなり遅いという話

昔、形態素解析にかかる時間を短縮するために調べた内容のメモです。

以前の記事で、mecab-python3 の使い方を書いたとき、tagger = MeCab.Tagger() という処理を関数の外側で行なっていました。

実は初めてmecab-python3を使った頃、僕は次のように書いてました。


def mecab_tokenizer(text):
    # 関数の中で、MeCab.Tagger()を呼び出す。これが遅い
    tagger = MeCab.Tagger()
    parsed_text = tagger.parse(text)
    parsed_lines = parsed_text.split("\n")[:-2]
    surfaces = [l.split('\t')[0] for l in parsed_lines]
    features = [l.split('\t')[1] for l in parsed_lines]
    bases = [f.split(',')[6] for f in features]
    # ここに、必要な品詞の単語だけ選抜する処理を入れることもある
    result = [b if b != '*' else s for s, b in zip(surfaces, bases)]
    return result

1個や2個のテキストを処理する分にはこの書き方で問題なかったのですが、
数十万件のテキストを処理するとこの関数がとても遅いという問題があり、調査をしていました。

結果わかったことは、タイトルの通り、MeCab.Tagger()が遅いということです。
jupyter で コードの前に %timeit とつけると時間を測れるのでやってみます。


%timeit tagger=MeCab.Tagger()
```
結果:
217 µs ± 6.17 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
```

ちなみに、形態素解析自体(parse)の実行時間はこちら


# 100文字のテキストを事前に用意しておきます
print(len(text))
```
100
```
%timeit parsed_text = tagger.parse(text)
```
結果:
26.9 µs ± 151 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
```

テキストがもっと長くなると話も変わるのですが、100文字くらいのテキストであれば、
parseにかかる時間よりも、Taggerのオブジェクトを作るのにかかる時間の方が8くらいかかっています。

対象のテキスト数(=関数が呼び出される回数)が数十万〜数百万件になってくると、
体感スピードがかなり違うので、
tagger = MeCab.Tagger()
は関数の中ではなく、事前に行うようにしておきます。

名前空間を汚染したりすることが気になる場合は、 class化するなどの対応をとりましょう。
また、形態素解析するテキストの数が少ない場合はあまり気にしなくても大丈夫です。

完全に余談ですが、この記事を書くために私物のMacで時間を計測したとき、職場のMacよりはるかに速いので感動しました。
職場の端末だとMeCab.Tagger()に 1.2ms (6倍!)かかります。
端末が5年物とそこそこ古いだけでなく、辞書指定などの問題もあるかもしれません。