[iOS 10][ニューラルネットワーク] OSSでAccelerateに追加されたBNNSを理解する ~XOR編~

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

数値計算処理全般をあつかったフレームワークAccelerateにはiOS10でニューラルネットワークのためのサブルーチンであるBNNS(Basic neural network subroutines)が追加されています。本記事ではGithubに公開されているOSS

のコードを見ながらBNNSのインターフェースを理解していきたいと思います。

尚、本記事ではOSSのサンプルのうち、XOR(排他的論理和)を再現するモデルを扱ったコードを中心に解説します。サンプルでは他にもMNISTを扱っていますが、こちらは別記事で解説予定です。

BNNSで扱われるニューラルネットワークの知識

ニューラルネットワークについては昨今話題の深層学習(Deep learning)でご存知の方も多いと思いますが、詳しい説明は他の専門書に譲ります。

こちらではBNNSのAPIの理解にあたって必要なニューラルネットワーク関連用語を列挙していき、各用語に関するざっくりとした説明をしていきたいと思います。

ユニット

ユニットはニューラルネットワークの処理単位の一つです。複数の入力に対して重みを付けて足しあわせ、それにバイアスを加えることで総入力とし、それに対して活性化関数を適用することでユニットの最終的な出力とします。式でかくと、\(x_i\)を入力、\(w_i\)を重み、\(b\)をバイアス、\(u\)を総出力、\(z\)を最終的な出力、\(f\)を活性化関数として

\[ z = f(u) \]

\[ u = \sum^I_{i = 1} w_i x_i + b \]

となります。活性化関数はいろいろな値を取ることが出来ますが、BNNSで取れるものは限定されています。これは後ほど見ていきます。

ニューラルネットワークはこのようなユニットの処理を並べたり、ユニットの出力を再び新たな入力とすることで複雑な問題に対処できるモデルを作成します。

ユニットの処理が徐々に重ね合わされると一本の数式で表現するのも不便になるため、ユニットの処理はグラフで表現されることもあります。例えば先ほどの処理で入力が4つの場合を以下の様な記法で表現します。

このユニットで得られるモデルのうち、活性化関数がステップ関数

\[ \begin{eqnarray} f(x) = \begin{cases} 1 & ( x \geq 0 ) \\ -1 & ( x \lt 0 ) \end{cases} \end{eqnarray} \]

であるような場合はパーセプトロンと呼ばれます。

入力と活性化関数が同じユニットを同列に並べることでを構成できます。

\[ z_k = f(u_k) \ \ (k = 1, \dots, K) \]

\[ u_k = \sum^I_{i = 1} w_{ki} x_i + b_k \ \ (k = 1, \dots, K) \]

入力が4つ(I=4)、出力が3つ(K=3)の層は下記のグラフで表現されます。

このように得られたOutputは新しい層のInputとすることで層を積み重ねることが出来ます。

例えば層が2つ重なったモデルは次のように書けます。

\[ z_j = f(u_j) \ \ (j = 1, \dots, J) \]

\[ u_j = \sum^I_{i = 1} w^{(1)}_{ji} x_i + b^{(1)}_j \ \ (j = 1, \dots, J) \]

\[ y_k = g(t_k) \ \ (k = 1, \dots, K) \]

\[ t_k = \sum^J_{j = 1} w^{(2)}_{kj} z_j + b^{(2)}_k \ \ (k = 1, \dots, K) \]

I=2, J=3, K=1のモデルは下記のグラフで表現されます。

入力と出力も層に含めてそれぞれ入力層、出力層と呼んだりします。

層の入力としては前段の層の出力全てを必ずしも対象とする必要はないです。入力対象の制限方法や出力の計算方法に応じて層の役割に名前が付けられています。BNNSが対象とする層の役割としては次の3つがあります

  • 全結合層:前段の層の出力全てを入力対象とするようなものです。
  • 畳み込み層:画像を対象に用いることが想定されており、出力を得るための重み付けとしてフィルタを用います。フィルタの濃淡パターンと類似したパターンが入力のどこにあるかを示す出力を得ます。
  • プーリング層:畳み込み層で得られた出力の感度を若干低下させるために、入力(画像)の特定の範囲に対する画素の最大値や平均値をとって出力を得る層です。

順伝播型ニューラルネットワーク

ニューラルネットワークには様々な種類がありますが、今回のOSSが対象とするのは順伝播型ニューラルネットワーク(Feed forward neural network)です。

順伝播型ニューラルネットワークは層状に並べたユニットが隣接層のみで結合し、入力から最終的な出力までが一方向に伝播していく性質をもったニューラルネットワークです。

尚、順伝播型ニューラルネットワークは多層パーセプトロン(Multi Layer Perceptron)と混同されることがよくありますが、後者は活性化関数としてステップ関数のみを扱っている点で微妙に異なります。1

Torch7

今回のOSSはTorch7で学習したニューラルネットワークのモデルをios向けのフォーマットとして出力したファイルを取り扱います。モデルのファイル出力用のOSSとしては下記ライブラリが公開されています。

Torch7はGPUを中心とした機械学習アルゴリズムをサポートするフレームワークで、その中にニューラルネットワークを手軽に構築するためのAPIが提供されています。本記事ではTorch7を深追いしません。

サンプルを動かしてみる

2016/09/11現在のバージョンのソースコードをもとにOSSの例と照らし合わせながらBNNSの基本的な機能について確認していきます。

まず、Githubからクローンしてシミュレータで動かしてみます。

するとつぎのようなログを得ます。

2016-09-11 21:11:59.325 KSJAIKit[20195:1230058] ============================================================================================
=====================XXX=XXX===XXXXXXX===XXXXXX=============================================
======================XX=XX====X=====X===X====X=============================================
=======================XXX=====X=====X===XXXXXX=============================================
======================XX=XX====X=====X===X==X===============================================
=====================XX===XX===XXXXXXX===X===XX=============================================
============================================================================================
============================================================================================
2016-09-11 21:11:59.367 KSJAIKit[20195:1230058] result: -1.541313
2016-09-11 21:11:59.367 KSJAIKit[20195:1230058] result_2: 1.325536
2016-09-11 21:11:59.368 KSJAIKit[20195:1230058] 

 MODEL ARCHITECTURE, ON DEVICE :1 

Linear => [1]
Input Size => [2]
Output Size => [20]

Linear => [2]
Input Size => [20]
Output Size => [1]

2016-09-11 21:11:59.368 KSJAIKit[20195:1230058] ============================================================================================
======================XXXXXXXX====XX====X====XX====X========================================
======================X===========X=X===X====X=X===X========================================
======================X===========X==X==X====X==X==X========================================
======================X===========X===X=X====X===X=X========================================
======================X===========X====XX====X====XX========================================
======================XXXXXXXX====X=====X====X=====X========================================
============================================================================================
2016-09-11 21:11:59.603 KSJAIKit[20195:1230058] 

 MODEL ARCHITECTURE, ON DEVICE :1 

SpatialConvolution => [1]
Kernel Size => [5 x 5]
Input Size => [32.00 x 32.00]
Output Size => [28.00 x 28.00]
Input Channels => [1]
Output Channels => [32]
  Activation Function => [4]

SpatialMaxPooling => [2]
Kernel Size => [3 x 3]
Input Size => [28.00 x 28.00]
Output Size => [9.00 x 9.00]
Pooling Type => [0]
Input Channels => [32]
Output Channels => [32]
  Activation Function => [0]

SpatialConvolution => [3]
Kernel Size => [5 x 5]
Input Size => [9.00 x 9.00]
Output Size => [5.00 x 5.00]
Input Channels => [32]
Output Channels => [64]
  Activation Function => [4]

SpatialMaxPooling => [4]
Kernel Size => [2 x 2]
Input Size => [5.00 x 5.00]
Output Size => [2.00 x 2.00]
Pooling Type => [0]
Input Channels => [64]
Output Channels => [64]
  Activation Function => [0]

Linear => [5]
Input Size => [256]
Output Size => [200]

Linear => [6]
Input Size => [200]
Output Size => [10]

2016-09-11 21:11:59.621 KSJAIKit[20195:1230058] Target: 8
2016-09-11 21:11:59.623 KSJAIKit[20195:1230058] Prediction: 8
2016-09-11 21:11:59.623 KSJAIKit[20195:1230058] Target: 3
2016-09-11 21:11:59.624 KSJAIKit[20195:1230058] Prediction: 3
2016-09-11 21:11:59.625 KSJAIKit[20195:1230058] Target: 2
2016-09-11 21:11:59.626 KSJAIKit[20195:1230058] Prediction: 2
2016-09-11 21:11:59.627 KSJAIKit[20195:1230058] Target: 1
2016-09-11 21:11:59.628 KSJAIKit[20195:1230058] Prediction: 1
2016-09-11 21:11:59.628 KSJAIKit[20195:1230058] Target: 5
2016-09-11 21:11:59.629 KSJAIKit[20195:1230058] Prediction: 5

サンプルにはXOR問題を全結合層を2つ重ねて再現するコードと、MNISTデータ・セットを畳み込み層とプーリング層と全結合層を組み合わせて識別するコードの2つが含まれますが、今回のXOR問題編では前半を中心に解説します(MNIST編では畳み込み層とプーリング層の仕組みを含めて解説予定です)。

XOR問題のソースコードを見てみる

プロジェクトの構成はおおまかに次のようになっています。

  • KSJAIKit : プロジェクトルート
    • Library : ニューラルネットワークを表すクラス(KSJNeuralNetwork)、ソフトマックスの計算に必要な関数等(KSJBNNSExtras)が含まれる
    • ArchConfigurations : 全結合層(KSJFullyConnectedArchConfigulation)、畳込み層(KSJConvolutionArchConfigulation)、プーリング層(KSJPoolingArchConfigulation)を表す設定クラスが含まれる
    • Torch7ES : Torch7で作成したモデルファイル等が含まれる
      • THESCore : モデルファイルからニューラルネットワーククラスを生成するローダカテゴリ(KSJNeuralNetwork+Torch7ES)や層を表すクラス(THESLayer)やバイナリデータから層のArrayを読み込むクラス(THESDiskFile)が含まれる

XOR問題を扱っている部分のソースをBNNSのAPIを触っている箇所を中心に辿ってみます。

まず、画面クラスがロードされた時に呼ばれるコードを見てみます。

ViewController.m#viewDidLoad

NSLog(@"============================================================================================\n=====================XXX=XXX===XXXXXXX===XXXXXX=============================================\n======================XX=XX====X=====X===X====X=============================================\n=======================XXX=====X=====X===XXXXXX=============================================\n======================XX=XX====X=====X===X==X===============================================\n=====================XX===XX===XXXXXXX===X===XX=============================================\n============================================================================================\n============================================================================================");

NSDictionary *xor_network_components = [KSJNeuralNetwork NeuralNetworkWithTorchESFilePath:[[NSBundle mainBundle] pathForResource:@"ios_xor" ofType:@"t7ios"] on:KSJNeuralNetworkDeviceCPU];
KSJNeuralNetwork *xor_net = xor_network_components[kNeuralNetworkKey];
BNNSDataType xor_datatype = [xor_network_components[kNeuralNetworkLayersDataType] integerValue];
[xor_net buildGraphWithDataType:xor_datatype];

float f [] = {1.0f,1.0f};
float f2 [] = {-1.0f,1.0f};
NSValue *xor_input = [NSValue valueWithPointer:f];
NSValue *xor_input_2 = [NSValue valueWithPointer:f2];
NSValue *xor_result = [xor_net forward:xor_input];
NSValue *xor_result_2 = [xor_net forward:xor_input_2];
NSLog(@"result: %f",((float *)[xor_result pointerValue])[0]);
NSLog(@"result_2: %f",((float *)[xor_result_2 pointerValue])[0]);
NSLog(@"%@",[xor_net graphToString]);

free([xor_result pointerValue]);
free([xor_result_2 pointerValue]);
[xor_net destroyGraph];

l3

torch7で学習したモデルファイル(ios_xor.t7ios)からCPUを用いた学習モデル等が入った辞書を取り出します。辞書の中身には下記キーに合わせて次のオブジェクトが入っています。

  • kNeuralNetworkKey: ニューラルネットワークモデル本体を表すKSJNeuralNetworkのインスタンス
  • kNeuralNetworkLayersDataType: ニューラルネットワークで扱う数値のenum(BNNSDataType)が入ります。この例ではBNNSDataTypeFloat32が入っています。

  • kNeuralNetworkLayersKey: ファイルからロードされたTHESLayerのArrayです。

NeuralNetworkWithTorchESFilePathメソッド内部の処理についてはKSJNeuralNetworkの説明で後述します。

l6

KSJNeuralNetworkクラスのbuildGraphWithDataTypeメソッドを呼び出し、KSJNeuralNetworkクラスに含まれる各層の設定クラスからBNNSFilter(後述)を構築していきます。

メソッド内部の処理についてはKSJNeuralNetworkの説明で後述します。

l8-11

実際に構築されたモデルに与えるサンプルの出力をNSValueとして定義します。サンプル入力として次の2つのベクトルが与えられています

\[ \left( \begin{array}{c} 1 \\ 1 \end{array} \right), \left( \begin{array}{c} -1 \\ 1 \end{array} \right) \]

XORの出力としては上のベクトルのそれぞれに対して負の値と正の値が出力されることが期待されています。

l12,13

構築されたモデルにサンプル入力を与えて予測するメソッドforwardを呼び出し、出力を得ます。

メソッド内部の処理についてはKSJNeuralNetworkの説明で後述します。

l14, 15

出力をログに残します。実際に

2016-09-11 21:11:59.367 KSJAIKit[20195:1230058] result: -1.541313
2016-09-11 21:11:59.367 KSJAIKit[20195:1230058] result_2: 1.325536

のログが得られていることから期待される出力が得られることがわかります。

l16

排他的論理和のモデルのグラフをログに残します。実際に

 MODEL ARCHITECTURE, ON DEVICE :1 

Linear => [1]
Input Size => [2]
Output Size => [20]

Linear => [2]
Input Size => [20]
Output Size => [1]

のログが得られます。一行目はCPUデバイスで動くモデルであることを表し、二行目以降については

このモデルが全結合層のみによって構成され(Linear) 上記の図でLeyer1の中間層が二十個になったものであることを表しています。

graphToStringメソッドはモデル内部の構造をログに見やすい形式の文字列にする関数です。

l18-20

ロードしたモデルのメモリからの開放を実施します。

KSJNeuralNetworkクラス

ViewControllerで呼ばれたメソッドを中心に見ていきます。

NeuralNetworkWithTorchESFilePath:on:メソッド

+ (NSDictionary *)NeuralNetworkWithTorchESFilePath:(NSString *)path on:(KSJNeuralNetworkDevice)device
{
    KSJNeuralNetwork *neuralNetwork;
    if (device == KSJNeuralNetworkDeviceCPU)
    {
        neuralNetwork = [[KSJNeuralNetworkCPU alloc] init];
    }
    else if (device == KSJNeuralNetworkDeviceGPU)
    {
        neuralNetwork = [[KSJNeuralNetworkGPU alloc] init];
    }

    NSArray *layers = [THESDiskFile readLayersBinary:path];

    for (int i = 0; i < layers.count; i++)
    {
        THESLayer *layer = layers[i];
        //Linear
        if ([layer.layerType integerValue] == THES_LINEAR_LAYER_TYPE_INDEX)
        {
            BNNSActivationFunction f = BNNSActivationFunctionIdentity;
            if (i + 1 < layers.count)
            {
                f = [self activationFunctionForTorchIndex:[((THESLayer *)layers[i+1]).layerType integerValue]];
            }
            int *net_structure = (int*)[[layer structureBuffer] pointerValue];
            KSJFullyConnectedArchConfiguration *full_conn_arch = [KSJFullyConnectedArchConfiguration archFromStructureBufferWrapped:net_structure weightBuffer:layer.weightsBuffer biasBuffer:layer.biasBuffer dataType:BNNSDataTypeFloat32 activation:f];
            [neuralNetwork addFullyConnectedLayerWithConfiguration:full_conn_arch];
        }

        ...... // 中略

    return @{kNeuralNetworkKey:neuralNetwork,kNeuralNetworkLayersKey:layers,kNeuralNetworkLayersDataType:@(dataType)};
}
l3-11

ニューラルネットワークを表すクラスのうちデバイスで行う計算処理をCPUに依頼するかGPUに依頼するかでKSJNeuralNetworkのどのサブクラスを用いるかが変わります。今回のサンプルではCPUに計算処理を行なってもらうクラス(KSJNeuralNetworkCPU)を用いていますがGPUを用いるクラスも存在します。2016/09/12現在では未実装のようです。iOS 10で新たに導入されたMetalPerformanceShaderというGPU資源を用いたニューラルネットワークの計算モジュールがあり、GPUのクラスの方にはそのモジュールを用いて計算機能実装されるのではと推察されます。

l13,15

Torch7のレイヤを表すクラス(THESLayer)のArrayの中身を順に見ていってKSJNeuralNetworkを構成していきますが、Torch7のレイヤはニューラルネットワークの層の概念と対応していないです。

Torch7のニューラルネットワークは次のXORモデルの構造を生成しているコード(l4-6)に見られるように入出力の個数等を表すレイヤと活性化関数を表すレイヤを交互に重ね合わせて構成していきます。

require "nn"
mlp = nn.Sequential();  -- make a multi-layer perceptron
inputs = 2; outputs = 1; HUs = 20; -- parameters
mlp:add(nn.Linear(inputs, HUs))
mlp:add(nn.Tanh())
mlp:add(nn.Linear(HUs, outputs))

Training a neural network - torch/nn Github

そのためTorch7のモデルから入出力の個数等を表すレイヤと活性化関数のレイヤをとりだし、ニューラルネットワークの層に対応するクラス(KSJArchConfigulation)に構成し直す必要があります。

l19

この部分から始まるif文で層の種別が決定されます。今回扱うXORについては全結合層のみであり、その部分を中心に見ていきます。

l21-25

モデルファイルから読み込んだレイヤを表すクラス(THESLayer)のうち活性化関数を表すレイヤを読み込み、BNNSActivationFunctionに変換しています。これは活性化関数を表すenumですが、BNNSでは次のような関数が活性化関数として指定できます。

  • 恒等関数(BNNSActivationFunctionIdentity)
  • 正規化線形関数(BNNSActivationFunctionRectifiedLinear)
  • 正規化線形関数に定数αを掛けたもの(BNNSActivationFunctionLeakyRectifiedLinear)
  • シグモイド関数(BNNSActivationFunctionSigmoid)
  • tanh関数(BNNSActivationFunctionTanh)
  • tanh関数の入力に定数βをかけ、全体に定数αをかけたもの(BNNSActivationFunctionScaledTanh)
  • 絶対値(BNNSActivationFunctionAbs)
l26

モデルファイルから入出力の個数を表すバッファstructuredBufferのポインタを読みだします。

l27

モデルファイルと先ほど読み込んだ活性化関数をもとに全結合層を表すクラス(KSJFullyConnectedArchConfiguration)をロードします。入出力の個数等を表すレイヤには重みとバイアスのバッファも含まれます。

l28

ニューラルネットワークモデルの構造を表すArray(#nnGraphStructure)に全結合層を追加します。

buildGraphWithDataType:メソッド

- (void)buildGraphWithDataType:(NSInteger)dataType
{
    ...... // 中略 

    self.nnDatatype = dataType;
    [self.nnGraphStructure enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        KSJArchConfiguration *configuration = (KSJArchConfiguration *)obj;
        if ([configuration.layerName isEqualToString:@"Linear"])
        {
            [self createFullyConnectedLayerWithConfiguration:obj];
        }
        else if ([configuration.layerName isEqualToString:@"SpatialConvolution"])
        {
            [self createConvolutionLayerWithConfiguration:obj];
        }
        else if ([configuration.layerName isEqualToString:@"SpatialMaxPooling"])
        {
            [self createPoolingLayerWithConfiguration:obj];
        }
        else if ([configuration.layerName isEqualToString:@"SpatialAveragePooling"])
        {
            [self createPoolingLayerWithConfiguration:obj];
        }
    }];
}

このメソッドはニューラルネットの構造を表すArray(#nnGraphStructure)からBNNSFilter(後述)をロードする処理を行う起点となっています。XORモデルでは全結合層のみをロードしているため、l10のcreateFullyConnectedLayerWithConfigulationを見てみます。

KSJNeuralNetworkCPU#createFullyConnectedLayerWithConfigulation:

- (BOOL)createFullyConnectedLayerWithConfiguration:(KSJFullyConnectedArchConfiguration *)configuration
{
    BNNSVectorDescriptor i_desc =
    {
        .size = configuration.inputSize,
        .data_type = (BNNSDataType)configuration.dataType,
        .data_scale = 0,
        .data_bias = 0,
    };

    BNNSVectorDescriptor h_desc =
    {
        .size = configuration.outputSize,
        .data_type = (BNNSDataType)configuration.dataType,
        .data_scale = 0,
        .data_bias = 0,
    };

    BNNSActivation activation =
    {
        .function = configuration.activationFunction,
        .alpha = 0,
        .beta = 0,
    };

    BNNSFullyConnectedLayerParameters in_layer_params =
    {
        .in_size = i_desc.size,
        .out_size = h_desc.size,
        .activation = activation,
        .weights.data = configuration.weightBuffer.pointerValue,
        .weights.data_type = (BNNSDataType)configuration.dataType,
        .bias.data_type = (BNNSDataType)configuration.dataType,
        .bias.data = configuration.biasBuffer.pointerValue,
    };

    BNNSFilterParameters filter_params =
    {

    };

    BNNSFilter ih_filter = BNNSFilterCreateFullyConnectedLayer(&i_desc, &h_desc, &in_layer_params, &filter_params);
    if (ih_filter == NULL) { fprintf(stderr,"BNNSCreateFailedFullyConnected failed\n"); return NO; }
    [self.nnModel addObject:[NSValue valueWithPointer:ih_filter]];
    return YES;
}

このメソッドでBNNSのうち、全結合層を表すパラメータを指定し、BNNSFilterをロードしています。

BNNSFilterは実際に構築された層をもとに予測を行うための構造体で、後述するBNNSFilterApply関数を用いて入出力のポインタとともに引数に渡すことで予測を実施出来ます。

l3-9

入力ベクトルを記述するために、ベクトルを表す構造体BNNSVectorDescriptorをサイズ、データ型を指定して宣言しています。

l11-17

出力ベクトルを記述するために、ベクトルを表す構造体をサイズ、データ型を指定して宣言しています。

l19-24

活性化関数を表す構造体BNNSActivationをロードしています。(微妙にサポートされていない活性化関数がありそうです…)

l26-35

全結合層のパラメータを表す構造体BNNSFullyConnectedLayerParametersをロードしています。各フィールドの内容は次の通りです。

  • in_size: 入力ベクトルのサイズ
  • out_size: 出力ベクトルのサイズ
  • activation: 活性化関数
  • weights: 重みの係数を表す行列
  • bias: バイアス

weightsとbiasはともに層のパラメータを表す汎用構造体であるBNNSLayerDataですがこの構造体はfloatのパラメータについては次のようなフィールドで構成され、他は無視されます。

  • data: パラメータのデータ本体の先頭ポインタ
  • data_type: BNNSで用いられるデータ型BNNSDataType
l42-44

全結合層のパラメータと入出力のベクトルを表す構造体からフィルタを生成します。 ロードされたフィルタはnnModelのArrayにロードされ、後の予測処理に用いられます。

KSJNeuralNetworkCPU#forward:メソッド

ニューラルネットワークのモデルを用いて実際に予測を行うメソッドになります。

- (NSValue *)forward:(NSValue *)data
{
    ...... // 中略

    float *input_buffer = (float *)[data pointerValue];
    float *output_buffer = NULL;
    for (int net_iterator = 0; net_iterator < self.nnModel.count; net_iterator++)
    {
        KSJArchConfiguration *configuration = self.nnGraphStructure[net_iterator];
        if ([configuration.layerName isEqualToString:@"Linear"])
        {
            KSJFullyConnectedArchConfiguration *fc_configuration = (KSJFullyConnectedArchConfiguration *)configuration;
            output_buffer = (float *)calloc(fc_configuration.outputSize, sizeof(float));
        }

        ...... // 中略

        BNNSFilter *f = [self.nnModel[net_iterator] pointerValue];
        int status = BNNSFilterApply(f, input_buffer, output_buffer);
        if (status != 0) fprintf(stderr,"BNNSFilterApply failed\n");
        input_buffer = output_buffer;
    }
    return [NSValue valueWithPointer:input_buffer];
}
l7

予測を行う層の数でイテレーションして各層に入力を与え、出力を得て、その出力をまた新しい層の入力としていきます。

l9

各層での出力をバッファにロードするため、出力ベクトルのサイズが必要となるため、そのための層の構造を表すデータをロードするため、KSJArchConfigurationクラスをロードします。

l10

例によってこのif文でもロードされた層がどのような種別の層かに応じた処理の分岐を行なっています。

l12, 13

全結合層の設定に応じた出力バッファのメモリ領域の確保を行っています。

l18

層での予測を実施するために先ほど構築済みのBNNSFilterをロードします。

l19, 20

BNNSFilterApply関数を用いて入力バッファとBNNSFilterを用いて出力バッファに予測出力をロードして出力の予測を実施します。BNNSFilterApply関数そのものは処理結果のステータスを返却するというよくある実装になっているため、l20で結果ステータスの検証を実施しています。

l21

現在の層で得られた出力をまた次の層での入力とします。

l23

最後に得られた出力のポインタ(l21でポインタを渡している)を結果としてNSValueにくるんで返却します。

上記でXOR問題に関して用いているBNNSのインターフェイスが一通り出揃いました。

MPSについて

本記事では深く取り上げませんでしたが、iOS10ではGPGPU(General-purpose GPU)の応用としてMetal Performance Shaderに新しくニューラルネットワーク用のAPIが追加されています。

What's New in Metal - WWDC2016

詳細はまた別の記事で取り上げられればと思います。

本記事で解説したBNNSのAPIまとめ

本記事で紹介したAPIを表形式にして復習してみます。

API 役割
BNNSDataType データ型を表すenum
BNNSActivationFunction 活性化関数種別を表すenum
BNNSVectorDescriptor 入出力のベクトル形式を記述する構造体
BNNSLayerData 層の重みとバイアスのパラメータを表す共用の構造体
BNNSActivation 活性化関数を表す構造体
BNNSFullyConnectedLayerParameters 全結合層で用いられるパラメータを表す構造体
BNNSFilterCreateFullyConnectedLayer 全結合層での入力から出力を得るためのBNNSFilterを生成するための関数
BNNSFilter 層での入力から出力を得るための予測子(実態はポインタ)
BNNSFilterApply 入力バッファへのポインタとBNNSFilterを用いて出力バッファに結果をロードする関数

最後に

Deep learning周りの技術を追うのは最近やっていませんでしたが、今回の発表で学習を再開する良いきっかけになりました。今回iOS向けに最適化されたフレームワークが提供されたことで、サーバサイドで学習されたモデルを用いて画像の認識や変換などをモバイル単体で行う事例が多くなると思われます。

またTorch7以外の機械学習プラットフォーム(ライブラリ)がBNNSに今後対応していくことも十分予測されます。現に、TensorFlowにもBNNS向けにモデルをエクスポートする機能が計画されているようです。

Use Basic neural network subroutines (BNNS) on iOS #3001 - tensorflow/tensorflow Github

参考


  1. C.M ビショップ パターン認識と機械学習 第五章 シュプリンガー・ジャパン