Swiftで書かれた人工知能・機械学習ライブラリ「Swift-AI」をiOSで動かしてみる

全編Swiftで書かれたオープンソースの人工知能／機械学習ライブラリが出てきました。その名も「Swift-AI」。

- https://github.com/collinhundley/Swift-AI


デモが入っていて、こんな感じで手書き文字認識してくれます（詳細は後述します）。


<img src="http://f.st-hatena.com/images/fotolife/s/shu223/20160124/20160124095306.gif">


今のところ <b>iOS と OS X をサポート</b> しているとのこと。MITライセンス。


##できること

README の Features を見ると、2016年2月現在、<b>フィードフォワード（順伝播型）ニューラルネットワークと、高速行列演算ライブラリ</b>はできあがっているようです。


それぞれドキュメントがあります。

- https://github.com/collinhundley/Swift-AI/blob/master/Documentation/FFNN.md#multi-layer-feed-forward-neural-network
- https://github.com/collinhundley/Swift-AI/blob/master/Documentation/Matrix.md#matrix


<b>ベクトル・行列演算には Accelerate Framework を利用</b>しているとのことで、パフォーマンス面も考慮されてそうです。


##デモを試してみる

同梱されているデモプロジェクト「Swift-AI-iOS」を実行してみると、下記のように、「サイン波（sin）のグラフがあって、トレーニング開始するとオレンジの点の集合がそのサイン波のグラフに近づいていく」というものでした。


<img src="http://f.st-hatena.com/images/fotolife/s/shu223/20160124/20160124095129.jpg">


・・・学習の過程を視覚化したデモなのかもしれませんが、ちょっとありがたみがわかりにくいなと。


で、そのXcodeプロジェクトの中を見てみると「HandwritingViewController」というソースコードがありました。


あれ、手描き文字認識のデモもあるのかなと思ってソースを追いかけてみると、どうやら他にもデモがあるらしい。デモを起動して画面を見てみて、もしや、と思い<span class="deco" style="color:#FF0000;">左上のSwift燕画像をタップ</span>してみると・・・


<img src="http://f.st-hatena.com/images/fotolife/s/shu223/20160124/20160124095428.jpg">


ハンバーガーメニューが出てきました。なんともわかりにくいUI。。


まぁそれはここでは本質的な問題ではないので置いといて、Handwriting のデモを試してみると、


<img src="http://f.st-hatena.com/images/fotolife/s/shu223/20160124/20160124095306.gif">

バシッと手書き認識してくれました。


##手書き文字認識デモの実装

ライブラリの中身を読んでもたぶん理解できないので、あくまで<b>ライブラリを利用する側</b>として、デモではどう実装しているのか「Handwriting」デモのソースを見てみました。


###フィードフォワードニューラルネットワーク（FFNN）の準備

学習済みデータのファイルを指定して、FFNNクラスを初期化します。

```swift
let url = NSBundle.mainBundle().URLForResource("handwriting-ffnn", withExtension: nil)!
self.network = FFNN.fromFile(url)
```


ニューラルネットの準備としてはこれだけ。


###手書き画像の領域を切り出す

認識するタイミングですが、本デモでは <b>`touchesEnded` イベントが検出さてから0.4s以内に次の `touchesBegan` もしくは `touchesMoved` が検出されなければ認識処理が走る</b>ように実装されています。


で、手書き画像領域の切り出しは `scanImage()` というメソッドで行われています。

```swift
private func scanImage() -> [Float]? {
    var pixelsArray = [Float]()
    guard let image = self.handwritingView.canvas.image else {
        return nil
    }
    // Extract drawing from canvas and remove surrounding whitespace
    let croppedImage = self.cropImage(image, toRect: self.boundingBox!)
    // Scale character to max 20px in either dimension
    let scaledImage = self.scaleImageToSize(croppedImage, maxLength: 20)
    // Center character in 28x28 white box
    let character = self.addBorderToImage(scaledImage)
    
    self.handwritingView.imageView.image = character
    
    let pixelData = CGDataProviderCopyData(CGImageGetDataProvider(character.CGImage))
    let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)
    let bytesPerRow = CGImageGetBytesPerRow(character.CGImage)
    let bytesPerPixel = (CGImageGetBitsPerPixel(character.CGImage) / 8)
    var position = 0
    for _ in 0..<Int(character.size.height) {
        for _ in 0..<Int(character.size.width) {
            let alpha = Float(data[position + 3])
            pixelsArray.append(alpha / 255)
            position += bytesPerPixel
        }
        if position % bytesPerRow != 0 {
            position += (bytesPerRow - (position % bytesPerRow))
        }
    }
    return pixelsArray
}
```


画像をクロップしたりリサイズしたり、といった処理は本ライブラリを利用するにあたって本質的な部分ではないので割愛しようと思ったのですが、あえて載せたのは、その後の処理に渡すにあたって、出力データの型が重要だと思ったからです。ポイントとしては、<b>RGBAではなくA1チャンネルだけ</b>を取り出していること（手書き文字認識なので色情報は利用しない）と、<b>UIImage型ではなくFloatの配列</b>としてピクセルデータを返していること、といったあたりでしょうか。


###認識処理の実行

上述した処理で取り出した手書き画像情報（Floatの配列）を引数に渡して、FFNNクラスの `update` メソッドを実行します。

```swift
let output = try self.network.update(inputs: imageArray)
```


###認識結果を取り出す

先ほどの `update` メソッドの返り値である Float の配列は、数字0&#12316;9を配列のインデックスとして、値には Confidence（信頼度／その数字である可能性の高さ？）が入っているようです。


なので、次のように、最も Confidence の高い数字（＝認識結果）を探し、その認識結果の数字の値と、Confidenceを取り出しています。

```swift
private func outputToLabel(output: [Float]) -> (label: Int, confidence: Double)? {
    guard let max = output.maxElement() else {
        return nil
    }
    return (output.indexOf(max)!, Double(max / 1.0))
}
```


こうしてみてみると、実質的には

- FFNNクラスの初期化
- `update:` メソッドを呼ぶ

この<b>2ステップだけで手書き文字認識</b>ができていることがわかります。非常に簡単そうです。


##自分のプロジェクトへの導入方法

CocoaPodsやCarthageはサポートしてないとのこと。

> Swift is open-source now, and it remains to be seen how these dependency managers will cooperate with other platforms.



まだ試してないですが、デモプロジェクトを見る限り、

- FFNN.swift
- Storage.swift
- FFNN+Storage.swift

あたりのファイルを追加すれば使えそうです。


他にも設定がいるかもしれませんが、ちゃんと動くデモプロジェクトが用意されているので、何かあればそちらと比較してみればよいかと。


##所感

Swiftで書かれていて、MITライセンスなオープンソースで、ちゃんとパフォーマンスも考慮されていて、簡単に使える、ということでかなり期待の持てるライブラリではないでしょうか。


今後いろんな学習済みデータや応用事例が出てくるとさらに楽しくなりそうです。