2
2

More than 5 years have passed since last update.

PyTorchで生成した姓名分類モデルをiOSアプリ(swift4)上で利用するまで

Posted at

概要

DeepLearning+スマホアプリ(iOSアプリ)でなにかするための導入としてやってみました。

本記事の目的は自然言語処理の学習モデルをcoreMLのモデルに変換し、iOSアプリで使用できるようにすることです。そのため、PyTorchでの学習法については触れません

参考にさせていただいた記事 →
  https://qiita.com/kamata1729/items/e54bcb7d0ed3d296daf8

手順

  • PyTorchで学習モデル作成
  • PyTorchのモデル(.pth)をcoreMLのモデル(.mlmodel)に変換
  • iOSアプリ上で前処理 → 予測 → 結果を出力

バージョン

  • Python 3.7.3
  • PyTorch 1.1.0
  • swift4
  • Xcode 10.3

PyTorchで学習モデル作成

繰り返しになりますが、本記事の主眼は学習モデルの変換とcoreMLの利用法のため、学習モデルの生成については公式のサンプルを利用しております。

ソースコードは上記リンクのgithubから取得しました。また、そのままだと動かなかったので、エラーは取りました(エラーまとめるのは面倒なので略。すみません)。

色々調べてみると、モデルの保存時の宣言が推奨のやり方と異なっていたため、そこは変更。

train.py
# 変更前
torch.save(rnn, 'char-rnn-classification.pth')

# 変更後
torch.save(rnn.state_dict(), 'char-rnn-classification.pth')

※GPUを使って学習させていた場合、変更前の保存方法で保存してしまうと学習時のGPUの情報が乗ってしまうとかなんとか。

学習モデルを変換するにあたって必要な情報だけまとめます。

  • Input : 名前を1単語ずつ入力する。入力の形式はフォーカスしている単語のOne-Hotベクトル。小文字+大文字+" .,;'-"=58列
  • Hidden : 学習結果で更新していく隠れパラメータ。128列
  • Output : 18ヶ国語に分類するため、18列

学習モデル基本情報

モデル名 : char-rnn-classification.pth

パラメータ  サイズ
Input [文字列][58]
hidden [128]
Output [18]

PyTorchのモデル(.pth)をcoreMLのモデル(.mlmodel)に変換

PyTorchモデルを直接coreMLモデルにする手段はないようです。
そのため一度onnxフォーマットに変換してからcoreMLモデルにします。
※ONNXもPyTorchからのexportはできるけど、importはできないらしいです。今後に期待

PyTorch → ONNX

ONNXとは、様々なDeepLearning用フレームワーク間でモデルを交換することが可能なフォーマットです。まずはPyTorchのモデルをこいつに変換します。

ONNXをインストール

$ pip install onnx

ONNXモデルへ変換

inputとhiddenのダミーデータを用意してあげる必要があります。

  • input(1, 1, 58)
  • hidden(1, 128)
convertPyTorchToONNX.py
from data import *
from model import *
import torch
import onnx
from torch.autograd import Variable
from onnx_coreml import convert

# input_size, hidden_size, output_size
rnn = RNN(58,128,18)
rnn.load_state_dict(torch.load('char-rnn-classification.pth'))

# dummy_input
dummy_input = Variable(lineToTensor('nomura'))[0]
# initialize as zero matrix
hidden = rnn.initHidden()
torch.onnx.export(rnn,(dummy_input,hidden),'classification.proto',verbose=False)

coreMLモデルへ

convert関数のパラメータは最低限で問題ないようです。class_labelsには出力サイズを設定します。

convertONNXTocoreML.py
from onnx_coreml import convert

model_onnx = onnx.load('classification.proto')

coreml_model = convert(
    model_onnx,
    'classifier',
    class_labels=[i for i in range(18)],
)

coreml_model.save('classification.mlmodel')

モデルの変換が完了しました!それではiOSアプリに組み込んでいきます。

iOSアプリ上で前処理 → 予測 → 結果を出力

モデルのインポート

前章で生成したcoreMLモデルをXcodeのナビゲータエリアにドラック&ドロップすると、プロジェクトと紐付けるかどうか聞かれるので、OKします。
ナビゲータエリアに表示されたclassification.mlmodelを選択するとmlmodelのプロパティが表示されます。このプロパティ内のModel Evaluation Parametersの項目にinput,outputの番号が書いてあります。この番号で入力値と出力値の設定と取得ができます。

スクリーンショット 2019-09-23 10.04.05.png

私の環境では以下の様になっていました。

Name Type
inputs 0 MultiArray (Float32 58)
inputs 1 MultiArray (Float32 128)
outputs 9 Dictionary (Int64 → Double)
outputs 7 MultiArray (Float32 128)

インポートしたcoreMLモデルは以下のように利用できます。

import CoreML

// ml_hiddenを更新することで予測を進める
for i in 0 ... n_string - 1 {
    if let output = try? self.classification_model.prediction(_0: input[i], _1: ml_hidden) {
        ml_hidden = output._7;
    }
}

前処理

我流で泥臭くやってしまいましたが、良い方法はあるのでしょうか・・・?

    // convert input name to one hot vector
    func convertStringToOneHotVector( string : String! ) -> [MLMultiArray] {
        var one_hot_vectors: [MLMultiArray] = [];

        var name_counter = 0;
        var letter_counter = 0;

        let n_letter = all_letters.count;
        let ns_n_letter: NSNumber = n_letter as NSNumber;


        for string in string {
            // define ml multi array by one word
            let ml_input = try! MLMultiArray(shape: [ns_n_letter], dataType: MLMultiArrayDataType.float32);
            letter_counter = 0;
            for letter in all_letters {
                if ( string == letter ) {
                    ml_input[letter_counter] = 1;
                } else {
                    ml_input[letter_counter] = 0;
                }
                letter_counter+=1;
            }
            one_hot_vectors.append(ml_input);
            name_counter+=1;
        }

        return one_hot_vectors;
    }

    // initialize vector as empty vector
    func initEmptyVector( vector: MLMultiArray, size: Int ) {
        for i in 0 ... size - 1 {
            vector[i] = 0;
        }
    }

予測、出力

まずは出力時の対応です。予測結果は0~18のkey値にしか紐付かないため、理解できる結果と紐付けておく必要があります。PyTorchソースから以下のall_categoriesに格納された言語の順番をswift側でも宣言しておきます。

data.py
all_categories = []
for filename in findFiles('data/names/*.txt'):
    category = filename.split('/')[-1].split('.')[0]
    all_categories.append(category)
    lines = readLines(filename)
    category_lines[category] = lines

n_categories = len(all_categories)

私の結果は以下のようになっていましたので、

['Czech', 'German', 'Arabic', 'Japanese', 'Chinese', 'Vietnamese', 'Russian', 'French', 'Irish', 'English', 'Spanish', 'Greek', 'Italian', 'Portuguese', 'Scottish', 'Dutch', 'Korean', 'Polish']

swiftで以下のようにdictionary型を定義しました

    let classDic: [Int64 : String] = [
        0: "Czech",
        1: "German",
        2: "Arabic",
        3: "Japanese",
        4: "Chinese",
        5: "Vietnamese",
        6: "Russian",
        7: "French",
        8: "Irish",
        9: "English",
        10: "Spanish",
        11: "Greek",
        12: "Italian",
        13: "Portiguese",
        14: "Scottish",
        15: "Dutch",
        16: "Korean",
        17: "Polish"
    ]

出力結果はソートしておきます。
トップ3件の候補を表示します。

    // predict what language input name is
    func predictNameLanguage( string: String, input: [MLMultiArray], hidden: MLMultiArray) {
        var output_array: [Int64: Double] = [:];
        let n_string = string.count;
        var ml_hidden: MLMultiArray = hidden;

        for i in 0 ... n_string - 1 {
            if let output = try? self.classification_model.prediction(_0: input[i], _1: ml_hidden) {
                ml_hidden = output._7;
                output_array = output._9;
            }
        }

        // sort result
        let output_sorted_array = output_array.sorted{ $0.value > $1.value }
        dispResult(array: output_sorted_array);
    }

    // display result
    func dispResult( array: [(key: Int64, value: Double)] ) {
        result_1.text = classDic[array[0].key] as! String;
        result_2.text = classDic[array[1].key] as! String;
        result_3.text = classDic[array[2].key] as! String;
    }

実行

IMG_0278.png

スクリーンショット 2019-09-23 10.59.42.png

pythonで実行した場合と結果が一致していれば成功です!

おわりに

Nomuraを分類してみたところ・・・

IMG_0279.png

Arabicと予測されました・・・
次はモデルの精度向上を目指します。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2