はじめに
間も無く終了する2010年代はJavaScriptが大きく成長した十年でした。Reactなどの革新的なフレームワークが登場し、Node.js、React Naitive などブラウザ以外でのエコシステムも大きく成長しました。また言語自体もES2015以降日々進歩しており、JavaScriptは2010年代でもっとも多くの人に使われたプログラミング言語となりました。さらにJavaScriptに静的型付を導入したTypeScriptとそのエコシステムもここ数年で大きく成長しており、JavaScriptは大規模なソフトウェアの開発にも耐えうるようになってきています。
出典: https://octoverse.github.com/#top-languages一方で2010年代にJavaScript以上に大きく人気を伸ばしたプログラミング言語がありました。Pythonです。2010年代に大きく伸びた分野であるデータサイエンスと機械学習(AI)の分野ではPythonはその利便性で他のメジャーなプログラミング言語を圧倒しています。2010年代はPython2から下位互換性のないPython3への移行という混乱と困難があったにも関わらず、Pythonはデータ分析・機械学習分野の成長と歩みをともにして大きく人気を伸ばしました。2019年のGitHubの報告ではJavaを抜いて遂にユーザー数が2位となっています。JavaScriptはまだPythonにユーザー数では優っているもののデータ分析・機械学習の分野では大きく遅れをとっており、この分野の今後の伸び次第では近いうちにランキングが逆転するかもしれません。逆にユーザー数で優位にあるJavaScriptがこの分野でも人気を獲得し、その地位を不動のものとするかもしれません。
2020年代がJavaScript, Pythonにとってどのような時代になるのか楽しみです。この記事では2020年代におけるJavaScriptの機械学習、特にディープラーニングにおける成長の可能性を探るべく、JavaScriptのみを使ってディープニューラルネットワークモデルを構築することに挑戦してみます。
TensorFlow
TensorFlowはGoogleが公開しているディープラーニングのフレームワークです。FacebookのPyTorchと人気を二分するディープラーニングの二大フレームワークの1つです。学術分野では最近PyTorchに追い上げられているとの指摘もありますが、産業分野では一日の長があり2019年現在もっとも広く使用されているディープラーニングのフレームワークです。
TensorFlowのメインのターゲットはPythonですが、Python以外にもJavaScript, C++, Java, Go, Swiftなどが(実験的に)サポートされています。
TensorFlow.js
TensorFlow.js is a JavaScript Library for training and deploying machine learning models in the browser and in Node.js.
TensorFlow.jsはTensorFlowのJavaScript版のAPIです。TensorFlow.jsのもっとも重要な用途は他の環境で作成したTensorFlowのトレーニング済みモデルをウェブブラウザ上のアプリケーションに組み込めるようにすることです。ただしTensorFlow.jsはブラウザ上でのトレーニング済みモデルの実行だけでなく、Node.js上での実行やJavaScriptを使ったディープラーニングモデルのトレーニングもサポートしています。そのためTensorFlow.jsを使えばPythonを使うことなく、JavaScriptのみでディープラーニングモデルの構築からトレーニング、実際のアプリケーションとしての利用までエンドツーエンドで行うことが可能になっています。
Jupyter
Pythonをブラウザからインタラクティブに実行するための環境です。コードや実行結果が「ノート」として保存されるので非常に便利です。JavaScriptユーザーにはあまり知られてないツールかもしれませんが、Pythonを用いたデータ処理や機械学習、あるいは教育分野で非常に人気のあるツールであり、ここ数年で急激にユーザを増やしています。2019年のGitHub OctoverseでもJupyterの急激な成長が言及されています。Jupyter Notebook自体についてもっと知りたい人はGoogleでJupyterを検索して下さい。
Jupyterは「カーネル」を追加することでPython以外の言語もサポートすることが可能です。さまざまな言語のカーネルが存在しています。今回は僕が作っているJavaScript/TypeScript用のカーネルを使用してJupyter上で機械学習を行います。
出典: https://octoverse.github.com/#industry-spotlight-data-sciencetslab
Jupyter上でJavaScriptおよびTypeScriptをインタラクティブに実行するためのJupyterカーネルです。
yunabe/tslab - Interactive JavaScript and TypeScript programming with Jupyter (GitHub)
(気に入ったらぜひStarもして下さい)
tslabはTypeScriptをベースに作られており、以下のような特徴があります:
- Jupyter上でのJavaScriptおよびTypeScriptのインタラクティブな実行
- TypeScript由来の静的型情報の活用
- JavaScriptでも静的型チェックが行われる。
- 静的型情報を利用した、コード補完やtipsの表示
- TypeScript 3.7のサポート
- Top-level await のサポート
またtslabはJavaScript/TypeScriptのリッチなREPLとして使用することもできます。
この記事ではtslabを使って、Jupyter上でインタラクティブにディープラーニングモデルを構築していきます。
環境構築
Node.js
Node.jsがインストールされていない場合はNode.jsを導入しましょう。現在の最新のLTSであるv12を前提に話を進めますが、v10, v13でも問題はないと思います。
Jupyter
Jupyterを使ってインタラクティブにディープラーニングモデルの構築を行いたいので、Jupyterもインストールしましょう。Pythonがよくわからない場合はAnacondaを使って導入するのが一番簡単だと思います。最新版のJupyterはPython2のサポートをすでに停止しています。特に理由がなければPython3版を使用して下さい。
tslab
tslabのREADME.mdに従ってインストールしてください。
npm install -g tslab
インストールに成功したら、tslabをJupyterに登録します
tslab install
tslab install
が成功したら、jupyter kernelspec list
でカーネルがJupyterに登録されていることを確認しましょう。
$ jupyter kernelspec list
Available kernels:
jslab /usr/local/google/home/yunabe/.local/share/jupyter/kernels/jslab
tslab /usr/local/google/home/yunabe/.local/share/jupyter/kernels/tslab
これでJupyter上でJavaScript/TypeScriptを実行できる環境が整いました。
jupyter notebook [--port=8888]
を実行して、JavaScript, TypeScriptの「ノートブック」がJupyterで作成でき、JavaScriptをインタラクティブにノートブック上で実行できることを確かめてください。
npmプロジェクトの作成
Node.js, Jupyter, tslab のセットアップができたら環境のセットアップは完了です。npmのプロジェクトを作ってTensorFlowをインストールし、ディープニューラルネットーワークをJavaScriptで行っていきましょう。今回は僕が作成したnpmプロジェクトとJupyterのノートがGitHubに置いてあるのでそれを取得してください。
git clone https://github.com/yunabe/qiita-20191202-jsml.git
cd qiita-20191202-jsml
npm install # or yarn
一からプロジェクトを構築する場合は通常通り、npm init
やyarn init
でプロジェクトを作ってください。
TensorFlow.js のインストール
チュートリアルTensorFlow.js in Nodeに従って、TensorFlow.jsをプロジェクトにインストールしてください。
npm install @tensorflow/tfjs-node
TensorFlow.jsはGPUによる高速化にも対応しています。
LinuxでGPU/CUDAが使用可能な環境であれば、tsjs-node
ではなく、@tensorflow/tfjs-node-gpu
をインストールしてください。
これでJupyter上でTensorFlow.jsを使ってディープラーニングを行う準備は完了です。Jupyterを起動してJavaScriptあるいはTypeScriptのノートブックを作成してください。ここから先のコードはJupyter上で実行していきます。
jupyter notebook
ここで使ったノートブックはGitHubにコミットしてあります。ここから先はGitHub上のノートブックを直接参照してもらっても構いません。この記事ではJavaScriptを使用していますが、ノートブックはTypeScript版も用意しています。TypeScriptが分かる人にはTypeScript版をお勧めします。
GitHub
nbviewer (GitHubよりノートブックがみやすいです)
JavaScriptと静的型チェック
今回使用するJavaScript/TypeScriptの実行環境 tslabはTypeScriptをベースに実装されており、JavaScriptに対してもある程度の静的型チェックを行います。
let x = 123;
x += ' hello';
console.log(x);
例えば上のコードは1行目ではx
の型はnumber
であることを意図しているようにみえるのに、2行目でstring
を代入しています。tslabはこのようなケースに対してエラーを表示します。
2:1 - Type 'string' is not assignable to type 'number'.
ただし、上記のコードはstring
の代入が本当に意図的なのであれば正当なJavaScriptのコードです。そのような場合にはJSDocを使ってx
が任意の型(any
)をとりうることを明示する必要があります。
/** @type {any} */
let x = 123;
x += ' hello';
console.log(x);
このノートブックでは
を利用しているので留意して下さい。型チェックの詳細についてはType Checking JavaScript Files
を参照して下さい。
またtslabでは静的型情報を使っているので、強力なコードの補完(Tab
)と変数・関数定義情報の表示(Shift-Tab
)が使用可能です。活用しながらコーディングして下さい。
TensorFlow.jsがインストールされていることの確認
まず初めに、TensorFlow.jsが正しくimportでき実行できるかバージョンを表示して確かめてみましょう。ちなみにtslabでHTMLや画像を表示するにはtslab.display
を使用します。無事バージョン番号が表示されたでしょうか。
ちなみにJupyter上ではTab
でコード補完、Shift-Tab
で関数定義などの表示が行えます。活用しながらコードを書いてください。
// const tf = require('@tensorflow/tfjs-node') も可能。
import * as tf from '@tensorflow/tfjs-node'
import * as tslab from "tslab";
tslab.display.html('<h2>tf.version</h2>')
console.log(tf.version);
tslab.display.html('<h2>tslab.versions</h2>')
console.log('tslab.versions:', tslab.versions);
MNIST をダウンロードする
TensorFlowが無事にNodeにインストール出来たので、ニューラルネットワークモデルを実際に構築してJavaScriptでトレーニングを行ってみましょう。ここでは機械学習のチュートリアルで常に使わる手書き文字認識のデータセットMNIST databaseを使って、数字の文字認識の機械学習を行ってみましょう。
TensorFlow.jsのサンプルコードの中にMNISTをダウンロードしてTensorFlowの内部表現に変換するコードの例が存在するので今回はそれを利用します。このコードはすでに例のレポジトリにコピーしてあるのでここではそれをimport
します。興味があればどのような実装になっているかのぞいてみてください。
loadData()
はPromise
を返すのでタスクの終了まで待機するのを忘れないでください。tslabはtop-level awaitをサポートしているのでawait
をつけるだけでOKです。
import mnist from '../lib/mnist';
await mnist.loadData();
データの確認と可視化
WikipediaのMNISTの記事にも書かれているように、MNISTのデータは60,000の訓練用データ(training data)と10,000の評価用データ(test data)に事前に分けられています。実際にダウンロードされたデータの大きさを確認してみましょう。
訓練データに60,000個、評価データに10,000個の数字の画像(images
)と文字認識の正解データ(labels
)が存在することが確認できたと思います。数字の画像データは28x28の白黒1チャンネル(グレースケール)であることも分かります。
ちなみにデータの内部表現として使われているTensor
のAPIはここにドキュメントがあります。
tslab.display.html('<h2>訓練データのサイズ</h2>')
console.log(mnist.getTrainData());
tslab.display.html('<h2>評価データのサイズ</h2>')
console.log(mnist.getTestData());
// 訓練データのサイズ
{
images: Tensor {
kept: false,
isDisposedInternal: false,
shape: [ 60000, 28, 28, 1 ],
dtype: 'float32',
size: 47040000,
strides: [ 784, 28, 1 ],
dataId: {},
id: 0,
rankType: '4'
},
labels: Tensor {
kept: false,
isDisposedInternal: false,
shape: [ 60000, 10 ],
dtype: 'float32',
size: 600000,
strides: [ 10 ],
dataId: {},
id: 10,
rankType: '2',
scopeId: 6
}
}
// 評価データのサイズ
{
images: Tensor {
kept: false,
isDisposedInternal: false,
shape: [ 10000, 28, 28, 1 ],
dtype: 'float32',
size: 7840000,
strides: [ 784, 28, 1 ],
dataId: {},
id: 11,
rankType: '4'
},
labels: Tensor {
kept: false,
isDisposedInternal: false,
shape: [ 10000, 10 ],
dtype: 'float32',
size: 100000,
strides: [ 10 ],
dataId: {},
id: 21,
rankType: '2',
scopeId: 14
}
}
次にMNISTのデータの可視化もしてみましょう。MNISTの画像データは28x28の0
から1.0
のグレースケールのデータの配列です。画像ライブラリjimp
を使用してPNGに変換して可視化してみます。
import Jimp from 'jimp';
import {promisify} from 'util';
/**
* @param {tf.Tensor4D} images
* @param {number} start
* @param {number} size
* @return {Promise<Buffer[]>}
*/
async function toPng(images, start, size) {
// Note: mnist.getTrainData().images.slice([index], [1]) is slow.
let arry = images.slice([start], [size]).flatten().arraySync();
let ret = [];
for (let i = 0; i < size; i++) {
let raw = [];
for (const v of arry.slice(i * 28 * 28, (i+1)*28*28)) {
raw.push(...[v*255, v*255, v*255, 255])
}
let img = await promisify(cb => {
new Jimp({ data: Buffer.from(raw), width: 28, height: 28 }, cb);
})();
ret.push(await img.getBufferAsync(Jimp.MIME_PNG));
}
return ret;
}
{
const size = 8;
const labels = await mnist.getTestData().labels.slice([0], [size]).argMax(1).array();
const pngs = await toPng(mnist.getTestData().images, 0, size);
for (let i = 0; i < size; i++) {
tslab.display.html(`<h3>label: ${labels[i]}</h3>`)
tslab.display.png(pngs[i]);
}
}
ディープラーニングで文字認識を行う
MNISTのデータの素性が一通り分かったので、TensorFlow.jsを使って「ディープニューラルネットワーク」機械学習のモデルを設計、訓練し文字認識を行ってみます。
初めはPythonのTensorFlowチュートリアルでも利用されている、128ノードの中間層を一つ持つ単純なニューラルネットワークモデルを使って文字認識を行います。
TensorFlow.jsは、PythonのTensorFlowでも使われているkerasをベースにしたAPIを提供しています。そのためこの程度のシンプルなディープニューラルネットワークはTensorFlow.jsでも非常に簡単に実装できます。Layer APIの詳細はTensorFlow.jsのドキュメントを参照してください。
const model = tf.sequential();
model.add(tf.layers.flatten({inputShape: [28, 28, 1]}));
model.add(tf.layers.dense({units: 128, activation: 'relu'}));
model.add(tf.layers.dropout({rate: 0.2}));
model.add(tf.layers.dense({units: 10, activation: 'softmax'}));
model.compile({
optimizer: 'adam',
loss: 'categoricalCrossentropy',
metrics: ['accuracy'],
});
/**
* @param {tf.Sequential} model
* @param {number} epochs
* @param {number} batchSize
* @param {string} modelSavePath
*/
async function train(model, epochs, batchSize, modelSavePath) {
// Hack to suppress the progress bar by TensorFlow.js
process.stderr.isTTY = false;
const {images: trainImages, labels: trainLabels} = mnist.getTrainData();
model.summary();
let epochBeginTime;
let millisPerStep;
const validationSplit = 0.15;
const numTrainExamplesPerEpoch =
trainImages.shape[0] * (1 - validationSplit);
const numTrainBatchesPerEpoch =
Math.ceil(numTrainExamplesPerEpoch / batchSize);
const batchesPerEpoch = Math.floor(trainImages.shape[0]*(1-validationSplit)/batchSize);
/** @type {tslab.Display} */
let display = null;
await model.fit(trainImages, trainLabels, {
callbacks: {
onEpochBegin: (epoch) => {
display = tslab.newDisplay();
},
onBatchBegin: (batch) => {
display.text(`Progress: ${(100*batch/batchesPerEpoch).toFixed(1)}%`)
},
},
epochs,
batchSize,
validationSplit,
});
const {images: testImages, labels: testLabels} = mnist.getTestData();
const evalOutput = model.evaluate(testImages, testLabels);
console.log(
`\nEvaluation result:\n` +
` Loss = ${evalOutput[0].dataSync()[0].toFixed(3)}; `+
`Accuracy = ${evalOutput[1].dataSync()[0].toFixed(3)}`);
if (modelSavePath != null) {
await model.save(`file://${modelSavePath}`);
console.log(`Saved model to path: ${modelSavePath}`);
}
}
const epochs = 5;
const batchSize = 32;
const modelSavePath = 'mnist'
await train(model, epochs, batchSize, modelSavePath);
_________________________________________________________________
Layer (type) Output shape Param #
=================================================================
flatten_Flatten1 (Flatten) [null,784] 0
_________________________________________________________________
dense_Dense1 (Dense) [null,128] 100480
_________________________________________________________________
dropout_Dropout1 (Dropout) [null,128] 0
_________________________________________________________________
dense_Dense2 (Dense) [null,10] 1290
=================================================================
Total params: 101770
Trainable params: 101770
Non-trainable params: 0
_________________________________________________________________
Epoch 1 / 5
Epoch 2 / 5
Epoch 3 / 5
Epoch 4 / 5
Epoch 5 / 5
14906ms 292us/step - acc=0.974 loss=0.0845 val_acc=0.975 val_loss=0.0796
Evaluation result:
Loss = 0.083; Accuracy = 0.980
Saved model to path: mnist
トレーニングしたモデルを実行する
98%とそこそこ精度のよい文字認識のモデルができたので実際にテストデータを使って文字認識を行ってみましょう。
const predicted = /** @type {number[]} */(tf.argMax(/** @type {tf.Tensor} */ (model.predict(mnist.getTestData().images)), 1).arraySync());
const labels = /** @type {number[]} */(tf.argMax(mnist.getTestData().labels, 1).arraySync());
console.log('predictions:', predicted.slice(0, 10));
console.log('labels:', labels.slice(0, 10));
predictions: [
7, 2, 1, 0, 4,
1, 4, 9, 5, 9
]
labels: [
7, 2, 1, 0, 4,
1, 4, 9, 5, 9
]
正しく文字認識ができていますね。せっかくなので文字認識に失敗する例を可視化してみましょう。精度が98%程度あるとはいえ、人間であれば間違えないようなものが多いですね。
const predicted = /** @type {number[]} */(tf.argMax(/** @type {tf.Tensor} */ (model.predict(mnist.getTestData().images)), 1).arraySync());
const labels = /** @type {number[]} */(tf.argMax(mnist.getTestData().labels, 1).arraySync());
const numSamples = 32;
let count = 0;
for (let i = 0; i < predicted.length && labels.length; i++) {
const pred = predicted[i];
const label = labels[i];
if (pred === label) {
continue;
}
tslab.display.html(`<h3>予測: ${pred}, 正解: ${label}</h3>`)
const pngs = await toPng(mnist.getTestData().images, i, 1);
tslab.display.png(pngs[0]);
count++;
if (count >= numSamples) {
break;
}
}
CNN (convolutional neural network) による画像認識
MNISTの文字列認識は典型的な画像を対象としたディープラーニングなので、CNNによるディープラーニングも試してみましょう。モデルの構造はTensorFlow.jsの例から拝借してきます。
const cnnModel = tf.sequential();
cnnModel.add(tf.layers.conv2d({
inputShape: [28, 28, 1],
filters: 32,
kernelSize: 3,
activation: 'relu',
}));
cnnModel.add(tf.layers.conv2d({
filters: 32,
kernelSize: 3,
activation: 'relu',
}));
cnnModel.add(tf.layers.maxPooling2d({poolSize: [2, 2]}));
cnnModel.add(tf.layers.conv2d({
filters: 64,
kernelSize: 3,
activation: 'relu',
}));
cnnModel.add(tf.layers.conv2d({
filters: 64,
kernelSize: 3,
activation: 'relu',
}));
cnnModel.add(tf.layers.maxPooling2d({poolSize: [2, 2]}));
cnnModel.add(tf.layers.flatten());
cnnModel.add(tf.layers.dropout({rate: 0.25}));
cnnModel.add(tf.layers.dense({units: 512, activation: 'relu'}));
cnnModel.add(tf.layers.dropout({rate: 0.5}));
cnnModel.add(tf.layers.dense({units: 10, activation: 'softmax'}));
const optimizer = 'rmsprop';
cnnModel.compile({
optimizer: optimizer,
loss: 'categoricalCrossentropy',
metrics: ['accuracy'],
});
最初のモデルに比べると構造が複雑なのでトレーニングには時間がかかります。GPUが使える場合はGPUによる高速化の威力がよく実感できると思います。
const epochs = 20;
const batchSize = 128;
const modelSavePath = 'cnn_mnist'
await train(cnnModel, epochs, batchSize, modelSavePath);
Layer (type) Output shape Param #
=================================================================
conv2d_Conv2D5 (Conv2D) [null,26,26,32] 320
_________________________________________________________________
conv2d_Conv2D6 (Conv2D) [null,24,24,32] 9248
_________________________________________________________________
max_pooling2d_MaxPooling2D3 [null,12,12,32] 0
_________________________________________________________________
conv2d_Conv2D7 (Conv2D) [null,10,10,64] 18496
_________________________________________________________________
conv2d_Conv2D8 (Conv2D) [null,8,8,64] 36928
_________________________________________________________________
max_pooling2d_MaxPooling2D4 [null,4,4,64] 0
_________________________________________________________________
flatten_Flatten3 (Flatten) [null,1024] 0
_________________________________________________________________
dropout_Dropout4 (Dropout) [null,1024] 0
_________________________________________________________________
dense_Dense5 (Dense) [null,512] 524800
_________________________________________________________________
dropout_Dropout5 (Dropout) [null,512] 0
_________________________________________________________________
dense_Dense6 (Dense) [null,10] 5130
=================================================================
Total params: 594922
Trainable params: 594922
Non-trainable params: 0
_________________________________________________________________
Epoch 1 / 20
...
Epoch 20 / 20
Evaluation result:
Loss = 0.022; Accuracy = 0.994
Saved model to path: cnn_mnist
テストデータに対する精度が99.4%まで上昇しました。先の精度98%の単純なモデルでやったように、CNNでも認識に失敗している画像を表示してみましょう。
最初の単純なモデルの失敗例に比べると、人間でも認識に失敗しそうな、あるいはラベルが間違っていると言いたくなるような例が多くなっており、文字認識の精度が大きく向上していることが体感できると思います。
const predicted = /** @type {number[]} */(tf.argMax(/** @type {tf.Tensor} */ (cnnModel.predict(mnist.getTestData().images)), 1).arraySync());
const labels = /** @type {number[]} */(tf.argMax(mnist.getTestData().labels, 1).arraySync());
const numSamples = 32;
let count = 0;
for (let i = 0; i < predicted.length && labels.length; i++) {
const pred = predicted[i];
const label = labels[i];
if (pred === label) {
continue;
}
tslab.display.html(`<h3>予測: ${pred}, 正解: ${label}</h3>`)
const pngs = await toPng(mnist.getTestData().images, i, 1);
tslab.display.png(pngs[0]);
count++;
if (count >= numSamples) {
break;
}
}
以上でJavaScriptのみで複雑なディープラーニングモデルを構築・訓練から実際にアプリケーション上で推論を行うところまでエンドツーエンドで行うことができました。TensorFlow.jsを使っているので、完成したモデルをブラウザ上で実行することも最小限の追加コードで実現できます。詳しくはTensorFlow.jsのドキュメントや他の人による解説記事を参照してください。
最後に
JavaScript (TypeScript) でもTensorFlowとJupyterを使用してディープラーニングのトレーニングが行えることを示しました。TensorFlow.jsを使えばPythonを使用しなくてもJavaScriptだけで最新のディープラーニングをモデルのトレーニングから実際のプロダクションでの利用までエンドツーエンドで行うことが可能です。
もちろん現状では機械学習の分野でのPythonの地位は圧倒的なものであり、TensorFlowがJavaScriptをサポートしているとはいえ、APIやドキュメントの充実度には同じTensorFlowの中でも天地ほどの差があります。また周辺のデータ分析関係のライブラリの充実度やエコシステムの規模も考えると機械学習のプロジェクトにJavaScript(というよりPython以外の言語)をメインで採用する合理的な理由は現時点ではほとんど存在しないと思います。
一方でPythonは2010年代に大きく成長したもう一つの分野であるモバイルアプリ開発やWebの分野ではそれほど成功していない現状や、Webフロントエンド開発でのJavaScriptの地位は当分の間は揺らぎそうにないこと、TypeScriptとそのエコシステムが大きく成長しているおり大規模開発にも耐えうるようになってきていることなどを踏まえるとデータ分析分野でのPythonの不動の地位と、それによるPythonの人気の上昇も必ずしも向こう10年間安泰ではないかもしれません。いずれにしてもプログラミング言語のある分野での人気と人気を裏付ける利便性は鶏と卵の関係にあるので誰かが初期投資をする必要があります。2019年末現在、この分野におけるJavaScriptのライブラリ、フレームワークは非常に貧弱であり、Pythonにおけるnumpy, pandas, sklearnなどのようなデファクトスタンダードは存在しません。JavaScriptユーザのみなさん、ブルー・オーシャンであるJavaScriptの機械学習分野での成長に賭けてみるのも面白いかもしれませんよ。