jupyter notebook をコマンドラインで実行し HTML ファイルを生成する

はじめに

備忘録です。

最近は非エンジニアの分析官に一次分析内容を共有して定性分析を依頼したりする事が多いのですが、多数のモデルの場合 jupyter をある程度自動化したい場合があります。

私のよくやる流れとしては以下ですが、これを多数のモデルで PDCA を何回も回すことも珍しくありません。

  1. jupyter で分析する.
  2. html を出力して分析官に依頼する.
  3. 分析官は自分の手慣れた方法(Excelなど)で分析、レポーティングする

今回はこういったことに役立つ、jupyter notebook をコマンドラインで実行し HTML ファイルを生成する方法を紹介します。

jupyter notebook をコマンドラインで実行する

コマンドラインで実行する

コマンドラインで実行することに関してはこちらが詳しいです。 qiita.com

コマンドラインで .pynb の HTML 化する

今回の HTML 化についてはこちらに殆ど書いています。

python - How export a Jupyter notebook to HTML from the command line? - Stack Overflow

# 以下を実行すると notebook.html ファイルが生成されます
$ jupyter nbconvert --execute --to html notebook.ipynb

私がよくやるのは、雛形となる jupyter notebook を作り、変更される値や設定を変数化し、コマンドラインで置換(perlsed など)しながら様々な分析レポートを出力することです。

簡単な例

for model in model_A model_B .....;
do
    # 置換、或いは何らかのパーサー
    cat '雛形となる .ipynb' | perl -pe "s/置き換える文字列/${model}/s"  > ${model}.ipynb

    # html 生成
    jupyter nbconvert --execute --to html ${model}.ipynb
done

関数データ解析(FDA, Functional Data Analysis)とは

関数データ解析とは

関数データ解析とは

各個体や対象に対して、複数の離散点で時間や空間の変化に伴い観測・測定されたデータを、関数の集合として捉え、解析する方法
Ramsay and Silverman, 2005

とされています。

一般的にデータ解析では、データそのものの統計量をとったり、クラスタリングしたり、回帰分析したりします。 関数データ解析では、データを関数で表現して、その関数を改めてデータとして扱う手法です。

とはいえ、「データを関数で表現してそこで何か(分類や予測など)をする」という手法は、従来の機械学習にも使われていますし(例えばガウス線形回帰)、関数データ解析固有のものではありません。

関数データ解析では、特定の手法ではなくデータの関数表現をデータそのものの分析と同じように扱う方法論、ということになろうかと思います。

例えば、テキスト*1 では次のように書かれています。

Ramsay & Silverman (2005)は、関数データにおける平均、分散、共分散を定義した後、
主成分分析、線形モデル、正準相関分析、判別分析の関数データ対応版を扱っている。
さらに、関数の定義域を調整するRegistration (見当合わせ)、通常のデータを
関数データにする各種平滑化などについても詳細に検討している。

近年では、これらに加えクラスタリング(Kmeans の関数データ適用がよく使われるようです)、スパース解析(https://sites.google.com/site/hidetoshimatsui/field/fda)なども検討が進んでいます。

関数データ解析のながれ

主に次の二段階法が使われるようです。

  1. 離散観測を連続の関数データで捉える(平滑化と呼ばれています)

    機械学習の手法で言うところのカーネル線形回帰などで、モデル関数を実データにフィッティングさせる。

  2. 捉えた関数集合を分析する

    関数集合をデータ集合と同じように平均・分散・相関などを計算して分析する

所感

関数データ解析ではデータを関数で表現することで、データを集合で捉えより表現力のある構造(関数)で分析ができるようになります。 例えば従来のデータ解析でのデータの代表点(平均や中央値)や広がり(分散など)、関連(相関)などと同様の統計手法が関数データ解析においても使え、そこでは関数平均や関数分散、関数相関などの手法で分析ができます。

とは言え、私の中でもまだイメージの域を出ませんので、引き続き注目していきたいと思います。

Decision Tree - ID3(Iterative Dichotomiser 3 - 1979 John Ross Quinlan)

Decision Tree - ID3(Iterative Dichotomiser 3 - 1979 John Ross Quinlan)

(注意) これは私の勉強・備忘録のために記したものであり、間違いがあるやもしれません。どうぞご容赦ください。

はじめに

決定木のアルゴリズムに ID3 というものがあります。 ID3 は Quinlan が 1979 に発表したアルゴリズムで、この改良の C4.5 や、決定木を並列に多数構築した RandomForest、Boosting に活用した Gradient Boosted Trees など、発展型の元となったものです。

私の勉強のためにも、まずは ID3 から初めて、今 Kaggle でも最も定評・人気がある XGboost までたどってみようと思います。

ID3

Wikipediaja.wikipedia.org によると

その学習方法はオッカムの剃刀の原理に基づいている。すなわち最低限の仮説による事象の決定を行う。 「この方法は各独立変数に対し変数の値を決定した場合における平均情報量の期待値を求め、その中で最大のものを選びそれを木のノードにする操作を再帰的に行うことで実装される。」

とあります。これは ID3 に限ったことではなく決定木の基本的な考え方・原理です。

決定木は「最低限の仮説による事象の決定」とあるように、うまくコントロール*1すれば最小限の条件でその事象を表現できます。

特に、ヘテロジニアスデータ*2での適切に最適化された条件は人の感覚に合うため、人と機械のよいコンビネーションで PDCA を回せるなど、ビジネスの現場でもよく使われています。

実装

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import os
import math
import numpy as np
import scipy as sp
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

"""
    ID3(Iterative Dichotomiser 3 - 1979 John Ross Quinlan)
    その学習方法はオッカムの剃刀の原理に基づいている。すなわち最低限の仮説による事象の決定を行う。

    「この方法は各独立変数に対し変数の値を決定した場合における平均情報量の期待値を求め、
    その中で最大のものを選びそれを木のノードにする操作を再帰的に行うことで実装される。」
    
    https://ja.wikipedia.org/wiki/ID3
    制約: 離散でないといけない

    S: 訓練例の集合
    A: 属性の集合
    default: Yes / No の既定値
"""


# log の底は平均情報量の最大値が 1.0, 最小値が 0.0 になるように、分類数に合わせたほうが良い
# ここでは 3 とする.
def entropy(y, base=2):
    ''' 平均情報量の算出
    y=pd.Series
    target
    bird       0.4
    manmal     0.4
    reptile    0.2
    '''
    p = y.groupby(y).count() / y.shape[0]
    return -1 * sum(map(lambda x: x * math.log(x, base), p))

def mean_entropy(S, c, t='target'):
    ent = []
    for k, v in S.groupby(c).groups.items():
        # weighted average
        y = S.ix[v, t]
        ent.append(entropy(y, base=3) * y.shape[0] / S.shape[0])

    return np.sum(ent)


"""
 S : Tree
"""
class Node(object):

    def __init__(self, data, parent=None, col=None, feature=None, gain=0.):
        self.data   = data
        self.parent = parent
        self.col    = col
        self.feat   = feature

        # 子の分割基準
        self.gain   = gain
        self.childs = []

    def entropy(self, base=2):
        return entropy(self.data['target'], base=base)

    def shape(self):
        return self.data.shape

    def add_child(self, c):
        self.childs.append(c)

    def __repr__(self):
        return '%s(%s)\tH:%.3f Gain:%.3f' % (self.col, self.feat, self.entropy(), self.gain)


def ID3(S, A, t='target'):

    if S.data.shape[0] <= 0:
        # S が空集合
        return None
    
    elif len(A) <= 0:
        # Aが空集合
        return S

    elif S.entropy(base=3) <= 0.:
        return S

    else:
    
        # 1. 各説明変数で平均情報量を算出する <- このロジック mean_entropy の差し替えで C4.5 とかにできる
        Ms = map(lambda a: (a, mean_entropy(S.data, a)), A)

        # 2. 情報ゲインが最大な説明変数を選ぶ
        col, gain = max(map(lambda x: (x[0], S.entropy(3)-x[1]), Ms), key=lambda x:x[1])

        # 3. 木を分割する
        #_A = list(set(A) - set([col]))
        for f, x in S.data.groupby(col).groups.items():
            S.add_child(ID3(Node(S.data.ix[x], parent=S, col=col, feature=f, gain=gain), A))

        return S
    

def pretty_print_node(n, depth=0):
    if n is None:
        return 
    pref = '\t'
    print(pref*depth, n)
    for _n in n.childs:
        pretty_print_node(_n, depth+1)


if __name__ == '__main__':
    '''
    例題    食性(a1 ) 発生形態(a2 )   体温(a3 ) 分類
    例題1(ペンギン) 肉食  卵生  恒温  鳥類
    例題2(ライオン) 肉食  胎生  恒温  哺乳類
    例題3(ウシ)   草食  胎生  恒温  哺乳類
    例題4(トカゲ)  肉食  卵生  変温  爬虫類
    例題5(ブンチョウ)  草食  卵生  恒温  鳥類
    '''
    df = pd.DataFrame(
        [[ 'carnivorous', 'oviparity',  'homothermal',  'bird'],
         [ 'carnivorous', 'viviparity', 'homothermal',  'manmal'],
         [ 'herbivorous', 'viviparity', 'homothermal',  'manmal'],
         [ 'carnivorous', 'oviparity',  'cold_blooded', 'reptile'],
         [ 'herbivorous', 'oviparity',  'homothermal',  'bird']],
        columns = ['a1', 'a2', 'a3', 'target'],
        index = ['penguin', 'lion', 'caw', 'lizard','finch']
    )
    
    S = df
    A = ['a1', 'a2', 'a3'] # 属性集合
    X = df[['a1', 'a2', 'a3']]
    y = df.target
    
    Tree = ID3(Node(df, col='top'), A)
    pretty_print_node(Tree)
            

実行結果は

:decision_tree $ python id3.py
 top(None)      H:1.522 Gain:0.000
         a2(oviparity)  H:0.918 Gain:0.613
                 a3(cold_blooded)       H:-0.000 Gain:0.579
                 a3(homothermal)        H:-0.000 Gain:0.579
         a2(viviparity) H:-0.000 Gain:0.613

*1:実課題のデータはとても多様性があるため、ナイーブに決定木を最適化すると逆に条件が複雑になりオーバフィットしてしまいます。そのため、木の深さや単調性制約などを与えて機械的な最適化にある程度の歯止めをかけたりします

*2:ヘテロジニアスデータ:いわゆる異なる種類の説明変数で構成された表タイプのデータ、対するは画像ピクセルなど同種類のデータで構成されたホモジニアス・データ

keras.datasets.imdb.load_data() に 'Object arrays cannot be loaded when allow_pickle=False' で失敗する.

Tensorflow tutorials の「映画レビューのテキスト分類」(https://www.tensorflow.org/tutorials/keras/basic_text_classification) にて、データセットのロードに失敗します。

In [6]: from keras.datasets import imdb
In [7]: (train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000)

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-7-7d41db3a3caa> in <module>
----> 1 imdb.load_data(num_words=10)

/anaconda3/envs/py36_base/lib/python3.6/site-packages/tensorflow/python/keras/datasets/imdb.py in load_data(path, num_words, skip_top, maxlen, seed, start_char, oov_char, index_from, **kwargs)
     84       file_hash='599dadb1135973df5b59232a0e9a887c')
     85   with np.load(path) as f:
---> 86     x_train, labels_train = f['x_train'], f['y_train']
     87     x_test, labels_test = f['x_test'], f['y_test']
     88

/anaconda3/envs/py36_base/lib/python3.6/site-packages/numpy/lib/npyio.py in __getitem__(self, key)
    260                 return format.read_array(bytes,
    261                                          allow_pickle=self.allow_pickle,
--> 262                                          pickle_kwargs=self.pickle_kwargs)
    263             else:
    264                 return self.zip.read(key)

/anaconda3/envs/py36_base/lib/python3.6/site-packages/numpy/lib/format.py in read_array(fp, allow_pickle, pickle_kwargs)
    690         # The array contained Python objects. We need to unpickle the data.
    691         if not allow_pickle:
--> 692             raise ValueError("Object arrays cannot be loaded when "
    693                              "allow_pickle=False")
    694         if pickle_kwargs is None:

ValueError: Object arrays cannot be loaded when allow_pickle=False

これは numpy のバージョンに依存するエラーで、numpy=1.16.3 から、read_array(fp, allow_pickle, pickle_kwargs) の allow_pickle のデフォルト値が False に変更されたことに起因しています。

https://stackoverflow.com/questions/55890813/how-to-fix-object-arrays-cannot-be-loaded-when-allow-pickle-false-for-imdb-loa

$ conda list
...
numpy                     1.16.3           py36hacdab7b_0
numpy-base                1.16.3           py36h6575580_0
...

ここで示される解決方法は単純に numpy=1.16.2 にダウングレードすることです。

$ conda install numpy=1.16.2

$ conda list | grep numpy
...
numpy                     1.16.2
numpy-base                1.16.2
...

動作を確認しました。

In [5]: imdb.load_data(num_words=1)
Out[5]:
((array([list([2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
...

ちなみに、colab では以下のコードが挿入されており、tf のバージョンを変えることで対応しています。 nightly build では修正されているようですので次のバージョンでは解消されるはずです。

# keras.datasets.imdb is broken in 1.13 and 1.14, by np 1.16.3
# (nightly build は最新バージョンのビルド)
!pip install tf_nightly

Mojave に機械学習とデータ解析の anaconda 環境を構築

Macbook Air を購入したので、何もインストールされていない真っさらな状態から、機械学習とデータ解析の環境を構築しました。 日々構築方法が様々に洗練され、前回のやり方が通用しない昨今ですが、今回は anaconda で全て構築しました。

brew

  1. App Storeに行ってXcodeを探しインストールをします。
  2. xcode-select --install
  3. Xcodeを一度起動してライセンスに同意しておく
  4. /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
  5. brew doctor でチェック

emacs

以下を参考にしました。 https://qiita.com/kokorinosoba/items/ecceaabe07d91c6f2c66

$ brew cask install emacs

Ricty

emacs では Ricty をフォントとして使っているので、そのインストールです。 https://qiita.com/segur/items/50ae2697212a7bdb7c7f

  1. brew tap sanemat/font
  2. brew install ricty
  3. 以下のファイルをコピーし、フォントキャッシュを更新します。
To install Ricty:
  $ cp -f /usr/local/opt/ricty/share/fonts/Ricty*.ttf ~/Library/Fonts/
  $ fc-cache -vf

Anaconda

anaconda で全部構築する

Anaconda インストール

https://www.anaconda.com/distribution/Python 3.7 version

Anaconda3-2019.03-MacOSX-x86_64.pkg

Anaconda 仮想環境構築

# tensorflow が python3.6 環境なので 3.6 で構築します。
$ conda create -n py36_base python=3.6
$ conda info -e
# conda environments:
#
base                  *  /anaconda3
py36_base                /anaconda3/envs/py36_base

Anaconda Navigator でやってもスムーズです。

tensorflow/keras/sklearn のインストール

conda で全部いれてしまいます。 

# 環境をアクティベートする
$ source activate py36_base

$ conda install tensorflow
$ conda install keras
$ conda install scikit-learn
$ conda install matplotlib
$ conda install seaborn

$ conda list
matplotlib                3.0.3            py36h54f8f79_0
...
numpy                     1.16.3           py36hacdab7b_0
numpy-base                1.16.3           py36h6575580_0
...
pandas                    0.24.2           py36h0a44026_0
...
scikit-image              0.15.0           py36h0a44026_0
scikit-learn              0.20.3           py36h27c97d8_0
scipy                     1.2.1            py36h1410ff5_0
seaborn                   0.9.0                    py36_0
...
tensorboard               1.13.1           py36haf313ee_0
tensorflow                1.13.1          mkl_py36haf07a9b_0
tensorflow-base           1.13.1          mkl_py36hc36dc97_0
tensorflow-estimator      1.13.0                     py_0

jupyter, jupyterlab, orange, glueviz, も navigator から入れます。

環境設定

bash

.bash_profile に anaconda インストーラによってスニペットが挿入されているので、その後にデフォルト起動スクリプトを挿入します。

# added by Anaconda3 2019.03 installer
# >>> conda init >>>
# !! Contents within this block are managed by 'conda init' !!
__conda_setup="$(CONDA_REPORT_ERRORS=false '/anaconda3/bin/conda' shell.bash hook 2> /dev/null)"
if [ $? -eq 0 ]; then
    \eval "$__conda_setup"
else
    if [ -f "/anaconda3/etc/profile.d/conda.sh" ]; then
        . "/anaconda3/etc/profile.d/conda.sh"
        CONDA_CHANGEPS1=false conda activate base
    else
        \export PATH="/anaconda3/bin:$PATH"
    fi
fi
unset __conda_setup
# <<< conda init <<<


# 追加:デフォルトの環境を起動します。
conda activate p36_base

matplotlib

matplotlib の import 時にエラーが発生した場合、バックエンドを変更します。

# /anaconda3/envs/py36_base//lib/python3.6/site-packages/matplotlib/mpl-data/matplotlibrc

#backend      : macosx
backend      : TkAgg

動作確認

以下のサンプルを実行できることを確認します。

  1. sklearn + matplotlib https://scikit-learn.org/stable/auto_examples/plot_multilabel.html#sphx-glr-auto-examples-plot-multilabel-py

  2. tensorflow, keras

import os
import tensorflow as tf
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
hello = tf.constant('Hello, TensorFlow!')
sess  = tf.Session()
print(sess.run(hello))