1. 序章

この記事では、 deeplearning4j (dl4j)ライブラリを使用して単純なニューラルネットワークを作成します。これは、機械学習用の最新の強力なツールです。

始める前に、このガイドでは、線形代数、統計、機械学習理論、および十分な知識を持つMLエンジニアに必要なその他の多くのトピックに関する深い知識は必要ありません。

2. ディープラーニングとは何ですか?

ニューラルネットワークは、ノードの相互接続された層で構成される計算モデルです。

ノードは、数値データのニューロンのようなプロセッサです。 入力からデータを取得し、これらのデータにいくつかの重みと関数を適用して、結果を出力に送信します。 このようなネットワークは、ソースデータのいくつかの例を使用してトレーニングできます。

トレーニングは基本的に、ノードに数値状態(重み)を保存することであり、後で計算に影響を与えます。 トレーニングの例には、機能を備えたデータアイテムと、これらのアイテムの特定の既知のクラスが含まれる場合があります(たとえば、「この16×16ピクセルのセットには手書きの文字「a」が含まれます)。

トレーニングが終了すると、ニューラルネットワークは これらの特定のデータ項目を以前に見たことがなくても、新しいデータから情報を引き出すことができます。 適切にモデル化され、十分にトレーニングされたネットワークは、画像、手書きの文字、音声を認識し、統計データを処理してビジネスインテリジェンスの結果を生成することができます。

近年、高性能並列計算の進歩により、ディープニューラルネットワークが可能になりました。 このようなネットワークは、 複数の中間(または非表示)レイヤーで構成されるという点で単純なニューラルネットワークとは異なります。 この構造により、ネットワークはより複雑な方法(再帰的、反復的、畳み込み的方法など)でデータを処理し、そこからより多くの情報を抽出できます。

3. プロジェクトの設定

ライブラリを使用するには、少なくともJava7が必要です。 また、一部のネイティブコンポーネントが原因で、64ビットJVMバージョンでのみ機能します。

ガイドを始める前に、要件が満たされているかどうかを確認しましょう。

$ java -version
java version "1.8.0_131"
Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)

まず、必要なライブラリをMaven pom.xmlファイルに追加しましょう。 ライブラリのバージョンをプロパティエントリに抽出します(ライブラリの最新バージョンについては、 Maven Central リポジトリを確認してください)。

<properties>
    <dl4j.version>0.9.1</dl4j.version>
</properties>

<dependencies>

    <dependency>
        <groupId>org.nd4j</groupId>
        <artifactId>nd4j-native-platform</artifactId>
        <version>${dl4j.version}</version>
    </dependency>

    <dependency>
        <groupId>org.deeplearning4j</groupId>
        <artifactId>deeplearning4j-core</artifactId>
        <version>${dl4j.version}</version>
    </dependency>
</dependencies>

nd4j-native-platform 依存関係は、利用可能ないくつかの実装の1つであることに注意してください。

これは、多くの異なるプラットフォーム(macOS、Windows、Linux、Androidなど)で利用可能なネイティブライブラリに依存しています。 CUDAプログラミングモデルをサポートするグラフィックカードで計算を実行したい場合は、バックエンドをnd4j-cuda-8.0-platformに切り替えることもできます。

4. データの準備

4.1. DataSetファイルの準備

機械学習の「HelloWorld」—iris花データセットの分類を記述します。 これは、さまざまな種の花( Iris setosa Iris versicolor 、および Iris virginica )から収集されたデータのセットです。

これらの種は、花びらとがく片の長さと幅が異なります。 入力データ項目を分類する(つまり、特定の花がどの種に属するかを決定する)正確なアルゴリズムを作成するのは難しいでしょう。 しかし、よく訓練されたニューラルネットワークは、それをすばやく、ほとんど間違いなく分類できます。

このデータのCSVバージョンを使用します。ここで、列0..3には種のさまざまな特徴が含まれ、列4には、値0、1、または2でコード化されたレコードのクラスまたは種が含まれます。

5.1,3.5,1.4,0.2,0
4.9,3.0,1.4,0.2,0
4.7,3.2,1.3,0.2,0
…
7.0,3.2,4.7,1.4,1
6.4,3.2,4.5,1.5,1
6.9,3.1,4.9,1.5,1
…

4.2. データのベクトル化と読み取り

ニューラルネットワークは数値を処理するため、クラスを数値でエンコードします。 実世界のデータ項目を一連の数値(ベクトル)に変換することをベクトル化と呼びます–deeplearning4jはdatavecライブラリを使用してこれを行います。

まず、このライブラリを使用して、ベクトル化されたデータを含むファイルを入力してみましょう。 CSVRecordReader を作成するときに、スキップする行数(たとえば、ファイルにヘッダー行がある場合)と区切り記号(この場合はコンマ)を指定できます。

try (RecordReader recordReader = new CSVRecordReader(0, ',')) {
    recordReader.initialize(new FileSplit(
      new ClassPathResource("iris.txt").getFile()));

    // …
}

レコードを反復処理するために、DataSetIteratorインターフェースの複数の実装のいずれかを使用できます。 データセットは非常に大規模になる可能性があり、値をページングまたはキャッシュする機能が役立つ可能性があります。

ただし、小さなデータセットには150レコードしかないため、 iterator.next()を呼び出して、すべてのデータを一度にメモリに読み込みます。

クラス列のインデックスも指定します。この場合、機能数(4)およびクラスの総数(3)と同じです。

また、元のファイルのクラスの順序を削除するには、データセットをシャッフルする必要があることに注意してください。

シャッフルの結果が常に同じになるように、デフォルトの System.currentTimeMillis()呼び出しの代わりに、定数ランダムシード(42)を指定します。 これにより、プログラムを実行するたびに安定した結果を得ることができます。

DataSetIterator iterator = new RecordReaderDataSetIterator(
  recordReader, 150, FEATURES_COUNT, CLASSES_COUNT);
DataSet allData = iterator.next();
allData.shuffle(42);

4.3. 正規化と分割

トレーニングの前にデータを使用して行う必要があるもう1つのことは、データを正規化することです。 正規化は2段階のプロセスです。

  • データに関するいくつかの統計の収集(適合)
  • データを均一にするために何らかの方法でデータを変更(変換)する

正規化は、データの種類によって異なる場合があります。

たとえば、さまざまなサイズの画像を処理する場合は、最初にサイズ統計を収集してから、画像を均一なサイズに拡大縮小する必要があります。

しかし、数値の場合、正規化とは通常、それらをいわゆる正規分布に変換することを意味します。 NormalizerStandardize クラスは、次の点で役立ちます。

DataNormalization normalizer = new NormalizerStandardize();
normalizer.fit(allData);
normalizer.transform(allData);

データが準備できたので、セットを2つの部分に分割する必要があります。

最初の部分はトレーニングセッションで使用されます。 データの2番目の部分(ネットワークにはまったく表示されない)を使用して、トレーニングされたネットワークをテストします。

これにより、分類が正しく機能することを確認できます。 トレーニング用に65% ofのデータ(0.65)を取得し、残りの35% fまたはテストを残します。

SplitTestAndTrain testAndTrain = allData.splitTestAndTrain(0.65);
DataSet trainingData = testAndTrain.getTrain();
DataSet testData = testAndTrain.getTest();

5. ネットワーク構成の準備

5.1. Fluent Configuration Builder

これで、洗練された流暢なビルダーを使用してネットワークの構成を構築できます。

MultiLayerConfiguration configuration 
  = new NeuralNetConfiguration.Builder()
    .iterations(1000)
    .activation(Activation.TANH)
    .weightInit(WeightInit.XAVIER)
    .learningRate(0.1)
    .regularization(true).l2(0.0001)
    .list()
    .layer(0, new DenseLayer.Builder().nIn(FEATURES_COUNT).nOut(3).build())
    .layer(1, new DenseLayer.Builder().nIn(3).nOut(3).build())
    .layer(2, new OutputLayer.Builder(
      LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD)
        .activation(Activation.SOFTMAX)
        .nIn(3).nOut(CLASSES_COUNT).build())
    .backprop(true).pretrain(false)
    .build();

ネットワークモデルを構築するこの単純化された流暢な方法でも、消化することがたくさんあり、微調整するパラメータがたくさんあります。 このモデルを分解してみましょう。

5.2. ネットワークパラメータの設定

iterations()ビルダーメソッドは、最適化の反復回数を指定します。

反復最適化とは、ネットワークが良好な結果に収束するまで、トレーニングセットに対して複数のパスを実行することを意味します。

通常、実際のデータセットと大規模なデータセットでトレーニングする場合、複数のエポック(ネットワークを介したデータの完全なパス)と、エポックごとに1回の反復を使用します。 ただし、最初のデータセットは最小限であるため、1つのエポックと複数の反復を使用します。

Activation()は、ノード内で実行されてその出力を決定する関数です。

最も単純な活性化関数は線形です f(x)=x。 しかし、ネットワークがいくつかのノードを使用して複雑なタスクを解決できるのは、非線形関数だけであることがわかります。

org.nd4j.linalg.activations.Activation列挙型で検索できるさまざまなアクティベーション関数が利用可能です。 必要に応じて、活性化関数を作成することもできます。 ただし、提供されている双曲線正接(tanh)関数を使用します。

weightInit()メソッドは、ネットワークの初期重みを設定する多くの方法の1つを指定します。正しい初期重みは、トレーニングの結果に大きく影響する可能性があります。 数学にあまり踏み込まずに、ガウス分布の形式( WeightInit.XAVIER )に設定しましょう。これは、通常、開始に適しています。

他のすべての重み初期化メソッドは、org.deeplearning4j.nn.weights.WeightInit列挙型で検索できます。

学習率は、ネットワークの学習能力に大きく影響する重要なパラメーターです。

より複雑なケースでは、このパラメーターの調整に多くの時間を費やす可能性があります。 ただし、単純なタスクでは、かなり重要な値0.1を使用し、 LearningRate()ビルダーメソッドを使用して設定します。

ニューラルネットワークのトレーニングに関する問題の1つは、ネットワークがトレーニングデータを「記憶」するときに過剰適合する場合です。

これは、ネットワークがトレーニングデータに過度に高い重みを設定し、他のデータに悪い結果をもたらす場合に発生します。

この問題を解決するために、 .regularization(true).l2(0.0001)という行でl2正則化を設定します。 正則化は、重みが大きすぎる場合にネットワークに「ペナルティ」を課し、過剰適合を防ぎます。

5.3. ネットワークレイヤーの構築

次に、密な(完全接続とも呼ばれる)レイヤーのネットワークを作成します。

最初のレイヤーには、トレーニングデータの列と同じ数のノードが含まれている必要があります(4)。

2番目の高密度レイヤーには3つのノードが含まれます。 これは変化させることができる値ですが、前のレイヤーの出力の数は同じでなければなりません。

最終的な出力レイヤーには、クラスの数(3)に一致するノードの数が含まれている必要があります。 ネットワークの構造を図に示します。

トレーニングが成功すると、入力を介して4つの値を受け取り、3つの出力の1つに信号を送信するネットワークができあがります。 これは単純な分類子です。

最後に、ネットワークの構築を完了するために、バックプロパゲーション(最も効果的なトレーニング方法の1つ)を設定し、 .backprop(true).pretrain(false)の行を使用して事前トレーニングを無効にします。

6. ネットワークの作成とトレーニング

次に、構成からニューラルネットワークを作成し、初期化して実行します。

MultiLayerNetwork model = new MultiLayerNetwork(configuration);
model.init();
model.fit(trainingData);

これで、残りのデータセットを使用してトレーニング済みモデルをテストし、3つのクラスの評価指標を使用して結果を検証できます。

INDArray output = model.output(testData.getFeatureMatrix());
Evaluation eval = new Evaluation(3);
eval.eval(testData.getLabels(), output);

eval.stats()を印刷すると、クラス1とクラス2を3回間違えたものの、ネットワークはアイリスの花の分類に非常に優れていることがわかります。

Examples labeled as 0 classified by model as 0: 19 times
Examples labeled as 1 classified by model as 1: 16 times
Examples labeled as 1 classified by model as 2: 3 times
Examples labeled as 2 classified by model as 2: 15 times

==========================Scores========================================
# of classes: 3
Accuracy: 0.9434
Precision: 0.9444
Recall: 0.9474
F1 Score: 0.9411
Precision, recall & F1: macro-averaged (equally weighted avg. of 3 classes)
========================================================================

流暢な構成ビルダーを使用すると、ネットワークのレイヤーをすばやく追加または変更したり、他のパラメーターを微調整して、モデルを改善できるかどうかを確認したりできます。

7. 結論

この記事では、deeplearning4jライブラリを使用して、シンプルでありながら強力なニューラルネットワークを構築しました。

いつものように、記事のソースコードはGitHubから入手できます。