プロクラシスト

今日の寄り道 明日の近道

2017年の振り返りと2018年のテーマ

こんにちは、ほけきよです。

ちょっとアドベントカレンダーで燃え尽きていた&プライベートでバタバタしていたもので、更新が滞っていました。 新年一発目ということで、去年の振り返りと今年の目標的なものを。

2017年の振り返り

年が明けました。皆様今年もよろしくお願いします。 昨年は「放」をテーマにしていました。 自分自身を変え、新たな挑戦をどんどんして行くという目標は、ある程度達成できたと思っています。

ブログでいえば、『技術×情報発信』ということで、プラグイン作ったり、自動記事作ったり、アドベントカレンダーしたり、色々しました。 この辺ふりかえって見ると、なんか楽しそうだな自分、、、という気になります。笑

どれも楽しそうなので、覗いてみてください!

また、オフ会に結構参加しました。こうやってブログを通じてたくさんの人と価値観に触れられたのは、自分の今後を考えるいい機会となりました。

去年1年の教訓は

  • 特に必死で頑張っているときに出来た繋がりは、良縁なことが多い
  • 運がいいというのは、行き当たりばったりではなく、チャンスを掴める準備をすること
  • 自分の過去の意思決定を振り返ると、結構自分の価値基準や行動理念が見えてくる

特に最後。結構やりたいことなんか無くて、適当に生きてると思ってたんですけど、案外一貫した基準があるんですよねー。私の価値基準は2つ

  • その選択が、振り返ったときに失敗しても「面白い」と思えるか
  • 自分が取りうる最大の難易度の場所か

はっきりとした目標がない人、結構いると思いますが、振り返って見ると案外知らない自分がわかるかもですよ。

2018年のテーマ:『届』

今年は去年とまた環境がガラリと変わりました。

今年のテーマはです。

  • 言葉を届ける : 英会話・マネジメント・プレゼン
  • 文章を届ける : 記事、執筆
  • サービスを届ける : webアプリケーション

とかかな。ウェブアプリとか作って一発当てたい気もする。。。笑 *1

人に言葉、文章、サービスを「届ける」ことを意識して仕事も趣味もブログも頑張ります。

皆さんに面白いと思ってもらえるコンテンツを届けられるよう、またいろいろとネタを仕込んでいきますので、本年もプロクラシストをよろしくお願いします!

*1:もちろん、技術メモについても引き続き書いていく予定です!

知識0だった僕がデータ分析をこれまでどう学び、これからどう使うのか

f:id:imslotter:20171225203840p:plain

長かったデータ分析ガチ勉強カレンダーも最終日*1

自分のこれまで歩いてきた道を軽く振り返ったあと、自分が思う機械学習/データ分析のあり方について書き連ねたいと思う。あくまで一つの価値観として楽しんでもらえればと思います。

データ分析って何?状態から

データ分析業務を行うにあたって、まずはじめの難関は、言葉の壁だった。ほぼコンピュータと無縁の世界で生きてきた私は、「Linuxって何?Vimって何?」っていうのを毎日繰り返していた。

データ分析を実務で扱うには、アルゴリズムを知っているだけじゃどうしようもない。 列挙すると下記のようなものが必要になってくる*2

  • アルゴリズムを動かすプログラム、用意する環境(Linux, VM, サーバーなど)
  • サーバーでコードを動かすための知識、ネットワークの知識
  • 計算量の見積もり、高速に動かすための並列化技術
  • データのin/outを扱うDBやqueueのシステム
  • プログラムの死活を監視するシステム
  • 分析アルゴリズムの理解
  • 複数の処理に対応するトランザクションの知識
  • サービスとして提供するアプリケーションの知識
  • 複数人で開発を進めていくプロジェクト管理技術

などなど。。。今だからこそ、そこそこ俯瞰的に見られるようになってきたが、はじめはまるで言葉が分からなかった。

じゃあ、どうやって知識を獲得していったのか。 自分が意識的にやってたことを振り返ってみる。

やったこと

わからないところを聞くレベルに達するまで

はじめは、どこが分からないかすら分からない状態。質問しろといわれても質問できない。だから、下記のことを行っていた。

  • ひたすら単語をメモ帳にメモしまくって、ググる。飲み会の間も分からない単語はメモしてた。調べるたびに分からない単語が増えるけど、耐えるしかない。辛抱強くメモ
  • その道のもっとも簡単な本を専門家に聞くサルでもわかるようなやつ。
  • 染み付くまで訓練を重ねる。一朝一夕でコマンドは打てるようにならない。Linuxコマンドなどは、lsさえ知らなかったので、無意識に基本コマンドが打てるようになるまで体に叩き込んだ。

そうすれば、ぼやっと聞きたいことくらいはわかるようになってきた。

わからないところが分かるようになると

ある程度のことが分かるようになると、このあたりから少しずつ楽しくなってくる。こういうときには、以下のことを意識していた。

  • 人に聞きまくる。わからないことだけじゃなくて、わかったことが正しいかの確認も込めて聞く。
  • 詳しい人が実際にコードを書いたり、デバッグしている過程をよく観察する。結果としての物よりも、過程に知見が詰まっていることが多い。ショートカットの使い方だとか、バグ取りにつかっているコマンドとか。とにかく、観察して、盗む
  • 知っている範囲内の言葉で説明ができないか試みる。「これはつまり~~ってことだな。」と、自分の言葉で納得するまで考える。
  • 複雑な要素は根気強くほどく。世の中の大半が実は難しくなくて、簡単な要素がたくさん組み合わさっていて難しく見えているだけ。
  • 訓練を重ねる。自分がやっていることは大したことではないと思えるレベルまで、理解と実装を重ねる。

とにかく、自分の言葉で説明できるっていうのに重きを置いていたように思う。あとはやっぱり、ひたすら手と頭を動かした。

アルゴリズムをどう理解したか。

物理出身ということもあって、数式の理解には少し自信があった。なのでアルゴリズムの理解はいろいろな技術領域の中でも自分の強みとしたかった。

  • 大事な理論は数式レベルで理解する。基礎的な理解は低レイヤーでの思わぬつながりを産み、未知の状況に対する応用のロバスト性**が高いように感じる。ここの式をこういじれば...みたいなイメージができるようになると強い。
  • 実装はデータのinput/outputを重要視する。特にディープラーニングにおいては、ここはベクトルで入っているのか?それともスカラーなのか?はたまたテンソルかもしれない...それをうやむやにすると魔法の箱化してしまう。あくまで演算にすぎないという意識を持つ。
  • アルゴリズム既存のものとの差分を意識する。何が変わったのか(精度向上、次元拡張、スピードアップ、対象拡大)、どうやって変えたのか(システムレベル、アルゴリズムレベル)などを意識して読む。

この中でも一番意識したいのは、魔法の箱じゃなくて、計算を行っているに過ぎないということ。だから、どういう計算過程なのかを強く意識した。そうすることで、何が出来て何が出来ないかがぼんやりと分かる。

自分が思う、これからのデータ分析

今まで分析業務に従事したり、一ヶ月間ガチで勉強してきたデータ分析を実務で使うものとしての視点で書く。

良いデータが集まるところに価値が生まれる

データ分析は料理みたいなものだと思う

  • データは食材、前処理は調味料、アルゴリズムは調理器具。
  • アルゴリズムはすぐに汎用化される。arXivにあげられた論文が1週間後にはgithub上で誰かが上げられている状況。
  • 調理器具の使い方をいち早く理解すること、元となるおいしい食材を集めるところが最重要課題。

つまり、データが集まるプラットフォームを作れるかどうかが今後のデータビジネスのカギとなってくると思う。メルカリとかお見合いアプリとか理想的だと思っている。人の心理をデータによって数値化できる基盤が整っている。特にスタートアップなどは、真似できないようなデータ収集プラットフォームをどうつくるか、ここに大きな労力を割くべきではないのかと思っている。

実務のデータ分析は高い精度よりも低コストと高い説明能力

現場において、ディープラーニングを果たして使うのか?というと、難しいのではと想像する。

  • 失敗原因がわからない怖さ
  • コストと納期の関係

が原因。実際データサイエンティストの泥臭いフィーチャーエンジニアリングの過程は、それ自体に価値があったりする。だからこそ現場の最前線でロジスティック回帰*3が用いられる。一方で分からないけどいい結果というのは、なによりもクライアントの不安を増長する恐れもある。

でも、だからといって、ディープラーニングが使われないのは、あまりにもったいなさ過ぎる。 あれだけの精度をわけも分からなく産めるものを、今後実務で使うためには、結果を説明出来る技術が必要だと思う。 機械学習の意味付けの研究はこれから実務的にも重要で、発展していくのではないかと思う。*4

データ分析はトップダウン

物理をやっていたものからすると、ボトムアップ的アプローチも利用していきたいものである。

  • データから推論できること、出来ないことをはっきりさせる
  • システムの隠れた状態などをうまくモデリング出来たら、強い
  • トップダウン的に分析し、ボトムアップ的に仮説を立てて実験をすることで、より本質的な理解となる

と思っている。 大量のデータがあって、それを魔法の箱に突っ込めがなんかしらの良い結果が出る時代にはなってきている。 しかし、データに頼りすぎず、どこが本質的に重要かをミクロな観点から見極めながら、分析できる能力は身につけておきたい。

あくまで人間の補助、人間とのシナジーを考える

最後に、私はあまり人間が得意なことを機械でやらせようとは思わない。 そういうことに労力を割くよりは、機械が得意な領域を磨いていくほうが、生産性が高いと思っている。

意思決定、連想、ばかみたいな発想、人との関わり。こういうのは人間が得意とするところだろう。 データ分析はこれを最大化する方向に利用したい。

  • 自動化できるところ、自動化出来ないトコロを明確に
  • 事務作業は自動化で良い。サービス的な要素は消えない。
  • 職人が自発的にひねり出したアイディアは、独創的で面白い

まとめ

とりとめもなく終わりますが、これが数年間データ分析技術を学んで、また現場で実践して考えているところです。 自分が数年後道を見失ったときに読み返せるようにと、書いてみました。

技術の進歩は著しく、データ分析の技術はこれからますます楽しみになっていきます。 それについていけるようこれからも精進していきたいと思っています。

これまでのアドベントカレンダーをマージして、ゼロからデータ分析を学べるようなサイトマップを作りました。

データ分析に興味を持った方は、コチラで勉強してみてはいかがでしょうか。ではでは。

*1:ながかった、、、ガチだった。。。

*2:レベル感のばらつきはお許し下さい

*3:回帰の最も簡単な手法

*4:発展していって欲しい。

Pythonでゼロから機械学習/データ分析を学ぶためのサイトマップ

f:id:imslotter:20171224195620p:plain

データ分析ガチ勉強アドベントカレンダー 24日目。

当サイトでも、Pythonを使ったデータ分析や機械学習について、勉強しながらそれをアウトプットとして出すと言うかたちで、何個も記事を書いてきました。 記事数で言えば50とかそのくらいあるような気がします。

カレンダーも完成しつつあるので、個々では当サイトの総まとめとして、機械学習やデータ分析に触れたいという人がゼロから始めて触れられるように、記事をまとめていきたいと思います。 何か面白いことを勉強したい学生、就職までの勉強に、急に機械学習を使わなければならない社会人方々は、読んで見てください。

0. 環境構築

まずは何と言っても環境構築。Pythonは他の言語に比べて構築が簡単な方だとは思いますが、ここでかなり苦戦する人が多いです。

0.1. Pythonの導入 (Anaconda)

当サイトではWindowsでも環境構築が簡単に出来るように記事を書いています *1*2。全くゼロから始める方はコチラをどうぞ。

0.2. エディタ (Pycharm/VSCode)

いわゆる高機能メモ帳。実際にコードを書き始めると、自分好みのエディタが欲しくなるものです。当サイトでは、PythonならPycharmを、またオールラウンドに使えるエディタとしてVSCodeを紹介しています。

0.3. バージョン管理 (Git)

(※あれば便利ですが無くてもコードは書けるので、面倒な方は飛ばしてもらってOKです。)

賢くコードを管理する仕組みです。ちょっと試したいことがあるだけなのに、コードを一部変更して、そのご間違って保存したりすると悲しいですよね。 そんなことがないように、しっかりと自分の作ったコードのバージョン管理しましょう。Gitの基本的な使い方はこちら

また、当サイトのGithubアカウントはコチラです。

github.com

作ったおもちゃや勉強の様子を保管しているので、よろしければぜひ。

1. Pythonの使い方(基本ライブラリ)

環境が作れたら、いよいよコードを書いていきます。 Pythonライブラリが充実しているので、それらを使いながらコードを書いていきます。 機械学習専用のライブラリもあるのですが、まず必ず入れておくべきライブラリであるnumpy, matplotlib, pandas, jupyterの使い方を紹介しています。

1.1. 数値計算 : numpy

これを用いることで面倒な基本計算(平均計算、行列計算、etc...)を一行で書けるようになります。スゴい!

1.2. 表計算/統計処理 : pandas

Pythonで表を読み込んだり、計算したり、また表として書き出したりするにはpandasがとても便利。 少し使い方にクセがありますが、かなり強力なので、是非身につけておきたいものです。

1.3. グラフ描画 : matplotlib

データ分析をしていると、結果をグラフで可視化したいということが多くあります。 そういうときにはmatplotlib. これ一つで色々なグラフを書くことが出来ます。

また、よりオシャレなグラフを作りたい方向けに、KibanaとElasticsearchで可視化する方法も書いています。この場合は、Python以外の知識も必要です。

1.4. インタラクティブなコード実行 jupyter

jupyterの良い点はちょっとずつコードを書きながら試すことが出来るというところです。 コード書いて、コンパイルして、実行...を短いサイクルで回すイメージ。 同時にメモも残せるので、研究や実験に最適です。

2. データ分析事始め

Pythonがある程度使えるようになったら、データ分析/機械学習のお勉強です。

その際に参考になる本やサイト、ライブラリなどをざっと洗い出した記事がありますので、リファレンス代わりにしていただければなと思います。

3. データ分析の流れ

ここから、実際に機械学習を交えながら、データ分析を進めます。

実務では、下図のようにデータ分析を進めていくのですが、その流れを、sklearnを使いながら説明していきます。

3.1 データを集める

まず、データを集めないといけません。 モデル/アルゴリズムの性能を調べたいときに、sklearnはいろいろなデータを予め用意してくれています。

また、情報を集めたいときはスクレイピング/クローリングなどの技術が必要です。 beautifulsoupを使ったり、公開APIを使うなどして、データを集めましょう。

3.2 データ分析の流れ

4. 時系列データ

時間依存のあるデータはそうでないデータに対して、少し扱いが面倒だったりします。 けれど、扱うことも多いので、まとめています。時系列に関する基礎的なまとめから、ディープラーニングに至るまで、まとまってますので、読んでみてください。

6. 深層学習

最近界隈を賑わせているディープラーニングに触れない訳にはいかないでしょう。 当サイトでは、KerasとPyTorchの2つに絞って、その使い方について説明しています。

6.1 自作

ライブラリを使う前に、ディープラーニングのココロを知るため、まずはPythonで自作しました。

1から書くと、どういう思想でネットワークが組まれているのかとか、学習の仕組みなどが分かるようになります。

6.2. Keras

とっつきやすさでは随一のKeras。簡単なデータ分析に使うにはこれで十分だと思います。ディープラーニングなんて知らなくても使えるそのお手軽さはスゴい。

6.3 PyTorch

研究者界隈で盛り上がりを見せているPyTorch。すこしコード記述量は増えますが、Define by Runの設計しそうなので、かなり柔軟な設計が可能。 更に、Sklearnとの連携で、より使いやすくなります。各ライブラリとの比較も書いていますので、是非ご一読を。

7. 教師なし学習(異常検知/次元削減など)

上記までが、正解データのラベルに従って学習を進めていく教師あり学習です。 実際の現場では正解データを使わないタスクも多くあります。

異常検知*3や、座標を変換する空間変換など、正解データという概念が無いようなものに対してもまとめましたので、必要な方はどうぞ。

8. 強化学習

強化学習は、教師あり学習とも教師なし学習とも違います。

実験して、その結果が帰ってきて、学習してまた実験する。

そのようなまるで人間のような学習の方法です。定式化が出来ない複雑な状況などに応用が期待されており、 実際にマーケティングの広告の配置などに使われたりしています。

9. 実際にデータ分析をしている記事

当サイトでは、実際に分析を行った記事をいくつか載せています。

そこまで高度なことはしていませんが、記事からデータ分析から分かること、データ分析のちからを感じてもらえればと思います。

まとめ

いかがでしたか。かなりいろいろなことをやっていたので、一つにまとめました

今回のアドベントカレンダーは、この記事の順番を意識して進めてきました。その流れで勉強していくと、割とスムーズに進められるのではないでしょうか。

データ分析業界が盛り上がっているので、これを機に一念発起して勉強したい方なども、ぜひご参考ください。ではでは!

*1:MacLinuxは環境構築方法をWebで漁れば簡単に出て来るし、またハマりどころもそんなに多くないので、ぐぐってみてください。

*2:機械学習ライブラリによっては、どうしてもwindowsで出来ないものもあります。

*3:厳密に言えば教師のある異常検知もあります。

【Day-23】機械学習で使う"距離"や"空間"をまとめてみた

f:id:imslotter:20171223191319p:plain

データ分析ガチ勉強アドベントカレンダー 23日目。

ここまでデータをどういう風に処理したり、どういうタスクをこなしていくかについて勉強してきたが、 一度基礎的な事項に戻ってみたいと思う。基礎だから簡単というわけではない。基礎だからこそ難しく、また本質的な内容

データ分析で使われている手法などをまとめて集約して、簡単な説明を付け加えていく。 しかし、このあたりの数学*1は苦手なので、なるべく直感的に自分のイメージを書いていく。

われわれが生きている空間や、距離は"正しい"のか

"正しい"というのは、データ分析に使うのに適切なのか。という意味で書いている。 たとえば、新宿まで1.2km"というような表示を見る。地球という座標空間の中で、新宿までの距離が1.2kmというわけだ。われわれは日常生活の中で、それに対して何の疑問も抱かない。それは、日常の距離はユークリッド空間が 大前提にあるからである。

ユークリッド空間/ユークリッド距離

簡単に言うと、高校まで*2われわれが生きてきた空間である。たとえば、(0,0)から(3,4)までの距離は?ときかれると、大体の人は三平方の定理だ!となって、5という数字が思い浮かぶ。

けれど、下図を見てほしい。空間が必ずしもx,yの直交座標であらわされるわけではない。厳密に言うと、地球は丸いので、曲がった空間での距離を求めるべきなのである。また、距離の測り方だってバリエーションがあっていいはずだ。 ユークリッド距離といえば、斜辺の距離と思いがちだが、たとえば街などは、斜めに横切れないことだってあるので、かくかくと進んでいくことを前提に距離を測ったりする*3。このように状況によって空間や測りかたは変わるべきなのである。

データ分析とは、いろいろなジャンル/いろいろなタスクがあるわけで、それが本当にユークリッド空間/ユークリッド距離で考えるべき話題なのかどうかというのは、考えておくべき問題なのである。 非常に根本的な話であるだけに、きれいな空間や距離で解けたものは美しく、汎用性がある。

点の距離

Name 名前 備考(あれば)
Euclid ユークリッド \sqrt{ \sum_{i}^{n} {\left(x_{1i}-x_{2i}\right)^{2}} } 一般的な距離
Manhattan マンハッタン \sum_{i}^{n} |x_{1i}-x_{2i}| 外れ値の影響を受けにくい
Minkowski ミンコフスキー \left(\sum_{i}^{n}{|x_{1i}-x_{2i}|^{p}}\right)^{1/p} Euclid, Manhattan, Chebyshevを一般化したもの
Chebyshev チェビシフ \max_{i}|x_{1i}-x_{2i}| 成分の差がもっとも大きい次元だけを抽出している
Mahalanobis マハラノビス \sqrt{\mathbf{ \left( x_{1}-\bar{x} \right)^{T} S^{-1}  \left(x_{2}-\bar{x}\right)}} 正規分布の共分散の形にあわせて算出する距離。よく使う
Hellinger ヘリンジャー \sqrt{ \sum_{i}^{n} {\left(\sqrt{x_{1i}}-\sqrt{x_{2i}}\right)^{2}} } 外れ地の影響を受けにくいの
Hamming ハミング dim(\mathbf{x})-\sum_{i}^{n}\delta\left( x_{1i},x_{2i} \right) ベクトルの要素中で一致していない素数。カテゴリ変数に利用

分布の距離

分布の距離というときにもいろいろな距離/距離尺度がある。厳密には距離の公理(非負性・同じ点なら0・対称性・三角不等式が成り立つ)*4を満たしていないものもあり、それらは距離とはいえないが、差をあらわすものとしてよく使われるものなので、距離尺度として用いられる。

名前 備考(あれば)
Histogram Intersection D_{HI}(p,q) = \sum_i \mathrm{min}(p,q) (距離じゃなくて類似度)ヒストグラムのような離散値に使う。2つの分布の共通領域。
KL divergence D_{KL}(p||q) = \int_{-\infty}^{\infty}p(x)\log{\frac{p(x)}{q(x)}}dx 相対エントロピーの概念に基づいて、2分布間の距離を算出(非対称)
JS divergence m(x) = \frac{p(x)+q(x)}{2}
 D_{JS}(p,q) = \frac{D_{KL}(p||m)+D_{KL}(p||m)}{2}
KLを改良して**p,qに対称性をもたせたもの
L1 norm D_{L1}(p,q) = \int_{\infty}^{\infty}|p-q|dx 連続的な分布における誤差の絶対値の和
L2 norm D_{L1}(p,q) = \int_{\infty}^{\infty}(p-q)^2dx 連続的な分布における二乗誤差の和
Wasserstein distance W_p(\mu, \nu)=\left( \inf_{\gamma\in \Gamma \left(\mu,\nu\right)} \int_{M\times M} d(x,y)^{p}d\gamma(x,y) \right)^{1/p} 分布を荷物量とみなし、荷物を他の分布に移しかえるときにかかるコスト

それぞれの違いの実装をgithubにあげておく(day23)

github.com

wasserstein計量

物理では昔からある輸送距離だが、WGANの登場により、分布の距離表現として注目される様になってきた。

  • 輸送コスト最小化問題を解いた場合の最小の輸送コスト
  • 最小コストをいちいち計算するため、計算時間はめちゃくちゃかかるが、その分物理的にも本質的な距離になっている。
  • 実装にはPOTというライブラリを使う(Optimal Transportation)github
pip install Cython
pip install POT

カーネル(再生核ヒルベルト空間)

非線形の問題を扱うものとしては、かなり伝統的な手法。数式を抜いて文字だけで説明するとこんな感じ

  • 線形の特徴量x_1,x_2を組み合わせて、非線形な特徴量を作りたい。
  • こんな特徴量の組み合わせは無数にある(例:x_{1}^2, x_1x_2,...,x_{1}^{4}x_{2}^{3},...)
    • 多いほど非線形性は高まるが、組み合わせが膨大
  • カーネル関数を使うと、無限次元の特徴ベクトルを用いているのと等価*6
  • でも、計算量は無限じゃなくて、データ数に依存する*7
  • 一般的化線形モデルは、カーネル関数によって書き換えることが出来るので、データ数に依存する形で非線形を学習できる

というわけで、データ数分の計算量で、無限次元空間の学習が出来るって感じ。 注意としては、入力するデータ数が多くなると、計算が厳しくなる(データ数×データ数の逆行列を解く)

以下、わかりやすい&数式もきちんと載っている説明。

Topological Data Analysis(TDA)

  • データを点群として扱うのでなく、データの持つ幾何学的な情報に基づいて識別や分類を行う
  • Persistent Homologyという分野
  • 点群の幾何学的な情報を抽出した二次元座標上の点集合(Persistence diagram)を得る
f:id:imslotter:20171223123208p:plain
persistent homologyの生成元の発生と消滅のイメージ
f:id:imslotter:20171223123233p:plain
上図のように円を広げていき、発生の時刻と消滅の時刻を2次元プロットにしたのがpersistent diagram

以下、わかりやすい説明と論文

次元削減/Embedding

特徴量が非常に多い場合、その特徴量全てがデータとして重要かどうかは微妙。 特徴量を重要な部分だけ抽出したり(Dimension Reduction) 相性の良い空間へ今ある雑多なデータを埋め込んだり(Embedding)することが多い。以下は主要/有望な手法と簡単な説明、文献を列挙する(随時更新)

PCA(principal component analysis)

典型的な次元削減の方法。次元を落としても情報をなるべく落とさないような軸を計算で求める。

ちなみに、AutoEncoderはPCAの純粋な拡張になりうることが数学的に証明できる *8

t-SNE(t-Distributed Stochastic Neighbor Embedding)

PCAが点だけを見るのに対し、t-SNEは分布単位で見る。圧縮前と圧縮後の確率分布のKLダイバージェンスを最小化することにより求める。

f:id:imslotter:20171223125426p:plain
[t-SNEの仕組み(ALBERT)](https://blog.albert2005.co.jp/2015/12/02/tsne/)より引用

Word2Vec

CBoW(Continuous Bag-of-Words)を用いて、文脈中の単語から対象単語が現れる条件付き確率を最大化するように学習を進めていく。

そうすることで、単語を座標空間上に埋め込むことができる。 これによって、言葉の足し算引き算ができる可能性が拓けた。

f:id:imslotter:20171223191909p:plain
男女間の関係が近い値のベクトルで表現される(参考記事より画像を引用)

最近では、word2vecだけではなくdoc2vecなどもあり、ますます応用可能性が増している。

Poincare Embeddings

Word2Vecの空間改良版といったところ。双曲空間にデータを埋め込むことで、

  • 階層構造/木構造を自然に空間の中に埋め込める。そういう構造のデータに良い。
  • リッチな空間表現のため、元の次元を減らすことも出来る
  • 空間に少しの補正を加えるだけで使えるので、拡張が容易

な点などから、注目されている。双曲空間というのは、下図真中のような、曲がった空間。外に行くほど密になっている(=空間が広い)

f:id:imslotter:20171223192945p:plain
[Fig]双曲空間。場所によって距離尺度が変わる

実際、単語は階層構造をなすことが多く、下図の結果のように、空間上に効率よく単語を配置することができる。

f:id:imslotter:20171223193551p:plain
[Fig] 論文の実験結果、ハブと周辺語の階層関係が表せている

Wasserstein Embeddings

この前読んだ論文が、Wasserstein空間が作る距離上の空間に埋め込むというものだった。

LEARNING WASSERSTEIN EMBEDDINGS

まとめると

  • wassersteinは計量として優秀。分布の差をうまく表す。
  • でも計算重い。だいたい次元の3乗の計算量
  • うまく近似する関数をディープラーニングで作ろう
  • 近似にはSiamese networkが有効そうだから学習して関数を作る
  • うまく近似できた。理論的裏づけが課題。

使ったのはSiamese network。距離学習によく使われる。

f:id:imslotter:20171223194521p:plain

2つのネットワーク\phi,\psiを用意して、パラメータの更新は

  • \phiで変換されたベクトル同士のの二乗誤差と、Wasserstein距離の差を近づけるように
  • \psi\phi(x)xの誤差(再構成誤差)を小さくするように

行う。今後、リッチな空間で、計算量がネックになる場合は、このようにニューラルネット空間の計量を近似するような手法が主流になるかもしれない*9

まとめ

今日は、かなり基礎的な内容になった。 個人的には、このレベルでの新しいアルゴリズムが今後出てくるのではないのかなと思っている。基礎的なところからの変更は、ドラスティックな変革を引き起こすことが多いので。今後も随時追っていきたいと思う。

いよいよあと2日。明日は、これで本サイトで書いてきたデータ分析/機械学習に関する内容を総まとめしたいと思う。必見です!ではでは。

*1:空間、計量、測度論...ムリ...

*2:極座標を除く

*3:マンハッタン距離

*4:長くなるので割愛。詳しくはこちら

*5:物理用語ではこういう。

*6:証明

*7:リプリゼンタ定理という

*8:深層学習 (機械学習プロフェッショナルシリーズ)などを参照

*9:と、個人的に注目している

【Day-22】データ分析技術で仮想通貨の暴騰・暴落を捉えられるか

f:id:imslotter:20171222012923p:plain

データ分析ガチ勉強アドベントカレンダー 22日目。

今日は、データ分析の練習も兼ねて、ちょっとした実験!! 対象は最近話題の仮想通貨。 乱高下の激しい通貨に対して、データ分析技術がどこまで通用するかと言うのを検証してみる。

使う技術 : Change Finder

change finder とは、データマイニングによる異常検知に出てくる、変化検知の技術。

ざっくりいうと、データをある時系列モデルで予測し、そこからの変化具合を評価する手法

全体の流れ

  1. 窓幅kで時系列モデル(ARモデル)を用意してSDARアルゴリズム(忘却型学習アルゴリズム)で学習する
  2. 上記の時系列モデルp_{t-1}に対して、時刻tでの異常値スコアを次のように算出
    • Score(x_{t})=-\ln{p_{t-1}(x_{t}})
    • もしくはヘリンジャースコア(参考)
  3. 幅Tのウィンドウを設けて、スコアの平均を計算する(smoothing)
  4. 平滑化された時系列データy_{t}に対して、再度ARモデルを用意してSDARアルゴリズムで学習する
    • 時刻t-kからt-1までの値で、それ以降の時系列を予測するモデルq_{t}が完成する
    • T'のウィンドウを設けて、スコアの平均を計算する(下式)
    • Score(x_{t})= \frac{1}{T'}\sum_{i=t-T'+1}^{t}\left(-\ln{q_{t-1}(y_{t}})\right)

ポイントは、2回学習しているところ。一回目のsmoothingで、ノイズに反応した外れ値の除去を行っている。 そのため、本質的な変動を捉えられるようになっている。

SDARで設定する忘却パラメータはかなり大事で、どのくらい過去の影響を重視するかに関わってくる。これについては後で見てみる。

ライブラリ

何気なく「changefinder python」で調べると、作っている素晴らしい人がいた。

changefinder - Argmax.jp

なので拝借。上記ページにも書かれているように、numpy, scipy, nose, statsmodelsは必要

pip install changefinder

で入る。二種類のアルゴリズムが用意されていて、通常のChangeFinderと、ARモデルをARIMAモデルに変更したChangeFinderARIMA *1

ChangeFinder

changefinder.ChangeFinder(r=0.5,order=1, smooth=7)

主要パラメータは下記の通り

  • r : 忘却パラメータ
  • smooth : 外れ値スコアをsmoothingするための区間
  • order : 時系列モデルの次数

実験に使うデータ

  • 12/13-12/21の仮想通貨の価格データ ((githubcoindata.csvに保管している))
  • 草コインも含めて868銘柄のデータを10分おきに保管している

データの集め方については以前記事にしたAPIを使っている。

コード

詳しくは、githubに上げている。 github.com

銘柄を指定し、changefinderにかけてグラフ表示するまで

コードをみる
%matplotlib inline
import changefinder
import pandas as pd 
from datetime import datetime as dt
import matplotlib.pyplot as plt
import numpy as np
import os


def str2datetime(tstr):
    if tstr.__class__.__name__ == "datetime":
        return tstr
    ts = tstr.split(".")[0]
    return dt.strptime(ts, "%Y-%m-%d %H:%M:%S")

def calc_cf(name, df, cf, threshold=30):
    values = df[name].values
    scores = np.array([cf.update(value) for value in values])
    print(scores)
    # 描画
    fig, ax1 = plt.subplots(figsize=(15,5))
    ax1.set_title(name)
    ax1.plot(df.index, scores,color="red")
    ax1.set_ylabel("anom_score")
    ax2 = ax1.twinx()  # 2つのプロットを関連付ける
    ax2.plot(df.index, df[name].values)
    ax2.set_ylabel("value")
    # しきい値越えリスト
    df["score"] = scores - threshold
    for i in range(1,len(df)):
        if df.iloc[i-1]["score"] < 0 and df.iloc[i]["score"] >= 0:
            print("time:{}".format(df.iloc[i]["timestamp"]))
            ax2.plot(df.index[i],df.iloc[i][name],"o")
    plt.show()
    return df

cf = changefinder.ChangeFinder(r=0.01, order=1, smooth=3)
df = pd.read_csv("coindata.csv").dropna()
df.index = df.timestamp.apply(str2datetime)
calc_cf(name="btc",df=df, cf=cf, threshold=15)

実行結果

r=0.01, order=1, smooth=3 で実行してみた。結果がコチラ

ふむ、一見よく捉えられているようにも見える。 でも、実際はそんなに簡単じゃない

パラメータによる違い。

r=0.01, 0.02, r=0.05で試してみる。結果は↓

r グラフ
0.01
0.02
0.05

忘却パラメータに関しては、与えるパラメータによってかなり変わってくる。 センシティブなので、ナゾの場所で変化検知する場合もあり、シビアな調整が必要そうだ。

また、r=0.01, order=1は固定で、smoothを3と18で比べてみた。

smoothing グラフ
3
18

やはり平滑化をしすぎると、反応は遅れるのだなという感じ。しかし、結構変化に敏感に反応はできているという印象を受けた。

注意したいのが、グラフの縦軸は全然値が変わっているところ。この辺のしきい値の調整もシビアそう。

結論

暴騰暴落を捉えられたか?

  • 正直微妙
  • ピークが捉えられたとして、少し反応が遅れると暴落に飲み込まれる
  • 後述するパラメータ調整がシビアすぎる印象

って感じ。正直、強いトレンドを見るならボリンジャーバンドでいいのでは?って思った。 そう思って、ボリンジャーバンドでもトレンド検知をしてみた。結果がこれ(赤が買いトレンド、青が売りトレンド)

若干ミスってはいるものの、トレンドを割と追従できてる気がする。真ん中の2点は要らないけど、超安定してたから仕方ない気もする。sigmaの絶対値でしきい値設ければ弾けそう。 これもパラメータ調整が難儀ではあるが、changefinderよりも直感的になので、やりやすそう。

モデルに関して

  • 特に忘却係数の調整がシビア
  • 基本的に反応は早めだが、smoothing次第で遅れる。ノイズとの兼ね合い
  • スコアもパラメータによって変わるので、相対的な判断が強いられる

あたりが難しそうだなと思ったところ。探索的に調べたが、なかなか直感にバシッと合うようなものは見つけられなかった。 パラメータの調整方法としては、Day15で書いたベイズ的最適化を使ってやってみるのも面白いと思うので、もうちょっと良いパラメータを見つけたいトコロ。

*1:ARIMAは計算が重い&非定常な時系列に対して時間窓を区切ってARIMAモデルを独立に作成しているので、モデルの妥当性は不明とのこと。

【Day-21】統計的異常検知/変化検知の基本をまとめる

f:id:imslotter:20171221005033p:plain

データ分析ガチ勉強アドベントカレンダー 21日目。

世の中のデータ分析のニーズは、何らかの異常を発見したいというところに多くある。 機械で言えば故障を検知する、マーケティングで言えば流行を発見する、株価で言えば相場変動を見つける...

普通と違うことを発見する技術、それが「異常検知/変化点検知」 今回は下記二点の本を参考にしながら、異常検知とは何か?変化点検知とは何か?をぎゅっとまとめる

データマイニングによる異常検知

データマイニングによる異常検知

異常検知と変化検知 (機械学習プロフェッショナルシリーズ)

異常検知と変化検知 (機械学習プロフェッショナルシリーズ)

異常検知と変化検知

データマイニングによる異常検知では、確率モデルの違いに重きをおいて異常検知を分類している。

機能 入力対象 確率モデル 検出対象 応用
外れ値検知 多次元ベクトル 独立モデル
(ガウス混合分布、ヒストグラム)
外れ値 不正検出
故障検知
変化点検出 多次元時系列 時系列モデル
ARモデル等
時系列の急激変化 攻撃検出
障害予兆検出
異常行動検出 セッション時系列 行動モデル
隠れマルコフモデル
異常行動パタン なりすまし検出
不審行動検出

異常検知と変化検知では、データの性質から異常検知を分類している。下図のように

  • (左上)ほかと比べて値が異なる(外れ値)
  • (右上)前後の時系列と比べて値が異なる(時系列的外れ値)
  • (左下)振る舞いが変化している(変化点)
  • (右下)外れ値と変化点が同時に起きている(異常部位検出)

f:id:imslotter:20171221040958p:plain

タスクによって、使うアルゴリズムも変わってくる。

『統計的』異常検知とは

統計的 = データが裏で、ある確率分布で動いていることを仮定

もうすこし噛み砕いて言うと、

  1. 自分が観測した情報から、"このシステムはこういう動き方をする"というのを統計的に推測する
  2. 統計的な推測から、どのくらいずれているかを統計的に算出する。

学生時代を思い出してみる。みんなの平均点が30点なのに、一人だけ100点だったらヤバい。なぜヤバいのか。それを論理的に説明するために、 平均点周りで点数がイイ感じに散らばっているという仮定を置く。イイ感じの分布の一つが正規分布で、ソレを元に、定量的にどのくらいヤバいのかを計算していく。

異常度の算出

さっきの例のヤバい定量的に表すことができれば、どの程度異常なのかがはっきりと分かる。 方法は、ラベル付きデータと、ラベルなしデータで違う。

ラベル付きデータ

ラベル付きデータの場合は、下図の様に正常・異常共に分布を描くことが出来るので、正常の確率分布と異常の確率分布の値の比から、新しく入力したデータを正常/異常を判定することが出来る。

これをネイマン・ピアソン決定則といい、次式で表す。

{\ln{\frac{p\left(x' | y=1,D\right)}{p\left(x' | y=0,D\right)}}}が所定のしきい値{\tau}を超えたら異常

この決定則は、正常な標本の精度を保ったまま、異常標本の精度を最大化するという条件付き最大化により数式で計算できる。 計算方法に関してはコチラの記事が詳しい。

ひとことで言うと、ラベルデータの異常判定は正常/異常の対数尤度比から判定できるということ

ラベルなしデータ

多くのデータは正常が圧倒的に多くて、異常が少ない/無い。この場合、異常の分布が出せないので、全体のデータから判定する。 ありふれた観測値より「珍しい」観測値を得たほうが得られる情報量が大きいので、それを数式で表すと、対数尤度の負の対数になる。

\mathbf{x'}に対する異常度a(\mathbf{x'})
a(\mathbf{x'}) = -\ln{p\left(\mathbf{x'}|D\right)} で表される。

統計分布で分ける異常検知

基本的な異常の計算は上記のように、確率分布を仮定し、対数尤度を求めて計算する。

問題は確率分布pをどうするか。いつもデータがきれいな正規分布とは限らない。 下記に、主要な分布と性質、計算方法をまとめる

分布 性質 計算方法
正規分布 単純な分布、大数の法則に従うようなデータには基本使えるので汎用性が高い ホテリングのT2(参考)
混合分布モデル 分布の山が2つ以上あるようなデータに適用できる。厳密計算が出来ない EM法による近似計算(参考)
経験分布 分布を仮定せず、データから分布を作る。複雑な形を作れる一方で、データに左右されすぎて非直感的な結果になることも。 k近傍法/LoF (参考)
ガウス過程 入力と出力が同時に観測できる場合ガウス過程により、関数を確率モデルで近似して異常検知に用いることが出来る ガウス過程回帰(参考)
ミーゼス・フィッシャー分布 方向データ(長さ1で方向にだけ情報がある)に対する分布、自然言語などに対して用いられる 積率法(参考)

時系列データの異常

時々刻々と変わるものに対しては、概ね下記のような手法で変化検知を行っていく

  • 一定の窓幅をとって、誤差を出す
  • それをスライドさせながら、誤差を足していく
  • ある閾値を累積の誤差が超えたところで異常

誤差に関して

  • 時系列モデルを作って予測してからの誤差 (参考)
  • 少し前の分布との誤差 (参考)

の二種類がある。

まとめ

他にも、密度比推定や、AutoEncoderを用いた異常検知など、まだ色々と異常検知手法はある。 今日のまとめとしては、基本的に

  • 分布をつくり
  • 分布の誤差を計算して
  • しきい値を超えたら異常

の流れ。分布の作り方、誤差の計算の仕方にバリエーションがあるというイメージ。 異常検知は何かとすることが多いタスクなので、どういうときに何を使うかまで、把握したいところ。

明日は、変化点検知を実際のデータでやってみたいなと思う。ではでは!

【Day-20】PyTorchを超絶使いやすくするsklearnラッパー『skorch』で快適ディープラーニング

f:id:imslotter:20171220124248p:plain

データ分析ガチ勉強アドベントカレンダー 20日目。

Skorchとは

f:id:imslotter:20171220123959p:plain
  • PyTorchのsklearnラッパー
  • sklearnのインターフェースでPyTorchを使える

自分が感じたメリット

  • sklearnが行うところとPyTorchが作るところがはっきりしていて、コードがすっきりまとまる
  • sklearnの関数(fit, predictなど)がそのまま使える
  • numpy配列をそのまま学習器に投げられるので、イメージしやすい
  • 前処理、GridSearch, 評価もsklearnのものを使えて、便利
  • インストール

    git clone https://github.com/dnouri/skorch.git
    cd skorch
    # create and activate a virtual environment
    pip install -r requirements.txt
    # install pytorch version for your system (see below)
    python setup.py install
    

    ※私の環境(ubuntu14.04, python3.6.1)では、sklearn0.18.1では実行時エラーが出たので、sklearn0.19.1にアップグレードしました。

    pip install -U sklearn

    使い方

    データ読み込みはsklearn

    データの作り方などはday-7の記事を参照

    コードをみる
    %matplotlib inline
    import matplotlib.pyplot as plt
    
    import torch
    from torch import nn
    import torch.nn.functional as F
    torch.manual_seed(0);
    
    import numpy as np
    from sklearn.datasets import make_classification
    import sklearn
    X, y = make_classification(1000, 20, n_informative=10, random_state=0)
    X = X.astype(np.float32)
    X.shape, y.shape,y.mean()
    

    学習ネットワークの構築はPyTorch

    day19の記事のように、ネットワークの設計をdefine by runでしていきましょう。

    コードをみる
    class ClassifierModule(nn.Module):
        def __init__(
                self,
                num_units=10,
                nonlin=F.relu,
                dropout=0.5,
        ):
            super(ClassifierModule, self).__init__()
            self.num_units = num_units
            self.nonlin = nonlin
            self.dropout = dropout
    
            self.dense0 = nn.Linear(20, num_units)
            self.nonlin = nonlin
            self.dropout = nn.Dropout(dropout)
            self.dense1 = nn.Linear(num_units, 10)
            self.output = nn.Linear(10, 2)
    
        def forward(self, X, **kwargs):
            X = self.nonlin(self.dense0(X))
            X = self.dropout(X)
            X = F.relu(self.dense1(X))
            X = F.softmax(self.output(X), dim=-1)
            return X
    

    skorchでwrap

    skorch.net 用意されているのは2種類

    関数 用途
    NeuralNetClassifier 分類器をsklearn風に
    NeuralNetRegressor 回帰をsklearn風に

    初期化の際に、学習の仕方を決める。パラメータは、PyTorchの関数を使用できる。

    • criterion : 損失関数の設定
    • optimizer : 最適化関数の設定
    • lr : 学習率の決定

    その他色々とパラメータはあるので、skorch.netを参照。

    PyTorchのパラメータに関しては『PyTorch入門』 使い方&Tensorflow, Keras等との違いとは?を参照

    • .fitで、自動的にtorch.tensorに変換される
    • validationまでやってくれて嬉しい
    コードをみる
    from skorch.net import NeuralNetClassifier
    net = NeuralNetClassifier(
        module=ClassifierModule,
        max_epochs=20,
        lr=0.1,
        # use_cuda=True,  # uncomment this to train with CUDA
    )
    net.fit(X,y)
    y_pred = net.predict(X[:5])
    y_proba = net.predict_proba(X[:5])
    for pred, proba in zip(y_pred,y_proba):
        print("score {}: class {}".format(proba, pred))
    

    sklearnとのその他連携

    pipeline

    • スケーリングなどsklearnの処理をデータの流れに組み込める
    • 前処理の話は day-8
    コードをみる
    from sklearn.pipeline import Pipeline
    from sklearn.preprocessing import StandardScaler
    
    # データの流れるPipelineを設計
    pipe = Pipeline([
        ("scale",StandardScaler()),
        ("neuralnet", net)
    ])
    print(pipe.named_steps)
    
    pipe.fit(X,y)
    
    y_pred = pipe.predict(X[:5])
    y_proba = pipe.predict_proba(X[:5])
    for pred, proba in zip(y_pred,y_proba):
        print("score {}: class {}".format(proba, pred))
    
    • NeuralNetClassifier/NeuralNetRegressorで設定できるパラメータを調べることができる(GridSearchCV or RandomSearchCV)
    • optimizerなどもtorch.optimのリストを使って網羅的に調べられる
    • クラス内変数も module__hoge(hogeはメンバ変数)と入力することで置き換えられる
    コードをみる
    from sklearn.model_selection import GridSearchCV
    params = {
        "lr":[i*0.01 for i in range(1,5)],
        "optimizer":[torch.optim.Adam, torch.optim.Adagrad, torch.optim.SGD],
        "module__num_units":[10,20],
    }
    gs = GridSearchCV(net, params, refit=False, cv=3, scoring="accuracy")
    gs.fit(X,y)
    import pandas as pd
    df = pd.DataFrame(gs.cv_results_)
    df_scored = df.sort_values(by=["rank_test_score"])[["params","mean_test_score","std_test_score","mean_fit_time"]]
    df_scored
    

    MNIST

    MNISTを実装してみる。sklearnで書けるところはsklearnで書いて、PyTorchで書くべきところはそちらで書く。使い分けがはっきりしていてかなりいい感じ。

    # データの読み込み(sklearn)
    from skorch import NeuralNetClassifier
    from torch import nn
    from sklearn.datasets import fetch_mldata
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import classification_report
    mnist = fetch_mldata('MNIST original')
    X = mnist.data.astype('float32')
    y = mnist.target.astype('int64')
    X /= 255
    XCnn = X.reshape(-1, 1, 28, 28)
    XCnn_train, XCnn_test, y_train, y_test = train_test_split(XCnn, y, test_size=0.25, random_state=42)
    # Networkの設計(PyTorch)
    class Net(nn.Module):
        def __init__(self):
            super(Net, self).__init__()
            self.conv1 = nn.Conv2d(1, 32, kernel_size=3)
            self.conv2 = nn.Conv2d(32, 64, kernel_size=3)
            self.conv2_drop = nn.Dropout2d()
            self.fc1 = nn.Linear(1600, 128) # 1600 = number channels * width * height
            self.fc2 = nn.Linear(128, 10)
    
        def forward(self, x):
            x = F.relu(F.max_pool2d(self.conv1(x), 2))
            x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2))
            x = x.view(-1, x.size(1) * x.size(2) * x.size(3)) # flatten over channel, height and width = 1600
            x = F.relu(self.fc1(x))
            x = F.dropout(x, training=self.training)
            x = self.fc2(x)
            x = F.softmax(x, dim=-1)
            return x
    # ラッパーを使う(skorch)
    net = NeuralNetClassifier(
        Net,
        max_epochs=10,
        lr=1,
        optimizer=torch.optim.Adadelta,
        # use_cuda=True,  # uncomment this to train with CUDA
    )
    # training
    net.fit(XCnn_train, y_train)
    
    # test
    y_pred = net.predict(XCnn_test)
    print(classification_report(y_test, y_pred))
    

    結果

    学習の様子

    結果の表示

                 precision    recall  f1-score   support
    
              0       1.00      0.99      0.99      1677
              1       0.99      0.99      0.99      1935
              2       0.99      0.99      0.99      1767
              3       0.99      0.99      0.99      1766
              4       0.99      0.99      0.99      1691
              5       0.99      0.99      0.99      1653
              6       0.99      0.99      0.99      1754
              7       0.99      0.99      0.99      1846
              8       0.98      0.99      0.99      1702
              9       0.99      0.98      0.99      1709
    
    avg / total       0.99      0.99      0.99     17500
    
    

    まとめ

    sklearnとPyTorchをつなげられる便利なライブラリを紹介しました。 PyTorchの持つ柔軟性と、sklearnの利用しやすさが相まって最高ですね。

    かなり使いやすいので、sklearnに慣れている人は、一回使ってみることをおすすめします!

    明日はどうするかな、ベイズ的な変化点検知をする予定だが、果たして、、、ではでは!

    【Day-19】『PyTorch入門』 使い方&Tensorflow, Keras等との違いとは?

    f:id:imslotter:20171219000627p:plain

    データ分析ガチ勉強アドベントカレンダー 19日目。

    2日間、Kerasに触れてみましたが、最近はPyTorchがディープラーニング系ライブラリでは良いという話も聞きます。

    とりあえずTutorialを触りながら使ってみて、自分が疑問に思ったことをまとめていくスタイルにします。

    また、同じく有名ライブラリであるKerasやTensorFlowについての比較もしたいと思っています(Define and RunかDefine by Runか)

    PyTorchとは

    f:id:imslotter:20171219043523p:plain

    ざっくりまとめると

    • 深層学習用ライブラリ
    • chainerからフォークされて作られている。思想はChainerを引き継いでいる。
    • GPUでの計算も楽に行える
    • Define by Run
    • コミュニティが活発で、後発にもかかわらず急速に発達

    特にDefine by RunはTensorflowやKerasと大きく異なる特徴です(後述)

    PyTorch入門

    変数の扱い方

    PyTorchでは、変数や関数をPyTorchオリジナルの型として扱う。大体はnumpyライクに扱えるので、ストレスは少ない

    x = torch.Tensor(5,3) #ゼロ行列
    y = torch.rand(5,3) # ランダム行列
    # 演算
    print(x.add_(y)) #アンダースコアをつけると破壊的変化
    print(torch.add(x,y)) #torchの関数から
    print(x+y) #通常の演算もいける
    # numpy likeに扱える
    print(y[:,1],y.mean(), y.std())  #ある程度の関数はそろっている
    print(torch.ones(10))
    print(torch.ones(10)+1)
    # numpyからの変換も可能
    import numpy as np
    a = np.arange(4).reshape(2,2)
    x = torch.from_numpy(a)
    

    Autograd

    • torch.autograd FunctionVariablesで定義すると、演算結果が保存されるので、 そのままさかのぼった微分が可能。

    下記の式は、

    • {x=} [[1,2],[3,4]]
    • {y = x^{2} +2}
    • {z = y^{2} = (x^{2}+2)^{2}}

    {out = \frac{1}{4} \sum_{i} z_{i} z_{i}  (x^{2}+2)^{2}} より、 {\frac{\partial out}{\partial x_{i}}=x_{i}(x_{i}^{2}+2)}

    その結果を返す より詳しい内容は公式

    from torch.autograd import Variable
    import numpy as np
    x = Variable(torch.FloatTensor([1,2,3,4]), requires_grad=True)
    y = x.float()**2 + 2
    z = y**2
    out = z.mean()
    out.backward() # 
    print(x.grad)
    

    出力

    <AddBackward0 object at 0x7f88d576ea90>
    Variable containing:
      3  12
     33  72
    [torch.FloatTensor of size 2x2]
    

    チュートリアル:NeuralNetの構築

    学習の手順

    • 学習ができるニューラルネットを定義する。
    • データセットを繰り返し投げられるようにする。
    • インプットに応じて、処理(計算)が走るようにする
    • 誤差(損失)を計算する
    • back propagationを実施する
    • optimizerによってパラメータを更新する

    この中でデータセットを繰り返し投げる以外の要素を説明していく。

    ライブラリ構成

    PyTorchのよく使うパッケージ

    パッケージ 説明
    torch NumPyのような配列(テンソル)ライブラリ。GPUも簡単に使える
    torch.autograd 自動微分ライブラリ。Torchで定義されたすべての関数に対して微分を可能にしている
    torch.nn ネットワークを構成する要素
    torch.optim パラメータ更新時に使う最適化パッケージ
    torch.utils DataLoaderなど、その他のユーティリティ関数

    torch.nn

    ネットワークを構成する要素関連はここに入っている

    • Conv1d,Conv2d,MaxPool1d, AvePool1d, AdaptiveAvgPool1d... : CNN向け
    • RNN, LSTM, GRU.... : RNN向け
    • BatchNorm1d,BachNorm2d, InstanceNorm1d,... : バッチ正規化
    • functional.hoge : 活性化関数(SoftMax, ReLUなど)
    • その他、dropout, sparse_embeddings,...

    詳しくはpytorch:nn

    すべてPyTorch内の関数で渡してやることができれば、autogradにより、back propagation時の微分演算などがスムーズ

    import torch
    from torch.autograd import Variable
    import torch.nn as nn
    import torch.nn.functional as F
    
    class Net(nn.Module):
    
        def __init__(self):
            super(Net, self).__init__()
            # 1 input image channel, 6 output channels, 5x5 square convolution
            # kernel
            self.conv1 = nn.Conv2d(1, 6, 5)
            self.conv2 = nn.Conv2d(6, 16, 5)
            # an affine operation: y = Wx + b
            self.fc1 = nn.Linear(16 * 5 * 5, 120)
            self.fc2 = nn.Linear(120, 84)
            self.fc3 = nn.Linear(84, 10)
    
        def forward(self, x):
            # Max pooling over a (2, 2) window
            x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
            # If the size is a square you can only specify a single number
            x = F.max_pool2d(F.relu(self.conv2(x)), 2)
            x = x.view(-1, self.num_flat_features(x))
            x = F.relu(self.fc1(x))
            x = F.relu(self.fc2(x))
            x = self.fc3(x)
            return x
    
        def num_flat_features(self, x):
            size = x.size()[1:]  # all dimensions except the batch dimension
            num_features = 1
            for s in size:
                num_features *= s
            return num_features
    
    net = Net()
    print(net)
    

    出力 : ネットワーク構成が分かる

    Net(
      (conv1): Conv2d (1, 6, kernel_size=(5, 5), stride=(1, 1))
      (conv2): Conv2d (6, 16, kernel_size=(5, 5), stride=(1, 1))
      (fc1): Linear(in_features=400, out_features=120)
      (fc2): Linear(in_features=120, out_features=84)
      (fc3): Linear(in_features=84, out_features=10)
    )

    値を投げる

    torchの型にしたがってデータを構成し、投げる。

    params = list(net.parameters())
    input = Variable(torch.randn(1, 1, 32, 32))
    out = net(input)
    

    損失関数

    誤差の算出のために、いろいろな関数が用意されている。シンプルなのはMean Squared Error。いろんな誤差を知りたい人は、機械学習で使う指標総まとめ(教師あり学習編)へ。

    • MSELoss
    • CrossEntropyLoss
    • NLLLoss(negative log likelihood loss) (1d, 2d)
    • Poisson NLL Loss
    • KLDivLoss (Kullback-Leibler divergence)
    • BCELoss (Binary Cross Entropy)
    • ...

    より詳しくは公式

    output = net(input)
    target = Variable(torch.arange(1, 11))  # a dummy target, for example
    criterion = nn.MSELoss()
    loss = criterion(output, target)
    

    back propagation

    損失が算出できたので。この損失からさかのぼっていく。PyTorchだと、backwardとだけ実行すれば、簡単にパラメータの微分値を算出できるようになっている。

    net.zero_grad() #とりあえず0で初期化
    loss.backward()
    

    重みのアップデート

    学習には、torch.optimを使う。下記のようなアルゴリズムがある。

    • SGD, ASGD, Nesterov-SGD
    • Adadelta, Adagrad, Adam, SparseAdam, Adamax
    • LBFGS
    • RMSProp, Rprop

    また、torch.optim.lr_scheduler学習率の調整もできる。

    import torch.optim as optim
    
    # create your optimizer
    optimizer = optim.SGD(net.parameters(), lr=0.01)
    
    # in your training loop:
    optimizer.zero_grad()   # zero the gradient buffers
    output = net(input)
    loss = criterion(output, target)
    loss.backward()
    optimizer.step()    # Does the update
    

    これで一通り終了。最終的にはデータを繰り返し投げて、学習を進めていく。 Githubのday19では、最後にMNISTの分類をPyTorchでやっている。そちらも是非どうぞ。

    主要フレームワークとの比較とDefine by Run

    Define and RunとDefine by Run

    ディープラーニングライブラリは上記の二種類の設計思想に大きく分かれる。 Chainerの血を受け継ぐPyTorchは、Define by Run。TensorflowやKerasとの最も大きな違い。

    どのように違うのか。下図は、PyTorchとKerasで定義した、Mnistに投げるCNN。 PyTorchがデータを投げて実行しながらネットワークを定義するのに対して、Kerasはネットワーク構成をかっちりと決めてからデータを投げる。定義の時点でデータは考えない。

    Define by Runのメリットとしては、

    • ネットワークを動的に変更できる。

    ということで、研究者界隈では、Define by Runが主流。

    使いやすさのKeras, 柔軟性のPyTorch

    とはいえ、Kerasはやはり圧倒的に使いやすい。 ネットワークを積み上げるだけで、簡単にディープラーニングができてしまう。

    その一方で、ネットワークの詳細設計はしにくい。

    通常のデータ分析業務ならば、Kerasの簡潔さが輝くだろうし、研究や難しいタスクならばPyTorchが優位なのかなという印象。

    コミュニティの強いPyTorch

    PyTorchの優位性は、コミュニティの強さにもある。 同じくdefine by runのChainerは、やはり日本が主流であって、海外での利用は少ない。

    一方でPyTorchは、研究者が続々と採用していて、主要な論文が出るとすごいスピードで実装されるので、最新論文のアルゴリズムをすぐに試すことができる。the incredible pytorchなどが好例。

    github.com

    まとめ

    PyTorchについて勉強してみた。 設計思想の違いなんかにも触れられたと思う。一長一短なので、両方とも使えるようになっていたら便利そうだなと思った。

    明日もPyTorch系の話題。どうやらsklearnラッパーがあるようなので、試してみたいと思う。ではでは!

    詳解 ディープラーニング ~TensorFlow・Kerasによる時系列データ処理~

    詳解 ディープラーニング ~TensorFlow・Kerasによる時系列データ処理~

    Natural Language Processing With Pytorch: Build Intelligent Language Applications Using Deep Learning

    Natural Language Processing With Pytorch: Build Intelligent Language Applications Using Deep Learning

    【Day-18】時系列のディープラーニング、RNNのまとめとKeras実装

    f:id:imslotter:20171218194346p:plain

    データ分析ガチ勉強アドベントカレンダー 18日目。

    Kerasの使い方を復習したところで、今回は時系列データを取り扱ってみようと思います。 時系列を取り扱うのにもディープラーニングは用いられていて、RNN(Recurrent Neural Net)が主流。

    今回は、RNNについて書いた後、Kerasで実際にRNNを実装してみます。

    今日の参考書はこの本!

    詳解 ディープラーニング ~TensorFlow・Kerasによる時系列データ処理~

    詳解 ディープラーニング ~TensorFlow・Kerasによる時系列データ処理~

    • 作者:巣籠 悠輔
    • 発売日: 2017/05/30
    • メディア: 単行本(ソフトカバー)

    RNNとは

    通常のNeural Networkとの違い

    Recurrent Neural Netの略。再帰ニューラルネット。何が再帰かは下図

    入力層、隠れ層、出力層の構造はDay-15で説明したNeural Netとほとんど同じだが、最も大きな違いは隠れ層同士がつながっているところ。これによって時系列に対応できるようにしている。順伝播の計算も、再帰性の要素が入ってくる。(添え字等はDay15参照)

    誤差逆伝播法のアルゴリズム

    基本的にニューラルネットと同じだが、隠れ層の時間方向に微分がつながっていくので、時間を遡りながら誤差の影響を伝えていく、しかし、本来は一番初めの時刻まで遡るものだが、遡りすぎると時間方向の微分がかけ粟さて勾配消失/爆発を引き起こし、うまく学習されないことも多くある。シンプルなRNNではBPTTを途中で打ち切る場合も多い。

    下記にアルゴリズムを記す

    1. 順伝播計算により{z}の更新式、出力{y}を求める

    2 ラベル{d_{nk}^{t}}との誤差を計算

    3. 重みを最小化する


    4. デルタはBPTTを行うことで再帰的に求められる

    5. 何らかのオプティマイザにより重みを更新

    勾配消失の工夫 : LSTMやGRU

    時間を遡ってBackpropagationを行うため、勾配消失が起こる。それを解消する工夫として、LSTM(Long-Short Term Memory)とGRU(Gated Recurrent Unit)がある。上の図の隠れ層に置ける、○の中身を工夫しながら、何を忘れて何を覚えておくか**まで賢く学習する。

    LSTM

    正直、本の図はちょっと分かりにくい、ウェブをあさってて一番厳密かつ分かりやすかったのは A Beginner’s Guide to Recurrent Networks and LSTMsというサイトの図

    複雑...一番のポイントは

  • 中心のcellと書かれている箇所(CECという)+forget gateで、過去情報をどのくらい記憶するかを調整する
  • これにより、BTPP中の勾配消失を数式的にも抑えることができた*1

    GRU

    LSTMの難点は複雑でパラメータが多いところ。上図で言えばinput/forget/blockと、それぞれのGateに入れるときの重みを最適化しないといけない。一方でGRUはLSTMベースだが、入力ゲートがLSTMよりも断然少ない。

    ゆえに計算量も少ない。さらに、タスクによってはLSTMより良い性能を発揮するということで、注目されている

    見た目の違いが論文に載っている。(Empirical Evaluation of Gated Recurrent Neural Networks on Sequence Modeling)

    だいぶシンプル。計算も軽いわけだから、とりあえずGRUから試してみるのがいいのかもしれない。

    自然言語をにぎわすAttention Model

    NLP(Natural Language Process)のRNN界隈では、Attention Modelというのがにぎわっているように見受けられる。

    勉強不足でまだまだ全然分かっていないので、勉強用のソースだけ張っておく。勉強。

    Keras実装

    練習がてら、KerasでRNNを実装してみる。 githubにもあげているのでそちらも参考に。

    github.com

    データの入力の仕方がちょっと難しかったので、そこだけ重点的に。

    データの変形、入力

    BPTTの都合もあり、投げるデータの配列をすこしいじってやらないといけない。いじり方について図でまとめてみた。このような3次のテンソル的な配列になる。

    コードをみる
    %matplotlib inline
    import matplotlib.pyplot as plt
    import pandas as pd
    import numpy as np
    import keras
    df = pd.read_csv("AirportPassengers.csv",delimiter=";").dropna()
    data = []
    target = []
    max_len = 24
    dim = 1
    # 正規化
    maximum = df.Passengers.max()
    minimum = df.Passengers.min()
    df["Passengers"] = (df.Passengers-minimum)/(maximum-minimum)
    # データを箱に入れる
    for i in range(len(df)-max_len-1):
        data.append(df.Passengers.values[i:i+max_len])
        target.append(df.Passengers.values[i+max_len+1])
    # データの整形
    data = np.array(data).reshape(len(data),max_len,dim)
    target = np.array(target).reshape(-1,1)
    # データの分割
    from sklearn.model_selection import train_test_split
    N_train = int(len(data)*0.7)
    N_test = len(data) - N_train
    X_train, X_validation, Y_train, Y_validation = train_test_split(data, target, test_size=N_test)
    

    学習モデル構築

    keras.layers.recurrentの中に、学習モデルが入っている。 LSTM, GRUも実装されていて、model.addするだけで使えるようになる。Optimizerの設定は普通のニューラルネットと同じ。Day-17にOptimizerについて書かれている

    コードをみる
    from keras.models import Sequential
    from keras.layers import  Dense
    #一番シンプルなやつ
    from keras.layers.recurrent import SimpleRNN
    from keras.layers.recurrent import LSTM, GRU #改良版
    model = Sequential()
    # ネットワーク構成
    input_shape=(max_len, dim)
    # model.add(SimpleRNN(units=64, kernel_initializer="random_uniform",input_shape=input_shape))
    #model.add(LSTM(units=64, input_shape=input_shape))
    model.add(GRU(units=64, input_shape=input_shape))
    
    model.add(Dense(input_shape[1],activation="linear"))
    # optimizerの設定
    from keras.optimizers import Adam
    model.compile(loss="mse", optimizer=Adam())
    

    学習と予測

    7割を学習用データにして、残りの3割を予測することにした。

    コードをみる
    from keras.callbacks import EarlyStopping
    early_stopping = EarlyStopping(monitor='val_loss', patience=10, verbose=1)
    epochs = 500
    batch_size = 10
    model.fit(X_train, Y_train,
              batch_size=batch_size, epochs=epochs,
              validation_data=(X_validation, Y_validation))
    prediction = []
    data_in = data.reshape(data.shape[0],data.shape[1])[N_train]
    
    iteration = len(data) - N_train
    for _ in range(iteration):
    #     print(data_in)
        pred = model.predict(data_in.reshape(1,-1,1))
        data_in = np.delete(data_in, 0)
        data_in = np.hstack((data_in, pred[0]))
        prediction.append(pred[0,0])
    passenger = list(df.Passengers)
    data_num = len(passenger)
    plt.plot(passenger)
    plt.plot(range(N_train+max_len, N_train+max_len+iteration), prediction)
    

    結果

    上記の学習を3回行ってみて、LSTMと普通のRNNとGRUを比べた。

    今回の結果は、LSTMが良かった。

    きちんとトレンド/周期共に捕らえられているようだった。ただ、時間を追うごとに値がずれていっている。もう少し精度を上げたいなら、前処理をしっかりしたほうがいいのかもしれない。前処理については以前時系列まとめで書いた(【Day-12】時系列分析の良リソースまとめ&基礎チュートリアル)

    まとめ

    せっかくKerasを学んだので、時系列もディープでしておこうと思い、まとめた。 ちょっと苦労したけど、これで学習器に入れられるようになった。予測がちゃんとできれば楽しい。

    明日はPyTorchやりたいなぁ。ではでは。

    *1:数式載せようかと思ったけど、異常にめんどくさいからやめますごめんなさい。

    【Day-17】DeepLearning系ライブラリ、『Keras』の使い方まとめ(2.x対応版)

    【最終更新 : 2017.12.17】
    ※以前書いた記事がObsoleteになったため、2.xできちんと動くように書き直しました。

    f:id:imslotter:20171217160749p:plain

    データ分析ガチ勉強アドベントカレンダー 17日目。

    16日目に、1からニューラルネットを書きました。 それはそれでデータの流れだとか、活性化関数の働きだとか得るものは多かったのですが、Kerasと言うものを使ってみて、何て素晴らしいんだと感動してしまいました

    今まで苦労して数十行書いていたものが、わずか3行で書ける! 正直、スクラッチで書く意味って、理解にはいいけど研究や分析には必要あんまないんですよね。車輪の再発明になるし。 と言うわけで、使えるものはどんどん使っていこうスタンスで、今日はKerasの紹介です!

    Tutorial+気になった引数を掘り下げて補足のような感じで書いています。 ちなみに、各部のコード以下をつなぎ合わせるとmnistの分類器が動くようになっています。 *1 *2

    Kerasとは?

    Keras is a high-level neural networks library, written in Python and capable of running on top of either TensorFlow or Theano. It was developed with a focus on enabling fast experimentation. Being able to go from idea to result with the least possible delay is key to doing good research. (Documentationより)

    TensorFlowやTheanoのラッパーで、簡単にディープ系の実験環境を整えられるようになっている。 実際に使ってみましたが、かなり直感的にネットワークを構成することが出来る

    使ってみる

    インストール

    前に記事でも少し書いた。

    www.procrasist.com

    pip install tensorflow
    pip install keras

    で入ると思います。その他のライブラリ(opencvとか)は必要に応じて入れてください。

    データを用意

    とりあえずMNIST(デフォルトでmnistデータセットは利用可能) from keras.datasets import hogehogeでhogehogeのデータセットを読み込める。以下は読み込めるデータセット一覧

    データセット 内容 trainデータ数 testデータ数
    mnist 28×28の手書き数字(白黒)10個に分類 60000 10000
    cifar10 32×32のカラー画像10に分類 50000 10000
    cifar100 32×32のカラー画像100に分類 50000 10000
    imdb 25000の映画のレビューのデータセット、ラベルは肯定否定の2種 25000 25000
    reuters 11228個のニュースデータセット46トピックに分類 8972 2246
    fashion_mnist 28×28の白黒画像を10種類のカテゴリに分類 60000 10000

    求めたいものがあれば各々で利用すれば良い。データの渡し方の参考にこれらのデータセットの型を知っておくのがいいかも。

    コード

    from keras.datasets import mnist
    from keras.utils import np_utils
    (X_train, y_train),(X_test,y_test) = mnist.load_data()
    X_train = X_train.reshape(60000,784).astype('float32')
    X_test = X_test.reshape(10000,784).astype('float32')
    #[0,255]の値を[0,1]に正規化
    X_train /= 255.0
    X_test /= 255.0
    # 1 of Kのベクトルに変換
    y_train = np_utils.to_categorical(y_train, 10)
    y_test = np_utils.to_categorical(y_test, 10)
    

    modelを作る

    model = Sequential()で初期化
    model.add()で層を積んでいく

    • 一層目:IN-784次元, OUT-64次元, 活性化関数-Selu
    • 二層目:IN-default(多分64次元) OUT-10次元 活性化関数-SoftMax関数

    入力のイメージは下図。MNISTは28×28の図だが、784次元の1次元ベクトルに変換してから入力している

    • デフォである活性化関数
    • より高度な活性化関数 : keras.layers.advanced_activations モジュール内

    関数の違いは下図 f:id:imslotter:20170107153956p:plain

    コード

    from keras.models import Sequential
    from keras.layers import Dense
    model = Sequential()
    model.add(Dense( activation="selu", units=64,input_dim=784))
    model.add(Dense(activation="softmax", units=10))
    

    学習プロセス

    model.compile

    • 損失関数 : どのくらいの誤差があるかを定量
    • 最適化関数 : 誤差からのパラメータ更新の仕方

    loss : 損失関数

    デフォで用意されている関数

    関数名 意味
    mean_squared_error, mse 二乗誤差
    mean_absolute_error, mae 絶対誤差
    mean_absolute_percentage_error, make 正解とのズレ(絶対値)の割合
    mean_squared_logarithmic_error, msle 正解とのズレ(二乗誤差)の割合
    squared_hinge マイナスのところは0, プラスは二乗誤差
    hinge マイナスのところは0, プラスは絶対値
    binary_crossentropy loglossとしても知られている
    categorical_crossentropy マルチクラスloglossとして知られている(ラベルがバイナリ配列であることが必要)
    sparse_categorical_crossentropy スパースラベルを取る
    kullback_leibler_divergence, kld 確率を分布とみなした時の差
    poisson 予測-正解*log(予測)の平均
    cosine_proximity 予測と正解間のコサイン近似の負の平均

    詳しい実装例はこちら GitHub

    optimizer : 最適化

    デフォで用意されている関数

    • SGD
    • RMSprop(デフォルトパラメータ推奨)
    • Adagrad(デフォルトパラメータ推奨)
    • Adadelta(デフォルトパラメータ推奨)
    • Adam(デフォルトは提案論文通り)
    • Adamax Adamの拡張(無限ノルム)
    • Nadam Adam+RMSprop with momentumらしい
    • TFOptimizer よくわかんない
    • 引数の詳細な設計も可能(こちらを参考 web)
    • 新しい最適化関数を作りたいなら GitHubを参考に

    指標の参考には以前書いたこちらの記事もどうぞ

    コード

    from keras.optimizers import SGD
    # optimizer = "sgd"でも、簡単に最適化関数の設定ができる
    #(その場合パラメータはデフォルト値)
    model.compile(loss="categorical_crossentropy", 
                  optimizer=SGD(lr=0.01, momentum=0.9, nesterov=True),
                  metrics=["accuracy"])
    

    モデルの学習

    学習

    model.fit()でモデルを学習。引数は以下

    • X_train : データ(MNISTだと、どこに何個データが入っているか)
    • y_train : ラベル(1-10)
    • batch_size : ミニバッチにデータを分けるこの場合60000個ある学習データを、32個ずつに分けて、重みを更新する
    • epochs : 学習の繰り返し。60000個を何回学習するか
    • validation_split : 学習データの何パーセントをvalidation用データにするか(各エポックのテスト。validationの説明はこの記事を参考にどうぞ : 【Day-10】Cross Validationとパラメータサーチでモデルの調整 )

    また、学習の様子も保存されている model.fit内の変数を見てみるとこんな感じ

    print(history.__dict__)
    >>> {'model': <keras.models.Sequential object at 0x110c78510>, 'params': {'verbose': 1, 'nb_epoch': 3, 'batch_size': 32, 'metrics': ['loss', 'acc', 'val_loss', 'val_acc'], 'nb_sample': 48000, 'do_validation': True}, 'epoch': [0, 1, 2], 'history': {'acc': [0.89862500000000001, 0.94735416666666672, 0.96150000000000002], 'loss': [0.35020409799863894, 0.18034435256198048, 0.132605804043822], 'val_acc': [0.94125000000000003, 0.95816666666666672, 0.96499999999999997], 'val_loss': [0.20651897403846184, 0.14732480475058157, 0.1263637450747192]}}
    

    コールバック関数

    keras.callbacks内の関数を使ってモデルの保存ができる。

    • check = ModelCheckpoint("modelname.hdf5")で、学習済みモデルの重みを保存する
      • その場合、model.fit(hogehoge,callbacks=[check])としてやる
    • tensorboardなどとも連携可能
    • 自分でcallbackしたい関数を作ることも可能
    • 詳しくはドキュメント参照

    コード

    from keras.callbacks import ModelCheckpoint
    check = ModelCheckpoint("model.hdf5")
    history = model.fit(X_train, y_train, epochs=20, 
                        validation_split=0.2, batch_size=32,
                        callbacks=[check])
    

    モデルの評価

    model.evaluate()という関数で、テストデータを用いたモデルの評価が可能。lossとaccuracyを見ている

    コード

    loss, accuracy = model.evaluate(X_test, y_test)
    print("\nloss:{} accuracy:{}".format(loss, accuracy))
    

    モデルの可視化

    可視化(tensorflow)

    モデルの可視化

    ニューラルネットのネットワークを可視化するとき
    keras.utils.visualize_util
    手順(Mac, Homebrew有り)

    • $brew install graphviz
    • $pip install pydot

    とすれば使える

    • to_file : 出力の画像ファイルの名前
    • show_shapes : グラフ中に出力形状を書くか(デフォはFalse)
    • show_layer_names : レイヤー名を書くか(デフォはTrue)

    コード

    from keras.utils.visualize_util import plot
    plot(model, to_file="model.png", show_shapes=True, show_layer_names=True)
    

    こんなのが出てくる
    f:id:imslotter:20170107113053p:plain

    学習の様子をplot

    matplotlibで可視化

    コード

    import matplotlib.pyplot as plt
    
    def plot_history(history):
        # 精度の履歴をプロット
        plt.plot(history.history['acc'],"o-",label="accuracy")
        plt.plot(history.history['val_acc'],"o-",label="val_acc")
        plt.title('model accuracy')
        plt.xlabel('epoch')
        plt.ylabel('accuracy')
        plt.legend(loc="lower right")
        plt.show()
    
        # 損失の履歴をプロット
        plt.plot(history.history['loss'],"o-",label="loss",)
        plt.plot(history.history['val_loss'],"o-",label="val_loss")
        plt.title('model loss')
        plt.xlabel('epoch')
        plt.ylabel('loss')
        plt.legend(loc='lower right')
        plt.show()
    # modelに学習させた時の変化の様子をplot
    plot_history(history)
    

    こんな感じに

    f:id:imslotter:20170107151009p:plain

    正解率98%とかって半端ないですね。人間と同じかそれ以上の識別性能かも!

    Keras 1.xからKeras 2.xの変更点

    詳しくはこちらの公式記事を参考

    上記チュートリアルで変更した点をメモとして記しておく。

    Layerを作る際に、活性化関数も一緒に入れるようになった

    from keras.layers import Dense, Activation
    model = Sequential()
    model.add(Dense(output_dim=64, input_dim=784))
    model.add(Activation("relu"))
    

    出力

    UserWarning: Update your `Dense` call to the Keras 2 API: `Dense(input_dim=784, units=64)

    変更後

    model = Sequential()
    model.add(Dense( activation="relu", units=64,input_dim=784))

    nb_epoch -> epochs

    from keras.callbacks import ModelCheckpoint
    check = ModelCheckpoint("model.hdf5")
    history = model.fit(X_train, y_train, nb_epoch=20, 
                        validation_split=0.2, batch_size=32,
                        callbacks=[check])
    

    出力

    UserWarning: The `nb_epoch` argument in `fit` has been renamed `epochs`.
      warnings.warn('The `nb_epoch` argument in `fit` '

    変更後

    from keras.callbacks import ModelCheckpoint
    check = ModelCheckpoint("model.hdf5")
    history = model.fit(X_train, y_train, epochs=20, 
                        validation_split=0.2, batch_size=32,
                        callbacks=[check])
    

    show_accuracyの廃止 : defalutでaccuracyが出力されるようになった。

    model.evaluate(X_test, y_test, show_accuracy=True)
    

    出力

    TypeError: evaluate() got an unexpected keyword argument 'show_accuracy'

    変更後

    model.evaluate(X_test, y_test)
    

    まとめ

    今日のポイントは
    ・Kerasを使うと、簡単にディープラーニングができる
    ・可視化も簡単にできるので、実験にはもってこい
    ・ただ、便利・簡単すぎるので、中身の仕組み知りたい人は一から書いてみるのもいいかも!

    実際にモデルを動かしてる部分って、たかだか十数行とかですものね。すごい。 いろいろな実験がしやすそうだし、これからどんどん使っていきたい。

    なお、Kerasを書籍で勉強したい方はこちらがオススメです。特にRNNに関しては丁寧に書かれているので!

    詳解 ディープラーニング ~TensorFlow・Kerasによる時系列データ処理~

    詳解 ディープラーニング ~TensorFlow・Kerasによる時系列データ処理~

    明日はKerasを用いたRNN実装にチャレンジしてみる(予定)です!お楽しみに!

    *1:日本語があると怒られる方は、ファイル先頭に#coding:utf-8をつけておきましょう。

    *2:順を追ってimportしているので、そのまま使うとものすごく見にくいかもです。コピペした後は適当に体裁を整えてください

    【Day-16】ニューラルネットを0から作り、仕組みを基礎から理解する

    f:id:imslotter:20171216170643p:plain

    データ分析ガチ勉強アドベントカレンダー 16日目。

    今日からは少しディープラーニングの勉強。

    ここ数年間、深層学習用ライブラリも猛烈に整備され、誰でも簡単にディープラーニングを使えるようになりました。

    その一方で、整備されすぎて、魔法の箱だという認識も多いですよね。 けれど、深層学習と言えど、しているのはほとんど線形代数微積分を組み合わせた数値計算です。

    だったら自分で作れるのでは? というわけで、仕組みを理解するために、0からスクラッチで作ることにしました。

    尚、勉強にはプロフェッショナルシリーズの深層学習を利用しています。

    爆速で技術が進む深層学習界隈では少々obsoleteかもしれませんが*1、きちんと基礎の基礎を知るにはいい本だと思います。詳しい計算方法を学びたい人は、どうぞ。(線形代数偏微分の知識が必要です。)

    深層学習 (機械学習プロフェッショナルシリーズ)

    深層学習 (機械学習プロフェッショナルシリーズ)

    作るニューラルネットワーク

    下図のような基本的な3層ニューラルネットワーク

    • 全結合
    • 活性化関数 : 隠れ層 : Sigmoid関数, 出力層 : ソフトマックス関数
    • ミニバッチ処理を採用

    の構成で作ってみる

    ニューラルネットの設計

    ニューラルネットには下記の要素が必要。

    • ネットワーク構成 : 何段構成にするとだとか、どういう計算を施すか解か、どこの層をつなぐとかを考えなければならない。今回は3段の単純なネットワークだけど、実際は用途によって様々な構成が出来る。Network Zooなどが、ネットワーク構成の参考になる。けど、今でも試行錯誤

    • 活性化関数 : ニューラルネット非線形性を生み出す重要な要素。

    • 重みのアップデート方法 : ニューラルネット学習を進める上で重要な要素。

    活性化関数も、試行錯誤で作られている事が多い。 近年よく使われるのはReLU等。活性化関数に関しては、以前記事にしたことがあるのでそちらを参考に。

    www.procrasist.com

    順伝搬計算

    入力(ベクトル)から、線形変換と活性化関数による非線形計算をしながら出力層の値を取り出す。 最終的に各値のスコアが出てくるので、回帰ならそのまま用いればいいし、分類ならスコアが最も高いものを選ぶ。

    逆伝搬計算

    出力値から、誤差を出すことが出来る。その誤差を元にして、各層の重みを調整していく。 このためには微分の知識が必要になってくる。出力層から入力層計算を進めるため、誤差逆伝搬と呼ばれる。 誤差にはクロスエントロピー関数を用いる事が多い

    重みの更新

    逆誤差伝搬を行うことで、誤差からの各重みが出せる。最もシンプルなのは、単に誤差を一回の微分値で調整する方法(勾配降下法)。確率的勾配法(SGD)などもこれを元にしている。 この更新方法も様々に考えられていて、もう少し高度なモメンタムや、学習率を自動的に更新してくれるAdam, AdaGradなどもある。

    f:id:imslotter:20171216162434p:plain
    (重みの更新式{\epsilon}は学習率)

    実装

    以上を踏まえて、実装をしてみた。いつものように

    ネットワークの設計

    必要なネットワークの構成と、活性化関数、順伝搬計算、逆伝搬計算を、実装

    コードをみる
    class NN:
        def __init__(self, num_input, num_hidden, num_output, learning_rate):
            self.num_input = num_input
            self.num_hidden = num_hidden
            self.num_output = num_output
            self.learning_rate = learning_rate
    
            self.w_input2hidden = np.random.random((self.num_hidden, self.num_input))
            self.w_hidden2output = np.random.random((self.num_output, self.num_hidden))
            self.b_input2hidden = np.ones((self.num_hidden))
            self.b_hidden2output = np.ones((self.num_output))
    
        ##活性化関数(シグモイド関数)
        def activate_func(self, x):
            return 1/(1+np.exp(-x))
        ##活性化関数の微分
        def dactivate_func(self,x):
            return self.activate_func(x)*(1-self.activate_func(x))
        ##ソフトマックス関数
        def softmax_func(self,x):
            C = x.max()
            f = np.exp(x-C)/np.exp(x-C).sum()
            return f
        ##順伝播計算
        def forward_propagation(self, x):
            u_hidden = np.dot(self.w_input2hidden, x) + self.b_input2hidden
            z_hidden = self.activate_func(u_hidden)
            u_output = np.dot(self.w_hidden2output, z_hidden) + self.b_hidden2output
            z_output = self.softmax_func(u_output)
            return u_hidden, u_output, z_hidden, z_output
        ##逆伝播でdeltaを求める
        def backward_propagation(self,t,u_hidden,z_output):
            t_vec = np.zeros(len(z_output))
            t_vec[t] = 1
            delta_output = z_output - t_vec
            delta_hidden = np.dot(delta_output, self.w_hidden2output * self.dactivate_func(u_hidden))
            return delta_hidden, delta_output
        ##wに関するgradient
        def calc_gradient(self,delta,z):
            dW = np.zeros((len(delta), len(z)))
            for i in range(len(delta)):
                for j in range(len(z)):
                    dW[i][j] = delta[i] * z[j]
            return dW
        # update(SGD)
        def update_weight(self,w0,gradE):
            return w0 - self.learning_rate*gradE
    

    学習

    ランダムにデータを選ぶことで、確率的勾配法にしてみた。

    コードをみる
    def train(nn, iteration,savefig=False):
        epoch = 0
        for epoch in range(iteration+1):
            grad_i2h = 0
            grad_h2o = 0
            gradbias_i2h = 0
            gradbias_h2o = 0
            rand = randint(0,len(data),100)
            for r in rand:
                u_hidden, u_output, z_hidden, z_output = nn.forward_propagation(data[r])
                delta_hidden, delta_output = nn.backward_propagation(target[r], u_hidden, z_output)
                grad_i2h += nn.calc_gradient(delta_hidden, data[r])
                grad_h2o += nn.calc_gradient(delta_output, z_hidden)
                gradbias_i2h += delta_hidden
                gradbias_h2o += delta_output
            nn.w_input2hidden = nn.update_weight(nn.w_input2hidden, grad_i2h / len(rand))
            nn.w_hidden2output = nn.update_weight(nn.w_hidden2output, grad_h2o / len(rand))
            nn.b_input2hidden = nn.update_weight(nn.b_input2hidden, gradbias_i2h / len(rand))
            nn.b_hidden2output = nn.update_weight(nn.b_hidden2output, gradbias_h2o / len(rand))
    

    実行

    sklearnでデータセットを用意し、実際に分類できるかを分離平面で表してみている。 自信度によって色の濃さを調整しているので、学習が進んでいくと、きちんと分類されて、濃くなっていくはず。。。!

    コードをみる
    #データの用意
    num_cls = 5
    data,target = make_blobs(n_samples=1000, n_features=2, centers=num_cls)
    # Neural Netの用意
    nn = NN(num_input=2,num_hidden=20,num_output=num_cls,learning_rate=0.2)
    # 学習
    train(nn, iteration=500, savefig=True)
    
    plt.figure(figsize=(5,5))
    #色の用意
    base_color = ["red","blue","green","yellow","cyan",
                  "pink","brown","gray","purple","orange"]
    colors = [base_color[label] for label in target]
    # 教師データのプロット
    plt.scatter(data[:,0],data[:,1],color=colors,alpha=0.5)
    print("plotting...")
    xx = np.linspace(-15,15,100)
    yy = np.linspace(-15,15,100)
    for xi in xx:
        for yi in yy:
            _,_,_,z_output = nn.forward_propagation((xi, yi))
            cls = np.argmax(z_output) #softmaxのスコアの最大のインデックス
            score = np.max(z_output)
            plt.plot(xi, yi, base_color[cls],marker="x",alpha=s)
        print(".",end="")
    plt.xlim=(-15,15)
    plt.ylim=(-15,15)
    plt.show()
    print("finish plotting")
    

    実行結果

    以下が、300イテレーション回したときの分離平面の様子である。

    また、学習が進んでいく様子もアニメーションにしてみた。(画像が粗くてすみません)

    うまく学習が進んでいる様子が見て取れます。

    まとめ

    今回は実際に作ることで、ニューラルネットの仕組みを理解してみた。 実際に自分で作るとどこで非線形な要素が生まれるのかとか、誤差をどう学習に反映させているのかというのが分かるようになるので、勉強になります。魔法の箱なんかじゃなく、ちゃんとした学習器として理解できたように思います。0空作ればここから柔軟にいろいろと発展させていくことも可能ですしね。

    今回は式からプログラムを書き起こしたが、最近は↓のようなスクラッチで書く人のためのとても良い本も売っているので、そちらも参考にしてみてはいかがでしょうか。

    また、この辺の基礎を知っている人は、正直ライブラリを使ったほうが早いです。 明日からは、深層学習用ライブラリをいくつか触っていきたいと思います。(KerasかPytorch) ではでは!

    *1:言うても2-3年前までは最先端

    【Day-15】ベイズ的最適化で最強のゴールデンクロスを見つける

    f:id:imslotter:20171215132801p:plain

    データ分析ガチ勉強アドベントカレンダー 15日目。

    最強のゴールデンクロス、それは最も儲かるように移動平均線を引いたときの交点 である。

    一説には、テクニカル分析は、チャートにすべての情報が詰まっているという前提があるそうですね。 ということは、データから最適な移動平均を求められるのでは?

    というわけで、今回はベイズ最適化の勉強もかねて、最適な移動平均のwindow幅をデータから出してみようと思います。

    ※始めに実験を載せてます。理論が気になる方は理論のところから見ると良いでしょう。 [:contents]

    ゴールデンクロス/デッドクロスとは?

    昨日の記事をちょっとだけ復習。

    www.procrasist.com

  • ゴールデンクロス : 株価が下落した後に短期移動平均線が、長期移動平均線下から上に抜ける現象。買いのサイン
  • デッドクロス : 株価が上昇した後に短期移動平均線が、長期移動平均線上から下に抜ける現象。売りのサイン
  • (わかる株式用語より)

    長期/短期2本の移動平均線が必要なわけです。

    【実験】

    実験設定

    二つの移動平均線を使って、売り買いを判断するシステムがあると仮定する。そいつの動きはこう

    • 2015年1月4日、資産100株&100万円からスタート
    • データとして使うのは3年分の日経平均
    • ゴールデンクロス買い(全資産)デッドクロス売り(全株)
    • 2017/12/13時点で株を全部売って、総資産を計算
    • 評価値は、何%資産が増えたか*1

    つまり、シグナルにあわせて売り注文、買い注文をしていき、資産の増分が評価になる。 昨日作ったアルゴリズムで、ゴールデンクロスデッドクロスを発見できるようになっているので、そこに売り買いを入れるだけ。いいタイミングで売り買いをし、波を捉えられるかというゲーム。

    最適化

    最適化については最後に理論的な説明を簡単につけているので、そこを参照。ベイズ的最適化を用いる。下記がその時に扱うものの設定

    要素 選択
    Acquisition function EI (Acqusition functionの参考)
    kernel Mattern52
    イテレーション回数 50

    尚、実装はgithubにまとめている。

    結果は・・・?

    さあ、ベイズ最適化で計算してみた結果どうなったか!! まずは図を御覧ください。

    最適化してみた結果の、平均、標準偏差、acquisition funciton *2

    試行を繰り返せば、一番右の図の濃い黄色の部分が、選ぶべき最適な点となっているはずです。

    この結果、

    window1=91, window2 = 208で移動平均を取ると、最も儲かる

    という結果を得ることができました。ちなみにこのとき、50%資産増でした。3ヶ月と7ヶ月で移動平均ですね!!

    ちなみに、その場合に移動平均を引いたらこうなる。

    あんまり売買しないほうが儲かるということかな?ちなみにまだ売りのタイミングは来ていないようです。 ここでのクロスポイントで、売るといいのかな?データを分析したらそういってる!!

    【実装】

    GPyOpt/GPy

    今回は、実装にGPyOptを利用することにする。GPyOptは、ガウス過程計算ライブラリGPyを利用してベイズ的最適化を行うライブラリ。なので、どちらもインストールしておく必要がある。

    pip install -U scipy #scipyも最新版にしておく
    pip install GPy
    pip install gpyopt

    ※実装は長くなるので、記事では割愛しますgithubを参考にしてください。評価関数を作って、GPyOptに投げ入れているだけです!(day15)

    github.com

    【理論】ベイズ的最適化とは?

    荒野の中から宝を探す

    こちらの記事が分かりやすかったです。ガウス過程に従うサンプルとしてモデル化して最適化を行うような手法

    難しいことは下記の参考に譲るとしましょう。どれも分かりやすくていい資料です!

    ざっくりとポイントだけ。

  • f(x)の最大値を求めたい
  • fの形は分かっていない
  • けれど、xを与えて、f(x)を計算するまでに時間がかかる
  • f(x)を何か別の関数で近似する方法がほしい
  • 普通の式ならいいのですが、たとえば、xがパラメータ、f(x)が何かしらのシミュレータの場合、 まずf(x)の形を定式化することが難しいです。コレをブラックボックスといいます。さらに、一回一回の評価にめちゃくちゃ時間がかかる**とすると、xをちょっとずつ変えて全探索など、厳しいですね。

    たとえて言うなら、荒野の中から、当てもなく土を掘り返して中に眠る宝を探すイメージです。途方もないですね。 その絶望的な状況でも、賢く範囲を絞っていこうというのが、ベイズ最適化です。

    関数近似

    ベイズ最適化を用いると、平均と標準偏差の確率分布が出てくる。点が与えられた場合はその値に収束するので自信を持ってそうだといえるが、それ以外の値はあてずっぽうなので、点以外の場所は自身がなくなる(≒分散が大きくなる)。つまり、グラフは下記のようになる。



    A Tutorial on Bayesian Optimization ...より。新しい点が与えられると、自信のない領域(青くマスクされた領域)が減っていることが確認できる。

    最適化のための指針、Acquisition function

    また、上の図のAcquisition Functionというのが、最適化のために次の点を探す指針となる。 acquisition関数にはいろんな物を設定できるが、EIやUCBなどが代表的。UCBの式は、平均と標準偏差の確率分布で、下記のようにあらわされる

    \displaystyle{UCB=\mu(x)+\sqrt{\beta}\sigma(x)}

    この関数の最大値を次の点に選ぶ。 第一項目の{\mu}が近似関数なのでその最大値を選べばよいが、自信のないところも探したいそれが第二項である*3。ここから新しい点を選んで、また関数近似してを繰り返すのが、ベイズ的最適化です。

    注意

    とても強力な手法だが、下記あたりはアルゴリズム上の欠点として注意したいところ。

    • 関数近似逆行列計算が入るので(データ数)3オーダーで計算が重くなっていく
    • どこからも離れた点では分散が大きくなるので、次元が大きくなると**選ばれる点が端っこに寄る

    ※今ではこれを解決するようないろんな工夫もなされているっぽい。

    まとめ

    実験的に最適化を行ってみました。ここ三年程好景気なので、波をつかめばかなり儲かるっぽいですね。あとはチャートと自分の作ったアルゴリズムを信じるのみ!!

    また、ベイズ的最適化は、探索と活用のバランス次第では、局所解に陥る可能性もあるので、試行によっては解が変わることもあります。安定させるためにまだ色々と改良できると思っていますので、試行錯誤を繰り返していきます。もうちょっとテストもしないといけなさそうだなぁ。

    とりあえず時系列データはこのあたりで一旦置いておいて、明日は深層学習について触れたいと思っています。それではまた明日!

    *1:手数料は考えない

    *2:実装上最小値にしたかったので、評価値に-1をかけている

    *3:いわゆる探索と活用のトレードオフ

    PROCRASIST