matplotlibで等高線

折れ線グラフや散布図に比べると利用頻度が落ちますが、
2次元から1次元への写像の可視化として等高線を使うことがあるので、そのメモです。

使う関数は、線を引く場合は、contour,色を塗る場合は contourf を使います。

サンプルの関数は何でもいいのですが、今回はこれを使います。
$$
1-\exp(-x^2+2xy-2y^2)
$$
まずはデータの準備です。


import matplotlib.pyplot as plt
import numpy as np

# 関数の定義
def f(x, y):
    return 1- np.exp(-x**2 + 2*x*y - 2*y**2) 

# プロットする範囲のmeshgridを作成する。
X = np.linspace(-2,2,41)
Y = np.linspace(-2,2,41)
xx, yy = np.meshgrid(X, Y)

そして可視化してみます。まずは等高線から。


fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.contour(xx,yy,f(xx, yy))
plt.show()

次に、色を塗る場合。


fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.contourf(xx,yy,f(xx, yy), alpha=0.5)
plt.show()

色の指定などをきちんとしていないのですが、まあまあみやすく可視化できていますね。
機械学習の決定境界の可視化などでも、これと同じ方法を使うことがあります。

データフレームの特定の文字列を含む行を抽出するときに None の列があってもエラーにならないようにする

自然言語処理をよくやっているので、データフレームに格納されたテキストデータから、
特定の文字列を含むものを抽出する作業は非常に頻繁に発生します。
そのときには、 pandas.Series.str.contains という非常に便利な関数を使うのですが、
Series の中に None や Nan があるとエラーになるのが地味に不便でした。


print(df)
```
出力結果:
     col
0  あいうえお
1  かきくけこ
2   None
3  たちつてと
```
df[df["col"].str.contains("く")]
```
出力結果:
(略)
ValueError: cannot index with vector containing NA / NaN values
```

まあ、空白文字列か何かで埋めてあげれば何の問題もないのですが、このエラーが出ると嫌な気持ちになります。
気をつけていてもデータフレーム同士を結合したりするとすぐ None は発生しますし。

これはしょうがないと思っていたのですが、ドキュメントを見ていると、na というパラメーターがー準備されているのを見つけました。

contains の引数に na=True を指定すると,Noneの列も抽出され、na=Falseとすると、Noneの列は含みません。


print(df[df["col"].str.contains("く",na=False)])
```
出力結果:
     col
1  かきくけこ
```
print(df[df["col"].str.contains("あ",na=True)])
```
出力結果:
     col
0  あいうえお
2   None
```

これは便利ですね。
空文字列と、Noneを区別したい場面も結構あるのでNoneをそのまま残せるのはありがたいです。

また、ついでですが、 regex というパラメーターで、正規表現の使用未使用を切り替えられることにも気づきました。
デフォルトで正規表現が使えるのでいつも便利だと思っていたのですが、
完全一致のみにすることもできたのですね。

scikit-learnのtrain_test_splitで、訓練データとテストデータのラベルの割合を揃える

自分の場合なのですが、普段の業務で機械学習を行う場合不均衡データを扱うことが非常に多くあります。
ラベルづけされたデータを train_test_split で訓練データとテストデータに分けるとき、
運が悪いと訓練データとテストデータで、ラベルの割合がずいぶん変わってしまうことがありました。


#  全データのラベルの割合は 99:1
df['label'].value_counts()
'''
0    9900
1     100
'''
# データの2割りをテストデータにする
df_train, df_test = train_test_split(df, test_size=0.2)

# 訓練データでは ラベル1 は 0.9625 %
df_train.label.value_counts() / len(df_train)
```
0    0.990375
1    0.009625
```
# テストデータでは ラベル1 は 1.15%
df_test.label.value_counts() / len(df_test)
```
0    0.9885
1    0.0115
```

この例ではまだ許容範囲かなという気もしますが運が悪いとかなりの差が開きます。

そこで、かつてはデータフレームをラベルごとに分けてから個別に訓練用とテスト用に分けて、
それをマージして訓練データとテストデータを作ると言った面倒なことをやっていたことがあります。

その後、 train_test_split のマニュアルを読んでいたら非常に便利な引数があることがわかりました。

stratify に、割合を揃えたい列を指定してあげると、訓練データとテストデータで同じ割合になるように分けてくれます。


#  全データのラベルの割合は 99:1
df['label'].value_counts()
'''
0    9900
1     100
'''
# データの2割をテストデータにする
df_train, df_test = train_test_split(df, test_size=0.2, stratify=df.label)

df_train.label.value_counts() / len(df_train)
```
0    0.99
1    0.01
```
df_test.label.value_counts() / len(df_test)
```
0    0.99
1    0.01
```

綺麗に分かれました。

scikit-learn でグリッドサーチ

機械学習のハイパーパラメーターを決定するとき、グリッドサーチという手法を使うことがあります。
よほど学習時にかかるケース以外では、ほぼ確実に行なっています。

そのとき、scikit-learn の GridSearchCV というクラスを使うことが多いのでその使い方をメモしておきます。
今回は題材として、 digits という手書き数字のデータセットを利用します。


from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split

最初にデータを準備します。
# データの読み込み
digits = load_digits()
X = digits.data
y = digits.target
# 訓練データとテストデータに分割
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

次にサーチするパラメーターを指定します。


# グリッドサーチするパラメーターを指定。変数名と値のリストの辞書 、それが複数ある場合はそれらの配列。
param_grid = [
    {
        'C': [1, 10, 100, 1000],
        'kernel': ['linear']
    },
    {
        'C': [0.1, 1, 10, 100, 1000],
        'kernel': ['rbf'],
        'gamma': [0.001, 0.0001, 'auto']
    },
    {
        'C': [0.1, 1, 10, 100, 1000],
        'kernel': ['poly'], 'degree': [2, 3, 4],
        'gamma': [0.001, 0.0001, 'auto']
    },
    {
        'C': [0.1, 1, 10, 100, 1000],
        'kernel':['sigmoid'],
        'gamma': [0.001, 0.0001, 'auto']
    }
]

モデルを作って、グリッドサーチの実行


from sklearn.model_selection import GridSearchCV
from sklearn.svm import SVC

# モデルの準備
model = GridSearchCV(
    SVC(),  # 予測機
    param_grid,  # サーチ対象のパラメーター
    cv=5,  # 交差検証の数
    # このほか、評価指標(scoring) や、パラレル実行するJob数なども指定可能(n_jobs)
)
# グリッドサーチの実行
model.fit(X_train, y_train)

最良のパラメーターを確認する


print(model.best_params_)

# 出力
{'C': 1, 'gamma': 0.001, 'kernel': 'rbf'}

最後に、テスト用に取っておいたデータで、出来上がったモデルを評価します。


from sklearn.metrics import classification_report

# 学習したモデルで予測
y_predict = model.predict(X_test)
# 作成したモデルの評価
print(classification_report(y_test, y_predict))

# 出力
             precision    recall  f1-score   support

          0       1.00      1.00      1.00        38
          1       0.97      1.00      0.99        34
          2       1.00      1.00      1.00        38
          3       0.97      1.00      0.99        34
          4       1.00      1.00      1.00        36
          5       1.00      0.97      0.99        35
          6       0.97      0.97      0.97        39
          7       0.97      1.00      0.98        31
          8       0.97      0.97      0.97        38
          9       1.00      0.95      0.97        37

avg / total       0.99      0.99      0.99       360

なかなか良い正解率ですね。

numpy の数値を表示するときの桁数を指定する

当然ですが、numpyを使っていると数値をprintして値を確認する機会が多々あります。
そこで問題になるのが、表示形式です。
本来は利便性のためだと思うのですが、小数点以下の桁が何桁も表示されたり、突然指数表記になったりします。
正直言って、配列内のどの値が大きくてどの値が小さいのか、ぱっと見でわかりにくいです。

表示例。


>>> import numpy as np
>>> ary = np.random.randn(3, 5)
>>> print(ary)
[[-8.69277072e-01 -4.72662319e-01  5.48868554e-01 -6.03789326e-01 1.95117216e-01]
 [-1.46386861e+00  9.92037075e-01  8.04045031e-01 -1.43756938e+00 7.46898368e-02]
 [-1.05065247e+00  3.72571551e-04 -1.15836779e-01 -5.80949053e-03 1.59979389e+00]]

numpy のドキュメントによると、絶対値が一番大きいものと一番小さいものの差が一定値を超えると指数表記になるそうです。

そこで、値を確認するときは、適当なくらいで四捨五入して表示したりしていたのですが、
実はnumpyのオプションで表示桁数を指定できることがわかりました。

設定を変える前に、デフォルトの設定は下記の関数で見ることができます。
(numpyのバージョンによって設定可能項目は変わります。)


>>> np.get_printoptions()
{'edgeitems': 3, 'threshold': 1000, 'floatmode': 'maxprec', 'precision': 8, 'suppress': False, 'linewidth': 75, 'nanstr': 'nan', 'infstr': 'inf', 'sign': '-', 'formatter': None, 'legacy': False}

各設定値の意味はこちら。set_printoptions
(get_printoptionsのページにはset_printoptions を見ろと書いてある。)

これらの設定値を、set_printoptions関数で変更することができます。
この中で、よく使うのはこの二つ。
precision = 3 # 小数点以下の表記を
suppress = True # 指数表記を禁止

設定してみたのがこちら。


>>> np.set_printoptions(precision=3, suppress=True)
>>> ary = np.random.randn(5,3)
>>> print(ary)
[[ 1.611 -2.259  0.022]
 [-1.937 -0.394  2.011]
 [-0.01  -0.162 -0.823]
 [-1.818 -2.474  0.341]
 [ 0.363 -2.018 -0.667]]

見やすくなりました。

mecab-python3をつかってみる

前回の記事でインストールした mecab-python3 の使い方を書いておきます。
MeCabについてはWikiがあるのですが、このライブラリについては詳細なマニュアルはなく、
リポジトリの test.py を読むようにとそっけなく書いてあります。

ただ、実際のところつかのは非常に簡単です。
次の例のようにMeCab.Tagger() と parse を呼び出すだけで結果を得られます。


>>> import MeCab
>>> text = 'すもももももももものうち'
>>> tagger = MeCab.Tagger()
>>> print(tagger.parse(text))
すもも	名詞,一般,*,*,*,*,すもも,スモモ,スモモ
も	助詞,係助詞,*,*,*,*,も,モ,モ
もも	名詞,一般,*,*,*,*,もも,モモ,モモ
も	助詞,係助詞,*,*,*,*,も,モ,モ
もも	名詞,一般,*,*,*,*,もも,モモ,モモ
の	助詞,連体化,*,*,*,*,の,ノ,ノ
うち	名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ
EOS

各行の出力結果は次の形です。

表層形\t品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用型,活用形,原形,読み,発音

注意点としては、 parseした戻り値は一つのテキストなので非常に使いにくいことです。

多くの場合、必要なのは原型の列です。
そこでプログラムでこのテキストから原形の情報を取り出すことになります。
僕はいつも下記のような関数を作って実行しています。


tagger = MeCab.Tagger()


def mecab_tokenizer(text):
    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

Mac(Mojave) に pip で mecab-python3をインストールする時にはまった

環境
MacOS Mojave 10.14.2 (OS)
mecab-python3==0.996.1 (入れようとしたライブラリ)

本当はサクッとインストールして使い方について説明するはずだったのに、非常に苦戦したので記録しておきます。
MeCabをpythonから使うために、mecab-python3をインストールしようとしました。
コマンドはサイトに書いてある通り、こちらです。


pip install mecab-python3

これ、自分のや職場のPC,クラウド環境など、過去にいろんな環境で実行してきましたが、今回初めて失敗しました。

まず最初のエラーは、swig が入ってないとのことだったので、Homebrewで入れます。


brew install swig

この後再実行すると、別のエラー。しかもかなりの長文が出て失敗しました。
問題の箇所を抜粋したのがこちらです。


  warning: include path for stdlibc++ headers not found; pass '-std=libc++' on the command line to use the libc++ standard library instead [-Wstdlibcxx-not-found]
  MeCab_wrap.cpp:3051:10: fatal error: 'stdexcept' file not found
  #include 
           ^~~~~~~~~~~
  1 warning and 1 error generated.
  error: command 'gcc' failed with exit status 1

なにかのheaderがないと言われています。
これについて調べた結果、ネット上各所に command Line Toolsの
最新バージョンが問題であると指摘がありました。
ということで、command Line Toolsのバージョンを落とします。
こちらにアクセス
https://developer.apple.com/download/more/

どこまで古いバージョンなら良いのか確信が持てませんでしたが、
試しにXcode 9.4 向けのdmgファイルをダウンロードしてインストールしました。
その後、改めて最初のpipコマンドを打つと無事にpythonからMeCabが使えるようになりました。

jupyter notebook 上で pycodestyle を使う

テキストエディタで.py ファイルを使う時はコードを綺麗にするために、
pycodestyle (pep8の新しい名前)をよく使います。名前変更の経緯はこちら

これを jupyter notebook でも使えるようにします。
その際に必要になるのが、こちらの pycodestyle_magic というツールです。
flake8というライブラリも必要になるので、一緒に入れます。
(pycodestyleがない場合はそれも必要なのでpipインストールしてください。)

pip install flake8 pycodestyle_magic

使う時は、notebookで事前に読み込み、フォマットをチェックしたいセルで、マジックコマンドを使います。


# magicコマンドを使えるように読み込む
%load_ext pycodestyle_magic

そして、チェックしたいコードが書かれたセルの一番上の行にマジックコマンドを入れて実行します。


%%pycodestyle
#ここにチェックしたいプログラムが書かれている。

問題があれば下記のように警告が表示されます。# の後ろにスペースがありませんでしたね。


2:1: E265 block comment should start with '# '

注意として、この時プログラム自体は実行されないようです。
そのため、スタイルをチェックし終わったら、
マジックコマンドを外して改めてセルを実行する必要があります。

jupyter notebookをバックグラウンドで起動する

普段のPythonプログラミングには、jupyter notebook を使用しています。
AWSのサーバーに立てたnotebookを使っている時は良いのですが、
ローカルのMacで動かしている時は
ターミナルを立ち上げっぱなしにしておかないといけないので少し不便です。

そこで、jupyterをバックグラウンドで動かすようにします。
利用するのは nohupコマンドです。
頻繁に使用するので、下記のような内容でスクリプト化しておくと便利です。


#!/usr/bin/env bash
nohup jupyter notebook >> jupyter.log 2>&1 &

これを実行すると、notebookが立ち上がり、しかも端末を切っても動き続けます。

Simple Mathjax を導入

現状、ブログ構築やMacの環境構築関係の記事ばかりですが、近々データサイエンス関係の記事を増やしたいので、
このブログに数式を入れられるようにしておく必要があります。
ということで、Simple Mathjax というプラグインを追加しました。
これで記事中でTeXによる数式が書けるはずです。

インラインの場合は\$マークで囲めば良いそうです。たとえば、三角関数の公式であれば、$\tan\theta=\frac{\sin\theta}{\cos\theta}$と表示できます。

\$\$で囲むことによってブロック表示になります。
活性化関数としてよく使われる、ReLUを書いてみましょう。

$ReLU(x) = \max(0,x) =\left\{\begin{array}{ll} 0 & (x<0) \\ x & (x\geq0) \end{array}\right.$