渋谷駅前で働くデータサイエンティストのブログ

元祖「六本木で働くデータサイエンティスト」です / 道玄坂→銀座→東京→六本木→渋谷駅前

『機械学習のエッセンス』はゼロからガチで機械学習を生業にしたい人が「いの一番に」読むべき一冊

発売されてからだいぶ経ちますが、構想段階の頃より著者の「はむかず」さんこと加藤公一さんからお話を伺っていて注目していたこちらの一冊をようやく一通り読みましたので、サクッと書評めいた何かを書いてみようかと思います。


各章の概要


言うまでもなく実際の内容は皆様ご自身でお読みいただきたいのですが、これまでの書評記事同様に概要を簡単にまとめておきます。

第01章 学習を始める前に

Python環境やAnacondaのインストールについての説明もなされているんですが、重要なのは後述する「本書は何を含まないか」という節。ここに本書の狙いの全てが書かれていると言っても過言ではないので、最初に読まれる方はまずここを熟読することをお薦めします。

第02章 Pythonの基本

ゼロから機械学習を始める人にはそもそもPython自体が初めてというケースを想定してか、Pythonそのものの基本についても章ひとつが割かれています。実は僕はPythonはR以上に我流独学でやっていて基本のきも理解していないケースがある*1ので、こういうパートがあると助かります。

第03章 機械学習に必要な数学

いよいよ本書らしさが前面に出てきます。「基本事項の確認」の節では集合、配列、写像と関数という最も基礎的な話題に一応触れられています。そして大事なのが「線形代数」「微積分」の節。以前の記事にも書いたように線形代数と微積分は機械学習を学ぶ上で最低限必要な数学の範囲なので、この2つに多くのページが割かれているのは納得です。ただし、ここで取り上げられている線形代数微積分もかつて*2は高校か大学受験で習う範囲だったものが多く、大学の教養課程以降の内容はおそらく線形代数のランクと微積分の偏微分と勾配ぐらいかもしれません。

第04章 Pythonによる数値計算

ここではPythonというかNumPyの底力を存分に発揮させて、機械学習の裏側に必要な数値計算の話題を広汎に取り上げています。特に「数理最適化」の節は重要で、ここで線型計画法、二次計画法、勾配降下法、ニュートン法ラグランジュの未定乗数法と言った機械学習では必須の最適化問題解法の話題がカバーされています。詳しくは後述しますが、この章から実際にスクラッチも含めてPythonでコードを書いて実践するやり方の紹介が増えてきます。


また、他にもMatplotlibを用いた可視化の話題や、最低限の統計学の話題にも触れられています。確率密度関数の話題が取り上げられているのはいかにも本書らしいなと思いました。

第05章 機械学習アルゴリズム

そして最後の章になってようやく各種機械学習アルゴリズムが登場します。本書で取り上げているのは線形回帰、Ridge線形回帰(L2正則化線形回帰)、Lasso線形回帰(L1正則化線形回帰)、ロジスティック回帰、SVM、K-means、PCAです。またこの章で汎化と過学習の概念についてもバイアス=バリアンス分解のような基礎的な考え方から丁寧に説明されています。


本書の特色である「できるだけスクラッチに近い形でPythonコードを付して実践する」というのが最も表れているのもこの章で、例えばLassoの解説では定義からの式変形によってパラメータの更新式を導出し、得られるパラメータが疎になるというのを確認した上で、NumPyベースのスクラッチ実装でその挙動を確認するという流れを踏んでおり、初学者には非常に参考になるのではないでしょうか。以下は本書pp.315-316のLassoのコードです。これを読んで「おー、Lassoだー」と思ったのは僕だけじゃないはず(笑)。

import numpy as np

def soft_thresholding (x, y):
    return np.sign(x) * max(abs(x) - y, 0)

class Lasso:
    def __init__(self, lambda_, tol = 0.0001, max_iter = 1000):
        self.lambda_ = lambda_
        self.tol = tol
        self.max_iter = max_iter
        self.w_ = None
        
    def fit(self, X, t):
        n, d = X.shape
        self.w_ = np.zeros(d + 1)
        avgl1 = 0.
        for _ in range(self.max_iter):
            avgl1_prev = avgl1
            self._update(n, d, X, t)
            avgl1 = np.abs(self.w_).sum() / self.w_.shape[0]
            if abs(avgl1 - avgl1_prev) <= self.tol:
                break
    
    def _update(self, n, d, X, t):
        self.w_[0] = (t - np.dot(X, self.w_[1:])).sum() / n
        w0vec = np.ones(n) * self.w_[0]
        for k in range(d):
            ww = self.w_[1:]
            ww[k] = 0
            q = np.dot(t - w0vec - np.dot(X, ww), X[:, k])
            r = np.dot(X[:, k], X[:, k])
            self.w_[k + 1] = soft_thresholding(q / r, self.lambda_)
            
    def predict(self, X):
        if X.ndim == 1:
            X = X.reshape(X.shape[0], 1)
        Xtil = np.c_[np.ones(X.shape[0]), X]
        return np.dot(Xtil, self.w_)


ちなみに同様の流れをロジスティック回帰とSVMも踏んでおり、特にロジスティック回帰の勾配を手計算で出すのを諦めた数学弱者の僕には非常に参考になりました(泣)。


注目ポイント


昨今の数式だけが並んでいる本やライブラリの適用コードだけが並んでいる本に比べると格段にユニークな本書ゆえ、比較的どこを読んでも注目に値すると思いますが、時間がない方のために特に注目すべきポイントを3点挙げておきます。

最適化のところは一通り手を動かしてやるべし

上記のように、機械学習で必要な最適化計画の大部分がカバーされています。ただし線形計画と二次計画はそれぞれlinprogとcvxoptを使ってサラリと流されているのでそこまでゴツくはないです。


f:id:TJO:20181030153347p:plain
本当に大事なのは勾配降下法。色々な教科書に出てくるあの「誤差空間の等高線上をパラメータ点がジグザグに動いて最適解に向かう」図を、適当な多項関数の最適化問題のNumPy + Matplotlibによるスクラッチ実装によって描いているので、やったことのない方は試してみると良いと思います。実際に更新幅を大きくし過ぎて暴れる様子も紹介されていて、学習係数次第で安定しなくなる問題の再現にもなっています。


ニュートン法のところはスクラッチ実装の紹介がありますが、ラグランジュの未定乗数法は特に実装の記述がありません。ただし、この後に述べるSVMの実装においてラグランジュの未定乗数法は最重要になってきますので、熟読しておく必要があります。


なお「手を動かす」というのは、僕の場合は「写経」を意味しています。賛否両論あるのは承知していますが、プログラミングを伴う技術の習得にはコードを読みながら丸写ししてその感覚を覚える、つまり「写経」が効果があると思っています。上のLassoのコードも実は自分の手で写経したものです。

SVMのSMO実装は泣きながらでもゼロからやる価値あり

SVMは段々実務では使われなくなってきた手法の一つですが、その着想のシンプルさ・着想から最適化問題に展開するまでの美しさ・最適化問題解法の巧妙さ・理論解析の確からしさから、機械学習を学ぶ人にとっては「一度は必ずスクラッチから実装すべき」手法だと僕個人は考えています。


既にやったことのある方ならご存知かと思いますが、SVMは「マージン最大化」「サポートベクターのみ利用」というシンプルなアイデアを単純な数式に起こし、そこからラグランジュの未定乗数法を使って最終的な最適化計画に落とし込むところが醍醐味です。本書05章の当該パートでも、その流れをきちんと踏襲し、NumPyベースのシンプルなスクラッチ実装にまとめてあります。


そして実は明示されていないのですが、この本書のSVMパートではPlattのSMO(Sequential Minimal Optimization:逐次最小最適化)アルゴリズムのスクラッチ実装がさりげなく盛り込まれており、SMO-SVMOSS実装として名高いLIBSVMとの違いについてもコメントがあります*3。ここも僕個人の中では非常に高得点のポイントです。


何故本書のこちらのパートをここまでイチオシするかというと、これをちゃんと説明した邦書が事実上他にないからです。SVMというと以前はChristinini本の邦訳版が定番だったのですが、残念なことにSMOについては字面での説明と分かりにくい擬似コードによる解説しか書いておらず*4、初学者はweb上のそれっぽい資料を探して四苦八苦するというのが定番コースでした。ようやく良い邦書が出てくれたなぁというのが偽らざる感想です。

NNは含まれておりませんが、有用な理由はすぐ分かります

ところで画竜点睛を欠くと言うと失礼かもしれませんが、本書には今をときめく(?)ニューラルネットワーク(NN)の実装の説明が含まれていません。その理由として、上記の「本書は何を含まないか」の節にこうあります。

ディープラーニングは近年の機械学習研究ではとてもホットな分野ですが、本書ではあえて触れていません。なぜなら、本書に取り挙げられているアルゴリズムと比べると仕組みが複雑ではなく、その分野で他によい書籍が出ているからです。しかし、本書を理解することがディープラーニングのしくみを理解する上でも十分役に立つと思います。
原文ママ

個人的には、VC次元の辺りの話題を泣きながら読み解くのとか再生核ヒルベルト空間周りのゴツい数学の沼にハマるのに比べたら*5、確かにDeep Learningの仕組みは格段に易しいと思います*6。また、実際にNNを理解したいと思ったとしても、とりあえず本書で紹介されているロジスティック回帰の基礎知識があれば何とかなるはずです。Ridge / Lassoと言ったNNでも使われる概念についても解説があり、前提知識は全て本書で手に入ると言っても良いでしょう。


また、Deep Learningの場合多層化した場合の勾配計算の面倒さなどもあり、無理にスクラッチから実装する練習をするよりはTensorFlowやPyTorchのような「数式の感覚を残しつつモデルとしての新規性を追究するためには便利な」フレームワークを使う方が効率的だと思いますので、この判断は妥当かなと。


感想など


ということで、僕の本書に対する評価はタイトルにも書いた通りで「ゼロからガチで機械学習を生業にしたい人が『いの一番』に読むべき一冊」です。


機械学習を生業にするということは、ある程度の実装ができて、ある程度理論的背景も理解していて、必要ならある程度は論文や学術書も読めるというスキルが求められるということでもあります。本書には、そのスキルの基礎となるPythonによるスクラッチ実装・アルゴリズムの理論的背景・それらを理解するための数学や最適化計画の知識などなどがコンパクトにまとめられており、ベースラインとしては申し分ないです。


あえて言えば、これ以上のレベルを目指すならばもっときちんとした学術書が沢山あるわけで、入り口としてはこれで十分と言うか十分過ぎるのではないでしょうか。こういう本が現れるのを待っていました!というのが個人的な感想です。なお、Pythonの基礎についての解説はありますがプログラミングそのものの基礎の解説があるわけではないので、プログラミング自体が初心者という人にはこれでもつらいかもしれません(そういう人はいきなり機械学習エンジニアを目指したりはしないから問題ないかもですが)。


ということで、はむかずさんご自身もコメントされていましたが、レベル感としては大学の学部3・4年生ぐらいで、教養課程レベルの数学までは一通りやったが復習が必要、ぐらいの感じの読者層がターゲットになると思います。これはちょうど理工系の出自で社会人になってからエンジニアとして働いているような人たちにもよくマッチするのではないでしょうか。


本書である程度「モデルの定義を知る→数式を展開してパラメータの更新式など実装に必要な形に変える→プログラミングで実装する」の流れが身に付いたところで、いわゆるカステラ本やPRMLのような有名な本にチャレンジすれば良いわけで、その意味でも本書は非常に良い入門編になると思いました。


・・・最後に。こんなヌルい本で満足されるようなはむかずさんではないと思いますので、きっとこの本を遥かに超えるような壮大な機械学習ガチ本が今後出てくるのではないかと期待しております(笑)。と、勝手な期待感を表明してお開きにしたいと思います。

*1:恥を忍んで書くと、リストと辞書とタプルの違いも使い分けも分かっていなくて毎回ググったりStackOverFlowを見てる

*2:旧々課程だろうか

*3:LIBSVMではカーネル計算値が不変の時は更新せず使い回すことで計算を軽くしている

*4:そのくせVC理論の解説は死ぬほど丁寧

*5:前にHSICの論文読んだ時は轟沈した

*6:裏を返せばそこまで理論解析が進んでいないとも言える