その後のその後

iOSエンジニア 堤 修一のブログ github.com/shu223

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

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


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



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

できること

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


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


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

デモを試してみる

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




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


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


あれ、手描き文字認識のデモもあるのかなと思ってソースを追いかけてみると、どうやら他にもデモがあるらしい。デモを起動して画面を見てみて、もしや、と思い左上のSwift燕画像をタップしてみると・・・


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


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



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

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

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

フィードフォワードニューラルネットワーク(FFNN)の準備

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

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


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

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

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


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

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
}


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

認識処理の実行

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

let output = try self.network.update(inputs: imageArray)
認識結果を取り出す

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


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

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:` メソッドを呼ぶ

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

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

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


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