Hatena::ブログ(Diary)

Over&Out その後 このページをアンテナに追加 RSSフィード Twitter

2014-12-29

フリーランスを1年やってみて考えが変わったこと

お客さん仕事はわりと早いうちに納まったのですが、原稿書きがまったく納まる気配がないので、(現実逃避として)今年1年をふりかえろうと思います。


といっても「こういうお仕事をやりました」的なのは3ヶ月ごとにまとめているので、

ここでは主に、独立して最初の1年目 *1 を振り返ってみて「フリーランスになってみると意外だったこと」「考えが変わったこと」「やりかたを変えたこと」あたりの話を書こうと思います。


出勤した方が楽しい

独立当初は、フリーランスという働き方のメリットのひとつが、「場所・時間の自由」だと思っていて、必要な打ち合わせのとき以外は、家とか、自分が入居しているコワーキングスペースで作業するようにしていました。


で、お客さんもほとんどの場合それでいいと言っていただけるのですが、今は逆に、僕の方から「オフィスで一緒に仕事させてほしい」とお願いしています。


その理由としては、

  • お客さんにすぐ確認できる・・・優先度や不明点を確認でき、作業の無駄が減る
  • 遠隔でのコミュニケーションが苦手・・・誤解を与えない言い回しに苦労したり、でもそのあたりをサボるとお互い不要にイラッとしたり
  • 温度感がわかる・・・直接話したり、一緒にランチしたりする中でその会社の状況とか雰囲気とか、やろうとしていることが実感値として理解できる
  • 集中できる・・・やはり1人でやるより緊張感がある
  • 見積もりレス・・・後述

などなどあります。


寂しくない

「会社には仲間がいるが、フリーランスは1人でやるので、寂しい」


独立前は僕もそう思っていたのですが、意外とやってみると逆の印象を受けました。

  • フリーランスでも結局開発体制としてはチームの一員であることに変わりはないので、「一緒につくっている仲間」という感は会社のときと同様にある
  • 個人的なコミュニケーションも仕事に繋がったりするので、勉強会や飲み会に行く時間が以前よりもっと有意義に感じるようになった

といった理由から、独立前よりもコミュニケーションや人付き合いは活発になった感があります。


個人でも楽しい案件に関われる

フリーランスになるにあたって迷いが少しあって、その理由のひとつが「会社の大看板に来る仕事の方が楽しいんじゃないか」というものでした。実際、カヤック時代にやった仕事はどれもおもしろくて、やりがいがあって、実績になるものだったので。


で、蓋を開けてみると、今年やらせていただいた仕事はどれもおもしろくて、やりがいがあって、実績にもなるものばかりでした。


今年は運がよすぎただけかもしれませんが、とにかく粛々と腕を磨いて、ちゃんとやりたいこととかやったことをアピールし続ければ個人でも道は開けるものだなと。


見積もりを(あまり)しなくなった

見積もりが結構苦手でして。。


たいていの場合、見積もり時点でUIデザインとか、動き(遷移とかタップしたときのギミック的なもの)とかって決まってることってほとんどなくて、でもiOSアプリの場合そこがそもそも開発の肝だったりするわけで。標準UIコンポーネントのプロパティをいじる範囲で済むなら3分だけど、自分でカスタムUIコンポーネントつくるなら丸1,2日かかるかもしれないとか。


で、ほとんどの場合、企画書にある情報だけだと


「1人日〜3人月ですね」*2


とか答えたくなるわけですが、お客さんに対してそういうわけにもいかないので、


「ここはどうしますか?たとえばこうしたい場合、選択肢としてはAとBがあって、それぞれメリット・デメリットは・・・」


とかやりたいことを引き出すことが必要になり、そういう不明確なところを質問にまとめたり、A案・B案みたいな枝分かれをさせつつ語弊や認識違いのないように・・・と回答を整理していくと、見積もりだけで8時間とか平気でかかってしまいます。


かといってざっくり見積もるとトラブルの元になるのでよくない、かといってお客さんからすると見積もりがないと始まらない、最初からUIデザインや詳細要件が明確になっていてほしいというのも無理がある。。



そんなわけで、最近は「お客さんのところに行って1日作業して、その日数 ✕ 人日ぶんいただく」という稼動日ベース方式でやっています。いつどれぐらいお客さんのところに行って作業するかは、その時々でお互いの状況や残りタスクの量を見て相談する感じで。


このやり方だとお客さん側にもいろいろとメリットがあると思っていて、

  • 状況にあわせて仕様とか優先順位を日々変更できる
  • 見積のバッファみたいなものが入ってこないので、無駄が少ない

みたいなことがあるかと。


ただこれって、「堤の1日の作業」に対するお客さんの信頼ありきなので、最初は1人日でどれぐらいできそうか、みたいな目安は提示したりはします。



ちなみにプロトタイプ系の案件は例外で、規模的に数人日レベルのものは十分見通しが立つので、お客さんに不明事項を確認して普通に見積もり出してやってます。*3


おわりに

先にもちょっと書いたように、もともとフリーランスになるにあたって迷いもあったわけですが *4、約1年やってみた今となっては、働き方としてかなり自分に向いてるんじゃないか、と感じています。

  • 自己責任で、自分の働き方・生き方を自由度高く選択できる
  • 自分の行動の結果が、自分にダイレクトに返ってくる

というあたりがやっててとても心地いいなと。


というわけで2015年も引き続きフリーランスとしてがんばっていく所存ですので、どうぞよろしくお願いいたします!ではみなさま良いお年を。


*1:ちなみに2014年2月に独立しました。

*2:標準コンポーネントを並べるだけで、「それっぽく」つくるだけなら結構1日でできたりしますよね

*3:あと、今はスタートアップのお仕事が多いのでたまたまこれで成立しているだけで、もうちょっと規模の大きい会社とお仕事させていただく場合は普通に見積もりレスなんて無理かもしれません。

*4:独立の経緯について話したインタビュー: http://prosheet.jp/blog/engineer/4641/

2014-12-19

iOSエンジニアが初めてAndroid開発をやってみた第1日目のメモ

当方フリーランスエンジニアですが、iOS専業でやっております。これまで幾度と無く「ちなみにAndroidの方は・・・?」「すいません、できないんです・・・」と肩身の狭い思いをしてきましたが、ついに今日第一歩目を踏み出しました。


ちなみに現在のスペックですが、

  • iOSアプリ開発歴4年、Android開発歴ゼロ
  • Androidはユーザーとしても経験値ほぼゼロ(先週 Nexus5 購入して WiFi セットアップしただけ)

という感じです。


実況中継的に随時追記する形で書いていきたいと思います。iOS からプログラミングの世界に入り、これから Android もやってみたい、という人も今となっては結構多いんじゃないかと思うので、どなたかの第一歩目の参考になれば幸いです。


※(終了後に追記)あとから読んだ人には何のことかわからないかもしれませんが、本記事は昼ぐらいに出だし(午前中に進めた内容。書籍購入まで)をアップし、その日何かやる度に実況中継的に追記していったものです。思いつくままに手を付けていった順に書いてるので、勉強しやすい順序とかにもなっていないと思います。


Android Studio インストール

何はともあれ開発環境のインストールから。(Android Studioは、iOSでいうXcode みたいなもの)

  • http://developer.android.com/sdk/index.html
  • Setup Wizard 通り
  • 途中で JDK 7.0 のダウンロード&インストールが必要(リンクはウィザード内で提示されるので事前準備は不要)
  • Standard と Custom 選択するところがあるがとりあえず Standard
  • License Agreement の後、そこそこ長いダウンロードとインストールがある

プロジェクト作成&エミュレータで動かしてみる

開発環境を入れたところで「で、どうしようかな。。」と一瞬途方に暮れたかけたのですが、iOSだとどうするかな、と想像してみて、何はともあれプロジェクト作成してエミュレータで空のプロジェクトを動作させてみるのがいいのかなと。

  • バージョンはとりあえず 4.4 (自分の Android がそうだったので)以上
  • テンプレートは違いがわからないので Blank
  • ActivityNameとかLayoutNameなどなど、それぞれがどこに影響するかわからないのでデフォルトのままで

で、上部のバーに Xcode の Build & Run ボタンと同じようなのがあったので、


f:id:shu223:20141219131748p:image


押してみると、デバイス選択っぽいのが出てきて、無事エミュレータで動作しました!


f:id:shu223:20141219131817p:image:w250


ただ、ロック画面みたいなところから起動したので、Androidの操作自体がわからなくてちょっと困惑しましたが。。


書籍を入手

最初はWebでググるにも用語自体がわからなかったりするので、近所の書店に行って、参考書籍を購入しました。


つくるべきものは決まっているので、サンプルをチュートリアル的に進めるタイプのものではなく、「テーブルビュー的なのはどう実装するんだろう」という感じで逆引き的に調べられそうかどうか、を判断基準に、書店で目次を確認して選びました。


書籍1:Android StudioではじめるAndroidプログラミング入門

Android StudioではじめるAndroidプログラミング入門
掌田 津耶乃
秀和システム
売り上げランキング: 37,320


入門とありますが目次見るとわりと網羅的な感じがしたので購入しました。今こうやって見るとAmazonではなかなか厳しいレビューついてますね。。


書籍2:Androidアプリ開発逆引きレシピ (PROGRAMMER’S RECiPE)

Androidアプリ開発逆引きレシピ (PROGRAMMER’S RECiPE)
株式会社Re:Kayo-System
翔泳社
売り上げランキング: 94,035


逆引きレシピが欲しかったので最初に手に取りました。出版年月も新しめだし。


書籍3:Androidアプリ開発パーフェクトマスター―Android4/3/2.2完全対応 (Perfect Master)

Androidアプリ開発パーフェクトマスター―Android4/3/2.2完全対応 (Perfect Master)
金城 俊哉
秀和システム
売り上げランキング: 41,222


分厚すぎて絶対外に持ち出せないし、実用的じゃないんじゃないか。。と思いましたが、情報量は多いし、悩んでる時間がもったいないので購入。


UIImageView的なものを置いてみる

画像リソースの実態は、(フォルダ名の一般名称がわからないので自分の現在のプロジェクトで言うと)

MyFirstApplication/app/src/main/res

にあります。


が、画像の追加はプロジェクトウィンドウ内にある「drawable-mdpi」フォルダにドラッグ&ドロップするとのこと。(書籍1からの情報)


UIImageView的なUIコンポーネントは、Widgetsのところに「ImageView」というそのものズバリなものがあったので、それを配置。


画像の入れ方がよくわからなかったのですが(書籍1ではプログラムから入れていた)、ImageView を IB 的なところでダブルクリックしたら src 画像を選ぶダイアログが出てきて、無事 drawable-mdpi に入れたものを選択することができました。


f:id:shu223:20141219142442p:image:w220


UIViewContentMode的なプロパティと、このレイアウト周りがわからないけど、そのあたりは後回しで。。


ボタンを置いてみる

ボタンを押したらコンソールにログをはく、的なことをやってみます。

  • Widgetsに「Button」というのがあるので、それを配置
  • 右にあるプロパティウィンドウから、「onClick」を探して、「button_onClick」と入力
  • MainActivity.javaを開き、ボタンクリックのイベントハンドラを実装
public void button_onClick(View view) {
    Log.d("MyApp", "view:" + view);
}

(記念すべき初めてのコード!)


ボタンまわりは書籍1を、Log の書き方はググって下記記事を参考にしました。


で、これだとエラーが出たので、import を追記

import android.view.View;
import android.util.Log;

(使用クラスごとに必要なのか・・・!?そんなわけないと思うけど、おいおい学んでいきます。。)


f:id:shu223:20141219142623p:image:w220


ボタンを押すと、コンソールに次のように出力されました。

12-19 14:21:48.467 1911-1911/com.shu223.myfirstapplication D/MyApp﹕ view:android.widget.Button{9cf6a48 VFED..C. ...P.... 372,921-707,1065 #7f080041 app:id/button}


画面遷移

次は UINavigationController の push / pop 的な画面遷移を・・・と思ったのですが、買った書籍(総ページ数約2000!)を調べてみても見当たらず・・・*1


ググるとすぐに出てきました。


インテントは他のアプリと連携するときに用いるもので、他のアプリ(例えばブラウザアプリ、メールアプリ等)を起動することができます。


同じアプリでも他の画面 (Activity) クラスを起動する場合には、インテントを使用します。

そうなんですね、カルチャーショック。。


それはさておき、

画面の遷移は, 元画面(メイン画面)のアクティビティから次画面(サブ画面)のアクティビティを起動することで実現できる.

とのことなので、

  • メニューから新規アクティビティを作成
    • [File] > [New] > [Activity]
    • SubActivityと命名
  • SubActivity に Button を置き、onClick プロパティに button_onClick と入力
  • SubActivity.java に以下のコード(と必要なimport)を追記
public void button_onClick(View view) {

    Log.d("MyApp", "onClick:" + view);

    Intent intent = new Intent(SubActivity.this, MainActivity.class);
    startActivity(intent);
}
  • MainActivity.java のボタンのイベントハンドラを以下のように修正
public void button_onClick(View view) {

    Log.d("MyApp", "onClick:" + view);

    Intent intent = new Intent(MainActivity.this, SubActivity.class);
    startActivity(intent);
}

上記記事では AndroidManifest.xml へ SubActivity の定義を追加する必要がある、とありますが、自動的に追加されてました。


f:id:shu223:20141219152052g:image


テーブルビュー的なものを実装する

iOS でいう UITableView は、Android では「リストビュー」と呼ぶそうです。で、「アダプター」なるものが UITableViewDataSource 的役割をするっぽい(いや、それ以前の、単なる配列かも。。)

  • Containers にある「ListView」を配置
  • SubActivity.java の onCreate メソッド内に、以下のコードを追記
ListView listView = (ListView)findViewById(R.id.listView);
ListAdapter adapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, DAYS);
listView.setAdapter(adapter);
  • 同じく SubActivity.java に、以下のコードを追記(static な配列を生成)
private static final String[] DAYS = new String[] {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

以上、「書籍2」の写経です。正直なところ、`R.id.listView` の `R` とか、`android.R.layout.simple_dropdown_item_1line` が何なのかとか全然わかっておりません。。


f:id:shu223:20141219162621p:image:w220


ちなみに コードから IBOutlet 的に UI オブジェクトにアクセスするにはどうしたらいいのか と思ってましたが、上記のように `findViewById` メソッドで取得するのが定石のようです。iOSでいえば `getViewWithTag` ですね。id は自動補完で出てきますが、IB的な画面の Properties の「id」からも確認できます。


実機で動かしてみる

ここまで忘れてたのですが、せっかく Android 端末を買ったので、実機で動かしてみます。


実機の「デベロッパーモード」を有効にする必要があるとのこと。「書籍3」に全編スクショ付きで載ってたので、Androidの使い方がわからない自分でも簡単でした。

  • [設定] > [端末情報]
  • 「ビルド番号」のところを7回タップ(おもろいw) -> デベロッパーモードになる
  • [設定] > [開発者向けオプション] > [USBデバッグ] を有効にする

あとはエミュレータの代わりに、実機を選択するだけ。


f:id:shu223:20141219165446j:image:w300


CSVファイルの読み込み

書籍に見当たらなかったのでググりました。


新規クラスファイルの生成方法がわからなくてけっこう困ったのですが、

app/src/main/java の位置で [File] > [New] しないと、メニューに「Java Class」というのが出ないようです。さらに、「com.shu223.myfirstapplication」と書かれているディレクトリ(パッケージ?)の下に置くと、他のクラスから扱えるようになるようです。


あと「プロジェクトの一番上」という表現もよくわからなかったのですが、 app/src/main 直下に assets という名前でディレクトリを作成すればOKでした。


  • assets ディレクトリを作成
    • 直下に csv ファイルを置く
  • CSVParser クラスを作成
package com.shu223.myfirstapplication;

import android.content.Context;
import android.content.res.AssetManager;
import android.util.Log;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

/**
 * Created by shuichi on 14/12/19.
 */
public class CSVParser {

    public static void parse(Context context) {
        // AssetManagerの呼び出し
        AssetManager assetManager = context.getResources().getAssets();
        try {
            // CSVファイルの読み込み
            InputStream is = assetManager.open("params.csv");
            InputStreamReader inputStreamReader = new InputStreamReader(is);
            BufferedReader bufferReader = new BufferedReader(inputStreamReader);
            String line = "";
            while ((line = bufferReader.readLine()) != null) {
                // 各行が","で区切られていて4つの項目があるとする
                StringTokenizer st = new StringTokenizer(line, ",");
                String first = st.nextToken();
                String second = st.nextToken();
                String third = st.nextToken();
                String fourth = st.nextToken();

                Log.d("MyApp", "Parsed:" + first + second + third + fourth);
            }
            bufferReader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

  • SubActivity から呼んでみる
CSVParser parser = new CSVParser();
Context context = getApplicationContext();
parser.parse(context);

Context についてはググって下記記事を参考にしました。


実行結果については省略。


ボタンに応じてクリックイベントの処理を分岐させる

MainActivity にボタンを2つ置いて、iOS でいう tag で判別して次に遷移する画面を切り替える、みたいなことをやるには、id プロパティを使用するようです。

  • MainActivity.xml にボタンをもう1つ追加
  • 2つのボタンの id をそれぞれ、「button1」「button2」とする
  • MainActivity.java のイベントハンドラの実装を次のように変更する
public void button_onClick(View view) {

    switch (view.getId()) {
        case R.id.button1:
        {
            Intent intent = new Intent(MainActivity.this, SubActivity.class);
            startActivity(intent);
            break;
        }
        case R.id.button2: {
            Intent intent = new Intent(MainActivity.this, BLEActivity.class);
            startActivity(intent);
            break;
        }
    }
}

参考:クリックイベントに応答する - Android 開発入門


プロジェクトの API LEVEL を変更する

「APIレベル」って初耳でそもそも何なのかわかってないのですが、BLE 関連のコードを書き始めると、巷に情報がよく出ているサンプルコードの多くは `startLeScan`、 `stopLeScan` という BluetoothAdapter クラスのメソッドを使っていて、どうやらこれらは API LEVEL 21 で deprecated になっているらしい。


で、仕方なく推奨されている新しい方を使おうと思ってみたものの、付け焼き刃すぎて、サンプルがないとよくわからない。。


APIレベルってそもそも何なの?と調べてみると、

21とか全然新しすぎる模様。iOS でいえば 8 の API を使ってたようなものか。


で、どこで API レベルが設定されていて、どう変更するのが適切なのかなと調べてみると、build.gradle というファイルを更新する、と書いてあったのですが、

Android Studio の [File] > [Project Structure] > [Flavors] からも変更できました。


ここで「Target Sdk Version」を 19 に変えて、「Sync Project with Gradle Files」ボタン押下、リビルドしました。


結果・・・deprecated なメソッドの警告はなくなりませんでした。。


で、もう一度 [Project Structure] から、[Properties] の 「Comple Sdk Version」 にマニュアルで 19 といれてみたところ、SDKのインストールが始まって、なんやかんやあって、無事「API LEVEL 21 では deprecated だけど、それより下のバージョンでは OKなメソッド」の警告が消えました。。


BLEを利用する

BLE(Bluetooth Low Energy)については参考書籍3冊とも一切記載なし。


が、ググるといろいろと参考記事が見つかりました。


実際にやってみると、deprecated 問題(前項参照)とかいろいろ大変だったのですが、下記手順でいけました。

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
  • メンバ変数などの追加
private static final long SCAN_PERIOD = 10000;
private BluetoothAdapter mBluetoothAdapter;
private Handler mHandler;
static final int REQUEST_ENABLE_BT = 0;
  • 諸々初期化処理(onCreateにて)
BluetoothManager manager = (BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = manager.getAdapter();

if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

mHandler = new Handler();
  • スキャン開始ボタンのイベントハンドラ
public void onClickScan(View v) {

    Log.d("MyApp", "onClickScan");

    // 10秒後にスキャン停止
    mHandler.postDelayed(new Runnable() {
        @Override
        public void run() {
            mBluetoothAdapter.stopLeScan(mLeScanCallback);
        }
    }, SCAN_PERIOD);

    // スキャン開始
    mBluetoothAdapter.startLeScan(mLeScanCallback);
}
  • ペリフェラルが見つかると呼ばれるコールバック
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
        // 見つかったペリフェラル情報をログ出力
    }
};

実行してみると、無事部屋にあるペリフェラルデバイスが見つかってました。

12-19 19:38:38.578    2287-2299/com.shu223.myfirstapplication D/BLEActivity&#65109; name=Kontakt, bondStatus=10, address=EE:FB:3B:6C:B8:BE, type2, uuids=
12-19 19:38:38.768    2287-2300/com.shu223.myfirstapplication D/BLEActivity&#65109; name=null, bondStatus=10, address=59:F9:0E:E7:AD:41, type0, uuids=
12-19 19:38:38.958    2287-2299/com.shu223.myfirstapplication D/BLEActivity&#65109; name=null, bondStatus=10, address=59:F9:0E:E7:AD:41, type0, uuids=
12-19 19:38:39.078    2287-2300/com.shu223.myfirstapplication D/BLEActivity&#65109; name=Kontakt, bondStatus=10, address=EE:FB:3B:6C:B8:BE, type2, uuids=
12-19 19:38:39.128    2287-2299/com.shu223.myfirstapplication D/BLEActivity&#65109; name=null, bondStatus=10, address=59:F9:0E:E7:AD:41, type0, uuids=
12-19 19:38:39.318    2287-2300/com.shu223.myfirstapplication D/BLEActivity&#65109; name=null, bondStatus=10, address=59:F9:0E:E7:AD:41, type0, uuids=
12-19 19:38:39.498    2287-2299/com.shu223.myfirstapplication D/BLEActivity&#65109; name=null, bondStatus=10, address=59:F9:0E:E7:AD:41, type0, uuids=

(iOS のセントラルマネージャのように、重複するペリフェラルについてはコールバックをまとめる、ということはやってくれない


アニメーション

PulsingHalo的なのを実装したくて着手していたのですが、所用につき今日はここまで。*2


所感

Android Studio という専用 IDE があるし、エミュレータもまぁ確かに遅いけど全然アリだし、関連書籍は充実してるし、ググれば大量に開発情報が出てくるしで、開発環境としては超快適だと思いました。iOSと比べてどうの、という不満は全くなし


ただ、やはり Android は、API群(SDK)がまるっきり違う上に開発言語や IDE も違うので、思った以上に「覚え直し」感がありました。。ひとつのアプリをプロダクト品質で出すまでにはまだまだ先が遠い印象。。


最近始めて Mac OS X アプリをつくったときは新しい世界が切り開かれる感じがしてワクワクしたのですが、やはり「モバイル端末でのアプリ開発」という似ている分野では自分はあまり新鮮味を感じられないのかなと。(ObjC->Swiftへのコンバートにもあまり興味が無いように)


そんなわけで、1日やってみた後の所感としては「おとなしく iOS の世界に戻ろう」という気持ちになっております。。


*1:あとでわかってみれば、載っていました。が、そもそもインテント、アクティビティという概念を知ってないと目次からは辿りつけない。。

*2:この後の予定としては、「レイアウト」「ボタン等の見た目のカスタマイズ」「デバッグ」などなど予定していました。

2014-12-12

iOSと機械学習

ビッグデータとかの機械学習隆盛の背景にある文脈や、その拠り所となるコンピュータの処理性能から考えても「モバイルデバイス向けOSと機械学習を紐付けて考えようとする」ことはそもそもあまり筋がよろしくない・・・とは思うのですが、やはり長くiOSだけにコミットしてきた身としては、新たに興味を持っている機械学習という分野と、勝手知ったるiOSという分野の交差点はないのかなと考えずにはいられないわけでして。。


そんなわけで、「iOS と機械学習」について雑多な切り口から調べてみました。


iOSで使える機械学習ライブラリ

DeepBeliefSDK

コンボリューショナルニューラルネットワークを用いた画像認識ライブラリ。iOSとかのモバイルデバイスで処理できるよう、高度に最適化してある、OpenCVと一緒に使うのも簡単、とのこと。


何はともあれ SimpleExample というサンプル実行したら、


f:id:shu223:20141211210821j:image


頼んでもないのにいきなりノートパソコンを認識しました!すごい!


SimpleExample のソースを見てみると、フレーム毎に得られるピクセルバッファの処理はこんな感じでした。

- (void)runCNNOnFrame: (CVPixelBufferRef) pixelBuffer
{
  assert(pixelBuffer != NULL);

	OSType sourcePixelFormat = CVPixelBufferGetPixelFormatType( pixelBuffer );
  int doReverseChannels;
	if ( kCVPixelFormatType_32ARGB == sourcePixelFormat ) {
    doReverseChannels = 1;
	} else if ( kCVPixelFormatType_32BGRA == sourcePixelFormat ) {
    doReverseChannels = 0;
	} else {
    assert(false); // Unknown source format
  }

	const int sourceRowBytes = (int)CVPixelBufferGetBytesPerRow( pixelBuffer );
	const int width = (int)CVPixelBufferGetWidth( pixelBuffer );
	const int fullHeight = (int)CVPixelBufferGetHeight( pixelBuffer );
	CVPixelBufferLockBaseAddress( pixelBuffer, 0 );
	unsigned char* sourceBaseAddr = CVPixelBufferGetBaseAddress( pixelBuffer );
  int height;
  unsigned char* sourceStartAddr;
  if (fullHeight <= width) {
    height = fullHeight;
    sourceStartAddr = sourceBaseAddr;
  } else {
    height = width;
    const int marginY = ((fullHeight - width) / 2);
    sourceStartAddr = (sourceBaseAddr + (marginY * sourceRowBytes));
  }
  void* cnnInput = jpcnn_create_image_buffer_from_uint8_data(sourceStartAddr, width, height, 4, sourceRowBytes, doReverseChannels, 1);
  float* predictions;
  int predictionsLength;
  char** predictionsLabels;
  int predictionsLabelsLength;

  struct timeval start;
  gettimeofday(&start, NULL);
  jpcnn_classify_image(network, cnnInput, JPCNN_RANDOM_SAMPLE, 0, &predictions, &predictionsLength, &predictionsLabels, &predictionsLabelsLength);
  struct timeval end;
  gettimeofday(&end, NULL);
  const long seconds  = end.tv_sec-- start.tv_sec;
  const long useconds = end.tv_usec - start.tv_usec;
  const float duration = ((seconds) * 1000 + useconds/1000.0) + 0.5;
  NSLog(@"Took %f ms", duration);

  jpcnn_destroy_image_buffer(cnnInput);

  NSMutableDictionary* newValues = [NSMutableDictionary dictionary];
  for (int index = 0; index < predictionsLength; index += 1) {
    const float predictionValue = predictions[index];
    if (predictionValue > 0.05f) {
      char* label = predictionsLabels[index % predictionsLabelsLength];
      NSString* labelObject = [NSString stringWithCString: label];
      NSNumber* valueObject = [NSNumber numberWithFloat: predictionValue];
      [newValues setObject: valueObject forKey: labelObject];
    }
  }
  dispatch_async(dispatch_get_main_queue(), ^(void) {
    [self setPredictionValues: newValues];
  });
}

・・・と決してシンプルとは言い難いですが、ここで行っている分類処理のコアは、

jpcnn_classify_image(network, cnnInput, JPCNN_RANDOM_SAMPLE, 0, &predictions, &predictionsLength, &predictionsLabels, &predictionsLabelsLength);

この1行にあるのかなと。で、あとはバッファまわりの諸々とか、得られた結果の処理とか。


おもしろそうなので、また別の機会にちゃんと見てみようと思います。


ML4iOS

iOSでの機械学習を行うためのオープンソースライブラリ。ライセンスは Apache License, Version 2.0 。


コミット履歴を見ると、3年前(2012年1月)からあり、つい最近も更新されています。


LearnKit

iOS, OS X 向け機械学習フレームワーク、とのこと。


サポートしているアルゴリズム

  • Anomaly Detection
  • Collaborative Filtering
  • Decision Trees
  • k-Means
  • k-Nearest Neighbors
  • Linear Regression
  • Logistic Regression
  • Naive Bayes
  • Neural Networks
  • Principal Component Analysis

READMEに下記のようなサンプルコード載ってるので、今度試す。

LNKNeuralNetClassifier *classifier = [[LNKNeuralNetClassifier alloc] initWithMatrix:matrix 
                                                                 implementationType:LNKImplementationTypeAccelerate
                                                              optimizationAlgorithm:algorithm
                                                                            classes:[LNKClasses withRange:NSMakeRange(1, 10)]];
[classifier train];

LNKClass *someDigit = [classifier predictValueForFeatureVector:someImage length:someImageLength];

使いやすそう。


mlpack-ios

mlpack を Objective-C プロジェクトにリンクできるようにしたもの、とのこと。


mlpack というのは「スケーラブルなC++機械学習ライブラリ」らしい。


Swift-Brain

Swiftで書かれた人工知能/機械学習ライブラリ。ベイズ理論、ニューラルネットワーク、その他AIが実装されているとのこと。


  • Matrices
    • Matrix operations
  • Machine Learning algorithms
    • Basic Regressions
    • Neural Networks
    • Support Vector Machines
    • Bayesian Classifiers
    • Self Organized Maps (maybe?)
    • Clustering
  • Statistics
    • Bayes Theorem/Naive Classifier
    • Kalman Filter
    • Markov Model

学習結果をiOSで利用

機械学習によって得られたモデル等をiOSで利用する」ケースです。


冒頭で、「iOSと機械学習を結びつけて考えるのはあまり筋がよろしくない」と書きましたが、このケースでは学習自体はiOSデバイスではなくバックエンド(という表現が正しいかは不明)で行うので、至極真っ当、というか王道かと。


画像認識

OpenCV for iOS に機械学習で作成したモデル(分類器)のxmlファイルをアプリに持たせれば、人の顔以外にもいろんなものを認識できますよ、という記事。



分類器自体はプラットフォーム依存ではないので、iOSという限定を外せばかなり色々とチュートリアル記事とかモデル配布記事がでてきます。


文字認識

iOS で使える OCR ライブラリ。


機械学習で精度を向上させたり、日本語を覚えさせたり。


参考:no title


音声認識

こういう話でいえば、同様に、iOS用の音声認識エンジンも学習データを用意してモデルを自作することができます。

OpenEars は PocketSphinx (CMU Sphinx) というカーネギーメロン大学によるオープンソース認識エンジンのラッパーライブラリで、それ用の音響モデルを自作することはできます(とある案件で試したことあり)。


国産の「大語彙連続音声認識エンジン Julius」(iOSで利用可能)も、もちろん学習によりモデルを自作できます。


実例集

ストアに出ているアプリなど。


Deep Belief by Jetpac - teach your phone to recognize any object

上でも紹介した、DeepBeliefSDKを使ったアプリ。


f:id:shu223:20141211211050j:image:w200


このアプリでネコを認識するデモ動画。


Deep Learning

ディープラーニングで画像を分類するアプリ。


f:id:shu223:20141211211118j:image:w200


Summly

2011年12月と、ちょっと古い記事ですが、Summlyという「ウェブのコンテンツを箇条書きとキーワードの一覧に要約する」アプリのニュース記事。

まずは、特別なアルゴリズムでHTML処理を使って、ウェブページからテキストを抜き出すことから始まる。そのテキストを分析して、記事から選び出された「凝縮された部分」を箇条書きで吐き出す。Summlyのアルゴリズムは、いくつもの機械学習の手法と「遺伝的」アルゴリズム――進化をまねた発見的探索法――を利用してこれを行っている。

ダロイジオ氏のアルゴリズムは、さまざまな出版社によるいろいろなタイプの記事の、人間による要約を調査した。そしてこれらの要約を、Summlyが吐き出すべきものや、情報キュレーションを行う人間の仕事をうまく真似るための、メトリクス[尺度]を調整する際のモデルとして活用した。


その後 Yahoo! に買収され、Yahoo!のニュース要約とパーソナライズ表示、動画、画像検索機能等にその技術が用いられているとのこと。


Plant Recognition: Bringing Deep Learning to iOS

ディープラーニングで植物を認識する、という論文。


f:id:shu223:20141211211217j:image


おわりに

iOS と機械学習を絡めたお仕事、お待ちしております!


2014-12-11

「FILTERS」で学ぶ GLSL

GLSL を書いてオレオレフィルターをつくれる」というコンセプトのカメラアプリがリリースされました。


f:id:shu223:20141211164529j:image:w600

no title



シェーダを書いて動的に適用する、というアイデア自体は昔からあるものですが、


wonderfl や jsdo.it をつくったカヤック製アプリなので、

  • フォークできる
  • リアルタイムプレビューしてくれるエディタでコード(GLSL)を書ける
  • シェアできる

という非常に魅力的な点があります。あと前述の従来品はサンプルだったりするので、もちろんカメラアプリとしてのクオリティも全然違います。



既に魅力的なフィルタがいろいろとアップされています。


f:id:shu223:20141211164641g:image


GLSL には興味があったものの、なかなか勉強する機会がなかったので、他の方がつくったフィルタをフォークしてコード(GLSL)を読みつつコメントを入れていくということをやってみました。


タイトルの後ろに (commented) と入れているので、よろしければ参考にしてみてください。*1


以下、いくつか紹介していきます。


グレースケールにする

入力画像をグレースケールに変換するフィルタ。


f:id:shu223:20141211164705j:image


これはフィルタを新規作成すると生成されるデフォルトテンプレートの最後の行だけいじってコメントを入れたもの。GLSLを書いてみる最初の1歩として、この3行を理解するといいかもしれません。


grayscale (commented)
void main()
{
    // 座標を取得
    vec2 uv  = iScreen;
    
    // カメラからの入力テクスチャから、該当座標の色を取得する
    vec4 color = texture2D(iCamera, uv);
  
    // RGBのGとBをRで置き換えた色を生成して適用
    gl_FragColor = vec4(color.r,color.r,color.r, 1.0);
}

歪める&青っぽくする

入力画像を歪め、全体的に青っぽくするフィルタ。


f:id:shu223:20141211164756j:image


iCamera(カメラからの入力テクスチャ) + texture2D関数で、入力画像の任意の座標の色をとってこれることを利用して、座標をx,yそれぞれ2乗した先の画素値をとってきて歪みを実現しています。


water (commented)
void main()
{
    // 座標取得
    vec2 scr = iScreen;
  
    // 座標に応じて近隣の色を取得する
    // (カメラの入力テクスチャから、注目座標のx,yを2乗した座標の色をとってくる
    vec4 color  = texture2D(
      iCamera,
      vec2(
        pow(scr.x, 2.0),
        pow(scr.y, 2.0)
      )
    );
  
    // 青っぽくして適用
    gl_FragColor = vec4(
      vec3(
        color.r *   0.0 / 255.0,
        color.g * 153.0 / 255.0,
        color.b * 204.0 / 255.0
      ),
      1.0
    );
}

画像の一部にモザイクをかける

ピンポイントにモザイクをかけるフィルタ。ピンチイン・アウトにも対応。


f:id:shu223:20141211164814j:image


モザイク処理は、座標値を floor 関数で粗くして周辺画素と同じ色を使用することで実現されています。


また、出力位置が円の中にあるかどうかでモザイクをかける・かけないを判定して、円形のモザイク処理が実現されています。


ピンチイン・アウトに対応しているので座標処理がちょっと複雑な感じがする場合は、いったん iSize をなくして考えてみるとわかりやすいかもしれません。


Pinpoint Mosaic
// 白色を定数として定義
const vec3 white = vec3(1.0, 1.0, 1.0);

// 座標positionが、半径size・中心座標offsetにある円の内側にあるかを判定する
bool inCircle(vec2 position, vec2 offset, float size) {
    float len = length(position - offset);
    if (len < size) {
        return true;
    }
    return false;
}

void main( void ) {
    // 座標取得
    vec2 uv = iScreen;
    // 座標を粗くする(15 x ピンチサイズ倍して切り捨て)
    uv = floor(uv * iSize * 15.0) / 15.0 / iSize;
    // 粗くした座標の色を取得
    vec4 color = texture2D(iCamera, uv);
    
    // 白色(whiteは定数として定義済み)を生成
    vec3 destColor = white;
    // 出力位置を計算
    vec2 position = (gl_FragCoord.xy * 2.0 - iResolution) / min(iResolution.x, iResolution.y);
    
    // 出力位置が円の内側にあるか?
    if (inCircle(position, iPosition, 1.0 + (1.0 - iSize))) {
        // モザイク化する(粗くした座標の色を使用)
        destColor = color.rgb;
    }
    else {
      
        // モザイク化しない(元々の色を使用)
        destColor = texture2D(iCamera, iScreen).rgb;
    }
    
    // 決定した色を出力出力
    gl_FragColor = vec4(destColor, 1.0);
}

所感

FILTERS、GLSLの勉強におすすめです!


(GLSL関連記事)


(余談)iOS 8 とシェーディング言語

シェーダが書けると iOS の Core Image のフィルタ(CIFilter)も自作できるようになります。(iOS 8 から)


拙作『iOS8-Sampler』にもシェーダを書いて作成したカスタムフィルタのサンプルがいくつか入っています。


(2014.12.12追記)他のアプリからFILTERSのフィルタを使う



ってツイートしたら、中の人からリプライがあり、なんと既に Photo Editing Extension 対応してるとのこと。すごい!


標準の「写真」アプリから試してみました。


f:id:shu223:20141212134814j:image


現状では、上位30個ぐらいのフィルタが選べるようです。



*1:なお、コードをシンプルにするため、処理に使われていない関数や定数は削除しました。

2014-12-10

アップルによるBluetoothアクセサリの設計ガイドラインに書かれていたこと

『Bluetooth Accessory Design Guideline for Apple Products』という、Appleによる公式ドキュメントがあります。Mac や iOS デバイス、iPod 等の Apple 製品の Bluetooth アクセサリの設計についてのガイドラインです。


本記事では、このドキュメントから iOS エンジニアも知っておいた方が良さそうな部分 を抜粋していきたいと思います。


※本記事は、no title 9日目の記事です。


アドバタイズ間隔

アドバタイズ開始から少なくとも30秒間は、推奨アドバタイズ間隔である 20 ms を使用すべき」と明記されています。

To be discovered by the Apple product, the accessory should first use the recommended advertising interval

of 20 ms for at least 30 seconds.


で、この最初の30秒間で発見されなければ、バッテリー節約のため下記のより長いアドバタイズ間隔のいずれかを使用することを推奨する、と。

If it is not discovered within the initial 30 seconds, Apple recommends using

one of the following longer intervals to increase chances of discovery by the Apple product:

  • 152.5 ms
  • 211.25 ms
  • 318.75 ms
  • 417.5 ms
  • 546.25 ms
  • 760 ms
  • 852.5 ms
  • 1022.5 ms
  • 1285 ms

ちなみに BLE の規格で定められているアドバタイズ間隔は、最小 20[ms] 〜 最大 10.24[s]の範囲で 0.625[ms]の整数倍 なので、Appleによる「最初の30秒間の推奨アドバタイズ間隔」はBLE規格上の最小間隔でもあります。


iOSエンジニアとしては、このあたりのアドバタイズ仕様を把握しておくと、セントラルとしてふるまうアプリにおいてスキャンをどう行うかの設計や、iBeacon の利用する際に役立つかもしれません。


Connection Parameters

以下の全てのルールに従っていないと、接続パラメータ要求は(iOSから)拒絶される可能性がある、と書かれています。

  • Interval Max * (Slave Latency + 1) ≤ 2 seconds
  • Interval Min ≥ 20 ms
  • Interval Min + 20 ms ≤ Interval Max Slave Latency ≤ 4
  • connSupervisionTimeout ≤ 6 seconds
  • Interval Max * (Slave Latency + 1) * 3 < connSupervisionTimeout

If Bluetooth Low Energy HID is one of the connected services of an accessory, connection interval down to

11.25 ms may be accepted by the Apple product.


The Apple product will not read or use the parameters in the Peripheral Preferred Connection Parameters

characteristic


iOSエンジニアとしては、Connection Interval が「20 ms 以上」と明記されている部分は情報として非常に有用なのではないでしょうか。


こういう計算が考え方として正しいものなのか、ちょっと自信がない(他に考慮すべきことがあるかも)のですが、Connection Intervalが20msということは、1秒間に50回データの送受信が可能、ということなので、1パケット20byteとして、


20 * 50 = 1000 bytes / sec = 1k byte / sec = 8k bits / sec

というわけで、iOSとBLE接続するデバイスとの最大通信速度は約8kbps、ということがいえるのではないでしょうか。


他にも、connSlaveLatency(スレイブが送信するデータがなにもないときに、コネクション・イベントを無視出来る回数)や connSupervisionTimeout (接続が失われたと判断する、コネクション・イベントの失敗回数)の条件式も明示されているので、どういう条件で接続が失われるのか、を具体的に把握する指針となり、参考になります。


参考:no title


Service Changed Characteristic

そのアクセサリがサービスを変更する可能性のある場合(つまり、GATT を変更する可能性がある場合)は "Service Changed Characteristic" を実装しましょう、と書かれています。

The accessory shall implement the Service Changed characteristic only if the accessory has the ability to change

its services during its lifetime.


アップル製品は、前回読んだ(キャッシュした)情報に依るかどうかを "Service Changed Characteristic" を使って決めますよ、と。

The Apple product may use the Service Changed characteristic to determine if it can rely on previously read

(cached) information from the device.


これの話ですね。


Core Bluetooth でいうと、サービス変更時に CBPeripheralDelegate の `peripheral:didModifyServices:` が呼ばれる、という件ですが、そもそもアクセサリ側で "Service Changed Characteristic" が実装されている必要があります。(実装されてなければ iOS の Bluetooth 設定を off/on してキャッシュクリアする)


Siri

「カスタム Siri コマンドを有効にする」「Siri のアベイラビリティ情報を取得する」「Siri セッションの初期化」「Siri Eyes Free Mode」「Siriセッションを最適化する」などなど興味を惹かれるタイトルが並んでいますが、HFP プロファイルとかのクラシック Bluetooth の話で Core Bluetooth でどうこうできる範囲ではないのでスルー。


「6.5 Improving Voice Recognition」も iOS エンジニアがどうこうできる部分ではないが、S/N比の推奨は 20 dB 以上 とか書いてあって、Siri とか関係なく、良好な音声入力の基準値として今後 iOS で音声処理やる際の改善のヒントになりそうです。


関連

上原さんのブログにBLE部分を日本語訳したものがまとめられています。


2014-12-09

機械学習はじめの一歩に役立つ記事のまとめ

「機械学習」というワードになんとなく惹かれつつも、具体的にやりたいことがあるわけでもないので、手を動かすことなくただひたすら「いつかやる」ために解説記事やチュートリアル記事を集める日々を過ごしていたのですが、このままじゃイカン!と no title に参加登録してみました。


が、やはり何もしないまま当日を迎えてしまったので、お茶濁しではありますが、せめて「機械学習ってどんな手法やライブラリがあって、どんな応用先があるのか?」というあたりをざっくり把握して最初に何をやるのか方向付けをするためにも、たまりにたまった機械学習系の記事をいったん整理してみようと思います。


機械学習の概要

特定のライブラリや手法の話ではなく、機械学習全般に関する解説。


no title


冒頭に、

  • 初めて機械学習を聞いた⼈人向けです
  • 数式を使いません
  • ガチな⼈人は寝てて下さい

とある通り、機械学習ってそもそも何?どう嬉しいの?というところがスタート地点である自分にとってすごくありがたかったスライド。


ざっくり目次的なものをまとめると(かなり抜粋)、

  • 機械学習って何?
    • 例: スパム判定、商品推薦、コンピュータ将棋・囲碁・チェス
    • その他適用分野
    • 機械学習が向かないタスク
    • ルールベースとの比較
  • 機械学習って何してるの?
    • 教師あり・教師なし学習とは?両者の目的の違い
    • 線形分類器(図解がわかりやすかった)


Python / scikit-learn

Python で機械学習しよう!(環境構築 on Mac編) - もろず blog

Mac 上に Python で数値計算、機械学習を行うための環境構築の手順。全編スクショ付きですごくわかりやすいです。(が、あくまで環境構築手順だけで、実際に機械学習を行ってみるところまではカバーされていません)


インストールする数値計算、機械学習ライブラリは以下の4つ。

  • NumPy

数値計算を効率的に処理するためのライブラリです

配列の操作がとても簡単になるので、行列計算には必須っぽいです

  • SciPy

様々な科学計算が実装されたライブラリです

内部でNumPyを利用しています

  • matplotlib

グラフ描画のライブラリです

内部でNumPyを利用しています

  • scikit-learn

機械学習に関する様々なアルゴリズムが実装されたライブラリです


機械学習の Python との出会い — 機械学習の Python との出会い

NumPy や SciPy などの科学技術計算モジュールの具体的な使い方を学べるチュートリアル。PDF版やePub版も用意されていて、もはや書籍。


前書きにもある通り、機械学習のごくごく初歩的な話とか、Pythonのごくごく初歩的な話は省略されているので、はじめの二歩目ぐらいに目を通すとよさそうです。

このチュートリアルでは,いろいろな機械学習の手法を Python で実装する過程をつうじて,NumPy や SciPy など科学技術計算に関連したモジュールの具体的な使い方を説明します. 機械学習の手法についてはごく簡単な説明に留めますので,詳細は他の本を参考にして下さい. また,クラスなどのプログラミングに関する基礎知識や,Python の基本的な文法については知っているものとして説明します.


(目次)

  • はじめに
    • 本チュートリアルの方針
  • 単純ベイズ:入門編

最初に実装するのは,特徴量がカテゴリ変数である場合の単純ベイズ (Naive Bayes) です. この単純ベイズの実装を通じて,NumPy / SciPy を用いた行列・ベクトルの初歩的な扱いについて説明します.

    • NumPy 配列の基礎
    • 単純ベイズ:カテゴリ特徴の場合
    • 入力データとクラスの仕様
    • 学習メソッドの実装(1)
    • 予測メソッドの実装
  • 単純ベイズ:上級編

単純ベイズ:入門編 で実装した NaiveBayes1 クラスを,NumPy のより高度な機能を利用して改良します. その過程で,NumPy の強力な機能であるブロードキャストの機能と,この機能を活用する手順を紹介します.

    • クラスの再編成
    • 単純ベイズの実装 (2)
    • 配列の次元数や大きさの操作
    • ブロードキャスト
    • クラスの分布の学習
    • 特徴の分布の学習
    • 実行速度の比較

pythonの機械学習ライブラリscikit-learnの紹介 - 唯物是真 @Scaled_Wurm

Python の機械学習ライブラリ scikit-learn のチュートリアル。scikit-learn でできること(機能)がカタログ的に紹介されています。


それぞれの機能について簡単なサンプルコードと実行結果が示されているので、色々とつまみ食い的に試してみるのによさげ。

  • サンプルデータの自動生成

sklearnにはIrisなどのトイデータセットやサンプルデータの自動生成などの機能もあります。

(0のデータ)

  • 線形SVMによる二値分類

データをトレーニング用とテスト用に分けて、トレーニングデータで訓練したモデルでテストデータを予測してみます。

  • 分類結果の評価

分類器で得られた推定結果がテストデータとどれぐらい一致しているかでモデルの評価を行います。

  • クロスバリデーション(交差検定)

上のほうで二値分類を試すときにデータをトレーニング用とテスト用に分解しました。しかし、データを分けるとそれぞれに使えるデータが少なくなってしまいます。

クロスバリデーションではデータをいくつかに分割して、1個をテスト用、残りをトレーニング用に使ってスコアの計算をします。このとき分けられたデータのすべてがテストに選ばれるようにくりかえし評価を行い、そのスコアの平均を使って評価をします。

  • グリッドサーチ

適切なパラメータを選ぶのによく使われるのがグリッドサーチという方法で、これはいくつかのパラメータの組み合わせを実際に試して評価関数を計算し、スコアがよかったパラメータを選ぶというものです

  • 不均衡データ

ラベルごとのデータ数が大きくアンバランスなデータだと学習がうまくいかないときがあります。たとえば正例:負例=1:100とかだったりすると、十分なデータがあっても訓練したモデルはすべてを負例に分類してしまったりします。こういうときはクラスに対する重み(LinearSVCならclass_weight)を変えたりresample関数を使ってトレーニングデータ内の比率が1:1に近くなるようにアンダーサンプリングやオーバーサンプリングをしたりすると結果がよくなることがあります。

  • 特徴量の抽出

分類器のモデルの入力(データのベクトルによる表現)をどうやって作るかという話


no title

scikit-learn のチュートリアルスライド。


スライドというメディアの特性上、コードは少なく図が多いので、上の「pythonの機械学習ライブラリscikit-learnの紹介」に出てくる機能の補助資料として読むとよさそうです。


Deep Learning

no title

ずっと「ディープラーニング」というキーワードは「何かすごそう」ぐらいに気にはなってて意味はわかってなかったのですが、冒頭の説明が超わかりやすかったです。

つまるところ、Deep learningの特徴は「特徴の抽出までやってくれる」という点に尽きると思います。

例えば相撲取りを判定するモデルを構築するとしたら、普通は「腰回りサイズ」「マゲの有無」「和装か否か」といった特徴を定義して、それを元にモデルを構築することになります。ちょうど関数の引数を決めるようなイメージです。

ところが、Deep learningではこの特徴抽出もモデルにやらせてしまいます。というか、そのために多層、つまりDeepになっています。

具体的には頭のあたりの特徴、腰のあたりの特徴、そしてそれらを複合した上半身の特徴・・・というように、特徴の抽出を並列・多層に行って学習させて、それでもって判定させようというのが根本的なアイデアです


deep learning の代表的なライブラリも挙げられていました。

  • pylearn2
  • Caffe
  • nolearn
  • deepnet
  • yusugomori/DeepLearning

通常なら最新の実装も搭載されているpylearn2、画像認識ならCaffeらしいです(経験者談)。


研究やとりあえず試してみる場合に必要になる学習データを提供してくれているサイトのリストもまとめられていて、即ストックさせていただきました。


最後に pylearn2 を用いた実践手順もあり。


no title


いろいろなところからリンクされていたスライド。Deep Learning の手法について図解でわかりやすく解説 ・・・されているはずなのですがちょっとよくわからなかったのでまた読もうと思います。


no title

専門家向けではなく、一般向けに Deep Learning について説明してくれているスライド。


no title

たぶん全然はじめの一歩ではないのですが、かなり詳しく書かれていて、実装についても追記される(2014年12月9日現在未完とのこと)なので、deep learning がちょっとわかってきた頃にまたあとで読むと勉強になりそうだなと。


no title


Python の Deep Learning ライブラリ、THeano を使用して手書き文字認識。前段の Deep Learning 自体の解説も噛み砕かれていて、わかりやすそうです。


no title

Deep Learning ライブラリ、THeano のチュートリアル。


Weka

機械学習(データマイニング)ソフトのWeka。Mac版もあり。*1



とある知り合いの大学院生が使っていて、GUIをポチポチしていくだけであらかじめ実装されている各種アルゴリズムによりデータが自動分類されていくという様子を目の当たりにして、今度自分も試してみよう、と思い記事だけ集めて今に至ります。


@PAGES サービス終了のお知らせ

インストール〜実際にデータを分析するまでのチュートリアル。全編スクショ付きで非常にわかりやすそうです。Windows版ですが、たぶんMacでもほぼ同じかと。


決定木をつくる、入力データを用意する、のあたりはあらかじめ用意されているサンプルデータを使用するのですぐに試せるし、分析結果の見方も解説されているので、とりあえずプログラミングなしで体験してみるのによさそうです。


社会人MBA?技術者編
  • どうマイニングする?
  • 決定木分析?
  • 決定木分析?-1
  • 決定木分析?-2
  • 決定木分析?予測する
  • 決定木分析?因果関係を知る
  • 記憶ベース推論?
  • 記憶ベース推論?
  • ニューラルネット
  • 複数の分析を行う
  • アソシエーション分析?
  • アソシエーション分析?
  • ROC曲線とlift chart 補足1
  • 補足 研究開発部門での使用
  • 関連リンク集
  • データマイニング入門

リンク先に、これらの内容を再構成してまとめられたPDFもあります。


はじめてのweka勉強会 −修正版− - sleeping vote

arff形式のデータをテキストエディタで自分でつくってみる、クラスタリングするプログラムを書いてみる、ちょっとソースをいじって手法を変えてみる、といった方法が説明されていて、Wekaを使う第2歩目ぐらいによさそうです。


応用・実例

機械学習は手法も応用も多岐にわたるので、具体的な応用・実用例も集めています。


Googleの猫認識 (Deep Learning) - 大人になってからの再学習

ディープラーニングの記事を見ていると、事例として必ず出てくる、Googleの猫認識の話(ネコという概念を、コンピュータがYoutubeの動画を見続けることで自動学習したという話)について、この記事では、Deep Learning がどのように用いられているか、を噛み砕いて解説してくれています。



画像認識によって焼きたてパンの種類を判断できるレジ装置
  • 購入客がパンをトレーに載せてレジ横にあるカメラの下に置くと、BakeryScanが撮影画像を基に、パンの種類を自動判断
  • 準備作業として、パン1種類につき10個分の写真を撮影してBekaryScanに読み込ませる
    • BakeryScanが10個分の画像から、あんパンやメロンパンなど種類ごとの「画像モデル」を自動的に生成
  • 大きさや形、色、表面の状態、テクスチャーといった画像モデルの「特徴」は、B開発元があらかじめ100種類ほど設定
  • 特徴の「重み」については、店が読み込ませたパンの画像データを機械学習して、BakeryScanが自動調整

低解像度の画像から、自動車のナンバープレートのナンバーを識別
  • あるナンバープレートの画像について、高解像度のデータと低解像度に圧縮したデータをペアとして読み込ませて機械学習を行い、ナンバープレートの圧縮パターンの「辞書」を作成
    • この辞書を逆引きすることで、低解像度の画像から高解像度の画像を類推・生成
  • 自動車のナンバープレートのほか、人間の顔の圧縮パターンも機械学習させた
    • 低解像度の監視カメラ画像の解像度を高め、写った人物の顔写真を正確に推測

(元記事より)

英文の誤りを見つけ出し、ネイティブが書いたかのような文章に校正
  • Lang-8が大量に保有する、非ネイティブである学習者が書いた「誤った文章」と、ネイティブの指導者が校正した「正しい文章」のペアのデータを利用
  • これらペアのデータを100万件以上提供してもらい、機械学習させた
    • 間違いのパターンや、その正答パターンをモデル化
    • こうしたモデルを、新たな「間違った英文」に適用することで、誤り箇所とその正答候補を探し出す
  • 現在の校正精度は36.2%
    • 言語学者が人力でデータをつくり、機械学習させる従来手法の校正精度は23%程度だった
    • データの件数を100倍以上に増やすことで、校正精度を13ポイント高めた

大規模AV画像データベースと類似顔画像検索を用いたAV検索システム

「顔画像をもとに似た顔の人が出ているAVを検索するシステム」について書かれた論文。

類似する顔の検索には、HOG 特徴 という特徴量を使用しているとのこと。

実装言語は Python、使用ライブラリは OpenCV, SciPy, Numpy。


指し手を「機械学習」することで将棋アルゴリズムを自動生成
  • 最新の将棋プログラムのアルゴリズムは、コンピュータがプロ棋士による数万局の対局データ(棋譜)を分析し、指し手を「機械学習」することで自動生成されたもの
    • 過去の将棋プログラムのアルゴリズムは、プログラマーが将棋の知識と経験を駆使して手作りしていた
  • ある局面の有利さや不利さを、その局面の「特徴」と「重み」の積の合計として数値化
    • 特徴とは、駒の種類や数、位置関係、王将の危険度といった要素
    • 特徴ごとに重みが設定してあり、「金」なら400点、「飛車」なら700点、王将の近くに敵の駒がある場合はマイナス500点といった具合
  • 機械学習によりこれらの重みを自動調整

no title

Ruby の naivebayes を使用。


あと、「フィードデータをためる方法」で触れられている Fastladder が気になりました。


no title

scikit-learn で線形分離。


no title

K-Means クラスタリング


no title

OpenCV の分類器作成

  • Boosting でのモデル作成
  • LBP特徴量を使用

ちなみにこのテーマについては前段として昨年書いたこの記事もよろしければ併せてお読みください:


no title

言語は Ruby、単語抽出に mecab 使用。

  • 記事に含まれる単語をmecabで抽出
  • → その記事のいいね!数に応じてその単語にポイントを付与
  • → 単語ごとに出現数とポイントをデータベース化して教師データとする

no title

no title

東京都議会議員選挙の党派マニフェストを自動分類したよ - sleeping vote

2009年7月12日投開票の東京都議会議員選挙において、主要6党(自民党、民主党、公明党、共産党、幸福実現党、生活者ネットワーク)の会派マニフェストに書いてある内容を品田方式と呼ばれる選挙研究で使う分類カテゴリに従い、分類したもの

分類作業は、stiqが研究している自然言語処理と機械学習*1の方法を用いて自動分類コーディングシステムを構築し、それを使っています。

具体的なシステムの仕組みは、論文にかかれているとのこと。


500 Internal Server Error

近年、自称作曲家・佐村河内守氏と外見の酷似した人物が増加し、彼らと佐村河内氏とを自動的に見分けるシステムの開発が望まれている。一方で、佐村河内氏は作曲時と謝罪会見時で大きく外見的に変化することが知られており、佐村河内氏を見分けるシステムはそのような変化に頑健である必要があるため、実現は容易ではない。本プロジェクトでは、高度なコンピュータ技術を活用し、佐村河内氏を適切に見分けるシステムを開発する。


以下2つの手法を適用したとのこと。

Fisher Vector Faces [Simonyan et al. BMVC 2013] では、一般の物体の認識に用いられる Fisher Vector (FV) と呼ばれる手法を顔画像へと適用し、顔のパーツを明示的にモデリングせずに、高精度の識別結果を記録した。顔の領域を検出し、顔の向きを合わせるなどの前処理を行った後は、ただ通常の物体認識と同様の手続きを適用するのみである。

DeepFace [Taigman et al. CVPR 2014] は Facebook 社の発表した研究であり、「ほぼ人間並みの識別能力を実現した」として大きな注目を集めた。こちらでは、顔検出と向きの補正を行った後に、Deep Convolutional Neural Networks (DCNN) と呼ばれる手法を用いて識別を行う。こちらも一般の物体の認識に用いられる手法であり、一部に独自の改良を施してはいるが、処理の大筋は顔画像に限るものではない。


(すごい精度・・・!)


*1データマイニングと機械学習を混同するのはよくなさそうで、Wekaはどっちかというとデータマイニングの文脈っぽいですが、せっかくなので一緒に整理しておきます。

2014-12-08

iBeacon と BLE

昨年末〜今年初めにかけて一世を風靡した iBeacon ですが、なんというか、本来期待されるべき方向ではない方向で期待され、「不当に」ガッカリされることが多いような印象を受けます。

  • 「コンテンツを内部に保持して直接配信することはできない」
  • 「双方向通信はできない」*1
  • 「距離はあてにならない」
  • 「検出できたりできなかったりする」
  • 「ビーコンのバッテリーが意外ともたない」

などなどなどなど。


で、個人的にはこういうガッカリは、「BLEをどのように利用することでiBeaconという領域観測サービスが実現されているか」を理解していれば、そもそもそういう期待は方向性違いであると気付けたり、何らかの改善指針を持てたりする部分があるなぁと感じてまして、本記事では「iBeacon と BLE」にフォーカスして書きたいと思います。


なお、本記事は 「no title」の8日目の記事となります。


ビーコン = アドバタイズ専用デバイス

iBeaconにおける「ビーコン」というのは、BLEの観点から簡単にいうと、「アドバタイズに特化したペリフェラル」です。*2


通常のBLEペリフェラルデバイスの場合は、アドバタイズしてセントラルから見つけてもらい、そのあと接続し、データのやりとりを行うわけですが、ビーコンはそれらの機能をすっ飛ばして自分の存在を知らせる機能(アドバタイズ)だけに特化することで、シンプル、低コスト、低消費電力に振り切っている、というのが最大のポイントです。*3


BLEガジェットは安いものでも5000円以上はする中で、ビーコンモジュールは安いものだと1台200円ほどだったり、寿命が数年間と謳われていたりするのは、このためです。


ちなみに、ビーコン界ではもっともよく知られている(と僕は思っている)、「Estimote」のビーコンはシリコンケースががっちり糊付けされていて、ケースをカッター等で切らないと(つまり破壊しないと)電池交換できないようになっていて、


f:id:shu223:20141209000558j:image

estimoteが届いたので分解してみた - yumulog | 社会人博士の日記 より)


もちろんスイッチ等でon/offできるわけでもないので、これが届いた当初は「まじで!?」「回路を見られたくないのかな?」とか思ったのですが、今思えば、

  • シリコンケース糊付け・・・安価な防水効果。設置して放置しておける
  • 電池交換ができない・・・ビーコンなので使い捨てでOK

と上述したiBeaconの思想から考えると、大いに納得がいきます。


アドバタイズ周期・電波強度とバッテリー消費

上述した通り、ビーコンはアドバタイズに(ほぼ)特化したデバイスであり、アドバタイズはざっくり言うと電波を発して自身の存在を知らせる機能なので、バッテリー寿命について「アドバタイズ周期」「電波強度」からシンプルに考えられることになります。*4


アドバタイズ周期を上げれば *5 電波を発する頻度が多くなるのでバッテリー消費は大きくなりますし、電波強度ももちろん強くすればより早くバッテリーを消費します。


逆に、アドバタイズ周期を下げればビーコンの発見されやすさが下がりますし *6、電波強度を下げれば領域観測の範囲が狭まります。


多くのビーコンモジュールはこれら「アドバタイズ周期」「電波強度」を変更できるようになっているので、適用しようとしているサービスに応じてバッテリー消費とのトレードオフを考慮しながら適切に設定することが肝要です。


またビーコンモジュールを販売している各社のページには、(バッテリーが)「約1年持ちます」「2年持ちます」等と書かれている場合がありますが、アドバタイズ周期や電波強度をどのように設定した上での話なのか、に注意する必要があります。


iBeaconのアドバタイズメントパケット

iBeaconのビーコン側の仕様は一般には公開されていない(要iBeaconライセンス取得)のですが、上述の通り要はBLEのペリフェラルなので、容易にそのアドバタイズメントパケットを観測できます。


そのフォーマットに沿ってパケットを構成してアドバタイズを行えば、MacでもAndroidでもBLE113等の開発キットでもiBeaconのビーコンモジュールとしてふるまえますし、そのルールに従ってビーコンのアドバタイズメントパケットを解析すれば、iOSデバイス以外からもそのビーコンのUUIDやmajor,minorを取得可能(すなわち、領域観測と同じような挙動を実現可能)です。


アドバタイズメントパケットのフォーマット

BLEのアドバタイズメントパケットの各バイトがどのように構成されているかは、Bluetooth SIG によるドキュメント「Bluetooth Specification」 にある下記の図がわかりやすいです。


f:id:shu223:20141209000805p:image:w582


ここでは詳細説明を省略しますが、AD Type が `FF` の場合、その AD Structure は "Manufactureer Specific Data" であることを意味し、Manufactureer が自由にその AD Structure の長さと内容を決めることができます


Apple はこの AD Type を利用し、Apple は iBeacon のアドバタイズメントパケットのフォーマットを定義しているわけです。


iBeaconのアドバタイズメントパケットの詳細

iBeacon のアドバタイズメントパケットのうち、iBeacon 特有の部分、すなわち AD Type が `FF` である AD Structure だけ抜き出すと、そのパケットフォーマットは次のようになっています。

1A # AD Structure の長さ(bytes)
FF # AD type
4C 00 # Company identifier code (0x004C は Apple を示す)
02 # Byte 0 of iBeacon advertisement indicator
15 # Byte 1 of iBeacon advertisement indicator
XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX # iBeacon の proximity uuid
XX XX # major 
XX XX # minor 
XX # Tx Power

(参考:http://stackoverflow.com/questions/18906988/what-is-the-ibeacon-bluetooth-profile


`XX` とした部分以外のバイト値はiBeaconにおいて固定値です。


Bluetooth Explorer 等のアプリケーションで iBeacon のアドバタイズメントパケットを監視してみると、上記のフォーマットに則っていることが確認できます。


f:id:shu223:20141209002358j:image:w582


Core Bluetooth に対する iBeacon のアドバンテージ

ここまでで、iBeaconにおける「ビーコン」はBLEにおけるペリフェラルのサブセット仕様である、ということを書いてきました。BLEのペリフェラルになれるあらゆるBLEモジュールは、「ビーコン」になれます。


そして、ビーコン自体がただのアドバタイズだけを行うペリフェラルだとすると、セントラル側であるiOSデバイスからは当然 Core Location のビーコン領域観測の機能を使用しなくても、Core Bluetooth を使えば問題なく検出できる、ということになります。むしろ、Core Bluetooth を使用した方が、ペリフェラルのより詳細な情報を得られるというメリットさえあります。


しかし、Core Bluetooth での実装では実現できない、Core Location の領域観測サービスならではのアドバンテージ もあります。


ロック画面表示をトリガとするビーコン検出

ビーコン自体はただのペリフェラルなので、Core Bluetooth で検出できるわけですが、バックグラウンドモードにおける Core Bluetooth のスキャンにはいくつかの制限があり、その一つにスキャン間隔が長くなる、というものがあります。


これが、Core Location の領域観測サービスを使用してビーコン領域を検出する場合、ユーザーがiPhoneのロック画面を表示した瞬間にだけビーコン領域検出 を行ってくれるようになります。


ユーザビリティの観点からも、バッテリー消費の観点からも、非常にメリットの大きい機能です。


参考書籍:

iBeacon ハンドブック
iBeacon ハンドブック
posted with amazlet at 14.12.08
(2014-03-25)
売り上げランキング: 11,647


ロック画面へのアプリアイコン表示

iOS 8 より、あるアプリで観測している領域が検出された場合に、そのアプリのアイコンがロック画面の左下に表示されるようになりました。


f:id:shu223:20141209000735j:image


この状態でアイコンをドラッグして上方向にスライドすると、そのアプリが起動します。


Core Bluetooth を利用してバックグラウンドでペリフェラルを検出しても、iOSのロック画面に直接の動線を置く等ということはできないので、これもまたiBeaconならではの大きなアドバンテージといえます。


「検出できたりできなかったり」への対処

ビーコンがBLEのアドバタイザに過ぎないということを知っていれば、Core Location の `locationManager:didEnterRegion:` や `locationManager:didRangeBeacons:` でビーコン検出を観測する以外に、いろいろな検証手段の選択肢が増えてきます。


たとえば、iOS の LightBlue や BLExplr でスキャンして検出されるか確認してみたり、そちらでは検出されるようであれば、Mac の Bluetooth Explorer でアドバタイズメントパケットの中身を確認 し、パケットのフォーマットが正しいかを確認したり、等々。


またビーコン本体のアドバタイズ周期が長すぎると検出されにくくなりますし、電波強度が弱く設定されていると電波が届いていない可能性も考えられます。


おわりに

iBeaconという領域観測サービスを実現するために、BLEがどのように用いられているのか、について書きました。


割り切った仕様なのでできないことも多いのは事実ですが、「安いので大量にバラまける」「低消費電力なので小型な電池で長持ちする+構造がシンプルなので簡単に防水にできる+安い = 放置して使い捨てにできる」などなど、iBeaconならではの特長も多くあります。


さらに、アドバタイズや電波のことを理解してサービス設計やチューニングを行えば、つながりやすさやバッテリーの持ちに関しても改善が見込めます。


これらiBeaconの方式や特性をうまく活かし、より有益なiBeaconサービスに繋げていきましょう!


関連記事:


*1:UUID・major・minor等のアドバタイズメントパケットの内容を動的に変更することでiBeaconと双方向通信的なものを実現することは可能ですが、やはり「そもそもBLEのサブセット仕様である」ということを考えるとそうした力技で双方向通信を行うことに意味はないかと。

*2:セントラルがスキャンで発見できるよう、ペリフェラルデバイスが「ここにいるよー」と電波を発することを「アドバタイズ」(日本語でいうと広告)と呼びます

*3:そもそもBLEにおけるセントラル・ペリフェラルという概念には、「高度な機能はセントラル側に持たせ、ペリフェラル側のデバイスはシンプル・低コスト・低消費電力に済むように」という思想が込められているのですが、さらにそれをシンプルにしたものがiBeaconです

*4:UUIDの更新やファーム更新等、ビーコンによってはアドバタイズ以外の要因でバッテリーを消費します

*5:BLEの規格ではアドバタイズ周期は最小 20[ms] 〜 最大 10.24[s]の範囲で 0.625[ms]の整数倍、と定められています

*6:たとえば、アドバタイズ周期を10.24sにして、アドバタイズが終わった直後に領域観測側がスキャンを行ったとすると、約10.24sビーコンの発見が遅れることになります

2014-12-05

ANCSでiOSの電話着信やメール受信の通知を外部デバイスから取得する

ANCS は「Apple Notification Center Service」の略で、電話着信やメール受信等、iOSで発生するさまざまな種類の通知に、BLEで繋がっている外部デバイスからアクセスするためのサービスです。


iOSアプリに携わるエンジニアとしてはリモート通知(プッシュ通知)の APNS (Apple Push Notification Service) と混同しそうになる略称ですが、もちろん別モノです。


IoTとかウェアラブル的な文脈では大抵のケースでiOSデバイスがセントラルになり、外部デバイスがペリフェラルとなりますが、ANCSはiOSデバイスがサービス提供側となるため、その立場が逆転し、iOSデバイスがペリフェラル、外部デバイスがセントラルとなります。


f:id:shu223:20141205065442p:image:w300




※本記事はBLEアドベントカレンダー5日目の記事です。



用語について

Appleが提供しているドキュメント「Apple Notification Center Service (ANCS) Specification」では、サービス提供側(つまりiOSデバイス)を NP (Notification Provider)、サービスを受ける側(外部デバイス)を NC (Notification Consumer) と呼んでいるので、本記事でも以降そのように表現します。


また、同ドキュメントでは、iOSにおけるPush NotificationやLocal Notificationといった通知と、GATTにおけるNotificationを呼び分けるため、"iOS Notification", "GATT Notification" という表現が使用されています。本記事内でも、これらの区別のため「iOS通知」「GATT通知」という表現を使用します。


ANCS の GATT

ANCSサービスのUUIDは次のように定義されています。

7905F431-B5CE-4E99-A40F-4B1E122D00D0

そして、ANCSサービスは、以下のキャラクタリスティックを持っています。

Characteristic UUID Properties
Notification Source 9FBF120D-6301-42D9-8C58-25E699A21DBD notifiable
Control Point 69D1D8F3-45E1-49A8-9821-9BBDFDAAD9D9 writeable with response
Data Source 22EAC6E9-24D6-4BB5-BE44-B36ACE7C7BFB notifiable


Notification Source は、

  • NPでのiOS通知の到着
  • NPでのiOS通知の変更
  • NPでのiOS通知の削除

をNCに知らせるためのキャラクタリスティックです。

また Control Point は NC が NP に iOS通知のより詳細な情報を要求するための Write キャラクタリスティックで、Control Point への書き込みが成功すると、NP は Data Source キャラクタリスティックでのGATT通知によりリクエストに応答します。


これらのサービス/キャラクタリスティックでは、別のiOSデバイスからセントラルとして接続しても見つからないようになっています。ANCSが提供されるようになったiOS7より前のiOSデバイス、あるいはそれ以外のMac等のデバイスからは見つけることができます。



実装サンプル

以下、Notification Source キャラクタリスティックを介して、iOS通知の到着や変更をNC側で受け取る実装方法について説明します。


NPの実装

NC側からペリフェラル名で発見できるように、`CBAdvertisementDataLocalNameKey` に何らかのデバイス名を指定してアドバタイズ開始しておきます。(一般的なペリフェラルマネージャの実装なのでコードは省略)


NCの実装

上述した通りiOSデバイスはNCになれないため、ここでは Mac OS X のアプリとして実装します。

なお、スキャン、接続、サービス/キャラクタリスティックの探索まではセントラルとしては通例通りの処理なので説明を省略します。


Notification Source をサブスクライブする

Notification Source キャラクタリスティックの値の更新通知を有効にします。

- (void)                      peripheral:(CBPeripheral *)peripheral
    didDiscoverCharacteristicsForService:(CBService *)service
                                   error:(NSError *)error
{
    CBUUID *notificationSourceUuid = [CBUUID UUIDWithString:@"9FBF120D-6301-42D9-8C58-25E699A21DBD"];
    
    for (CBCharacteristic *aCharacteristic in service.characteristics) {
        
        if ([aCharacteristic.UUID isEqualTo:notificationSourceUuid]) {

            self.notificationSourceCharacteristic = aCharacteristic;
            
            // Notification Source の subscribeを開始する
            [peripheral setNotifyValue:YES
                     forCharacteristic:aCharacteristic];
        }
    }
}

Notification Source の値を読む

Notification Source キャラクタリスティックの値は、次のような8バイトで構成されています。


f:id:shu223:20141205065849p:image:w588


本サンプルでは、どのようなiOS通知があったのかを最低限判別できるよう、「EventID」「CategoryID」の2つを読み取ることにします。


EventIDはiOS通知が「新規」「変更」「削除」のどれなのかを示す値で、次のように定義されています。

EventIDNotificationAdded 0
EventIDNotificationModified 1
EventIDNotificationRemoved 2
Reserved EventID values 3-255


CategoryIDはiOS通知の種類を示す値で、次のように定義されています。

CategoryIDOther 0
CategoryIDIncomingCall 1
CategoryIDMissedCall 2
CategoryIDVoicemail 3
CategoryIDSocial 4
CategoryIDSchedule 5
CategoryIDEmail 6
CategoryIDNews 7
CategoryIDHealthAndFitness 8
CategoryIDBusinessAndFinance 9
CategoryIDLocation 10
CategoryIDEntertainment 11
Reserved CategoryID values 12–255


Notification Source キャラクタリスティックの値に変更があると、すなわちNP側で電話着信などのiOS通知が新規で発生した/変更された、等のイベントが発生すると、サブスクライブしているNCに対してGATT通知が送られます。


このタイミングでキャラクタリスティックからEventIDとCategoryIDを読み取り、ログ出力するコードは次のようになります。

- (void)                 peripheral:(CBPeripheral *)peripheral
    didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic
                              error:(NSError *)error
{
    NSLog(@"%@", characteristic.value);

    // 8バイト取り出す
    unsigned char bytes[8];
    [characteristic.value getBytes:bytes length:8];    

    // Event ID
    unsigned char eventId = bytes[0];
    switch (eventId) {
        case 0:
            NSLog(@"Notification Added");
            break;
        case 1:
            NSLog(@"Notification Modified");
            break;
        case 2:
            NSLog(@"Notification Removed");
            break;
        default:
            // reserved
            break;
    }

    unsigned char categoryId = bytes[2];
    switch (categoryId) {
        case 0:
            // Other
            break;
        case 1:
            NSLog(@"Incoming Call");
            break;
        case 2:
            NSLog(@"Missed Call");
            break;
        case 3:
            NSLog(@"Voice Mail");
            break;
        case 4:
            NSLog(@"Social");
            break;
        case 5:
            NSLog(@"Schedule");
            break;
        case 6:
            NSLog(@"Email");
            break;
        case 7:
            NSLog(@"News");
            break;
        case 8:
            NSLog(@"Health and Fitness");
            break;
        case 9:
            NSLog(@"Business and Finance");
            break;
        case 10:
            NSLog(@"Location");
            break;
        case 11:
            NSLog(@"Entertainment");
            break;
        default:
            // Reserved
            break;
    }
}

実行結果

次のようにログ出力されました。(見やすいよう各通知のログを1行にまとめています)

<001d0201 00000000>, Notification Added, Missed Call
<001d0202 01000000>, Notification Added, Missed Call
<001d0203 02000000>, Notification Added, Missed Call
<00150601 03000000>, Notification Added, Email
<00150602 04000000>, Notification Added, Email
<00100603 05000000>, Notification Added, Email
<00100604 06000000>, Notification Added, Email
<00150605 07000000>, Notification Added, Email
<00150401 08000000>, Notification Added, Social
<00150b01 09000000>, Notification Added, Entertainment
<00150b02 0a000000>, Notification Added, Entertainment
<00150b03 0b000000>, Notification Added, Entertainment
<00150a01 0c000000>, Notification Added, Location
<00150801 0d000000>, Notification Added, Health and Fitness
<00150802 0e000000>, Notification Added, Health and Fitness
<00150901 0f000000>, Notification Added, Business and Finance
<00150001 10000000>, Notification Added
<00150002 11000000>, Notification Added
<00150003 12000000>, Notification Added
<00150402 13000000>, Notification Added, Social

電話着信やメール受信、SNSのiOS通知の発生や削除がNC側(ここではMacアプリ)で検知できていることがわかります。


iOSでの通知設定について

PebbleのSupport Centerのページによると、iOS側の通知をどこまで許可することでANCSによりNCに通知されるかがアプリによって違うようです。

The following apps need nothing more than "Allow Notifications" for their notifications to appear on Pebble.

  • iOS 7 email
  • SMS and iMessages
  • Calendar
  • CalenMob
  • BBM

At least one of the alert styles (banners, alerts, badge app icon, or alert sound) must be enabled for the following apps, in addition to having "Allow Notifications" on.

  • Gmail
  • Hangouts
  • WhatsApp
  • Facebook
  • Facebook Messenger
  • Snapchat

The following apps require that the banners alert style specifically be chosen in order to deliver notifications to Pebble:

  • Twitter

# 同ページでは "Here is what we have discovered so far:", "If you are aware of any other apps which require a special set up, please contact our Support Team" 等と表現されていることから、どういうルールでこれらが決まるのかははっきりしていないようです。


参考記事


2014-12-02

Core Bluetooth トラブルシューティング

iOSでBLEを利用するアプリを開発していると、「スキャンで見つからない」「つながらない」といった場面はよく出てきますし、相手が新規開発デバイスだとそっちを疑いたくなることもあるわけですが、けっこうiOS側での「あるある」な実装ミスや勘違いというのが多くあります。


そんなよくあるトラブル、その解決のためのチェックポイント等をまとめました。


(本題に入る前に・・・)

このトラブルシューティング集は、下記書籍の執筆にあたりまとめていたものです。最終的に書籍内では ここに書いてある分量の2倍ぐらい*1のトラブル&対策について書いてあります。

iOS×BLE Core Bluetoothプログラミング
堤 修一 松村 礼央
ソシム
売り上げランキング: 1,106


iOS x BLE というニッチな内容で480ページ!下記に紹介記事があります。どうぞよろしくお願いします!


トラブル1: スキャンに失敗する

→ スキャンの直前に CBCentralManager を初期化していないか?

たとえば「1回目のスキャンに失敗するけど、2回目ではたいていうまく繋がる」という場合には、CBCentralManager の初期化タイミングが遅く、スキャンを開始するタイミングでまだ `CBCentralManagerStatePoweredOn` になってない、という可能性があります。


この実装ミスは、再試行ではうまくいくだけに、「何かハードか電波の調子悪いのかなー」ぐらいに思い過ごしがちなので要注意です。1発でバシッと繋がるようになるのはユーザー体験の改善効果としても大きいと思うし、修正も簡単なので、ぜひ今一度ご確認を。


→ サービスを指定している場合、そのサービスをペリフェラル側でアドバタイズしているか?

セントラル側で `scanForPeripheralsWithServices:options:` の第1引数にサービスのリストを渡している場合に、ペリフェラル側でそのサービスを提供はしていてもアドバタイズメントデータに入れていない と、スキャン時に発見できないことになります。


たとえばを `scanForPeripheralsWithServices:options:` の第1引数に `nil` を渡してみるとそのペリフェラルが見つかるようになる、といった場合はこのケースに当てはまっている可能性があります。


トラブル2: 接続に失敗する

→ 発見したCBPeripheral の参照を保持しているか?

Core Bluetooth に慣れていないと忘れがちなのが、スキャンにより発見した CBPeripheral オブジェクトを、strong のプロパティなり配列に格納するなりして参照を保持しないと解放されてしまう可能性がある、という点です。


`centralManager:didDisconnectPeripheral:error:` の引数に入ってくる CBPeripheral オブジェクトは、必要であれば(接続したりするのであれば)きちんとその参照を保持する必要があります。


これを怠ると、`connectPeripheral:options:` をコールしても失敗するとか、デリゲートメソッドが呼ばれない、といった事態を引き起こします。


トラブル3: サービスまたはキャラクタリスティックが見つからない

→ UUIDが間違っていないか?

`discoverServices:` , `discoverCharacteristics:forService:` で検索対象のサービス/キャラクタリスティックの CBUUID オブジェクト(の配列)を渡しているのであれば、いったん確認のため `nil` を渡してみます。


これで見つかるようなら、渡してるUUIDが間違っている、と考えられます。


→ ペリフェラル側でGATTを変更したのではないか?

iOSアプリと連携する新規ハードウェア開発をしているとものすごくあるあるで、ハマる人が多いのですが、開発途中でペリフェラル側(外部デバイス)で GATT の内容を変更すると、iPhone の Settings から Bluetooth を Off/On しないと変更が反映されない というのがあります。


このことを知らないと、たとえばキャラクタリスティックの `value` が取れず、

  • BLE の接続状態を疑う
  • Central / Peripheral 間での UUID の食い違いを疑う
  • etc...

と、無駄なデバッグ作業をしてしまいかねません。


で、とりいそぎ上にも書いた通り iPhone の off/on で GATT の変更を反映することはできるのですが、正しい解決方法をAppleの中の人に聞いてきたので、下記記事を併せてご参照ください。


トラブル4: Writeで失敗する

→ CBCharacteristicWriteType を間違って指定していないか?

`writeValue:forCharacteristic:type:` の第3引数に指定する CBCharacteristicWriteType (レスポンスありの場合は `CBCharacteristicWriteWithResponse`、なしの場合は `CBCharacteristicWriteWithoutResponse`) は、GATTの当該キャラクタリスティックのプロパティ設定と合ってないとエラーになります。


アプリ側では「確認のためレスポンス欲しいなー」ってな動機で WriteWithResponse を指定したくなる場合もあるかもしれませんが、GATT側では WriteWithoutResponse となっていると失敗します。


トラブル5: バックグラウンドでのスキャンが動作しない

→ サービスを指定しているか?

バックグラウンドでのスキャン時は、`scanForPeripheralsWithServices:options:` の第1引数にスキャン対象とするサービスを1つ以上渡す必要があります。

> they must explicitly scan for one or more services by specifying them in the serviceUUIDs parameter.


→ もう少し待ってみる

フォアグラウンドのときと比べ、バックグラウンドでのスキャンは間隔が長くなります。


Appleのドキュメントでは具体的な数値を発見できなかったのですが、わふうさんの記事によると12分に1回とか。


おわりに

以上、日々の開発の中で書きためていたトラブルシューティングでした。他にもあれば追記していきます。


*1:※ざっくり感覚値です

2009 | 08 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2012 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2013 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2014 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2015 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2016 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 11 | 12 |
2017 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2018 | 02 |