ユニファ開発者ブログ

ユニファ株式会社システム開発部メンバーによるブログです。

Swift for TensorFlow をさわってみる

iOSエンジニアのしだです。最近はiOSを書いていませんが、iOSエンジニアと言い続けたいと思います。
TensorFlow Developer Summit 2018 にアナウンスされていた Swift for TensorFlow が先日公開されたのでさわってみます。

github.com

準備

Swift for TensorFlow は、TensorFlow API の単純なラッパーというわけでなくSwift のコンパイラと言語が拡張されているということで、それ用のSwiftをインストールする必要があります。

インストール手順 に従ってビルド済みのパッケージをダウンロードして手順に従ってインストールできます。

ニューラルネットワークの実装

UCLA のアドミッションのデータを使って、入力層、隠れ層、出力層の3層のニューラルネットワークを作ってみます。
tensorflow/swift-models に含まれている MNIST のサンプルコード を元に実装しています。

データセット

// ダウンロード
$ curl -o /tmp/binary.csv https://stats.idre.ucla.edu/stat/data/binary.csv

// データサンプル
$ python
>>> import pandas as pd
>>> pd.read_csv('/tmp/binary.csv').head()
   admit  gre   gpa  rank
0      0  380  3.61     3
1      1  660  3.67     3
2      1  800  4.00     1
3      1  640  3.19     4
4      0  520  2.93     4

GRE スコア、 GPA と rank(1 ~ 4) を元に admit: 0, 1 を予測します。 前処理を行い、GRE と GPA の値は標準化して、rank は カテゴリー変数として4つにバラします。 入力層は6つ、隠れ層を3つ、出力層を1つのニューラルネットワークにします。

f:id:unifa_tech:20180511123126p:plain

Swift の実装

より単純にするためにバイアスはなしで計算しています。

#!/usr/bin/env swift -O

import Foundation
import TensorFlow

// ... 前処理など

func main() {
    /// CSV ファイルパス
    let csvPath = "/tmp/binary.csv"
    
    print("Loading csv...", csvPath)

    /// CSV を読み込んで前処理を行いTensorを返す
    /// 学習用のデータ
    ///   - x: Tensor<Float>
    ///   - y: Tensor<Float>
    /// テスト用のデータ
    ///   - xValid: Tensor<Float>
    ///   - yValid: Tensor<Float>
    let (x, y, xValid, yValid) = readAdmissions(path: csvPath)
    print("----------")

    /// 学習回数
    let epochs = 100
    /// 学習率
    let learningRate = Float(0.01)

    var w1 = Tensor<Float>(randomUniform: [6, 3])
    var w2 = Tensor<Float>(randomUniform: [3, 1])
    var losses: [Float] = []

    for _ in 0..<epochs {
        
        /// 順伝播
        let z1 = x ⊗ w1
        let h1 = sigmoid(z1)
        let z2 = h1 ⊗ w2
        let pred = sigmoid(z2)

        /// 誤差逆伝播
        ///   - シグモイド関数: f(x) = 1 / (1 + e−x)
        ///   - シグモイド関数の微分: f'(x) = f(x)(1 − f(x))
        let dz2 = (y - pred) * pred * (1 - pred)
        let dw2 = h1.transposed() ⊗ dz2
        let dz1 = dz2 ⊗ w2.transposed() * h1 * (1 - h1)
        let dw1 = x.transposed() ⊗ dz1

        /// 重みを更新
        w1 += learningRate * dw1
        w2 += learningRate * dw2

        /// lossを計算
        let loss = dz2.squared().mean(squeezingAxes: 1, 0).scalarized()
        losses.append(loss)
    }
    
    /// バリデーション用のデータを使って精度を計算する
    let hidden = sigmoid(xValid ⊗ w1)
    let out = sigmoid(hidden ⊗ w2)
    let predictions = out.scalars.map { $0 > 0.5 ? 1.0 : 0.0 }.map { Float($0) }
    let y_ = yValid.scalars
    let accuracy = zip(predictions, y_).map { $0 == $1 ? 1 : 0 }.mean
    
    print(losses)
    print("accuracy: ", accuracy)
}

main()

実行結果

loss も下がって学習してそうです。精度も 73%でした。

$ export PATH=/Library/Developer/Toolchains/swift-latest/usr/bin:"${PATH}"
$ ./main.swift
IMPLICIT COPY TO HOST OF:   %2157 = builtin "__tfop_Reshape,$in,$in"(<<NULL OPERAND>>, <<NULL OPERAND>>) : $TensorHandle<Float> // user: %2159
IMPLICIT COPY TO HOST BY:   %2159 = apply %1594(%2157, %1593) : $@convention(method) (@owned TensorHandle<Float>, @thin Float.Type) -> Float // users: %2228, %2160
./main.swift:134:60: warning: value implicitly copied to the host, use .toHost() to make transfer explicit
        let loss = dz2.squared().mean(squeezingAxes: 1, 0).scalarized()
                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~
Loading csv... /tmp/binary.csv
----------
[0.015572316, 0.0163158029, 0.0163647104, 0.0159751847, 0.0154276136, 0.0148837836, 0.0144050093, 0.01400341, 0.013672946, 0.0134026865, 0.0131816845, 0.0130004203, 0.0128510511, 0.0127272531, 0.0126239872, 0.0125372233, 0.0124637503, 0.0124010015, 0.0123469178, 0.0122998478, 0.0122584607, 0.0122216837, 0.0121886451, 0.01215865, 0.0121311238, 0.0121056084, 0.0120817311, 0.0120591866, 0.0120377317, 0.0120171662, 0.0119973291, 0.0119780907, 0.0119593414, 0.0119410008, 0.0119229928, 0.0119052669, 0.0118877795, 0.0118704876, 0.0118533643, 0.0118363844, 0.0118195312, 0.0118027842, 0.0117861321, 0.0117695658, 0.011753073, 0.0117366444, 0.0117202755, 0.0117039569, 0.0116876885, 0.011671463, 0.0116552738, 0.0116391191, 0.0116229914, 0.0116068907, 0.0115908086, 0.011574747, 0.0115586976, 0.0115426602, 0.0115266349, 0.0115106134, 0.0114945937, 0.0114785749, 0.0114625553, 0.0114465347, 0.0114305085, 0.0114144739, 0.0113984356, 0.011382388, 0.0113663347, 0.0113502732, 0.0113342032, 0.0113181276, 0.0113020455, 0.0112859569, 0.0112698618, 0.0112537667, 0.0112376707, 0.0112215756, 0.0112054823, 0.0111893946, 0.0111733172, 0.0111572472, 0.0111411922, 0.0111251511, 0.0111091277, 0.0110931266, 0.0110771498, 0.011061199, 0.011045279, 0.0110293915, 0.0110135376, 0.0109977219, 0.010981949, 0.0109662153, 0.0109505299, 0.010934893, 0.0109193055, 0.0109037701, 0.0108882915, 0.010872866]
accuracy:  0.73

まとめ

Python for TensorFlow は sess.run で実行を明示しますが、Swift の場合、どこで実行されるのか意識して実装する必要があるように感じました。
本当は Playground 上で計算させて loss をグラフ表示するようにしたかったですが、Playground が動かなすぎて諦めました 😭
Swift のコンパイル時にTF Graph が作成されたり技術的に面白そうなので、 Swift for TensorFlow を引き続き見ていきたいと思います。