seri::diary

プログラミングのこととかポエムとか

2018年をふりかえって

今年は仕事を辞めて大学院生になったりして変化の大きい年だったと思う。 例年のように振り返ってみる。

1月-3月

今年で一番忙しかった時期だと思う。引っ越しと入学手続きと退職の3つが重なり、まさに「公私共に忙しい」というやつだった。今考えると、大学卒業以来初めて仕事を辞めるという大きな変化のタイミングではあったのだが、感慨に浸る余裕もなく、ただ大量のタスクをこなすだけの毎日だった。

仕事の方は3月頭ぐらいまで結構デカい機能の開発をしたりしていたが、並行して引き継ぎ資料を大量に作っていた。在籍中は普段から知識がブラックボックスにならないようにドキュメントはこまめに書く方だったが、それでも引き継ぎ資料を書いていたら、結構自分しか知らなそうなことが残っていたのを発見したりして、完全なオープン化は難しいとつくづく感じた。もっとも、前職ではほぼ一人でクレカ決済用のマイクロサービスの設計と実装をしたり、社外システム連携の調整と設計と実装を複数案件やっていたりしたので、多少ブラックボックス化してしまうのも無理もない仕事内容だった気がするが。

f:id:serihiro:20180324155150j:plain
東京を離れる数日前に家の近くで撮った桜

4月-6月

筑波大学院での生活がスタートした。最初は朝から夕方まで講義出てたらめっさ疲れるかなと思ったが、会社員時代よりは楽だったようでそこまで肉体的にはキツくはない、というのが最初の数週間の印象だった。
講義はとりあえず難易度を気にせず、シラバスを読んで面白そうなやつを片っ端から取ったら、どうも取りすぎてしまったようで6月末ぐらいにレポートラッシュがやってきて軽くヤバかった。秋学期も講義取りすぎてヒーヒー言ってるのでこの頃と比べても何も進歩していない。それでも受講した講義は全部単位が取れたのでまぁよかった。

serihiro-graduate-school.hatenadiary.jp

研究の方は「HPCで深層学習を何となくやる」以外の何もテーマが決まっていなかったので、事前調査のためにTensorflowやChainerMNのホワイトペーパーを読んだり深層学習関係の本を読んだりしてデータ並列とモデル並列とは何かみたいなことを調べていた。結果的に、この辺の並列深層学習ネタは現在研究のスコープから外れているのだが、計算ノード並べて同期データ並列でスケールさせるのが現在の世界的なトレンドではあるようなので、そのベースとなるアイデアと実装方法をじっくり調査できたのは良かったと思う。

あと筑波大学内の計算科学センターにNVIDIAのV100とP100が刺さってる計算ノードで構成されたPre-PACS-Xという実験用HPCクラスタがあったので、そこでChainerMNのサンプルを動かしたりもしていた。ただ、研究テーマは全く決まらずに徐々に焦りを感じ始めた。同じ研究室のM1の学生は、学部時代から研究成果を出しており、その延長で研究を続けているため大学院でも研究の方向性は入学時点で決まっているようだった。一方で、自分は新参者なのでゼロからすべて考えなくてはならない。特に何も考えずにホイホイ大学院に来たはいいが、どこにでもいるウェッブエンジニアに過ぎなかった自分の場合、研究に関しては圧倒的なビハインドを抱えてのスタートだった。

また、研究室内ではB4,M1を対象とした輪講を週2でやっていて、「High Performance Computing: Modern Systems and Practices」というHPC関連の技術を網羅した本を読んだ。HPCというかコンピュータの歴史から始まって、HPCでよく採用されるNUMAなどのアーキテクチャの説明、B/Fの概念、Linpackなどのベンチマークの紹介、アムダールの法則を中心とした分散処理システムのパフォーマンスの話、OpenMPやMPIの使い方まで多岐に渡る内容だったが、マトモに計算機アーキテクチャを勉強したことがなかった自分にとっては本来学部4年間で勉強してくるはずの計算機アーキテクチャの基礎を一通り学ぶことができて大変為になったと思う。この本自体は結構表現が回りくどかったりして読むのが大変だったのが正直な感想ではあるが、これからHPC上で何か動かしたい人が必要な基礎知識を学ぶには良いと思う。

High Performance Computing: Modern Systems and Practices

High Performance Computing: Modern Systems and Practices

学業以外の活動としては、大学院生らしくインターンに応募したりしていた。おっさんだし、まだ全然研究で成果を出してないのでまさか書類選考を突破できるまい、と思っていた企業の書類選考+コーディングテストを何故か通過して面接まで行ったりしまいビビり、面接では前職時代からよく読んでいたブログを書いていた人が出てきてさらにビビったが、紆余曲折あってこの企業のインターンには参加せずに、先にブログに書いたようにTreasure Dataのインターンに参加することになった。今年はインターンどうするかはまだ考え中だが、修論の進捗が良くて時間的に可能であれば、今年行かなかった企業のインターンに再挑戦してみたいものである。

f:id:serihiro:20180601070148j:plain
森でしょうか

7月-9月

7月もまだ春学期の講義期間ではあったのだが、ほとんどの講義が6月で終わってしまうため時間ができた。
時間ができたからと7月から始まる神経科学の講義をうっかり取ってみたらかなり前提知識が要求される講義で全然ついていかなかったため、慌てて神経科学の本を一冊買って知識を補強することでなんとか単位をゲットした。

Fundamentals of Computational Neuroscience

Fundamentals of Computational Neuroscience

結果的にはcNNのConvolutional層,Pooling層の処理と視覚野の処理の類似点や相違点などを学べたので面白かった。ニューラルネットワークの字の如く、本当に脳でやっている計算処理がベースなのだなということを改めて実感した。脳から模倣したのはパーセプトロンだけだと勝手に思っていたので新しい発見だった。

8月からはTreasure Dataのインターンに参加した。詳細は以前書いたエントリに詳しい。

serihiro.hatenablog.com

感想としては、久々に仕事をして純粋に楽しかった。大学に入って以来全く余裕がない日々だったので、このタイミングで学業から少し離れて、長年の馴染みのあるソフトウェア開発に没頭できたのはかなり精神的に良かったと思う。自分のキャリアとしてはBtoBのサービス開発(転職サイトの企業向けシステム、飲食店向け予約システム etc)に長く関わってきたという背景もあり、同じくBtoBであるTreasure Dataでの仕事はカルチャー面でやりやすかったと感じる。

10月-12月

1月から3月が一番忙しかった時期なのに対し、この時期は一番つらい時期だった。

何がつらかったかといえば、この時期にM1の学生は修論中間発表のようなものがあり、何人かのグループに分かれてそれぞれの研究進捗を毎週交代で発表せねばならない。*1別段、研究がちゃんと進んでいればビビる必要はない。しかしだ、自分の場合は自分の発表の番が回ってくる2週間ぐらい前まで、人に話せるような内容の研究ネタがなかった。先に書いたように、先行研究のサーベイをしてChainerで遊んでいましたという話ならできるのだが、それは研究ではない。他の学生がどんな発表をしているかというと、一番進んでいるものではすでに学会で発表済み、そうでないものでも、最低限何らかのアイデアはあって、そのアイデアを実現するための何らかの実験を行ってその結果を報告している、というものだった。つまり自分のようにサーベイだけしてました、という進捗は自分のグループ内では報告されていない。それを見て実にヤバいということに気づいてしまった。

自分でアイデアを出して研究ができないのなら仕事辞めてまで大学院に来た意味はあるのか?そもそも入学できてしまったことが間違いなのではないか?みたいなことも考えてしまい、この時期は随分とどんどん悪い方悪い方に考えが向かってしまっていた。

しかし、そうは言ってもアイデアがないものは仕方がないので、ホントに春学期に読んだ論文の紹介でもするしかない。。と思っていた矢先、Chainerで遊んでいた時に偶然にもプロファイルしていたGPUのプロセッサ利用率とChainerのログから算出した1 iteration辺りの処理時間の相関性から、とあるアイデアをギリギリで思いつき、そのアイデアをChainer本体で実装することを最終ゴールとした内容でどうにかこうにか自分の発表をまとめることができた。やれやれである。
この発表の前の週はロクに眠れずにかなり肉体的にも精神的にもキツい思いをした。春学期の講義ではまぁ良い成績が取れていたので完全に油断していたが、大学院の修士課程というのは研究を行い、あわよくば対外発表もして、最終的に修士論文としてまとめることが目的の場所である。なのでこういったチェックポイントが必ず存在する(次回は来年7月の中間発表)。それを忘れてはならぬと肝に銘じさせる出来事であった。

しかしまぁ、上記の出来事から1ヶ月ぐらい経った今時点でも、これが研究になるのかどうか全く自信がない。自分の指導教官も深層学習が専門ではないので具体的な助言ができないようなので、全て自分で考えて進めていくしかない。自分に何か強みがあるとしたら、馴染みが薄い言語やプラットフォームでもキャッチアップするのが多少人より早いぐらいのものなので、とにかく今は自分のアイデアを実装して、動くものを使って客観的な評価を行い判断するしかない。

講義の方は相変わらず取りすぎてしまい、春学期同様に期末のレポートラッシュを先週、今週と乗り切ったばかりである。ただ、秋学期は実装の課題が多く、その辺は論文要約や論述ばかりだった春学期よりも楽ではあった。まぁ論文要約の講義もあったのだけど、最近は流石になれてきたのか英語の論文も大分早く読めるようになった気がする。英語に慣れたというよりは論文そのもののフォーマットに慣れて、どこをどう読めば抑えるべきポイントが分かるかが分かった、という感じである。春学期の頃はとりあえず全部精読していたが、今はもう少し効率の良い読み方ができるようになった気がする。多少は進歩したのかもしれない。

総括

  • 学業については計算機アーキテクチャ周り、並列処理、分散処理に関して新しい知識が多く得られて良かった
  • 研究については先に述べたようにまだまだこれからという感じで、今年は何も成果を出していない
  • 私生活については会社員時代と変わりはない

*1:筑波大生向けに説明しとくとCSセミナーの話である

『1日ひとつだけ、強くなる。』を読んだ

プロゲーマーという立場で書かれてはいるが、内容は格ゲーに特化したものではなく、割とどんな分野でも応用の効く内容だと感じた。

特に、ソフトウェアエンジニア(以下SWE)として働いていた身としては、SWEとしての仕事と「eスポーツとしてのゲーム」との共通点を感じる部分が多かった。SWEもウェブ上のメディアで成長に関するトピックが頻繁に話題に上がる。実際自分も自分の成長について悩む事がこれまでに少なくなかった。SWEをやっていると、知らなければならないこと、身につけなければならないことが延々と増え続けるので、その中でどのような道を決定して自分を成長させていくのか、ということは実際難しいことだと思う。

そういった『成長』とどう向き合っていくか、ということがこの本の主題のように思った。
強くなるには成長が必要だが、どのように成長するか。成長するために新しいことを試すにはリスクがある。しかし、一方で変化を恐れたり、怠慢して何もしないといずれ時代遅れになって通用しなくなり、結果的に自分の価値を失う。SWEとして働く世界においても全く同じだと思う。

本書ではウメハラさんが彼の後輩から「どうして成長しないといけないんですか」と聞かれたことがあるというエピソードが紹介されている。*1それに対してどう答えたかは書いてないが、代わりに成長に関してどう考えているかが述べられている。

成長しながら力をつけることができれば、自分が楽しいし、いい結果も生まれやすい。 だから、成長は義務などではなく、楽しく生きて、この時代にコースアウトしないためのツールとて考えればいいのではないか。

(中略)

コースアウトする可能性があったとしても、より良い走りをしようと前に出て戦う。そのときは駄目だとしても、成長(変化)を続けて、次のレース、次の次のレースで良い成績を出すようにする。 このような姿勢でいることが、最終的に自分の身を守る。僕は、そう考えている。

自分としては同意出来る考え方である。新しい言語、アルゴリズム、設計手法なんかを使えるようになったりして手数が増えたり、前よりも早く書けるようになったりすると単純に楽しいし、それが結果的に自分の強みにつながる。最終的に報われないこともあるが、それでもまずは楽しむためにやる、という考え方で取り組む姿勢はSWEにとってもマッチするのではないだろうかと思った。

もちろん「それでも成長するだけの価値は感じられないのでやはり疑問が拭えない」という人もいると思うが、どこかで無茶振りをされたりして急に勉強したりしなければならないケースは仕事していると大体どっかである。そういう時に役に立ちそうな考え方が本書には多く記載されている。今後も自分は自分の成長について悩んだら読み返したい一冊である。

*1:「リスクを負わない姿勢が一番のリスクになる」の章より

Treasure Data Summer Internship 2018 に参加した #td_intern

8月13日から9月28日までの約Ⅰヶ月半に渡ってTreasure Dataのインターンに参加してきた。
毎年インターンに参加した学生が報告ブログを書くのが恒例行事になっているので、自分も書いてみることにする。 なお、Treasure Dataの方には、ブログを書くことおよび成果発表スライドを公開することの許可は得ている。

インターンで何をやったのか

今回のインターンではDigdagの機能開発を主に行った。DigdagとはTreasure Dataが公開しているオープンソースのワークフローエンジンであり、似たようなproductとしてはAirFlow、Luigiなどがある。

メンターとして @muga_nishizawa さんと @komamitsu_tw さんの2人にお世話になった。

インターンの成果

Treasure DataのMountain Viewオフィスで行った成果発表プレゼンに使った資料にまとめまっているのでこちらを参照。なお日本オフィス側にもZoomで配信しながらプレゼンを実施した。

外部のデータストアにアクセスできるOperatorを作った

github.com

Digdagには、task間でデータを共有する手段としてstore parameterという、hash形式でオブジェクトを共有する仕組みがある。しかし、一度workflowの実行が終わってしまうとそのデータを次の実行時に引き継ぐことができなかった。スケジュール実行などで、前回の実行時に作成した値を次の実行時にも参照したいというニーズを満たすには、ユーザが自分でscriptを書いてshell operatorなどで実行し、どこかにその値を保存しておく必要があった。
それを気軽にoperatorだけで実現するために、外部のデータストアにDigdagからデータを保存、およびロードできるようにする仕組みを実現するOperatorを実装した。利用できるデータストアとしてPostgreSQLとRedisをサポートしている。詳細はdocument を参照していただきたい。*1

digdag-uiのログを見やすくした

github.com

digdag-uiのsessionページなどでworkflowのログを閲覧できるのだが、1つのworkflowが出力するログ全てが一箇所に出力されてしまうため、どのログがどのtask(attempt)に対応しているか分かりづらくなるという問題があった。
なので、試しに単純にattemptごとにログを分割して表示するようにフロント側を修正してみたら、ええやん、ということになりマージされた。

DigdagのREST APIでレスポンスできるattemptやsessionの数を増やせるようにした

github.com

Digdagは内部で組み込みwebサーバを起動しており、REST形式のAPIを提供している。そのAPIの中に、指定されたprojectのattemptやsessionの一覧を返すEndpointが存在するが、一度のリクエストで返せる件数が 100 とハードコーディングされていたので、それ以上の件数を一回のリクエストで取得できなかった。大量にattemptやsessionが存在するprojectの場合にこれだと不便という声があったため、 100 以上に上限を増やせるようにした。何も設定しなければ、これまで通りの 100 が上限として適用される。

tdプラットフォームの実行済みjobの結果をexportするOperatorを作った

github.com

tdプラットフォームに対してQueryを実行した結果などを、後から別のデータストアなどに出力したい場合に使えるOperatorである。

今まではDgidagのオペレータ経由でQueryを実行することはでき、その際にオプションを指定することでQueryの結果を別のデータストアなどに出力することはできた。しかし、同じQueryの結果を、複数のデータストアに書き出すことができなかった。(例えばtdプラットフォームの別テーブルとシステム連携用に自前で運用しているFTPサーバの両方に結果を書き出したい、というような場合)

tdオペレーターで実行したjobの件数をstore parameterに保存するようにした

github.com

詳細はPRを見て欲しい(Digdagを普段から使ってれば見ればわかるハズ)。

Kubernetsに関する調査

詳細は↑のスライドを見て欲しいのだが、コンテナが使えるディスクサイズの限定とコンテナからEC2 APIへのアクセスを遮断する方法について調査した。

この辺の調査結果をどう活用するかについては、 10/17に渋谷で開催される PLAZMA TD Tech Talk 2018 at Shibuya : Part 2 のMugaさんのセッションを聞いていただければと思う。

techplay.jp

こんな感じで働いた

インターン前半は東京オフィスにて勤務し、後半は主にUS Mountain Viewオフィスにて勤務した 。

2人のメンターのうち @komamitsu_tw さんは東京オフィス勤務で、 @muga_nishizawa さんはUS MountainViewオフィス勤務であった。そのため、東京オフィスで働いていた間は主に @komamitsu_tw にサポートしていただきながら仕事を進め、同様にMountainViewオフィスでは @muga_nishizawa さんにお世話になった。

8月13日〜8月31日 / 9月25日〜9月28日

東京オフィスにて勤務した。

初日にいくつかDigdagのタスクを振ってもらい、それについて自分が簡単なDesign docを書いて、それを2人にメンターに見てもらって問題なければそのまま実装に入る、という流れだった。普段、チームでwebサービスなどの開発をしている人なら、恐らく日常的に実践しているような開発フローだと思う。

f:id:serihiro:20180928131552j:plain

また、東京オフィスでは毎日ケータリングでランチを無償で提供していただき、実に最高であった。普段はコンビニ弁当と松屋で生きているので、久々の栄養バランスの取れた食事は大変ありがたかった。

9月4日〜9月21日

US MountainViewオフィスにて勤務した。

f:id:serihiro:20180904093428j:plain

USで勤務することになった経緯としては、メンターの @muga_nishizawa さんがUS在住であり、現在Digdagについては @muga_nishizawa さんがメインでメンテナンスをしているということもあったので、時差がある状態でリモートでコミュニケーションを取るよりは同じタイムゾーンで直接会話できる環境で働いた方がやりやすかろう、という会社側のお気遣いによるものである。

英語で1on1していただいた話

USにいる間も普通に開発をしていたが、せっかくUSに来たのだからということで、MountainViewオフィスのエンジニア以外の方と何人か1on1をさせていただく機会をいただいた。もちろん英語である。

繰り返す、英語である。

自分の英語は大学院ブログにも書いているがTOEIC700点のスキルしかなく、Speakingに関しては中学校時代で完全に止まっていた。
MountainViewオフィスの方々としても、はるばる日本からUSまで1人でやってきた大学院生(おっさん)がここまで英語が話せないということについてはきっと驚いただろうが、自分自身もこんな英語力でいきなり外資企業のオフィスに来てしまって正直驚いた。人生、それなりに歳をとってみても、まだまだびっくりするような事態が突然身の上に起こることがあるのだな、ということを久しぶりに思い出した。

結果的に、英語1on1は何度か回数を重ねる度に多少マシになって、何度も聞き返しながらも、かろうじて高校1年生ぐらいの英語を使って自分の専攻について説明したり、相手の業務内容について質問できるようにはなったので、せっかくだから日本に戻ってからもちゃんと英語勉強を継続しようと思った。近々TOEICの勉強を再開する*2のとオンライン英会話サービスとかを始めようと思っている。

帰国する頃になると、疲れていたのか以下のような謎ポエムをインターネッツに残しているが、きちんとした人間らしい高度なコミュニケーション手段を使えるにこしたことはないので、さっさと英語は勉強した方がよいというのが今の結論である。

所感

実際に働いていたのは33日間ということで、かつてフルタイムで働いていた身からすると短期間であるため、その短い期間の中で、どのように早く立ち上がって確実なアウトプットを出していくかが今回のチャレンジだったように思う。結果として、ビビり過ぎて全体像が把握できそうな単純なタスクからやりすぎたか、という反省はあるが、Treasure Dataのメンターのおふた方は自分のアウトプットについて概ね良い感想を抱いていただいたようなので安心した。

自分はフルタイムで働いたことはあるがインターンは初めてという珍しいパターンだったと思うが、TreasureDataの場合は両者の間にほとんど差異はなかったように思う。逆に、そのように働ける学生でないとインターンとして採用されるのは厳しいかも知れないが、興味があって腕に自信のある学生はぜひ来年応募してもらえればと思う。*3

*1:このOperatorについては別途解説記事を書く予定

*2:院試以来サボっていた

*3:多分来年もあるはず

システム運用の現場でしか学べないことは他メンバーに積極的に経験してもらうべきだった

基本的に自分はタスクを拾いすぎてしまう傾向にある。それに加えて比較的朝型なこともあり、前職ではエンジニアの中で一番朝早く出社していることも多かった。*1

その結果どうなるかというと、朝出社して見つけた運用上のトラブルは大体自分がとりあえず手を付ける状態になっていた。前日の夜間バッチやその日の早朝に動くバッチがコケて問い合わせが来ているのでそのリカバリをする、前日にデプロイした後レスポンスが高くなってアラートが出ているのでその調査をする、web appがやたらと500系エラーを吐いているのでBugsnagを見る、等々。

出社している以上無視するわけにもいかないというのもあるが、見つけてしまうと放っておけない性格ということもあり最優先でこれらの対応をしてしまっていた。お陰で前職で触っていたproductについてはかなり広範囲の知見があり、その行動がそれなりに社内での評価につながっていたのではないかと思われるのだが、一方で今はその行動については後悔している。

そういう球拾いを自分だけがやりまくっていた結果、何が起きたかというと、自分しか対処できないトラブルのようなものが増えてしまった。そしてその状態でそのチームから離れてしまった。もちろん引き継ぎ資料は大量に書いたし、自分が何か早朝とか深夜とかに対応した日は、その日何があってどういう対応をしたかはesaなどに障害内容と作業内容を決められたフォーマットで記載して貯める運用をしていたし、それなりに大事であればその日のうちにmtgをセッティングして口頭でサーバーサイドチーム内で説明した。知見を共有するという点では出来る限りのことをやったという自負はある。しかし、SREなどをしていてシステムの運用を経験したことがある人なら分かると思うが、障害対応において「知っている」ということと「やったことがある」というのでは経験値にかなりの差がある。自分は他のメンバーが「やってみる」という経験を大分奪ってしまっていたと思う。 早朝や深夜では自分に限らず気づいた人間が真っ先にやるしかないのだが、それでも、もし自分の作業中に出社してきたら一緒に調査するとか、実際にやった対応をステージング環境とかで一緒にやってみるとか、もう少し実践的な経験を積ませるようなことを意識すべきだったと思う。人が少ないベンチャーで常に余裕がない状態ではあったが、人が少ないからこそ、現場で学ぶことが一番の近道であるようなことを経験してもらってレベルアップしてもらうべきだった。新しい言語やライブラリの使い方はいくらでも好きな時間に学べるが、いざシステムで障害が起きた時の対応方法やその後のリカバリはいくらマニュアルを作ってそれを学んでもらってもカバーしきれないところがある。

だからこそ、普段から知識だけでなく機会をシェアできるような運用の仕組みを作らないといけないと感じる。例えば、以前、受託開発をしていた時代に常駐していた客先では、社内からの問い合わせを受け付ける窓口のエンジニアを当番制で決めており、何かあったらまずはその当番のエンジニアが調査を行うという仕組みを作っていた。この仕組は非常によくワークしていて、プロパーだろうがSESだろうが新人だろうがベテランだろうが強制的にアサインされるため強制的に知見を吸収する機会を作ることができる。こういう機会を設けて、組織として運用経験をシェアするような仕組みが必要だと感じる。インフラエンジニアがいるから安心ということは全くなく、むしろサーバーサイドに関わるエンジニアがSQLを書いたりhotfixを出さないと治らない障害の方が圧倒的に多い訳なので、専門のSREチームを構築できないような規模の組織では(多分そのケースがほとんどだと思うが)サーバーサイドエンジニアが運用知見をどれだけ持っているかが運用において重要だと今にして思う。

*1:多分9時前には大体出社していたし、忙しい時は7時台ということもあった。

春学期に読んだ論文まとめ

といってもあまり読めてない気がする。6月以降は完全にレポートに忙殺された。

とりあえず読んだ順に紹介。

深層学習関連

TensorFlow: Large-Scale Machine Learning on Heterogeneous Distributed Systems

TensorFlowのwhite paper。TensorFlowの歴史、google社内での利用用途、計算グラフの構築と実装、parameter serverを使った並列訓練の話などかなり多紀にわたってtensorflowについて紹介されている。

今でこそdefine by runのアプローチが柔軟で良いとされる風潮にある気がするが、この論文を読んだ限りではモデルの表現の柔軟さと実装時の最適化を両立できるdefine and runのアプローチも悪くないのではないかという気がする。論文によればTensorflowは同一の計算グラフでモバイルデバイスからサーバ上でまで同じコードで実行できるとあり、実際Androidでも学習済みモデルを使った推論が可能だし、MobileNets というモバイル用に省メモリで動作できるcNNも提案されている。広いプラットフォームを持つgoogleとしては実行とネットワークの定義を切り離すことは会社としても重要な戦略であったと考えられる。

Large Scale Distributed Deep Networks

TensorFlowの前身であるDistBeliefという機械学習フレームワークの紹介。こちらはwhite paperというよりは、parameter serverを用いた分散深層学習のアルゴリズムとそれを実現するシステムアーキテクチャの話題が中心である。

ここで扱っているアルゴリズムは非同期に勾配を更新するDownpour SGDと、バッチ処理で非同期に勾配を更新するSandblaster L-BFGSである。正直、両者の根本的な違いがよく分からんかったのだが、前者はパラメータープロセスは1台で、後者はパラメータサーバを複数台に分散し、それらに指示を出すコーディネータープロセスが全体の処理を管理する点が異なるらしい。

ChainerMN: Scalable Distributed Deep Learning Framework

日頃お世話になっているChainerの分散プロセス実行ができるように拡張してくれるライブラリのChainerMNについてのwhite paperである。 書かれている内容はChainerMNの紹介がメインだが、2章のPreliminariesで深層学習、分散深層学習についての解説がとても細かく載っているので、ここを読むだけで深層学習と分散深層学習についての基本的な知識を得られると思う。データ並列とモデル並列の図解も*1とてもわかりやすい。

A Data and Model-Parallel, Distributed and Scalable Framework for Training of Deep Networks in Apache Spark

Apache Spark上で動作するモデル並列訓練を行うための分散深層学習フレームワークの紹介。 実装はspark上で行列計算にJBlas、自然言語処理用にMalletを使用して全結合NN、cNN、LSTMを実装した、とあるが、コードが全く出てこないのでどのように実装されたのかよくわからない。

パラメータの更新には独自の分散バックプロパゲーションを考案したとあり、これがこの論文の貢献のようなのだが、数式を見る限りはどの辺が新しいバックプロパゲーションなのかよく分からなかった。。モデル分散してるから行列を列単位に分散してるのでバックプロパゲーションの数式がそういう風に変わるよねというのは分かるが、何か自分が見落としてるのかもしれない。。

AMPNet: Asynchronous Model-Parallel Training for Dynamic Neural Networks

これもモデル並列を行うための分散深層学習フレームワークを作ったぜという話。ただ、これはモデル分散といっても処理対象のパラメータの行列を分割するようなものではなく、レイヤーごとに別のノードに配置してパイプラインで処理を行うというアプローチ。

途中で読むのを辞めてしまったので若干正確性に乏しいが、深層学習の訓練においてはメモリからデータをロードする部分がボトルネックになったりGPUのプロセッサがボトルネックになったりとハードウェア的に処理の負荷がかかる箇所が計算フェーズによって異なるという事情があるのに対し、既存のフレームワークは一切それを考慮しておらず、ハードウェアを使い切れていないということを批判している。*2

そこで、レイヤーごとに実行するノードを分割し、1 iterationをパイプラインに分割してそれぞれの処理を別々のマシンで行うことで、各レイヤーの計算に特化したハードウェアを詰むことでハードウェアを使いきれるようになるね、という主張らしい。

Convolutional LSTM Network: A Machine Learning Approach for Precipitation Nowcastin

レーダーエコーで計測した降水データを時空間予測問題として解くための畳み込みLSTMを提案する論文。要するに動画のフレーム予測と同じようなアプローチで降水予測を行おうというアプローチである。

降水データは6分ごとの観測点の降水強度を行列にマッピングし、それを時系列的に連続する20枚のフレームを1 windowとして、最初の5frameをinput、残り15frameを予測する問題設定としている。

ネットワーク構成がユニークで、2層のLSTMレイヤーでencodingした後にそのレイヤーをコピーした別のレイヤーで予測を行い、inputと同じ行列サイズのアウトプットになるように畳み込んで予測フレームを出力している。なぜこのような構成にしたかは不明。あとなぜかloss関数がSoftmaxである。

専攻研究であるReal-time Optical flow by Variational methods for Echoes of Radar(ROVER)という数理モデルを用いた手法よりも高い予測精度を示した。

なお、この論文を参考に気象庁が提供するレーダーエコーデータを用いて同じようにConvLSTMで訓練を行ってみたが論文ほどの精度は出なかった。やはり10分毎計測、1km間隔の観測データしかないので、動画のように扱って予測するにはちょっと解像度的に厳しいかもしれない。

その他

Software model checking

とある講義のレポート課題で読んだやつ。 ソフトウェアモデル検査に関する研究についてまとめたサーベイ論文である。述語論理の用語が全然分からなくて訳すのすら大変だった記憶。

Halide: A Language and Compiler for Optimizing Parallelism, Locality, and Recomputation in Image Processing Pipelines

これも↑と同じ講義でレポート課題になっていたので読んだやつ。

最近一部で静かなブームとなりつつある、ハイパフォーマンスなコードを生成するコンパイラと記述するためのDSLを提供するHalide。Adobeの画像処理エンジンなどで使用されているらしく、この論文ではどのようにして画像処理一般で行うstencil計算をDSLでシンプルに記述してコンパイラで高速化したコードを吐くか、という手法について書かれている。Halideで生成されたコードと人間が手でチューニングしたコードのパフォーマンス比較もしているが、この結果がかなり凄くて、Halide DSLで34行で書いたコードが人間が手で書いた122行のCのコードよりも4.4倍高速だったりする。またcudaを使用したコードも出力でき、そちらでも人間が書いたどのコードよりも早いという結果になっている。人間が書いたアセンブラよりもccが吐くアセンブラの方が速い!みたいな感じだろうか。

面白いのでもう少し個人的にいじってみたい所存である。なお、Halideコンパイラを用いたコード高速最適化フレームワークTiramisuというやつもある。こっちも気になる。

*1:これはPFNの中の人のスライドでよく見るやつだが

*2:実際これはnVidia visual profilerとかでprofileしてみると分かるのだが、モデルサイズが大きいネットワークを回すとボトルネックになっているのはCPUからGPUへのデータ転送だったりしてGPUのプロセッサは50%も使ってなかったりするケースがある。これは実際に自分が実験した確認した実例である。

分散深層ニューラルネットワークの実装アプローチまとめ(2018年6月版)

これは何か

  • 自分が研究テーマとして扱っている分散深層ニューラルネットワークには、「分散」処理の部分において複数のアプローチが存在する
  • このエントリでは、自分の知識の整理のためにこれまで調べたことをまとめておく
  • (2018年6月版) と書いたのは、深層学習業界は変化が激しすぎて半年後には状況が変わっていてこのエントリが役に立たなくなることを想定しているためである(その時はまた新しい版を書こうかなと)

分散深層ニューラルネットワークとは

一般に「深層学習」と呼ばれる機械学習手法においては、隠れ層が多数連なる「深層ニューラルネットワーク」が使用される。
昨今では隠れ層の数を大規模に増加させて学習を行う手法が、主に画像認識の分野で効果を上げており*1、それに伴って訓練に要する時間も増加傾向にある。この問題を解決するために、スループットを向上させるためのアプローチとして分散処理を深層ニューラルネットワークに導入する手法が注目されている。

分散深層ニューラルネットワークでは大きくわけて2つのアプローチ、データ並列分散訓練モデル並列分散訓練 が存在する。以下、それぞれについて解説する。

データ並列分散訓練

f:id:serihiro:20180602214523p:plain

同一のネットワークの複製を複数用意し、それぞれのネットワーク(以下レプリカと呼称)に対して訓練データを適用して並列に訓練を行うアプローチである。 各レプリカは、通常のニューラルネットワークと同様に訓練を行い、個別に勾配を計算して重み行列やフィルタなどのモデルパラメータを更新する。

ここまでは並列でない通常のニューラルネットワークと変わらないが、データ並列分散訓練においては、各レプリカの訓練で得た勾配を、数iterationごと、あるいは毎iterationごとに集約して、平均化したもので各レプリカの勾配を置き変える。これにより、訓練データを分割して並列に訓練を行いつつも、逐次実行で全訓練データを用いて訓練したかのような結果を得ることができる。

この際、レプリカ間で訓練に用いる勾配の「鮮度」が課題となる。常に最新の(つまり鮮度が高い)勾配を用いて訓練を行うには、毎iterationごとにレプリカ間で処理同期を取った上で、勾配を集計し同期する必要がある。しかし、この集計処理がボトルネックとなり、ネットワークのスループット低下につながる。

一方で、勾配の更新頻度を下げすぎると、勾配が同期されるまでは、各レプリカ上のローカルな勾配を用いて訓練が進み、勾配の「鮮度」が劣化する。これにより、レプリカ間のモデルパラメータの差異が大きくなり、損失の収束を遅らせるなどの悪影響をもたらすという問題が生じる。

実際の深層学習フレームワークにおいては、同期処理によるスループット低下を防ぐために、非同期でレプリカ毎に異なるタイミングで勾配を更新する手法を採用しているものもある。以下、同期、非同期それぞれの手法について解説する。

勾配の同期更新

f:id:serihiro:20180602215407p:plain

ニューラルネットワークにおいて、勾配は訓練におけるBackwardフェーズでレイヤー毎に計算される。そして求めた勾配に学習係数をかけた値を使ってモデルパラメータを更新するのが一般的な手法である。

勾配の同期更新においては、モデルパラメータを更新する前に、レプリカ間で同期を取り、各レプリカで求めた勾配の平均値(以下、平均勾配と呼称)を用いて全パラメータを更新する。つまり、各レプリカで計算した勾配をモデルパラメータの更新に用いるのではなく、平均勾配を用いて各レプリカのモデルパラメータを更新する。これにより、常に最新の平均勾配を用いて訓練を行うことができる。

このアプローチは、具体的なプロダクトとしてはChainerMNが採用している。バージョン1.3では、同期更新時に他の処理と一部オーバーラップさせることで同期による処理遅延を低下させる DOUBLE BUFFERING という機能が搭載された。この機能を使うと、一時的に古い勾配のまま訓練を行うワーカーが生じるが、精度には影響のないレベルであるとのことである。 *2

勾配の非同期更新

f:id:serihiro:20180602221811p:plain

非同期に更新を行う場合、各レプリカ間で同期を取らずに平均勾配を求めるため、勾配の集約と平均勾配の計算を行うための別のプロセスが必要である。かつてGoogle社内で使われていたDistBeliefという深層学習フレームワークにおいては、パラメーターサーバという専用プロセスがこの役割を果たしている。Googleのプロダクトとしては後発であるTensorFlowを分散実行させる場合においてもこのアプローチは同じようではある。

非同期更新においては、各レプリカが求めた勾配をパラメーターサーバに送信し、その勾配を用いてパラメータサーバが平均勾配を更新する。各レプリカは次のiterationの訓練を開始する前に、パラメーターサーバから最新の鮮度の高い勾配を取得し、それを用いて訓練を行う。

各レプリカは同期を取らずに平均勾配が更新されるため、レプリカ間で使用している勾配に差が生じている状態が発生する。このことにより、より良い勾配を古い勾配を使った訓練により生じたより劣悪な勾配によって、悪化させてしまう事も考えられる。 このような最新のパラメーターサーバーにある勾配と比べて古い勾配を「陳腐化した勾配(Stale gradient)」と呼ぶ。陳腐化した勾配の影響を緩和するには、学習率を下げる、陳腐化した勾配を定期的に捨てる、ミニバッチサイズを調整する、等が考えられる。*3

モデル並列

f:id:serihiro:20180602220457p:plain

レプリカを作らず、ネットワーク上のパラメータを複数のプロセスに分割し訓練を行う並列化手法。 これにより、例えば前結合ネットワークにおいて、巨大な行列となった重み行列と入力ベクトルとの計算を複数のマシンで分散して実行することで、要求されるマシンスペック要求の低下*4と、並列化による高速化が望める。

一方で、ネットワーク的に分断されたマシンクラスタで実現する場合、各プロセス間のデータ通信が大きなボトルネックになると考えられる。そのため、全結合ネットワークやcNNではスループットが大きく低下する可能性が考えられる。この手法を適用して高速化を行うには各プロセス上での高い密度を高くすることができるネットワークが適していると考えられる。例えば、TensorFlow: Large-Scale Machine Learning on Heterogeneous Distributed Systemsではモデル並列の実装例としてLSTMを挙げている。

自分が論文を調べた限りでは、Apache Spark上に全結合ネットワークやcNNをモデル並列で実際に実装している例*5や、レイヤーごとに複数のマシンに分割し、訓練処理をパイプライン化して並列効率を向上させる実装*6もあるが、まだ実例が少ないアプローチであると考えられる。

2018年6月時点では同期更新・非同期更新どちらが良いのか

様々な条件下で両者のアプローチを比較した2016年の論文Revisiting Distributed Synchronous SGDによると、同期更新の方が非同期更新よりも収束速度も正解精度も高いことを示している。また、訓練を実行するワーカー数を増加させた場合も同期更新の方が収束速度がより効率的にスケールすることを示している。近年の画像コンペにおいても上位入賞者チームにおいて同期型が支持されているという意見もある。*7

よって、非同期型に特化した新しい手法が開発されたら状況は変わる可能性はあるが、2018年6月現在では同期型の方を選択する方が正解である可能性が高い。例えば2017年9月にarXivに投稿されたImageNetの訓練に関する論文、ImageNet Training in Minutesでも、同期型データ並列アプローチを使っていることが明記されている。

まとめ

訓練精度という観点で見ると、データ並列・同期更新が現時点では最も高い精度を得つつ、高いパフォーマンスを発揮できるアプローチだと考えられる。しかし、メモリに乗り切らない大規模なパラメータを扱う場合や、マシンリソースの有効活用という観点では、モデル並列を活用できるシーンもあると考えられる。*8

実際、自分は大規模なパラメータを想定したネットワークにおいてモデル並列訓練を行った場合の性能特性について研究する予定であり、現在c++Blas系ライブラリとMPIで、モデル並列ネットワークを実装している最中である(作るのはとりあえず全結合ネットワークのみ)。自分の研究成果については追ってまた別のエントリで紹介したい。

参考文献

*1:例えばここ数年の画像コンペでかなり使われているcNNの1つであるResNetは、作者らの報告によるとImageNetで152層のネットワーク、CIPHER-10で1202層のネットワークを構築した報告がある

*2:https://chainer.org/general/2018/05/25/chainermn-v1-3.html

*3:https://www.oreilly.co.jp/books/9784873118345/

*4:例えば大規模な行列計算を行うノードとそうでないノードとでGPUの搭載数を変えたりすることで、スペックを要求されない部分には低スペックなマシンを採用することができる

*5:https://arxiv.org/abs/1708.05840

*6:https://arxiv.org/abs/1705.09786

*7:https://logmi.jp/285424

*8:完全に余談だが、過去に非同期処理を多用するマイクロサービスを開発してきた元ウェブアプリケーションエンジニアの感想として、DistBeleifに代表されるパラメータサーバを用いたアーキテクチャの方が馴染みがあるので作ってみたい気持ちにはなる。

outputは最大のinput

outputがないとinputできない

仕事でも勉強でも、日々何かをinput ∈ {調べる, 勉強する, 調査する, 聞いてみる, まとめる, やってみる} しないといけないケースが多い。今は学生なので日々やってることの9割はinputである。

ただ、他の人はどうかは知らないが、自分にはoutputのイメージがないとinputが続かないという問題がある。outputのイメージがないと途中で迷走してやる気がなくなってやめてしまったりすることが多い。

例えば「とりあえずScala勉強してみよう」みたいなのがすごく苦手で、具体的な成果として何をoutputするかが決まってないと続かない。例えば「とりあえずPlay Frameworkのチュートリアルやる」みたいなのがすごく苦手で、大抵途中で飽きてしまう。「今度Play使ったプロダクトの開発するからPlayチュートリアルやってCRUD一式を持つweb appを自分で作って概要を知る」とか、「こういう設計をしたいけど分からないので類似プロダクトのコードを読んで設計を理解して自分のプロダクトに反映させる」みたいな感じでないと続かないタイプである。

なので何も考えずに勉強するだけ、というのもすごく苦手である。そのため、今取ってる講義は全部何らかの応用目的があるものだけに絞っている。とりあえず直近で使わないものは全部「競プロに使えそう」ぐらいなのだが、それでも「ただ単位のため」という目的しかないよりは遥かにマシである。

ささやかでもoutputしながらinputする

自分が普段採用しているスタイルはこれである。*1 以下は研究室で使っているpukiwikiに自分が調べたことをまとめているページである。 基本的に備忘録としてoutputしている感じだが、それ以外にも研究室の定例ミーティングで進捗を報告するときに「XXXについて調べました」というような報告をするときに、当該ページのURLを貼って報告する、という使い方をしている。

f:id:serihiro:20180602091248p:plain

あと今気づいたがMPIとかBLASみたいな、個別の研究に影響しない一般的な内容についてはあとで編集しなおしてQiita辺りに投稿してもいいかもしれない。

同様に論文も要約を書きながら読んでいる。

f:id:serihiro:20180602092142p:plain

論文をまとめるフォーマットはいろいろあるのだが、とりあえず自分の場合は以下のようにまとめている。

  • 3行でまとめると
  • この論文が批判していること
  • この論文の貢献
  • 以下要旨(ここはフォーマット決めてない)

f:id:serihiro:20180602092545p:plain

*1:もちろん具体的なソフトウェアを書くことがoutputの場合もあるが、直近だと具体的なブツが公開できてないので今回は説明を割愛。