概要
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から取得しました。また、そのままだと動かなかったので、エラーは取りました(エラーまとめるのは面倒なので略。すみません)。
色々調べてみると、モデルの保存時の宣言が推奨のやり方と異なっていたため、そこは変更。
# 変更前
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)
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には出力サイズを設定します。
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の番号が書いてあります。この番号で入力値と出力値の設定と取得ができます。
私の環境では以下の様になっていました。
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側でも宣言しておきます。
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;
}
実行
pythonで実行した場合と結果が一致していれば成功です!
おわりに
Nomuraを分類してみたところ・・・
Arabicと予測されました・・・
次はモデルの精度向上を目指します。