技術記事 by 岡部 健

明日のES Modulesを今日使おう!(esm ライブラリ開発者 @jdalton による解説記事の翻訳)



この記事はesmライブラリの開発者@jdalton自身による解説記事
https://medium.com/web-on-the-edge/tomorrows-es-modules-today-c53d29ac448c
を本人の許可と協力を得て翻訳したものです。

ES Modules(ESM)の解説も含め、前回のブログ記事:
2018年9月時点のJavaScriptモジュール(ES Module/ESM)界隈の最新情報、これまでの経緯とこれからの見通しを解説
もあわせて読むとより全体像がつかみやすくなると思います。


オリジナル記事著者:

Go to the profile of John-David Dalton

John-David Dalton

JavaScript思想家, バグフィクサー & ベンチマークランナー • lodash開発者 • 前職 Chakra Perf PM • 現在 Web Apps & Frameworks PM @Microsoft  主張は個人の見解です。

オリジナル記事投稿日 3月29日

Tomorrow’s ES Modules Today!(明日のES Modulesを今日使おう!)

⚵ より深く知るには Node Module Summit videoをチェック

7ヶ月前、 私はNodeでESモジュールを有効にできる実験的なモジュールローダをリリースしました。100万を超えるダウンロード、数千のコミット、そしてJavaScriptコミュニティのアーリーアダプター達のとてつもない援助を経て、安定版リリースの準備が整いました!🎕🎕🎕

esmを紹介しましょう ー 高速でプロダクション(本番)利用可、パッケージ依存なしのESモジュールローダで、Node 6+で利用可能、素晴らしい開発者体験をもたらします!

これまでの環境のままで、開発者は好みのツールのオプションに “require” オプションをつけることで、 容易にESモジュールを有効に出来ます。

node -r esm  
mocha -r esm  
nyc -i esm  
webpack -r esm

AVA テストランナーはESMサポートのためにesmでdefer可能です。

加えて今回からは、パッケージ作者は以下のいずれかのコマンドを使用することによりesmが使えるパッケージを新規作成できます。

npm init esm (npm@6)
yarn create esm

-y フラグを活用することで、すべてのプロンプトに “yes”と答えられます

npx create-esm の使用例:esm有効化パッケージを新規作成している

Setting the Stage 舞台の前口上

esmを掘り下げる前に、あるバックストーリーを前口上させてください。

私は20年間JavaScriptを書いてきて、そのうち、オープンソースに参画して仕様を読んできたのが13年間です。 Web 2.0の時代に生きてきて、モダンなJavaScriptライブラリとフレームワークの誕生に貢献するために自身の役割を果たしてきました。JavaScriptのエコシステムでどのようなアプローチと姿勢がうまく機能して、どういうものが失敗して立ち消えになってしまのうかを眼の当たりにしてきました。

私はMicrosoftでtechnical program manager (PM) として6年間務めてきました。Chakra、MicrosoftのJavaScriptエンジンのperformance PMとして3年間務めた後、現在はWebプラットフォームチームのDeveloper Experiences PMです。Program management(プログラム管理)ではユーザーと共感し、ユーザの事情、機能要件、外部依存関係、潜在的なブロッカーを特定する必要があります。最初のアイデアから出荷されるコードまで機能を見守るために、チーム間、さらには企業間で作業する必要があります。

私はLodash JavaScriptユーティリティライブラリを作りました。LodashはUnderscoreライブラリのフォークとして始まりました。UnderscoreはNodeと同年にリリースされるとあっという間にそのデファクトユーティリティパッケージになります。3年後、私はLodashをリリースしますが、まったく何も継承することもなく、数百万のユーザに利用される最も依存される数が多いnpmパッケージに成長しました。 これを達成するには、開発者のプラクティスやアプローチはこちら側が修正していく問題として見なすべきではないというマインドセットが必要でした。エコシステムを押す代わりに、私は、彼らの様々なモジュールフォーマット、プログラミングスタイル、多様な環境設定をそのまま受け入れるようにしました。開発者達をブロックしてしまわないことで彼らの移行プロセスを苦痛のないものにしました。パフォーマンスクリティカルなシナリオについて改善し、開発をシンプルにさせました。信頼を養い、開発者がリーチしやすい何かを作ったのです。

これらの経験が、esmが互換性と相互運用性についてアプローチする方法を形成したのです。

Zero Configuration ゼロ・コンフィギュレーション

esm最初にリリースされたのは遡ること2017年8月ですが、そのときはNodeのESモジュールのフラグが外れるまで9ヶ月を残すのみでした。今、その8ヶ月後、NodeはESモジュールのフラグを外すことを暫定的にさらに9〜27ヶ月先延ばしにした上で、新しいNodeモジュールワーキンググループが形成 (私はメンバー) されました。そして、かつてのNodeロードマップは今後必ずしもその通りになるとは限りません。

現時点で「Nodeルール」に追従するのは、特にその仕様が未だ固まっていないような時は、正しい選択ではないように見えます。開発者はESMを選択し、すぐさまコーディングにとりかかれるようにすべきです。理想的には、ESMのサポートは、エコシステムが現在あるところに寄り添ったものでなければなりません。 esmは、ゼロコンフィギュレーションで可能な限りCommonJS (CJS) と 相互運用可能であるべきでしょう。今あなたが愛してやまないもの、アプリケーションパフォーマンスモニタ、バンドラ、 code coverage instrumenters、トランスパイラそしてタイプチェッカーは、引き続きそのままきちんと動作し続けるべきです。

NodeのESMプランでは今でも .mjsファイル拡張をモジュールパースのゴールを通知するためのデフォルト機構としています。しかし、Nodeの .mjs青写真は完全には書かれていないためJavaScriptエコシステムは、Babel 7やWebpack 4で見られるように責務としてかろうじて最小限の機能しかサポートしていません。 esmも追従して、すべてのesmオプションで.mjsを無効化してしまっています。これはつまり現状あなたが開発していくにあたり.jsがベストな選択であるということです。 esmは今日のESMから明日のESMへのブリッジです。 今後NodeのESMへの道筋が明確になっていけば、esmも開発者にその機能を解放するでしょう。

(*訳者注* 
@jdaltonに2018/09時点のNodeのESモジュールサポートの最新の状況を確認したところ、現在までさらに半年ほど、目に見えるような進展はないそうです。また今後、esmをNodeの標準ESモジュールローダとしてマージする心積もりはあるのか?という質問をしたところ、現状技術的にはマニュアルで esm をNodeにコンパイルすることは可能ではあるが、その心積もりはまるでなく、あくまでもこの記事で表明されている思想のとおり、仕様に沿った範囲で、移行パス、パフォーマンス、現実世界の利用シナリオに基づいて、引き続き改善を加え続ける、ということです。)

Mostly JavaScript is Mighty Good ほとんどがJavaScriptで書かれる事は大変素晴らしい

esmは完全にネイティブコードではなく、そのほとんどがJavaScriptで書かれていることから、本質的に劣っていると考えるかもしれません。しかし、ほとんどJavaScriptであることが、実は大きな強みとなっているのです。NodeのESMのサポートを追加するには、いくつかの外部のチームや標準化団体からの仕入れや協力が必要です。彼ら各々が異なるアジェンダ、優先順位、タイムラインを持っています。 NodeのESMサポートの統一されたビジョンに向けてすべての関係者を招集しようとするのは現実的ではありません。ほとんどJavaScriptであるということは、ESMのサポートが固まる間にesmはこれらの依存関係をゼロにする必要があり、他のアプローチよりも速い開発とより良いエコシステムのサポートを可能にします。これにより、esmをアンブロックするだけでなく、 vm.Moduleのようなネイティブヘルパーが利用可能になり次第活用できる自由度を与えます。ビルトイン機能をJavaScriptで記述することは何も新しいことではありません。Node自身のビルトインモジュールや、その現在進行系のESM関連はJavaScriptで書かれていますChakra, SpiderMonkeyV8のようなJavaScriptエンジンのパーツでさえ、セルフホストされたJavaScriptで記述されています。おそらくもっともエキサイティングなのは、esmがスタンドアロンのローダであるため、 ChakraCoreであってもJavaScriptエンジンを問わず動作可能であったり、極めて容易に Nodeに直接コンパイル可能であることでしょう。

Better than “Just works” 「ちゃんと動く」以上のこと

ESモジュールのシンタックスがあるのは素敵ですが、Nodeが仕事するために無くてはならないというものでもありません。適応させる障壁は驚くほど低い必要があり明確な恩恵が示されない限り、開発者はすでにこなれて確立しているCJSに固執してしまうことでしょう。これはつまり、 esm はESMに即座にシームレスに適応させる、それ以上のことをしなければいけないということです。

  • Resilient 堅牢性
    esm は独自のコンテクストで動作するのでプロトタイプやポリフィルの改竄に強いです。

  • Reduce pain points 苦痛なポイントを低減する
    esm はNodeのバージョン間APIの不一致加減を低減します。たとえば、require.resolverequire.resolve.paths はNode 8+での拡張ですが、Node 6では利用不能です。 esm を利用することで、たとえNode 6であってもこれらのAPIにアクセスできるようになります。

  • Improve performance パフォーマンスの向上
    esmエンジンコードキャッシュ を利用することで、あなたのアプリケーションのスタートアップタイムを改善させます。Parcel, Webpack, Yarn, そして数多くのElectronアプリケーションといったプロジェクトで今日から恩恵があります。

Powering-Up Developers 開発者をパワーアップさせる

esmは、モダンなシンタックスへの人工的な障壁を取り除き、開発者を増強するために存在します。

  • Full-stack フルスタック
    esmを使うことで開発者はサーバサイドとクライアントサイドの両方でESMを書けるようになります。例えば、esmSvelteコンポーネントのサーバサイドレンダリング を拡張しますし、 クロスプラットフォームアプリケーション(Electron)をすべてESMで完全に書くことを可能にします。(*訳者注* @jdalton によるESM版のelectron-quick-startが公開されているので、その具体的作法もつかめるはず)

  • Real-time リアルタイム
    Quokka.jsWallaby.js は、スクラッチパッドとテキストエディタと統合されたテスト環境を提供します、 VS codeのように。どちらのプロジェクトも、esmを利用することでさらなる面倒な手続き(ESMのみのローダーフック、MIMEタイプのジャグリング、またはコードベースのフォーク)なしで、リアルタイムのESMシンタックスのサポートができています。統合されたスクラッチパッドであるQuokka.jsでは、 esmはファイル拡張子なし、保存もされていないドキュメントの解析すらもサポートしています!

Pay it forward 事前に支払っておこう

私は、esmを開発している間ずっと、それが潜在的に他にインパクトを与える可能性について注視してきました。

  • Babylonで トップレベルのawaitをパースしてlintできるようにパッチ済み です。

  • Chakra, V8, JavaScriptCore,そして SpiderMonkey のProxy についてのいくつかの問題(issue)が、CJSモジュールの named exportsをサポートするための開発を通じて明らかになりました。

  • Nodeで --check--require オプションで問題なく動作するように パッチ済み です。

  • Nodeビルトインモジュール(*訳者注*fsなどのこと)にESM形式のnamed exportsがサポートされるように、esmの初期の実装に基づいてプルリクエストをしている最中(*訳者注*現在プルリクエストはマージ済み)です。

  • npm ES modules proposalNode モジュールワーキンググループの作成を促した)は、代替実装は可能であるとインスピレーションを与えるものとしてesmを取り上げています。

What’s Next 次は何か

esmは目出度く安定版リリースにはなりましたが、それでもまだ沢山すべきことがあります。

  • loader hooksの基礎はこれまでesmにあるので、Nodeの仕様が固まり次第、 esm は準拠します。

  • 私は、Nodeのバンドラ、 instrumenters、ローダそしてユーティリティの中心的パフォーマンスをJavaScriptエンジンがトラックできるNode tooling benchmarkの作成を任されています。

  • 私は、 npmでnpm init <create-pkg-name> が可能なようにチーム作りをしています!

  • 実験的WASMサポートが間もなく開始されます。wasmオプションを用いることで、CJSとESMの.wasm ファイルのロードが解禁されます!

これまで私は開発者たちのESMコードが きちんと動いた ときの彼らの感動を見てきましたし、esmがリファレンス実装として、そして何が可能なのか?を示す強力な実例として、貢献できることを願っています!


  • Go to the profile of John-David Dalton

    John-David Dalton

    JavaScript tinkerer, bug fixer, & benchmark runner • Creator of lodash • Former Chakra Perf PM • Current Web Apps & Frameworks PM @Microsoft. Opinions are mine.

2019年1月時点のJavaScriptモジュール(ES Module/ESM)界隈の最新情報、これまでの経緯とこれからの見通しを解説



具体的には、ES6(ES2015)より標準化されたimportexportをめぐる解説記事です。重要な割に日本語解説記事が大変少ないことと、いずれにせよこの手の記事は情報がすぐ古くなり賞味期限があるので、使い捨て記事、ナマモノ追加投入の意義があります。

そもそもモジュールとは?モジュール化することの重要性 世界はモジュールで溢れている

モジュール化 (Modularity)

1つの複雑なシステムを、機能完結的な部品と標準化された部品間インターフェースで構成すること。

単機能で独立した部品同士を組み合わせて全体システムを構成するという設計構想をモジュラー型アーキテクチャといい、システムをこうした部品(モジュール)に分割することをモジュール化といいます。

その対象は製品の物理的構造だけに留まらず、システム設計や生産工程、組織など多岐にわたります。

IT産業発展の原動力
パソコンはCPUやメモリ、ハードディスクなど標準化されたインターフェースによって接続されているモジュール化製品の典型といえます。

モジュール化は、(1)製品の複雑さを低減、(2)部品の組合せ自由度を向上、(3)設計変更時の局所的対応が可能といったメリット

USBインターフェイスで規格化されたUSB機器、PCならパーツの規格と、機能完結的かつ標準化された部品(モジュール)によるモジュール化は複雑なシステムを構築する際に当たり前のように採用されている手法です。

プログラミングとはそもそも単純な部品を組み立てていく作業であり、プログラミング言語であるJavaScriptもモジュール化が重要です。特に昨今、JavaScriptは大規模システムの構築に普通に利用されるようになりモジュール化は必須です。

ブラウザ戦争 混迷を極めるJavaScript界隈 node.jsとnpmの台頭 1990-

JavaScriptのモジュール化という超基本的な仕様の標準化がこれまで立ち遅れてきた理由は、JavaScriptがブラウザに付属してくるスクリプト言語である、という他に例を見ない特殊な事情によります。

ブラウザ戦争というIT界隈では悪名高い覇権争いがあり、JavaScriptの標準化は各ベンダーの政治的対立に翻弄され立ち遅れました。モジュール標準化もまとまらなかったということです。

  • 第一次ブラウザ戦争 (1990-2000年)
    Internet ExplorerかNetscape Navigatorの二択。 IEの勝利。

  • 第二次ブラウザ戦争 (2004-2014年)
    figure_1
    Mozilla Firefoxが2004年、Microsoft IEの完全かつ絶対的な市場支配に戦いを挑み、それから5年足らずでユーザー数を0人から数億人に伸ばすことに成功した。Googleも2008年、Chromeブラウザを発表して、それに続いた。Chromeは2012年にはFirefoxに追いついた。2014年には揺るぎない地位に納まったことで、第二次ブラウザ戦争は終結したとされる

2008年、GoogleはChromeのJavaScriptエンジンである、V8もブラウザから分離してオープンソースでリリースしています。これまでブラウザの付属物でしかなかったJavaScriptがブラウザとは独立してシステムのローカル環境で動作するようになったというのは結構大きな出来事で、2009年、V8をベースにイベント化された入出力を扱うJavaScript環境であるnode.jsがリリースされます。


node.jsは、非常に効率の良いWebサーバとしてサーバーサイドで活用されはじめると同時に、独自のモジュール/パッケージ管理システムであるnpmを発展させ(JavaScriptの標準仕様としてモジュールが存在しないので独自に発展させるしかしようがない)、2018年現在まで一大エコシステム・コミュニティを形成してきました。

2011- Browserifyの登場 “bundler”

JavaScriptのモジュール化の標準化が定まらない中、独自のエコを発展させたnodeベースの膨大なJavaScriptモジュール資産(npmエコ)をWebブラウザでも活用すべくBrowserifyが登場します。これは実際結構な力技で、当初私などはよくこんなハックが出来たなと衝撃を受けたものですが、npmの依存関係に従ってモジュールを単一のJSファイルにつなぎあわせる、というものです。現在は、webpackのほうが人気だとは思いますが、いわゆる"bundler"(バンドラ)と呼ばれるものの先駆的存在です。

2013- Reactの登場

Facebookはコンポーネントで仮想DOMを設計するReactをリリースします。Reactのコンポーネントはモジュールにそのまま呼応するので、Reactのドキュメンテーションではかなり早くからES6のimport/export準拠で書かれていました。ブラウザでモジュールが実装される前からこのようにES6準拠となるのは、同時期に台頭したトランスパイラ(Babel)と上記バンドラ(Browserify/webpack)に依存して技術を先取りできているからです。

従って、Reactなどの先進的なフロントエンド開発をする際には、もうトランスパイラとバンドラをセットで使うのがデフォという感じになっています。そして驚くべきことに、React開発陣が完全にこの大前提にたっているためにReactライブラリ自体をES6Module(ESM)の仕組みでimportさせるのにネイティブにESM対応モジュールをリリースしておらず、トランスパイラとバンドラにNPM用のモジュールを読ませて変換させるというリリースの仕方をしています。

現状、別に規格化されているわけでもなんでもないnpmというモジュール/パッケージ管理システムとそれを単一ファイルに統合するバンドラを前提にフロントエンド開発が当たり前のように行われている、というのは、すべてブラウザ戦争により標準化されたモジュール化が立ち遅れたことが原因で、なかなか不安定な過渡期であると言えます。

2014- ECMAScript標準化の加速

ブラウザ戦争が収束するのと並行して、ブラウザ世界でもJavaScriptの標準化= ECMAScript標準化が活発化していきます。

ES6(ES2015)ではようやくモジュールが標準化され、importexportの仕様が固まりました。今後も、 ES2015-2018と立て続けに続くようで標準化は順調に加速していくようです。

2018 すべてのモダンブラウザがES6-Module(ESM)を実装完了

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/import#Browser_compatibility

compati.png

より関数型プログラミング的な、dynamic import(動的読み込み)がFirefoxにはまだ実装されていませんが、とりあえず基本的なモジュール化はモバイルも含めすべてのモダンブラウザ(EdgeじゃないIE除く)で実装完了されており、プロダクションレベルでも積極的に利用可能な情況になっています。

Webブラウザがモジュールをそのまま実行してくれるようになってくると(本来そうであるべき)、わざわざなんか面倒で複雑なバンドラって必要?ということになってきます。ReactのJSXをトランスパイルしなければいけないというのも、実はTypeScriptのTSXで書いていたらTypeScriptがやってくれるのでBabelというトランスパイラは不要とかいろいろすっきりしそうな気がします。

実際今、自分はいろいろ外部ツールを組み合わせてセットアップしないとWebアプリケーションが開発できないという複雑な情況をシンプルにするために、Reactアプリでも基本TypeScript(TSX)だけ、Babelとwebpackなしでそのままブラウザで展開するようにしています。

Screenshot from 2018-08-19 16-48-09.png
Screenshot from 2018-08-19 16-46-44.png

https://github.com/kenokabe/esm-bundlerless
https://github.com/kenokabe/esm-bundlerless-react

ESM/NPM(CJS)2つの互換性のないモジュールシステムの問題発生

2018年になって、ようやくモバイル含めてもうすべてのモダンブラウザがES6-Module(ESM)が普通に使えるようになった、素晴らしい。ではこれから書くライブラリはすべてESMで行こう!それならわざわざwebpackつかって面倒なセットアップと開発フローはなくて済むんでしょ?

現状はこういう流れになりつつあるのは間違いありません。それが本来の標準化されたモジュール化なのだから。

しかしここで問題はすでに確立されたnode.モジュールのnpmエコがあるということです。nodeのモジュールフォーマットであるCommonJS(CJS)ば後発のブラウザ標準のESMと互換性はありません。2つのメジャーなモジュールエコが互換性なく並立しているというのが現状です。

すでにnpmエコにおいてもコミュニティは積極的にimport/export準拠でライブラリ・パッケージをリリースしているのですが、それはもちろんwebpack/browserifyのバンドラが処理してくれるという前提で皆そうしているようです。nodeはそもそもが自前のCJSモジュールベースで実装されているわけで、本来互換性のないESM対応についてはチーム内でもなかなか意見がまとまらないようで現状も混迷を極めており、早急な進展も望めない情況のようです。

静かにデファクトスタンダードになりつつあるnodeとESMのブリッジesmライブラリ

このような混迷した情況の中、あまりまだ知られていない(少なくとも2018/9月現在、日本語解説記事はまるでヒットしない)のがnpm/yarnに新たに実装された以下のコマンドです。

npm

npm init esm

yarn

yarn create esm

https://docs.npmjs.com/cli/init
によると、

Create a new esm-compatible package using create-esm:

ということで、実態は、
npm init あるいはyarn create する際に、
https://npm.im/create-esm
のコードをフックして「esmに対応した」新しいnpmプロジェクトを作成します。
実際に作成したに新規npmプロジェクトのpackage.jsonを見てみると、

{
  "name": "my-esm-lib",
  "version": "1.0.0",
  "main": "index.js",
  "module": "main.js",
  "license": "MIT",
  "dependencies": {
    "esm": "^3.0.82"
  }
}

となっており、このパッケージの依存パッケージ(dependency)(ライブラリ)としてesmが自動的に追加されているのがわかります。

esmcreate-esmは、MicrosoftのDeveloper Experiences PMでありnpmエコの中でももっとも多く依存されているライブラリlodash(関数型プログラミングのライブラリ)の開発者でもあるJohn-David Dalton @jdaltonによって開発メンテナンスされています。

esmライブラリのダウンロード数は増加し続けており、コミュニティの支持とともに確固たる地位を獲得しているように見受けられます。

nodeとESMのブリッジesmライブラリの役割

じゃあ、実際esmって何ができるのか?

webpackがnode/npmモジュールをブラウザでも活用できるようにしたのと対照的に、esmはブラウザで標準化されたESMをnode/npmでも活用できるようにするブリッジである、と理解すれば良いでしょう。

今後のJavaScriptのフロントエンド、あるいはサーバーサイドの開発者の基本方針としては、ES6以降で標準化されているESMでモジュールを書いていく、しかし同時にnodeのエコでもそのESM資産を無駄なく流用活用できるようにesmでブリッジできるので一石二鳥だ、そういう感じです。

esm(ライブラリ)の2大機能とは、

  1. ESMをラップしてnode/npmエコでシームレスに扱えるnpmパッケージ化
  2. nodeコマンドにフックをかけて直接ターミナルからESMを実行可能

です。おおざっぱにそういう理解でいいでしょう。

この(1)がまさに上で引用した

npm init esm

によってesmパッケージ依存込みで自動生成されるプロジェクトのテンプレです。

実際に作成されたpackage.jsonでは

  //...
  "main": "index.js",
  //...

と、index.jsがパッケージの起点となっており、その内容は以下のとおり自動生成されています。

index.js
require  =  require("esm")(module/*, options*/)
module.exports  =  require("./main.js")

通常のCJSのrequireは一旦("esm")関数でフックをかけられることで、esmの文脈に変換された上で再定義されています。

再定義されたrequireはESMファイルを読み込み解釈できるので、./main.jsにはESMそのものずばりのコードを書けば良い、という仕組みです。

main.js
// ESM syntax is supported.
export {}

実際の利用例

実際の例として、前回のエントリ### JavaScript/TypeScriptに自己参照する「型」(type)を与えるtypeselfnpmパッケージとして公開したばかりですが、上記のESM互換の手順でプロジェクトを作成しています。

https://github.com/kenokabe/typeself

本体は、
./dist/build/modules/typeself.js
にESMとして書かれており、依存モジュールであるESM
./dist/build/modules/primitive-obj.js
と共に、ブラウザ内のESMのimportとしてやればそのまま動作します。
https://github.com/kenokabe/typeself-dev/tree/master/dist/build

そして同時に、./package.jsonで起点と指定される./index.jsでは、

require = require("esm")(module/*, options*/)
module.exports = require("./dist/build/modules/typeself.js")

と本体のESMへesmフックをかけていて、あくまでCJSのmodule.exportsとされており、通常のnpmパッケージとしてnodeから利用可能です。

さて上記(2)について、

./dist/build/test/test-typeself.jsというテストコードもESMとして記述されていますが、

$ node -r esm ./dist/build/test/test-typeself.js

とesmフックをかけたnodeコマンドでESMがシームレスにターミナル上で実行できてしまいます。

  1. パッケージ内でESMへフックをかけてCJS/npmパッケージ化できてしまう、
  2. ESM単体でもターミナルからnodeコマンドにフックをかけて直接実行可能、

と、もの凄く洗練された設計と実装となっています。

次のエントリではesm開発者の@jdaltonのブログ記事を翻訳して公開

実は、そろそろESMの開発環境を整えようと、上記の「Reactアプリでも基本TypeScript(TSX)だけ、Babelとwebpackなしでそのままブラウザで展開」するための開発ツール
https://github.com/kenokabe/esm-bundlerless
https://github.com/kenokabe/esm-bundlerless-react
Electronをベースに構築しようとしていた時に、「Electronっていうのは、Chromiumブラウザとnodeランタイムのハイブリッド環境なので両方の技術おいしいとこどりでなんでも簡単にできるはず」とタカくくっていたところ、単純にやっぱりnodeではESMは扱えない事実が判明していたのでした。

すでに書いた通り、node組み込みのESMローダーはまったく迷走しているし、どうしたものかと、electron/node界隈を調査していると
Contexts: supporting new Node’s ESM Loader (without hacking)というIssueにたどり着き、同じように文句垂れている人を見かけたので、自分も便乗して「同意見だ」などと長文で遠回しに文句垂れていたところ、@jdaltonが「とりあえずesmみたいなものがある、これならElectronのmain/render両方のプロセスでも一貫性のあるESMの利用ができる」と親切に教えてくれたので、見てみると、英語圏でも知られているようでまだそれほど知られているわけではなさそうなesmですが、これがもの凄いハックで、はじめてBrowserifyというバンドラを見たときと同じくらい衝撃を受けました。それがesmのことを知った経緯です。

そもそもElectronとはGitHub製のAtomEditorのガワ部分が独立して公開されていたもので、先日Microsoftが自社製のVisualStudioCodeの土台にもなっているElectronと一緒にGitHubを買収してしまったので、今現在、ElectronはMicrosoftの資産となっています。その流れか、前からかは知りませんが、MicrosoftのPMの@jdaltonもElectronの開発メンバーになっているのでIssueをWatchしていたのでしょう。

彼はnode/moduleの策定メンバーにもなっているようなので、「今後esmをnodeのデフォルトESMローダーとしてマージする心づもりなのか?」と質問しましたが、そのつもりは全く無いようです。彼のブログを熟読すると彼の志向はどの開発環境、開発スタイルにでも馴染むような汎用的なライブラリ(lodashもたしかにそんな感じだ)の作成で、スタンダードに馴染み、開発者から自然に選択されるデファクトスタンダードを目指しているようです。

ということで、今回の記事では、筆者がES Module/ESM界隈の最新情報、これまでの経緯とこれからの見通しを俯瞰、そして勝手にesmの解説もしましたが、やはり開発者自身の設計思想も含めて理解できる一次ソースに勝る情報はないと思うことと、とても重要な文章だと思うので日本語圏の開発者がよりリーチしやすいように日本語でシェアするために、@jdaltonの許可と協力を得ながら、彼のブログ記事を翻訳公開したいと思います。

↓ 次に読む

明日のES Modulesを今日使おう!(esm ライブラリ開発者 @jdalton による解説記事の翻訳)

JavaScriptプログラマのための2019年の機械学習と関数型プログラミング

JavaScriptプログラマのための2019年の機械学習と関数型プログラミング

この記事では、TensorFlow.js界隈について個人的に俯瞰した内容をシェアしています。

1. ちゃぶ台返しの世界

前回のエントリ JavaScriptによる機械学習の未来(TensorFlow.js)と関数型プログラミングでは、あたかもJavaScriptによる機械学習の未来がTensorFlow.jsの登場により明るい、という論調で書きましたが、この記事ではちゃぶ台返しをします。

昨今のJavaScript界隈を見ても進化が目まぐるしくて、完全にデファクトスタンダードになったと思っていた技術を習得して、しばらくは安泰だな・・・と思っていたら、ちょっとボケっとしてる間に突然変異を起こした別のフレームワークの登場によりあっという間に自然淘汰が現在進行系というのは日常茶飯事。

脱jQueryからのReactは言うに及ばず(2015年にたしか、「ReactになるからjQueryはもう使わない」とか半ば強い主張をしたとき猛烈に反発してきた人もいた)、browserifyからのwebpackからのParcelだったり、そもそもESM使え、だったり、npmじゃなくてyarnを使おうとなったり、まあかなりJavaScriptには密にコミットしているつもりの自分ですら、ちゃぶ台返しされすぎてわけがわからなくなるときがあります。

JavaScriptによる機械学習の未来(TensorFlow.js)と関数型プログラミングが明るい、というのは、3-5年のダイナミズムを考えればあながちなち間違っていないのかもしれません。しかし、少なくとも2019年にヘビーにコミットしてしまうのは、コスパ悪い、と言うのが今回の主張です。

Python完全支配下の機械学習界隈も、3-5年のダイナミズムを考えるとJavaScriptにあっさりちゃぶ台返しされてしまうかもなあ、と思いつつも、そこを先物買いして、未成熟なエコで無理をするのはリスクが高い、コスパが悪い。

2. 2018年に機械学習フレームワークで起こったこと

機械学習フレームワークでも栄枯盛衰が激しいです。

2.1. Therano開発終了

正確には、2017年後半に開発終了のアナウンスがされました。

Deep Learning をPythonでやろうとした場合,Theanoしかなかった
私がTheanoの学習を始めたのが2015年でしたが,その頃の状況は,「やっぱりTheanoが"Defo"(default)でしょう」という感じでした.

たかだか、2-3年の話です。

2.2. PyTorchの台頭 FROM RESEARCH TO PRODUCTION

PyTorchが台頭してきて、ver.1.0がリリースされました。

pytorch logo dark

のブログ記事がわかりやすいですが、画像も引用させて頂くと、

deeplearningstar 640x360

2017年頃から台頭してきていたらしいですが、現在、第三位、おそらくもうすぐKerasを抜いて第二位になるんじゃないでしょうか。

PyTorchはその習得のしやすさや、研究開発との親和性の高さから、発表されてすぐに世界中で人気になりました。

とありますが、PyTorchの謳い文句である、"FROM RESEARCH TO PRODUCTION"の通り、研究開発と親和性が高く、しかもディプロイして実用に耐えると。GPUのサポートはTensorFlow以上にピカイチっぽいです。

Therano無き今、機械学習界隈の研究の後継はPyTorchぽいです。

2.3. TensorFlow.jsの登場

2.4. TensorFlow 2.0への「破壊的」アップデート

ポイントはコレです。

TensorFlow Advent Calendar 2018を見ていると勉強になりますが、

Eager Modeをデフォルトで採用するPyTorchの使い勝手の良さが注目されていることもあり、長らくデフォルト化し続けてきたGraph ModeからEager Modeへのデフォルト化に舵を切ったのでしょう。

強化学習におけるTensorflowの実装たるや、その多くは可読性が低いです。それに比べて、PyTorchやchainerといったDefine-by-Run型のフレームワークの実装は読みやすく作りやすい。しかし、その時代もEager Modeの出現により終わりました。

3. ちゃぶ台返し再び

前回のエントリで、TensorFlowが最初出たときに思ったのが命令型パダライムのAPI使いにくい、という不平不満を書いていましたが、それはこの辺のことで、TensorFlow2.0になってデフォルトで「かなりマシ」になると解釈しています。

前向きに捉えれば、もちろん改善ですが、メジャーアップデートというのは根源的な破壊的変更を意味するわけで、これまで蓄積されてきたTensoflowのコード資産は、2019初頭にリリースされるらしい2.0では通用しなくなるでしょう。

特にこれから本格的に機械学習はじめたい、という学習者にとってはTensorflowは急転直下、使いにくいフレームワークとなるはずです。何故なら、既存のチュートリアルはすべて1.*ベースで書かれており、巷のTensorFlow入門記事もコードも当面2.0互換で出揃うには相当なタイムラグが発生するはずです。

コピペしても、バージョン違いでコードが動かないというのは大変作業効率が悪いもので、いちいち手直しする羽目になるでしょう。

4. Tensorflow.js もちゃぶ台返しされる

「TensorFlow本体とTensorFlow.jsのサーバーサイドの等価性を高めていく」というポリシーもあり、Tensorflow.jsもTensorflowベースなので、当然影響を受けます。

5. なぜ結構強めの主張ができるか?

昨今、ライブラリ、フレームワークの選択する際に、Web上に情報が豊富にある、というのは生命線だと思います。

実際に、Tensorflow.jsなら、ちょっと本腰入れてやってみるか・・・と甘い気持ちでやりはじめましたが、状況は極めて厳しくめちゃくちゃ苦労しました。

たとえば、とりあえず、MNISTの一番単純なNNならトレーニング時間と精度はどんな感じか?とやろうとしても見つかるのは、CNNのコードだけだったり、そもそも,TensoflowのいわゆるGraphModeとEagerModeのコード資産(その多くは、GraphMode)で混乱する、本家と.jsの混在もあるし、なかなか本質的なところまでたどり着けません。

その折、Tensorflow2.0で破壊的変更がある、となったので、ああこれは無理だな、と。

関数型プログラミングVSオブジェクト指向みたいな議論もそうなんですが、パラダイム、根底となる考え方の違いというのはとても重要です。

6. PyTorchにしたら一瞬で問題が解決した

前提として、

  1. VisualStudioCode のPythonツールやらでPython書いたら、なんか自動でインテンドもしてくれて、普通にPython書けるようになった。

  2. const とかないのが気持ち悪とか思っていたが、つーかそもそも、関数型にあまり興味ないぽいPython界隈のイミュータブル事情ってどうなってるの?と思いたまたま読んだ Pythonの、変数と代入についての誤解を解くで、誤解が解けた。

Python普通に書けるな、と思って安心して、PyTorchで情報探すと、とりあえず「根本の思想が一貫している」ことから、コード資産の分散がなく、やりたいことの本質へすぐにたどり着けるようになりました。

加えて、JavaScriptで関数型プログラミングやるレベルの人なら、おそらくTensorFlow/.jsのフレームワークによる過度の抽象化というか、粒の大きさは、なんかただ、コードをコピペしているだけのようで、まったく勉強にならない危険性が大きいです。もちろん低層のAPIへ降りて、といくらでもできるんでしょうが、ここで一気にコード資産が減る、情報が見つからない壁にぶちあたるでしょう。

それに対比するように、PyTorchは、APIの粒が適度です。Pythonに根ざしているとアピールもされているようですが、抽象度の高すぎるフレームワークに組み入れられすぎることもなく、ちゃんとプログラミングできる感じ。

研究者がPyTorchのほうを好むというのも普通に合点が行きます。

7. PyTorchでも関数型パラダイムは未成熟

mnist

これはMNISTを学習する典型的なCNNですが、図の一番左でINPUTされる画像(図のAとかいうのは間違いで本来は数字の画像)ベクトルから一番右へ0−9の10個の数字へ分類するOUTPUTをもつ巨大な関数です。

NNの各レイヤ、それから活性化関数(ReLuとか)はそれぞれ関数で、合成関数となっています。

NNのレイヤの素子は線形(アフィン変換 y = Wx + b )ですが、各レイヤはその合成で非線形となっているので、各非線形関数を各々カーブフィッティングさせながら、全体の合成関数もカーブフィッティングするという問題に他なりません。

ネットワーク定義のコードを見ると、

DEFINE THE NETWORK
import torch
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)

といった感じです。これでもTensorflowが最初にリリースされてから長らく台頭してきたGraphModeで書くよりもずいぶんスッキリしているのですが、ずいぶんモッサリとしたコードが公式チュートリアルのサンプルコードとして提示されています。

見ればわかるとおり、クラスの中でわざわざ self.conv1 とか定義して、それを自身の foward メソッドをもってミュータブルな x の連鎖とともに消費する、という、まあ関数型プログラミングに慣れた人たちにすれば、「ないなこれは」というコードが提示されています。

これはCNNで計算時間が大きいので、手を汚してはいませんが、別のもっと単純なNNでは、こういうNNの設計は、PyTorchでも、

DEFINE THE NETWORK
net = nn.Sequential(nn.Linear(4, 10),
                    nn.ReLU(),
                    nn.Linear(10, 8),
                    nn.ReLU(),
                    nn.Linear(8, 3))

こう書けます。 何をやっているのか?というと、単に各レイヤの関数を並べて、それを nn.Sequential で合成しています。

f3 = compose(f1,f2) とやっているのと等価です。

部品としてのNNの筋の悪さについては、繰り返し、前回のエントリ JavaScriptによる機械学習の未来(TensorFlow.js)と関数型プログラミングで触れましたが、関数である、という視点を強く持てば持つほど、これがNNである必要はまったくないというのは明らかです。

8. 結論らしきもの

あと2年くらいは、特に初学者はTensorFlowでやると同一フレームワーク内のパラダイム混在による混乱で苦労するだろう。2.0以降では、1.*系のコードは大幅な手直しなしでは役に立たない。

TensorFlow.jsもその煽りを食う。

大幅な手直しを迫られるくらいなら、パラダイムに統一性がある今一番勢いのあるPyTorch使うのが100倍賢い。

今のちゃぶ台返しが著しいプログラミング界隈では、3-5年スパンでは、JavaScriptが機械学習のメインストリームになる可能性は小さくないが、大きいとも言い切れない。

関数型プログラミングではJavaSciptエコのほうがPythonエコよりもずいぶん成熟しているように見える、というか、TensorflowのAPIにせよ、PyTorchのサンプルコードにしろディープラーニングは関数を合成している、という視点がまるでないようにしか思えない。

総体的なコーディング力はJavaScriptエコのほうが人口もあわせて考えると多分上。裾野も広いので、長期的には取って代わったほうが人工知能研究のためにも良いように思える。歴史的な経緯以上にPythonの優位性、Pythonでなければならない合理性というのは存在しないが、Pythonもけして悪くはなく、JavaScriptへの積極的な置き換えを訴求する決定打には欠ける。

どうなるかわからないので、3−5年後に勝馬に乗るほうがいい。短くも長い期間なので、素直にPythonとPyTorchをやったほうがいい。

JavaScriptによる機械学習の未来(TensorFlow.js)と関数型プログラミング

JavaScriptによる機械学習の未来(TensorFlow.js)と関数型プログラミング

今から3年も前になるが、2015年末にGoogleが TensorFlowを発表したとき、率直な感想は「ああまたこんなものが出たのか・・・」だった。

そもそも自分の好きなプログラミング言語として、

1位 JavaScript/最近では特にTypeScript+VisutalStudioCodeの支援が驚異的
2位 Haskell 理論寄りの話題には事欠かないから

といった感じ。

PythonはSTEM(科学技術分野)でデファクトスタンダードであり、数学、確率統計、機械学習のライブラリもコード資産も情報も豊富でやったほうが良いとはわかっており、食わず嫌いは良くないとも思い好きになろうとしたが、バージョン2と3の互換性が致命的になくて苦労したり、まあいろいろな理由で結局好きにはなれなかった。その頃ちょうどJavaScriptがES6に進化し、特に関数型プログラミングでの記述力表現力が飛躍的に向上していたし、TypeScriptの登場、Reactの登場、AtomEditorの登場とJavaScript界隈もそうとう賑わっていた時期と重なったというのも大きい。今でもろくにPythonのコードは書けないだろう。

TensorFlowも例に漏れずPythonベースだったので、ああまたか、でもメインストリームだから仕方ないか、という諦めの感想と同時に、しかしPythonがJavaScriptも含め他の言語より殊更優れているようにはどうしても思えないのに、ただ単にいつもの歴史的偶然からSTEMのメインストリームになっていることについては不条理感と不満はあった。いつも思うのだが、このSTEM界隈で必然性や合理的理由に欠ける大勢による後追い現象はろくなものはないと思う。

基本的に自分の機械学習への取り組みについては熱中したり熱中しなかったりで普通に2,3年いや3,4年の周回遅れをとっては、また熱中して最先端に追いつこうみたいな繰り返しだ。さすがに3,4年も周回遅れだと、ありがたいことにその期間にそれなりのブレークスルーもあって面白い。逆に言うとそれくらい寝かさないと情熱が続かない程度にしか機械学習にコミットしていないのだけど、根本的にしっくりこないことが重なり興味と情熱が下火になってしまう。

根本的にしっくりこないこと、というのはどういうことか?

ディープラーニング、深層学習というのは、細部の関数の学習から、その各々の関数の合成(Function Composition)を繰り返して、なんかのn次元ベクトル空間に潜在している多様体を特定する作業に過ぎない。YouTubeで見た著名な研究者によるプレゼンでもそう発表されていたし、結局これまではCPUのパワー不足により、そういう多層の関数合成には至らなかったんだな、ということはおおよそ明らかになっている。

この辺の理論的土台が一旦しっかりと固まれば、あとは関数合成なんだから、理論面に秀でた情報科学者やプログラマーが加速度的になんかすごい仕事をするはずだし、近い将来必ずそうなるとは思う。しかしまだ夜は明けていない。

日本語版Wikipediaやその他一般向けの記事でのディープラーニングの紹介では、ひたすら、多層のニューラルネットのことだと強調されている。ニューラルネットがディープなのだと。

たしかに、ヒントン先生らがディープラーニングのブレイクスルーを巻き起こしたのは、多層のニューラルネットだったし、現在も実績を残しているのはほとんどニューラルネットだけれども、今の流れは単に過去の実績に依存する壮大なる後追いで、多大に偏向した人的リソースの投入と、職人技の連鎖に依存していると思う。

ディープニューラルネットの最近のブレイクスルーは、府大生が趣味で世界一の認識精度を持つニューラルネットワークを開発してしまったの元となる、Residual Network(ResNet)があると思うのだが、リンク先や数々の情報を参照するにつけ、チューニングに次ぐチューニングで、こういう問題があるからとりあえずこうしてみたら成績が向上した、というようなトライアンドエラーによる職人技の集積であって、その凄さと実績は素直に感嘆しながらも、これらの膨大な努力はニューラルネットが抱える根本的な問題をまったく解決していない応急処置みたいなものにすぎないので、優秀な頭脳がもったいないなあ、と毎度思う。

ディープラーニングの生い立ちがニューラルネットに深く根付いている事実は歴史的事実だけれども、最先端の研究でここまでニューラルネット一辺倒にやる必然性と合理性はとても見いだせないし、知的人的リソースの膨大なる浪費だと感じる。

念の為だけれども、Wikipedia日本語版やら一般向けのいい加減な技術紹介記事のディープラーニングの定義は仕方ないとしても、著名な研究者(Yoshua Bengio先生ら)によるモダンな教科書などでは、ディープラーニングとはニューラルネットに限定されたものではないということがそれなりの文面を割いて書かれてある。

1パラグラフだけ引用すると、

The modern term “deep learning” goes beyond the neuroscientific perspective on the current breed of machine learning models. It appeals to a more general principle of learning multiple levels of composition, which can be applied in machinelearning frameworks that are not necessarily neurally inspired.
現在の機械学習モデルにおける、モダンな用語としての「ディープラーニング」は、神経科学の視点を超えています。複数レベルのコンポジション(合成)を学習する、もっと一般的な原理をアピールしているのであって、必ずしもneurally inspiredのものではない機械学習フレームワークに応用できるものです。

ニューラルネットがかつては一世風靡していた過去の遺物に一向になる気配もなく、若手も含めて研究者をここまで非合理的に取り込む理由はなんだろうか?しょうもない仮説を建ててみると、

1.とりあえず機械学習の歴史的発展経緯から、いろはの「い」として最初に教えられる。メディアでもなんでもこれがディープラーニングなんだ、と信じて疑わない状況に染まってしまっている。

2.現在も大多数が活発に研究しており、興味深い成果が出続けており、絶賛ブレイク中で将来も有望の分野に見えている。

3.とりあえず本質的側面、小難しい理論面に切り込んで開拓せずとも、ニューラルネットという「モノ」が転がっているので好き放題遊びやすい。手軽。

TensorFlowについても、まずとりあえずニューラルネットだ、とまるでニューラルネットを組み立てるためにあるフレームワークだみたいなプレゼンスであったし、ああまた手軽なおもちゃのパフォーマンスチューニングで遊ぶだけで、根本的、本質的な研究をおろそかにする若手研究者を量産する種になるのかねえ、とウンザリしたのでした。

自分は特に関数型プログラミングをするので、部品の粒の大きさとか品質とかを気にする。これは何らかの本質的意味がある堅牢で良質な部品で、今後の関数合成の基盤として有用である、とかそうではない、とか。ひとつ前のエントリの30分でわかるJavaScriptプログラマのためのモナド入門でも、結合法則を満たすモノイドが良質な部品で、モナドも良質な部品で、ES6+Promiseはその観点から行くとちょっと筋が悪いなあ、とかそういう事を書いたつもり。

広く採用される部品となる関数は、歴史的経緯や大勢の後追い以上の堅牢な品質が担保されて然るべきだと信じるし、それは機械学習、ディープラーニングの部品にも当然適用されるべきだと思う。現状自分は、ニューラルネットがその部品たる資質があるとはまったく思っていない。

そもそもPython界隈は、昨今のJavaScript界隈のような関数型プログラミングへの目覚めというか賑わいをまるで感じない。それは偏見かもしれないが、とりあえずPythonベースのTensorFlowのAPIというよりフレームワークの実装そのものを眺めてみても、ゴリゴリの命令型パラダイムで設計されており、ああやっぱそういうことは気にしない感じで行くのね、ディープラーニングは本質的には関数型プログラミングなのに・・・と。それが当時がっかりした理由だった。

ごく最近、ディープラーニングと関数型プログラミングを結びつけて論じている人いないかな、と検索していると、Neural Networks, Types, and Functional Programmingという記事が見つかった。すでに3年以上前の記事なので、こういうところで周回遅れぶりを痛感する。

現在のニューラルネットは若く未成熟な分野で「アドホック」つまり具体的過ぎて十分に抽象化されていない、統一的視野も理解もない、30年後にはもっと違った視点を我々は獲得しているだろう、というような内容。それから、具体的に著名なニューラルネットモデルを列挙して、それが如何に関数型プログラミングの部品に対応しているのか?ということを例証している。

コメント欄でも、ヒントン先生の弟子でもありディープラーニングのブレイクスルーの立役者であるYann LeCun(コンピュータビジョン、特にCNNの仕事で有名)が参考になる論文を列挙してくれていたり、たいへん読み応えがある。元エントリを全文和訳!でもしたらこのエントリの価値も少しは向上するのだろうが、翻訳作業というのはものすごい時間と労力がかかるので、とてもそんな労力をかける気力はない。

基本的に日本のプログラマ界隈では「ポエム」(悪意、中傷からはじまった言葉だと認識している、現在は転じて自己謙遜を含む穏やかな意味にシフトしつつあるとは思う)とか言われるタイプの文章で、本人も「エッセイ」だとか、こういうのを論じるバックグラウンドはないので資格がないかもしれない、広く議論を呼びかけたいだけだとか、30年後はこうなっていてもおかしくないとか、謙虚というか予防線張りまくりなのだが、他のエントリも次に引用するとして素晴らしい洞察力をもった人物だと思う。

別のエントリ Neural Networks, Manifolds, and Topologyでは、タイトルの通り、トポロジーの視点を織り交ぜながら、ニューラルネットが多様体を特定するために具体的にどういう挙動をしているのか?というのが豊富なグラフィック(この人この辺がものすごいと思う)とともに丁寧に論証されている。つくづく思うのだけれども、もうこの分野での紙やらあと白黒のPDFの役割は終えたんじゃないだろうか?彼のこういう一連のエントリは高い代金の機械学習入門書以上の価値と品質がある。

具体的に例証した結果、Better Layers for Manipulating Manifolds? では、

The more I think about standard neural network layers – that is, with an affine transformation followed by a point-wise activation function – the more disenchanted I feel. It’s hard to imagine that these are really very good for manipulating manifolds.
標準的なニューラルネットワークのレイヤー、つまり活性化関数で処理するアフィン変換のことだけど、考えれば考えるほど、幻滅させられてしまう。これが多様体を操作するために本当に良いものだとは到底思えない。

と実質結論づけてしまっている。まあ完全に同意。ニューラルネット筋悪すぎ。

ということで、ニューラルネットが抱える問題などを改めていろいろ調べていると、東大の名誉教授で、情報幾何学(information geometry)という学問をつくった甘利俊一先生が居て、その枠組みの自然勾配法をやると、一般的な勾配学習よりも場合によっては収束が1000倍以上も速くなるとか書いてあった。

  1. 神 経 多 様 体 の 特 異 構 造
    自然勾配法は何故数千倍も速いのだろうか.パラメータ の空間が ユークリッド空間なら,自然勾配法は通常の勾配 法と同じである.それなら,多少曲がっていても,自然勾配法と通常の方法でそれほどの差はないだろう.もし,シミュレーションが示すような驚くべき差 があるならば,この空間は極端に曲がっていること,いわばブラックホール のような特異点を含んでいるのではないかと考えられる. これは事実であることが最近の研究でわかってきた4).

と、とんでもないことが書いてあった。さらに検索すると(Googleは本当に便利である)

今回はニューラルネットワークの学習における不思議の1つ、「学習の停滞」の原因について述べてみたいと思います。

まとめ

学習の停滞と再開は鞍点によって生ずる

鞍点は勾配が0になる点

勾配が0になる点⇛パラメータを少し変更しても出力にまったく変化を及ぼさない点

ニューラルネットには特異点という質の悪い領域が広がっている

と書かれていた。大まかな印象として、詰めの微妙な学習ほど難易度が高くなる、というのは直感的に理解できるとしても、たしかにニューラルネットの学習の収束はいくらなんでもあまりにも遅すぎるというか不安定な挙動が顕著だというのは大多数が痛感するところではないだろうか。

情報幾何の枠組みの自然勾配法をもって、まあワーストケースだろうけども1000倍も数千倍も高速になる!ということは、その裏を返せば、そもそもの特異点やらプラトーとも呼ばれる鞍点が多いニューラルネットというモデル選択自体が悪手なのだろうなと普通に思う。つまり、ニューラルネットではない他のモデルをベースにしていれば、そもそもそういう自然な高速化手法自体不要であろうと。

どうも数多くの職人技とも言えるニューラルネットのパフォーマンスチューニングや、本質的に関数合成であるディープラーニングのレイヤー以上に複雑なニューラルネットモデルの多大な構造は、この辺の致命的欠陥を覆い隠すための本質的には不必要な余剰なハックなんだろうな、というのが感想。あくまでエンジニアリングなのでトライ&エラーがあるのは当たり前だとしても、XXの欠陥をカバーするためにこうしたら良い結果になりました!というのが多すぎるように思うし、それによって今後の応用に耐える知見がどの程度もたらされるのか疑問。

ディープラーニングの多レイヤー化で、convolutional neural networks(CNN)は本質的だと思う。ただしこの中間のN(neural)については、実際に成功を収めた具体的な実装がニューラルネットだったとしても、本質的にニューラルネットである必要はない。同じブログでは、ニューラルネットなしで、convolutionsというのはいったい何なのか?というのが解説されている。

コンピュータビジョンのディープラーニングで、convolutionalであれば、別にその「素子」がニューラルネットでなくても性能を発揮するという事例は検索するとすぐ出てきた。

PCA(主成分分析)ベースのConvolutional Network。さすがにえ?ただのPCAなの?と思ってしまうが、結果はものすごい。要するにConvolutionalのディープラーニングがすごいのであって、ニューラルネットはむしろどうでもいいという証左。

このモデルの特徴は、

  1. 非ニューラルネットのConvolutional Network

  2. unsupervised(教師なし学習)

  3. 教師あり学習のときの back propagation がないので効率的

  4. "Dropout"だとかファインチューニングに依存しない

効率的ということだが、Back propagationもないただのPCAで構成されたディープラーニングと、既存のCNNの学習速度の差は圧倒的だろうと思うわけだが、実験結果を見て一番すごかったのは、texture datasetで、PCA-Based Convolutional Networkが、251.80秒の学習で99.89%の精度を出した一方で、トラディショナルなCNNは、10時間で50000回の学習で43.2%の精度しか出ず、その後過学習になって精度が悪くなっていったという。

PCAは研究されつくされていて理論的にも正体は知れている。もちろんPCAの他にも次元削減の方法はたくさんあるが、特徴量の抽出という意味ではこれほど率直で基本的な方法はないとも言える。ニューラルネットのように、いや実は、ブラックホールのような特異点がいくつもあることがわかってプラトーもあって、とか恐ろしい隠し玉は存在しない。速度はもちろん圧倒的だ。

TensorFlowのディープラーニングTutorialページにもお手軽にCNNを構築してテストできるというのがあるが、本来我々が優先して学んだり実践すべきなのは、こういう正体と挙動がよく知れたPCAが部品となっているディープラーニングなのではないだろうか?

NNでないのならやってみたい、素直なPCAならむしろディープラーニングのHelloWorldとして実装してみたい、と思う。そこで、今年4月くらいに出てきたTensorFlowのJavaScript版であるTensorFlow.jsだ。

本質的に関数合成のディープラーニングなのに、TensorFlowに満ち溢れる命令型パラダイムの強烈な違和感というのは、不思議なことにJavaScriptのテリトリーに入ってきたら、多分なんとでもなると思ってしまう。NNに関する一切合切のAPIはガン無視するか、レガシーなモデルとの比較対象のためにあると考えれば整合性もつく。個人的には、機械学習と相性の良い数値計算ライブラリ+学習・テスト用データセットのDL展開ユーティリティ+テストWeb出力のUIライブラリと考えている。

実際に現行のJavaScriptエコは関数型プログラミングへの進化圧はかなり強烈なので、Pythonエコではおそらく生まれなかっただろう機械学習、ディープラーニングの関数型プログラミング化という一大ムーブメントが起きないだろうか?何か大化けしないだろうか?と期待している。

道具立てさえ揃えば、研究者、開発者、学生には選択肢が増える。JavaScriptのコミュニティは現状圧倒的なので、STEM領域全部とは言わないまでも、少なくとも機械学習の分野でPythonを割食ってしまう流れになる可能性は小さくない。自分のように、ああPythonかあ、と思っている開発者が実際どの程度の割合いるのかは知らないが、単純に頭数は非常に多いだろうとおもう。特にTypeScriptとVisualStudioCodeの支援がある開発環境など、今のJavaScriptの開発効率の高さに満足している人たちはPythonを使わなくても同じことができるのならば、わざわざPythonを選ぶ合理性はないと思う。

ここで問題となってくるのがスピード。パフォーマンスだ。そもそもディープラーニングのブレイクスルーを果たしたのは、昔には存在しなかった現代のコンピューティングパワーのおかげなので、ここは肝だとも言える。

Pythonの強みは、numpyみたいな数値計算ライブラリがあって、しかもその実体はCかC++でコンパイルされたバイナリで、重い行列計算(PCAなんかもろにそれ)は、実質ネイティブコードとして高速に処理されてしまう。しかもGPUが使えればそっちでさくっとやります、という完全なチートレベルなので、もうこうなるといくらJavaScirptでMath.jsだ、なんだの言っても、戦闘力が違いすぎてお話にならなかった。そういう状況が今まで続いてきた。

TensorFlow.jsは、WebGL経由でGPUパワー使います、というのがひとつのウリだ。しかし、FAQを見ると、

How does TensorFlow.js performance compare to the Python version?
In our experience, for inference, TensorFlow.js with WebGL is 1.5-2x slower than TensorFlow Python with AVX. For training, we have seen small models train faster in the browser and large models train up to 10-15x slower in the browser, compared to TensorFlow Python with AVX.

WebGL使っても、jsのほうが2倍くらい遅い、15倍くらい遅くなることもある、とかかなり残念なことが書いてある。これは致命的だ。

tfjs02

TensorFlowでMobileNetをやったときに、Python+CUDA(GPU)とJavaScipt+CUDAだと3−4倍の開きがある。GPUはスピードをカネで買っているで、こういうのは受け入れられないと感じる人がほとんどだろう。

しかし、よく考えてみると、そもそもPythonは、バックエンドとしてC/C++のバイナリを持っているだけなので、JavaScript であっても、ブラウザ+WebGLのパターンではなく、node.jsならば同じことは可能なので、Google IO 2018で、そうしました、と発表された。

tfjs01

tfjs-node-gpu 1本落とすだけで、既存のコードがノードでGPUで動く。

TensorFlow.js(Node)のバックエンドで動いているのはTensorFlowのPythonが使うC++バイナリと同一なので、理論的には同じパフォーマンスが出るはずで、ほぼそのとおりになっている。

tfjs03

スピードのことを更に言うと、GoogleIOの動画でも言及されていたが、昨今のJavaScriptのスピードはとんでもなく速い。正確に言うと、この場合NodeのエンジンであるV8の開発元はTensorFlow.js(Node)の開発元のGoogle自身で、ふんだんにカネをかけて全力で開発しているので速い。Pythonの10倍速いと言っていた。

要するに、今現在、機械学習のパフォーマンスで、JavaScriptがこれまでSTEM業界を席巻してきたPythonに徹底的に劣るという懸念事項はもう完全になくなったと言えるし、TensorFlow.jsとV8の開発元が同じGoogleであるということを考えると、今後その気になりさえすれば、いくらでも最適化する余地はあるだろうから、JavaScriptのほうが有利だとも言える。

JavaScriptの重い数値計算がネイティブコードで動くようになった、というのは非常に大きい。

JavaScriptは機械学習のメインストリームになりえるだろうか?

今回はあえて、テーマに首尾一貫性がない感じにしてみました。