AI アプリ - CreateMLだけで作ってみた2020年02月21日 13:52

ビッグデータが流行ですが、”スモールデータ” ではだめなんだろうか? Xcodeの Playgroundには AIモデルを生成する機能を備えている、これを使って実際にアプリを作成してみた。

学習システムには、Apple社が学習させた基本モデルが組み込まれているから、それを使って転移学習する仕組みらしい。ディープラーニングに解説されている、犬猫の画像分類なども、フォルダー別に各々画像を用意するだけで、手元の Macだけで簡単に AIモデルを生成することができ、生成したオリジナルモデルを自分のアプリに組み込むことができる仕組みだから Macが有れば容易に試すことができる。

作成したのは、AI がベストショットを自動で撮ってくれるアプリ
写真を撮るとき、誰しもがベストな一枚を願うものだ。これを AI化するための以下の方針を決めた。

 ・ベストショット = 素敵な顔の表情とする
 ・学習データ = 素敵な顔と、そうでないのを各20枚程度用意する (*)
 ・写真の判断に客観性はない

(*) 学習データ
2019.1.1 施行の改正著作権法によると、著作物を含むデータを元に機械学習によって学習済みモデルを開発することは適法となった、だから Web上の写真データを使って AIモデルを開発することは可能である。

このように、用意したのはビッグデータとは程遠い学習素材です。実際に学習させてモデルを生成してみると面白い。枚数の少ない画像を有効活用するための手段として CreateMLにも画像の回転、反転、拡大、切り取り、などの学習時のオプション設定ができるが、あれこれ試して分類精度を上げすぎるより、ここでは適当に低い方がいい結果が得られた。

(学習とテストの精度 - CreateML - )
CreateML-learning


実際、色々試してみたが、90%以上の高精度より 60〜70%程度の方が良い結果が得られたが、この結果の確証はない。使った学習データのせい、あるいは考え方が間違っていたのかもしれない。元々、目標にした "ベストな写真” に客観性はない。App Storeからダウンロードして試して判断ください。
(iOS 12.0以降。iPhone、iPad、およびiPod touchで動作。)


  App Store(無料、課金・広告なし)
appLogo & appImage

image-QR

PickerVew によるメニュー選択 (3/3)解決篇2012年11月12日 18:07

解決策はロジックを飛び越えて訪れるものらしい・・・?
もう面倒になって、タイマーのあら捜しをあきらめかけていた。そんな時ふとスレッドのことが気になりだした。UIPickerView も名前どうり UIだからメインスレッドで表示しているからである。そこに横槍を入れるならサブスレッドしかないだろう。早速やってみた。
最初 (1)不思議篇 で示したピッカーの停止した時に呼ばれるデリゲードメソッドの次の行に移動させる部分 (太字) だけをサブスレッドで実行するのだ。

.....
.....
if (nowIndex_ == 0) {
    // 次の行に移動させる
    nowIndex_++;
    [picker_ selectRow:nowIndex_ inComponent:component
        animated:YES];
}

これで決まり!
きちんと書くとこんな感じ・・・

// 停止時のデリゲート
- (void) pickerView:(UIPickerView*)pickerView didSelectRow:(NSInteger)row
 inComponent:(NSInteger)component
{
    // 位置をキープするインスタンス変数
    nowIndex_ = row;
    // ドラムの表示 0の位置を index=0 と想定
    if (nowIndex_ == 0) {
        // 次の行に移動させる
        nowIndex_++;
        [self adjustPickerPosition:nowIndex_]; // 移動メソッドを呼ぶ
    }
}

// ドラムの移動メソッド
- (void)adjustPickerPosition:(NSInteger)row 
{
    NSLog(@"adjusted!");
    // サブスレッド
    NSOperationQueue *queue = [[[NSOperationQueue alloc]init] autorelease];
    [queue addOperationWithBlock:^{
        // 行の表示位置をデーターに合わせる
        [picker_ selectRow:row inComponent:0 animated:YES];
    }];
}
NSOperationQueueクラスを使用しているが、iOS 4以降は内部でGCDを使用しているため結果的には同じである (参照:「詳解 iOS 5プログラミング」) 。これでやっと決定打となった。

簡単に検証してみる
さて、せっかくだから逆方向に検証してみよう。手順は、この NSOperationQueueクラスの mainQueueメソッドで取得したキューを使って、このサブスレッドの中からメインのスレッドで実行してみたらどうなるか? これで最初の時と同じように上手く行かなくなれば理屈は成り立つ。
    .....
    .....
    // サブスレッド
    NSOperationQueue *queue = [[[NSOperationQueue alloc]init] autorelease];
    [queue addOperationWithBlock:^{
        // メインスレッドで実行
        NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
        [mainQueue addOperationWithBlock:^{
            // 行の表示位置をデーターに合わせる
            [picker_ selectRow:row inComponent:0 animated:YES];
        }];
    }];
    .....
さて、これは予想どうりに失敗する。これでメインスレッド同士の競合が原因であることの検証もできたが、ここから先の解析は力不足であるし追求しないことにする。

(おわり)

PickerVew によるメニュー選択 (2/3)試行錯誤篇2012年11月11日 18:50

興味のない方は、ここはスルーして次の (3/3)解決篇へ移ってほしい。

マルチタッチイベントの競合の影響を疑ってみる
ピッカー本来の動作と、想定していなかったタップ動作が競合している可能性を疑ってみたくなった。そこで、ピッカーに Gesture Recongnizer のタップジェスチャーを登録し、さらに複数のジェスチャーを認識できるような設定をしておく。この状態でデレゲートメソッドが呼び出された時にドラムの位置をチェックするようにしてみた。

// タップジェスチャーをピッカーに登録 (viewDidLoadに設定)
picker_.userInteractionEnabled = YES;
UITapGestureRecognizer *tapGesture =[[UITapGestureRecognizer alloc]
    initWithTarget:self action:@selector(pickerTap:)];
[picker_ addGestureRecognizer:tapGesture];
[tapGesture release];

// 複数のジェスチャーの認識を許可
// (UIGestureRecognizerDelegateプロトコルのオプションメソッド)
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
    shouldRecognizeSimultaneouslyWithGestureRecognizer:
    (UIGestureRecognizer *)otherGestureRecognizer
{
    return YES; //許可
}

// デレゲートメソッド
- (void)pickerTap:(UITapGestureRecognizer *)sender {
    // indexを取得
    NSUInteger index = [picker_ selectedRowInComponent:0];
    NSLog(@"タップ! index : %d", index);    
    if (!index) index++;
        // ピッカーを進める
        [picker_ selectRow:index inComponent:0 animated:YES];
        nowIndex_ = index;
}
これで確認してみると、少しは改善されたような気もするが残念ながら決定打にはなり得ない。

ダメついでに少し荒っぽいことを試してみたくなった・・・
ふりだしに戻して、今度は逆に他の Gesture Recongnizer のタッチ解析を禁止してみたらどう反応するかやってみた。他にもっと気の利いた方法もあると思うが、以下の UIGestureRecognizer のサブクラスを作成した。

// *** MyFlagGesture.h ***
#import 
#import 
@interface MyFlagGesture : UIGestureRecognizer
@end

// *** MyFlagGesture.m ***
#import "MyFlagGesture.h"
@implementation MyFlagGesture

// ジェスチャーの開始
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesMoved:touches withEvent:event];
}
// ジェスチャーの終了
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{    
    [super touchesEnded:touches withEvent:event];
    NSLog(@"GestureEnded!");
    // Recognized をセットすると親クラスにメッセージを送信する
    self.state = UIGestureRecognizerStateRecognized;
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesCancelled:touches withEvent:event];
}
- (void)reset
{
    [super reset];
}

// 他のジェスチャーの検出を禁止させる (オプションメソッド)
//  (Possible から遷移しようとする時に呼び出される)
- (BOOL)canPreventGestureRecognizer:
    (UIGestureRecognizer *)preventedGestureRecognizer
{
     return NO; // 禁止する (ディフォルトは禁止しない:YES)
}
// 他のジェスチャーの検出を禁止させる (オプションメソッド)
// (touchesBegan: を呼び出す直前に呼び出される)
- (BOOL)canBePreventedByGestureRecognizer:
    (UIGestureRecognizer *)preventingGestureRecognizer
{
    return NO;  // 禁止する (ディフォルトは禁止しない:YES)
}
@end
このクラスをインスタンス化してピッカーに登録してメッセージを受信できるようにする。
// MyFlagGesture のインスタンスを登録 (viewDidLoadに設定)
picker_.userInteractionEnabled = YES;
MyFlagGesture *gesture = [[MyFlagGesture alloc] initWithTarget:self
    action:@selector(pickerGestureFlag:)];
[picker_ addGestureRecognizer:gesture];
[gesture release];

// サブクラスからのジェスチャー完了メッセージを受け取る
- (void)pickerGestureFlag:(UIGestureRecognizer*)sender
{
    NSLog(@"MyFlagGesture Ended! (state = %d)",sender.state);
    // indexを取得
    NSUInteger index = [picker_ selectedRowInComponent:0];
    NSLog(@"タップ! index : %d", index);    
    if (!index) index++;
        // ピッカーを進める
        [picker_ selectRow:index inComponent:0 animated:YES];
        nowIndex_ = index;
    }
}
ロジック的には、サブクラスのジェスチャーの終了時に呼び出されるメソッドは、ピッカーのドラムを回転して操作が終了してしまうともう呼び出されない。だから後は想定外のタップ動作だけに反応してくれるから上手く行く筈であるが残念ながらそうはならないところが悩ましい。
以上のいずれも問題の解決策にはならなかった。もうお手上げであろうか、もしこれで上手く行ったとしても少々複雑すぎるのも気になるが...。

(以下、(3/3)解決篇へ)