頭と尻尾はくれてやる!

パソコンおやじのiPhoneアプリ・サイト作成・運営日記


最小二乗法で近似曲線/平面を得る(Swift)

以前(Objective-Cを使ってた頃)n次の近似曲線を得るためガウスジョルダン法で解く、というのをやったことがあった。

Swiftで最小二乗平面を得たいので調べてみた(※1)ら「あー、これもガウスジョルダン法でいい奴だよな?」と思い(実は思い違いだった)、Objective-Cの関数をSwiftに書き直した。

元はn次の近似曲線を得ていたので、それができているのを確認するサンプルが一つ。

もう一つは今回メインの、空間内の複数の点に対する最小二乗平面を得るというサンプル。

YamaguchiTatsuya/ApproximateCurveAndPlane: Sample code to calculate approximate curve and plane using Least squares method and Gauss-Jordan elimination in Swift, macOS
↑GitHubにアップしたそれらのサンプルコード。
macOSアプリで1つの画面に両方のを表示させてる(プロジェクト二つ作るほどでもないし?)。

近似曲線/平面を得た実行結果のスクショ
↑スクショはこんな感じ。左が近似曲線を描いたNSViewで右は最小二乗平面(を意味するたくさんの小さい点)を描いたSceneKitのview(なのでマウスで視点を動かせる)。


最小二乗平面はガウスジョルダン法でもいいけど、未知数3個に式が3つの連立方程式なので単に逆行列(simd_float4x4までは逆行列を得る関数がある)を作ってかけてやればいい、ということに後で気付きました、、、


※1 参考ページ
[数学] 最小二乗平面をプログラムで求める - Qiita



macOS 10.15.3 Catalina
Xcode 11.3.1
Swift 5.1.3
スポンサーサイト






Swiftでarray.remove(value)と記述するextension

SwiftのArrayに入っている値を削除したい。

使えそうなメソッドは↓この辺りか。
mutating func remove(at i: Self.Index) -> Self.Element

これだと何番目のindexか自分で調べないといけない。
それは面倒なので、削除する値を直接指定して
array.remove(value)
なんて感じで記述したい。

SwiftのExtensionで実装した。
extension Array where Element: Hashable  {

mutating func remove(_ removingValue: Element) {

var removingIndex: Int? = nil

for (index, value) in self.enumerated() {
if value == removingValue {
removingIndex = index
break
}
}
if removingIndex != nil {
self.remove(at: removingIndex!)
}
}
}

funcの前にmutatingとあって返り値がない。なので
array = array.remove(value)
などと記述せずに
array.remove(value)
でOK。
ElementにHashableとすることでこれを満たす型に対して処理してくれる。

使う時はこんな感じで。
    // 1)NSColorの場合
var colors: [NSColor] = [.red, .green, .blue,]

print("---before---")
for color in colors {
print(String(describing: color))
}

colors.remove(.green)

print("---after---")
for color in colors {
print(String(describing: color))
}


// 2)整数の場合
var ints: [Int] = [2, 3, 5, 7, 11]
ints.remove(7)
print(ints)

実行した結果のスクショ

↑実行した結果。

なお、コードをたどればわかるが配列内に削除したい要素が複数あっても1つしか削除しない。自分の用途ではこれでOKだったので。



macOS 10.15.3 Catalina
Xcode 11.3.1
Swift 5.1.3


ユニットテストを後から追加すると動かない

Xcodeでプロジェクト作成時にテストなしで作成したものの、後で追加したい場合がある。

プロジェクト名を仮にMyProjectとしておく。
テスト関連ファイルを追加するには、TARGETSから

XcodeのUnit Testing Bundle追加画面
↑このUnit Testing Bundleを選んで追加する。
追加されたMyProjectTest.swiftファイルの冒頭で
@testable import MyProject
って追加して、必要なファイルのTarget Membershipをチェックすればこれで動いて欲しいところだが、、、

The file "/Users/…/MyProject/YES" could not be opened. Verify the value of the CODE_SIGN_ENTITLEMENTS build setting for target "MyProjectTests" is correct and that the file exists on disk.

なんてエラーが出てテストコードの実行ができない。
調べたもののよくわからないので最初から追加しててユニットテストができてるプロジェクトと比較するとBuild Settings(テスト用のターゲットね)のSigningのCode Signing EntitlementsってところがYESになってる。動く方は何もなかった。そういやエラーメッセージにもYESってのがあったが、、、

XcodeのCode Signing Entitlementsの画面

ということで手動でこのCode Signing EntitlementsのYESを削除してみたら、期待通りにテストコードが動くようになった。

今度から最初のプロジェクト作成時に追加するようにしようっと。

ちなみにmacOSアプリでの話。



macOS 10.15.3
Xcode 11.3.1



SwiftでInt?をCGFloat?にしたい

Swiftの関数でオプショナル型の整数をもらって、それがnilならnilで、値があるならCGFloatに(つまりCGFloat?に)したい。
func f(_ i: Int?) {
//let cgf = CGFloat(i)//—(1)
//let cgf = i as? CGFloat//—(2)
let cgf = (i != nil) ? CGFloat(i!) : nil//—(3)

}

//(1)はオプショナル型に対してはダメ、という下のようなエラーメッセージが出る。
Cannot invoke initializer for type 'CGFloat' with an argument list of type '(Int?)'

//(2)だとコンパイルは通るけど下のような警告通り機能しない。
Cast from 'Int?' to unrelated type 'CGFloat' always fails

//(3)の三項演算子はちゃんと動くけど、、、見にくいぞ!数ヶ月後に見たら怒りが湧くだろう。

Swiftのことだから何かうまい書き方がありそうなものだけど、よくわからなかった、、、

ということでextensionでなんとかしてみた。
実はオプショナル型のにextensionを書いたのは初めてだ。こんな風に書くのね。
extension Optional where Wrapped == Int {
var toOptCGFloat: CGFloat? {
if self == nil {
return nil
}
return CGFloat(self!)
}
}

こうしておくとさっきのは↓このようにすっきり書ける。
func f(_ i: Int?) {
let cgf = i.toOptCGFloat

}



TensorFlow2.0の学習結果を.h5→.pb→.mlmodelと変換してiOSで使う

やりたいことは、TensorFlow 2.0 + Kerasで学習させた結果をiOSで使いたい。
iOSで係数だけをもらうことも可能(※1)だが、mlmodel形式だと一番使いやすいのでベスト。

度重なる試行錯誤の結果、自分の場合はこれでいけた、というのをシェア。


TensorFlow 2.0で学習し、その結果を.h5ファイルで保存する。

virtualenvでTensorFlow 1.14.0の環境を作成、そこで
(1).h5ファイルを.pbファイルに変換
(2)その.pbファイルを.mlmodelに変換
これでiOSでVision/CoreMLを使いすんなりと予測ができた。


(1).h5→.pbの変換
Deep Learningアプリケーション開発 (2) TensorFlow with Python - Qiita
.h5→.pbの変換は↑こちらのページにある通り。ただ感謝、感謝!

(2).pb→.mlmodelの変換
上記リンク内の通りに動かすと.pbファイルができる。同じフォルダに.jsonファイルも生成されて、そこを見て入出力の名前を確認しておく必要がある。確かにKerasで自分で付けた名前と違っていた。
後はtfcoremlを使って以下のようにして変換。
import tfcoreml as tf_converter

classLabels = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15']

tf_converter.convert(tf_model_path = PbFilePath,
mlmodel_path = MlmodelFilePath,
output_feature_names = ['y_out/Softmax:0'],
input_name_shape_dict = {'x_input:0':[1, height, width, 3]},
image_input_names = ['x_input:0'],
class_labels = classLabels,
image_scale = 1.0/255.0
)
なお、この2回の変換はTensorFlow 2.0だとすんなり動かず、、、私には書き換えられそうにないので諦めました。この変換だけ1.14.0で実行することにします。





以下は背景とか。

当初は、
Kerasでの学習結果をmlmodelにしてiOSで使う

ここに書いた方法の.h5ファイルを.mlmodelに変換、updateする、でなんとかなると思っていた。
MNIST(白黒、畳み込みなし)で検証していけることを確認したのだが、、、

いざ本番(※2)の学習結果を同様にやろうとしたらupdateがうまくいかない。一応保存まではするんだが、
RuntimeWarning: You will not be able to run predict() on this Core ML model. Underlying exception message was: Error compiling model: "compiler error: Espresso exception: "Invalid argument": generic_reshape_kernel: Invalid bottom shape (32 8 2 1 1) for reshape to (20480 -1 1 1 1)".
RuntimeWarning)

なんて警告が出る。
途中での計算がうまくいってないんだろうか?MNISTは畳み込みを使っていないモデル、白黒画像を入力だったが、今回は畳み込みあり、カラー画像入力というように多少異なる。そのあたりに起因するcoremltoolsのバグなのかなんなのか、、、?
試しにXcodeで使おうとしても、

Xcodeでのエラーメッセージ

↑同じエラーが表示されビルドできなかった。






※1
係数だけ出力してiOSでそれを読み込む、というのもやったことあるが、iOS内でニューラルネットを構成して係数を読み込んで、、、とか記述が膨大。一度mlmodelを味わうともうやってられない。

※2
個体値カメラ
↑先日リリースしたこのポケモンGO用の個体値チェックアプリではバーの部分の画像(カラー)を入力画像として予測している。リリース版ではTensorFlow 1.14を使っていたが、macOSの破損→クリーンインストールを機に、TensorFlow 2.0 + Kerasで学習させ、その結果をiOSで使えるようにしたいのだ。




Copyright ©頭と尻尾はくれてやる!. Powered by FC2 Blog. Template by eriraha.