Hatena::ブログ(Diary)

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

2016-07-20

TensorFlowをiOSで動かしてみる

TensorFlow に iOS サポートが追加された というニュースを見かけたので、ビルドして、iOSで動作させてみました。


f:id:shu223:20160720074930j:image:w240

たまたま目の前にあった扇風機もバッチリ認識してくれました)


本記事では最終的にうまくいった手順を書いています。この手順をなぞってみるにあたってTensorFlowや機械学習・ディープラーニングについての専門知識は不要ですのでぜひお試しください!


ビルド手順

iOSサポートバージョンであるv0.9.0をチェックアウトして、以下の手順でビルドしていきます。手順はこの通りになぞるだけですし、ほぼスクリプトを実行するだけで非常に簡単です(が、実行時間がかなりかかります)。


  • スクリプトを実行して Eigen や Protobuf 等の依存ライブラリをダウンロード
cd tensorflow
tensorflow/contrib/makefile/download_dependencies.sh

  • protobuf をビルド&インストール
cd tensorflow/contrib/makefile/downloads/protobuf/
./autogen.sh
./configure
make
sudo make install

ここで、僕の環境では `./autogen.sh` 実行時に

Can't exec "aclocal": No such file or directory at /usr/local/Cellar/autoconf/2.69/share/autoconf/Autom4te/FileUtils.pm line 326.
autoreconf: failed to run aclocal: No such file or directory
shuichi-MacBook-Pro-Retina-15inch:protobuf shuichi$ ./configure
-bash: ./configure: No such file or directory

というエラーが出ました。”aclocal”というのは、”automake”パッケージに含まれているらしいので、automakeをインストール。

brew instal automake

で、改めて

./autogen.sh
./configure
make
sudo make install

  • iOS native版のprotobufをビルド
cd ../../../../..
tensorflow/contrib/makefile/compile_ios_protobuf.sh

  • iOSアーキテクチャ向けにTensorFlowをビルド
tensorflow/contrib/makefile/compile_ios_tensorflow.sh

サンプルをビルドする

`tensorflow/tensorflow/contrib/ios_example` に2種類のサンプルがあります。


simple と名付けられたサンプルアプリは、今回ビルドした static library をアプリ内に組み込む際の最小実装としての参考にはなりそうですが、デモとして見て楽しい機能があるわけではないので、本記事ではもう一方の camera の方を紹介します。


まず、このファイルをダウンロードして、解凍すると入っている、

  • imagenet_comp_graph_label_strings.txt
  • tensorflow_inception_graph.pb

の2つのファイルを、`tensorflow/contrib/ios_examples/camera/data` に置きます。*1


この Inception というのはGoogleが提供する画像認識用の学習済みモデルのようです。


cameraサンプル実行例

cameraサンプルを実行すると、すぐにカメラが起動します。身近のものを色々と映してみました。(リアルタイムカメラ入力に対する認識結果を表示してくれます)


MacBook

f:id:shu223:20160720080028j:image:w240


"Notebook" "Laptop" どっちでも正解!


扇風機

f:id:shu223:20160720074930j:image:w240


"Electric Fan" 正解!


椅子

f:id:shu223:20160720080106j:image:w240


"Folding Chair"(折りたたみ椅子) 惜しい。。ですが、そもそも `imagenet_comp_graph_label_strings.txt` には chair が3種類しかなくて、その中では一番近いと言ってもいいかもしれません。


iPhone

f:id:shu223:20160720080129j:image:w240


"iPod"・・・惜しい!iPhoneなのでほぼ正解と言ってもいいかもしれません。


パフォーマンス

今回cameraサンプルを試したデバイスはiPhone 6だったのですが、認識はまだまだリアルタイムとは言い難く、数秒単位の遅延がある感じでした。まだiOS向けにビルドできるようになったというだけでiOSにおける GPU Accelerated はされてないですし、パフォーマンスの最適化はまだまだこれからのようです。


ちなみに iOS 10 で Accelerate.framework に追加された BNNS (Basic neural network subroutines) を使用した最適化もissueに挙がっています。


ビルド手順の情報ソース

iOSサポートバージョンである0.9.0トップ階層にあるREADMEを見ても、iOSについては書かれていません


上述したビルド手順は`contrib/makefile` 配下のREADMEに書かれています。

関連情報を以下にまとめておきます。


サンプルのソースコードを読んでみる

サンプルは全編Objective-C++で書かれています。読んでもわからないだろうと思いつつ、せっかくなので読んでみます。


tensorflow_utils

モデルとラベルデータを読み込むメソッドと、認識結果を返すメソッドを持つユーティリティクラスのようです。

tensorflow::Status LoadModel(NSString* file_name, NSString* file_type,
                             std::unique_ptr<tensorflow::Session>* session);
tensorflow::Status LoadLabels(NSString* file_name, NSString* file_type,
                              std::vector<std::string>* label_strings);
void GetTopN(const Eigen::TensorMap<Eigen::Tensor<float, 1, Eigen::RowMajor>,
             Eigen::Aligned>& prediction, const int num_results,
             const float threshold,
             std::vector<std::pair<float, int> >* top_results);

汎用的に使えそう。


ios_image_load

画像ファイルを読み込むクラス。

std::vector<tensorflow::uint8> LoadImageFromFile(const char* file_name,
             int* out_width,
             int* out_height,
             int* out_channels);

プロジェクトには追加されているけど使われてないっぽい。


CameraExampleViewController

サンプル本体。肝っぽいところだけを拾っていくと、


`std::unique_ptr<tensorflow::Session>` なメンバ変数を定義して、

std::unique_ptr<tensorflow::Session> tf_session;

viewDidLoadのタイミングでモデルを〜.pbファイルからロード

tensorflow::Status load_status =
  LoadModel(@"tensorflow_inception_graph", @"pb", &tf_session);

で、カメラからのリアルタイム入力が得られるたびに呼ばれるデリゲートメソッドを見てみると、

- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
       fromConnection:(AVCaptureConnection *)connection {
  CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
  [self runCNNOnFrame:pixelBuffer];
}

CMSampleBufferRef から CVPixelBufferRef を取得して `runCNNOnFrame:` を呼んでいるだけのようです。


`runCNNOnFrame:` はコードが若干長いですが、このへんと、

tensorflow::Tensor image_tensor(
    tensorflow::DT_FLOAT,
    tensorflow::TensorShape(
        {1, wanted_height, wanted_width, wanted_channels}));
auto image_tensor_mapped = image_tensor.tensor<float, 4>();
tensorflow::uint8 *in = sourceStartAddr;
float *out = image_tensor_mapped.data();

このへんが肝っぽいです。

if (tf_session.get()) {
  std::string input_layer = "input";
  std::string output_layer = "output";
  std::vector<tensorflow::Tensor> outputs;
  tensorflow::Status run_status = tf_session->Run(
      {{input_layer, image_tensor}}, {output_layer}, {}, &outputs);
  if (!run_status.ok()) {
    LOG(ERROR) << "Running model failed:" << run_status;
  } else {
    tensorflow::Tensor *output = &outputs[0];
    auto predictions = output->flat<float>();

    // ラベル取得
  }
}

が、すみません、それぞれ何をやっているのかを解説するにはTensorFlowの処理をちゃんと理解する必要がありそうです。。(勉強します)


自分のアプリに組み込む

試せていませんが、こちら に手順が示されていました。

  • ユニバーサルなstatic library(libtensorflow-core.a)をビルドしてプロジェクトに追加する
    • [Library Search Paths] にも追加。

The `compile_ios_tensorflow.sh' script builds a universal static library in tensorflow/contrib/makefile/gen/lib/libtensorflow-core.a. You'll need to add this to your linking build stage, and in Search Paths add tensorflow/contrib/makefile/gen/lib to the Library Search Paths setting.

  • libprotobuf.a と libprotobuf-lite.a をプロジェクトに追加する。
    • [Library Search Paths]にも追加

You'll also need to add libprotobuf.a and libprotobuf-lite.a from tensorflow/contrib/makefile/gen/protobuf_ios/lib to your Build Stages and Library Search Paths.

  • [Header Search paths] を追加

The Header Search paths needs to contain the root folder of tensorflow, tensorflow/contrib/makefile/downloads/protobuf/src, tensorflow/contrib/makefile/downloads, tensorflow/contrib/makefile/downloads/eigen-eigen-, and tensorflow/contrib/makefile/gen/proto.

  • [Other Linker Flags] に `-force_load` を追加

In the Linking section, you need to add -force_load followed by the path to the TensorFlow static library in the Other Linker Flags section. This ensures that the global C++ objects that are used to register important classes inside the library are not stripped out. To the linker, they can appear unused because no other code references the variables, but in fact their constructors have the important side effect of registering the class.

  • bitcodeを無効にする

The library doesn't currently support bitcode, so you'll need to disable that in your project settings.


まとめ

TensorFlowをiOS向けにビルドし、付属のサンプルを動かしてみました。パフォーマンスの最適化等、まだまだこれから感はありますが、扇風機や椅子、ノートパソコンやiPhone等、大きさも形も全然違うものを認識してくれて未来を感じます。


試してみるだけなら機械学習やTensorFlowについての専門知識を必要としませんし、ぜひお手元で動かしてみてください!


次のステップとしては、下記記事で紹介されているテンソルの計算部分に特化してSwiftで書かれたライブラリと比較したりしつつ、ちゃんとTensorFlowでやっていることを理解したいと思います。


また、本記事同様に「とりあえず動かしてみる」シリーズとして、こちらもよろしければどうぞ:


*1:ちなみにこのとき、 https://github.com/miyosuda/TensorFlowAndroidDemo/find/master にある同名ファイルを使っても、ちゃんと動きません。参考

2016-07-17

【書評】初学者はもちろん、中級者にもオススメのAuto Layout解説書

著者の川邉さん(@jeffsuke)および出版社のリックテレコム様より『よくわかるAuto Layout - iOSレスポンシブデザインをマスター』をご献本いただきました。


f:id:shu223:20160717140912j:image:h600


タイトルの通りAuto Layoutの解説書で、豊富な図やスクリーンショットを用いて、非常にわかりやすく書かれています。前書きによると、本書の対象読者は

過去一度はXcodeを用いてiOSアプリをつくったことがあるが、Auto Layoutとサイズクラスを用いたAdaptive Layoutと言われると尻込みしてしまう開発者

とのこと。


なんかもうベストセラーになったりしてるらしく、


AutoLayoutの必要性は今更僕が説くことでもないですし、監修は名著『UIKit詳解リファレンス』の著者 所さん


ということで、これはもう

  • iOSアプリ開発に携わっている(携わろうとしている)
  • AutoLayoutがよくわかってない

という方はもう買うしかないでしょう。買いましょう!



よくわかるAuto Layout  iOSレスポンシブデザインをマスター
川邉 雄介
リックテレコム
売り上げランキング: 14,747

(Kindle版もありますが、固定レイアウトのようです。)



・・・と、Auto Layoutに自信のない初学者の方には当然のようにオススメなのですが、AutoLayoutがそれなりにわかっている(と自分では思っていた)僕が読んでも、勉強になったポイントがいくつもありました。以下で紹介していきます。


Content Hugging Priority と Content Compression Resistance Priority と Instrinsic Content Size

250とか750とかのデフォルト値が振られてるアレです。


f:id:shu223:20160718095410j:image:w250


Auto Layoutはわかっているつもりでも、このへんの理解が曖昧なままwarningが出たときになんとなく優先度をいじってみたりしている、みたいな人は実は多いのではないでしょうか。私がまさにそうでしたが、本書では詳細かつ明解に解説されていて、曖昧に理解していたところがスッキリしました。以下メモ。

  • それぞれ Instrinsic Content Size についての「大きくなりにくさ」「小さくなりにくさ」を意味している*1
  • NSLayoutConstraintの優先度の値が同値の場合は、NSLayoutConstraintによる制約が優先される

レイアウトのライフサイクル(p35〜)

1. 制約の更新、2. フレームの更新、3. レンダリングというiOSにおけるレイアウトの3つのステップについて詳細に解説されています。


恥ずかしながら `updateConstraintsIfNeeded`, `layoutIfNeeded`, `layoutSubviews`, `setNeedsDsplay` といったメソッドでそれぞれ何が行われるのか、または何が行われないのか、といったことの理解が曖昧だったのですが、順序立ててわかりやすく解説されていて非常に勉強になりました。

  1. 制約の更新
    • `updateConstraints` が呼ばれる
    • ボトムアップ(子ビューから親ビュー)に制約の計算が実行される
    • 制約の有効/無効化、優先度変更、追加/削除などをトリガに実施される
    • この制約の更新を明示的に実行したい場合には `updateConstraintsIfNeeded` もしくは `setNeedsUpdateConstraints` を呼ぶ
  2. フレームの更新
    • `layoutSubviews` が呼ばれる
    • `layoutSubviews` が呼ばれると `updateConstraintsIfNeeded` も呼ばれ、必要に応じて制約の更新も行われる
    • トップダウン(親ビューから子ビュー)に実施される
    • ビューのフレームの変更・サブビューの追加・削除等をトリガに実施される
    • 明示的に実行したい場合には `layoutIfNeeded` もしくは `setNeedsLayout` を呼ぶ
    • `layoutSubview` をオーバーライドすると、`super.layoutSubviews()` の時点で既に制約の更新が完了しているので、そのレイアウト情報を用いて何らかの処理を実行したい場合に有効(+この方法で無限スクロールを実現する例)
  3. レンダリング
    • `drawRect:` が呼ばれる
    • 明示的に実行したい場合には `setNeedsDisplay` または `setNeedsDisplayInRect:` を呼ぶ

また `viewWillLayoutSubviews` と `viewDidLayoutSubviews` についても、それぞれの段階では(レイアウトサイクルの)どのステップは完了していて何は完了していないのか、というところの理解がスッキリしました。

  • viewWillLayoutSubviews
    • この時点では、サブリューのレイアウトは決定されていないが、画面の向きは決定している
    • この後、「制約の更新」と「フレームの更新」が実施される
  • viewDidLayoutSubviews
    • layoutSubviews による「フレームの更新」が完了すると呼ばれる
    • この後にレンダリングが行われるので、viewDidAppearはこれより後に呼ばれる

UIWindowの話

直接Auto Layoutとは関係ないですが、iOSでUIを実装するにあたってUIWindowに関して知っておいた方がいいことが簡潔に解説されています。`windowLevel` による重なり順の話とか、画面サイズを取得する場合に

UIScreen.mainScreen().bounds

UIScreen.sharedAppliation().keyWindow?.bounds

はどう違うか、とか。


UIStackView

iOS 9で追加された UIStackView についても、10ページを使ってしっかり解説されています。


ちなみに

Start with Stack View, use constraints as needed

まずはStack Viewを使って、必要に応じてconstraintsを使おう


WWDCのセッションでも言われていたとか。


コードで制約を設定する & NSLayoutAnchor

僕は基本的にはIB/Storyboardで制約を貼るのですが、仕事柄多くのプロジェクトに関わるので、「IB/Storyboard使わない派」な方々のコードに触れる機会も度々ありまして、コードで制約を設定することもたまにあります。


本書では、NSLayoutConstraint のイニシャライザを使う方法、VFL (Visual Format Language)を使う方法、iOS 9 で追加されたNSLayoutAnchorを使う方法等、まるまる一章を使ってその方法が解説されています。


ちなみにそろそろiOS 9の機能を使ってもいい(iOS 8を切ってもいい)空気になってきたと個人的には感じていますが、NSLayoutAnchor を使うと超シンプルかつ直感的にレイアウト制約をコードから設定できるようになります。


(before)

NSLayoutConstraint(item: subview,
                   attribute: .Leading,
                   relatedBy: .Equal,
                   toItem: view,
                   attribute: .LeadingMargin,
                   multiplier: 1.0,
                   constant: 0.0).active = true

(after)

let margins = view.layoutMarginsGuide
subview.leadingAnchor.constraintEqualToAnchor(margins.leadingAnchor).active = true

本章末尾のコラムではAuto Layoutを簡潔に書くためのオープンソースライブラリもいくつか紹介されています。


その他、細かい話

Auto Layout のアルゴリズム

この線型方程式系を解くアルゴリズムは、Greg J. Badros と Alan Borning、Peter J. Stuckey によって2001年に発表された、制約充足問題を説く Cassowary というツールキットで用いられたアルゴリズムを使用しています。(p.15)

明日から開発に活かせる、という話ではないのですが、こんなこともサラッと書かれていて、しっかり調べて執筆されている書籍なんだなということが伺えます。


外接矩形(alignment rects)をデバッグ時に確認する

[Edit Scheme] > [Run] > [Arguments] の [Arguments Passed On Launch] に `-UIViewShowAlignmentRects` を追加してチェックを入れると、実行時に外接矩形が表示される、というTips。


NSLayoutAttributeのLeft/RightとLeading/Trailingの違い

基本的にLeading/Trailingを使ってたので意識したことなかったですが、アラビア語とかヘブライ語のように右から左へ書く言語ではLeading/TrailingがそれぞれRight/Leftと同値となるそうです。


制約の矛盾が実行時に起きたときのエラーメッセージの見方

実行時にぶわーっと出てくるエラーメッセージ、ありますよね。僕は最近だとAutoLayoutを使ったアニメーションをやろうとして何かが間違っててこれが出てきました。で、メッセージ読んでもどの制約が悪いのかわかりづらい、と思ってたんですが、制約にちゃんとidentifierふればいいみたいです。(p86)


Xcode 7 の Storyboard Reference

Xcode 7 で追加された [Refactor to Storyboard...] メニューからの Storyboard の分割方法もしっかり書かれています。


Auto LayoutのデバッグTips

UIView に `exerciseAmbiguityInLayout()` なるメソッドがあり、これを用いると曖昧な制約を持つビューオブジェクトのフレームを、制約を満たす範囲でランダムに変更してくれるそうです。


他にもオブジェクトが持つインスタンス変数名と値を吐いてくれる `_ivarDescription` メソッド*2とか、ビューデバッガーの [Show Constraints] 機能とか、知らなかったことが色々と書いてありました。


まとめ

書籍『よくわかるAuto Layout』を紹介させていただきました。Auto Layoutがよくわかってない人を対象として書かれた書籍ですが、中級者にも勉強になる部分は多いと感じました。


本をまるっと一冊通読してAuto Layoutを勉強するというのはなかなか難しい、と感じる方もいらっしゃるかもしれません。が、目次を見れば分かる通り、Auto Layoutについてかなり網羅的かつ本質的に解説されてますので、リファレンス的に手元に置いておくのもいいのでは、と思います。


気になった方はぜひ!


よくわかるAuto Layout  iOSレスポンシブデザインをマスター
川邉 雄介
リックテレコム
売り上げランキング: 14,747


*1:この訳はAppleのドキュメントにあったのか著者のオリジナルかわかりませんが、簡潔で秀逸だなと思いました

*2:※Privateです

2016-07-16

【書評】モバイルエンジニア向けのAWS解説書

著者・監修の佐々木 拓郎さん、 高柳さん、および SB Creative さまより『Amazon Web Services クラウドネイティブ・アプリケーション開発技法 一番大切な知識と技術が身につく』をご献本いただきました。


f:id:shu223:20160506083601j:image:w600


昨今のモバイルアプリケーションには大抵の場合バックエンドも必要になりますが、たとえば僕はiOSアプリしかつくれません。そういう場合に、他のサーバーサイドエンジニアやインフラエンジニアと組む以外に、ちょっと前であれば Parse.com を利用してサクッと済ませてしまう、という選択肢もありましたが、来年にはサービス終了してしまうという悲しい事態になってしまった今、やはりモバイルアプリケーションのバックエンドのインフラとして真っ先に候補に挙がるのは AWS(Amazon Web Services)です。


ただそのAWS、LambdaとかMobile HubとかIoTとか色々ありすぎて、手を付け始める前からハードルを感じてしまう部分があることも否めません。例えば外部デバイスからアプリで吸い上げたデータを置いておきたい場合にはどれがいいんだろう、とか。お手軽さとか値段とかそのデータをどう利用したいか等にもよるのだと思いますが、どう検討・比較したらいいのか素人目には難しく思えてしまいます。


前置きが長くなりましたが、そんなAWSの各種サービスについてモバイルエンジニアを対象読者として(※)解説されている本がこちらです。


(Kindle版もありますが、固定レイアウトのようです。)


600ページ強の大作です。以下、個人的にいいなと思ったポイントを紹介していきます。


※ちなみに実際には『対象とする読者は、モバイルやJavaScript等でフロントエンドアプリを開発するアプリケーションエンジニア』とあるのでモバイルエンジニアだけではないのですが、僕がモバイルエンジニアなのでその視点で書かせていただきます。


AWS各サービスの詳細かつ丁寧な解説

Chapter 3 には、各サービスの特徴や使いどころ、機能・導入方法・料金・制限事項など、かゆいところに手が届く情報がまとめられています。

  • 3-1 Amazon S3
  • 3-2 Amazon API Gateway
  • 3-3 Amazon Simple Notification Service
  • 3-4 Amazon DynamoDB
  • 3-5 AWS Lambda
  • 3-6 Amazon Cognito
  • 3-7 Amazon Machine Learning
  • 3-8 Amazon Kinesis
  • 3-9 Amazon Simple Queue Service
  • 3-10 AWS IoT
  • 3-11 AWS Mobile Hub

全項目に利用方法と基本的な使い方が書かれていて、それぞれ10〜20ページにわたって豊富なスクリーンショット付きで丁寧に解説されているので、迷うことなく試してみることができそうです。


f:id:shu223:20160716164206j:image:w600


すぐに使う予定があるわけではなくても、これを見ながら一通り触ってみるのも良さそうです。一度でも触っておけば、どんなことができるのかとか、どれぐらい簡単なのかとかの感触がつかめるので。(個人的には、Lambda、Machine Learning、IoT、Mobile Hub あたりを本書を見つつ触ってみたいと思っています)


興味深いサンプル群

Chapter4からは具体的なアプリケーションを実装してみるチュートリアルとなっており、モバイルエンジニアにとっても魅力的なサンプルが並んでいます。


以下にそのリストを列挙してみます。(個人的に特に気になったものを太字にしてみました)

  • 4-1 Cognitoによる認証を利用したスマートフォン向け写真共有アプリケーション(iOS/Android)
  • 4-2 API GatewayとLambdaによるサーバ連携するモバイルアプリケーション(Android)
  • 4-3 API GatewayとCognito、Lambdaを連携した認証・認可サービス
  • 4-4 API Gatewayを使ったモバイルのスタブAPI
  • 4-5 DynamoDBとApple Watchによる健康情報の収集(iOS/watchOS)
  • 4-6 iBeaconと連動する勤怠管理アプリケーション(iOS/Android)
  • 4-7 Device Farmを利用したモバイルの多端末自動テストの実施(Android)
  • 4-8 S3とLambdaによるキーワードキュレーションサービス
  • 4-9 KinesisによるTwitter情報の収集
  • 4-10 Machine Learningを用いたWeb閲覧履歴保存&キュレーションサービス
  • 4-11 Cognito Syncを使った簡易メモアプリケーション(iOS/Android)

カッコ()内はiOSもしくはAndroidのどちらのサンプルが付いているかを示しています


いかがでしょうか。太字をつけたものの、私はいずれのサンプルも実際に仕事で必要になりそうなシーンが浮かんで、自分で手を動かして試してみたい、と思いました。


ちなみにiOSのサンプルはSwiftで書かれています。


まとめ

アプリケーションエンジニア向けに書かれたAWS解説本、『Amazon Web Services クラウドネイティブ・アプリケーション開発技法』を紹介しました。普段あまりサーバーサイドの開発に携わることのない私のようなクライアントサイドのエンジニアにもわかりやすく、興味深い技術書だと思います。バックエンドやAWSまわりに興味がある・知識をつけたい方はぜひ一度書店などで検討されてはいかがでしょうか。(僕も勉強します!)



2016-07-11

「プログラマとして食べていく」という話を福井県の学生さん達にしてきました

一昨日、福井県の「ふくい産業支援センター」さんが主催されたセミナーで、標題の講演をさせていただきました。資料はこちら。



参加者約70名のうち、75%は18歳以上の大学生・専門学校生、15%が高校生・高専生、10%が小中学校。これまでエンジニアの中で話をする機会は多々ありましたが、学生さんばかりの中で話すのは初めてでした。


内容

内容としては、「プログラミングでこんな感じでメシを食ってる人がいる」という一つの参考例として自分の働き方を紹介しつつ、プログラマとしてとりあえずやっていけるようになるまでの話と、フリーになってからおもしろい仕事を得るためにどんなことを考えながら働いているか、の3部構成でした。


50分と長尺の講演だったので、最後にFAQをくっつけて時間調整できるようにしておいたのですが、6つぐらい用意しておいたうち2つぐらいしかしゃべれず。話したうちのひとつは「お金の話」だったのですがネットに上げるとちょっと生々しいかと思いアップした資料からはカットしました。


「いっぱしのプログラマになるまで」編はこちらのスライドを使い、


「フリーランスになってから」編はこちらのスライドを使いました。


この話はこちらの記事にQ&A含め書いています。


Q&A

  • Q: 東京とくらべて福井でプログラマとして仕事をすることについてはどう思うか?
  • A: 仕事はやっぱり東京が圧倒的に多い。が、福井からであっても東京の仕事を獲得することはできると思う。(+僕ならこうする、という話)
  • Q: これをつくったときに実力が一気にあがった、というものはありますか?
  • A: 近道になったものもあまり意味がなかったものもなくて、最初は何をやっても実力につながったと思う。とにかくたくさんアプリをつくった。

温泉

主催者の方のオススメで、奥さんとの週末旅行も兼ねて芦原(あわら)温泉に泊まりました。


f:id:shu223:20160708184343j:image:w600



芦原温泉からはあの東尋坊まで車で15分・・・と旅館の人は言ってたけど本数の少ない電車とバスを乗り継いでいくとなんだかんだで1時間以上かかりました。


f:id:shu223:20160710111735j:image:w600

(東尋坊の岩牡蠣。めっちゃでかい)


福井県は恐竜の化石でも有名。恐竜博物館や発掘体験にも行きたかったのですが、福井駅からわりと離れてるので今回は断念。


おわりに

「フリーランスプログラマ」とひとくちに言っても働き方は本当に千差万別だし、働き方の好みも人それぞれまったく違うので、「こういう働き方をしている人もいるんだな」と若い人たちの今後の選択肢のひとつとして頭の片隅にでも留めておいてもらえれば、と思いつつこういう話をさせていただきました。


果たして片隅にでも残るような話ができたかどうかはわかりませんが、僕自身はこういう東京以外でプログラミングを学んでいる若い人たちや先生方に接することができて非常に新鮮な経験をさせていただきました。


主催されたふくい産業支援センターの大西さん・河野さん、紹介いただいた伴野さん、プログラミングコンテスト主催の皆様、参加された皆様、貴重な機会をいただきどうもありがとうございました!


(追記)ふくい産業センターさんのサイトでレポート記事が挙がっていました!


2016-06-15

【iOS 10】Speechフレームワークで音声認識 - 対応言語リスト付き

iOS 10のドキュメントが公開された当日に書いた下記記事で、最も反響が大きかったのが音声認識APIでした。


今回公開された SiriKit(Intents / IntentsUI)とは別のフレームワーク、Speech Framework として公開されたものです。リアルタイム音声にも、録音済み音声にも使えるようです。


f:id:shu223:20160616104103j:image


今までも色々と音声認識を実現する手段はありましたが、やはりApple純正となると一気に本命になってきます。*1


というわけで本記事では Speech フレームワークを色々いじってみて、何ができるのかとか、どうやるのかとか見てみたいと思います。


なお、NDA期間中につき、スクショは自粛します。


まずはサンプルを動かしてみる

"SpeakToMe: Using Speech Recognition with AVAudioEngine" というサンプルコードが公開されているのでまずは試してみます。


アプリが起動してすぐ、Speech Recognition の利用許可を求めるダイアログ が出てきます。そんなパーミッションも追加されたんだ、と思って [Settings] > [Privacy] を見てみると、確かに [Speech Recognition] の欄が追加されています。


録音を開始するボタンを押すだけで認識スタンバイOKです。試しに Hello と話しかけてみると・・・"How do" という結果が・・・気を取り直してもう一度 Hello と言ってみると・・・"I don't"・・・


これは自分の英語力の問題なので、諦めてターミナルから `say` コマンドで Hello と言わせると "Hello" と正しく認識してくれました。同時に過去の "I don't" とかも "Hello" に修正されたので、その後の入力からの認識結果によって過去の認識結果も修正するっぽいです。


サンプルの実装を見てみる

SFSpeechRecognizerの準備

初期化して、

private let speechRecognizer = SFSpeechRecognizer(locale: Locale(localeIdentifier: "en-US"))!

デリゲートをセット

speechRecognizer.delegate = self

音声認識の利用許可を求める
SFSpeechRecognizer.requestAuthorization { authStatus in
    OperationQueue.main().addOperation {
        switch authStatus {
            case .authorized:
                // 許可された

            case .denied:
                // 拒否された

            case .restricted:
                self.recordButton.isEnabled = false
                self.recordButton.setTitle("Speech recognition restricted on this device", for: .disabled)

            case .notDetermined:
                self.recordButton.isEnabled = false
                self.recordButton.setTitle("Speech recognition not yet authorized", for: .disabled)
        }
    }
}

コールバックはメインスレッドで呼ばれているとは限らないので `OperationQueue.main().addOperation` で実行しているようです。


認識リクエストの準備

プロパティを定義しておいて、

private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?    

認識開始前に初期化。

recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
guard let recognitionRequest = recognitionRequest else { fatalError("Unable to created a SFSpeechAudioBufferRecognitionRequest object") }

このプロパティをセットすると、録音が終わる前の "partial (non-final)" な結果を報告してくれる、とのことです。

recognitionRequest.shouldReportPartialResults = true

つまり、確定前の結果を取得できるようで、先ほどの、過去に戻って認識結果が修正される挙動はこのプロパティによるものではないかと思われます。リアルタイム認識時には有効にしておきたい重要プロパティですが、デフォルトでは `false` とのことです。


認識タスクの登録

SFSpeechRecognizer の `recognitionTask:with:resultHandler:` メソッドに SFSpeechRecognitionRequest オブジェクトと、結果を受け取ったときの処理を渡します。

recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest) { result, error in
    var isFinal = false
    
    if let result = result {
        self.textView.text = result.bestTranscription.formattedString
        isFinal = result.isFinal
    }
    
    if error != nil || isFinal {
        self.audioEngine.stop()
        inputNode.removeTap(onBus: 0)
        
        self.recognitionRequest = nil
        self.recognitionTask = nil
        
        self.recordButton.isEnabled = true
        self.recordButton.setTitle("Start Recording", for: [])
    }
}

戻り値として、 SFSpeechRecognitionTask オブジェクトが返ってきます。このサンプルでは、認識結果は SFSpeechRecognitionResult オブジェクトの `bestTranscription` プロパティより得ています。


録音開始(認識開始)

ここは AVAudioEngine の機能であって、iOS 8 からあるものなので、省略します。AVAudioEngine については下記記事にも書いたのでよろしければご参照ください。(残念ながら録音については書いてないのですが。。)


唯一 Speech フレームワークと直接関係するポイントは以下。

inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer: AVAudioPCMBuffer, when: AVAudioTime) in
    self.recognitionRequest?.append(buffer)
}

これで、マイクから得られる音声バッファが SFSpeechRecognitionRequest オブジェクトに渡されるようになり、録音開始と共に認識が開始されることになります。


対応言語は58言語!

基本的な使い方がわかったところで、以下ではサンプルに触れられてない部分も色々と見てみます。まずは、対応言語から。


SFSpeechRecognize は初期化時に以下のように Locale を指定できるようになっています。

private let speechRecognizer = SFSpeechRecognizer(locale: Locale(localeIdentifier: "en-US"))!

`supportedLocales()` というメソッドがあるので、全部出力してみます。

SFSpeechRecognizer.supportedLocales().enumerated().forEach {
    print($0.element.localeIdentifier)
}

出力結果:

nl-NL
es-MX
zh-TW
fr-FR
it-IT
vi-VN
en-ZA
ca-ES
es-CL
ko-KR
ro-RO
fr-CH
en-PH
en-CA
en-SG
en-IN
en-NZ
it-CH
fr-CA
da-DK
de-AT
pt-BR
yue-CN
zh-CN
sv-SE
es-ES
ar-SA
hu-HU
fr-BE
en-GB
ja-JP
zh-HK
fi-FI
tr-TR
nb-NO
en-ID
en-SA
pl-PL
id-ID
ms-MY
el-GR
cs-CZ
hr-HR
en-AE
he-IL
ru-RU
de-CH
en-AU
de-DE
nl-BE
th-TH
pt-PT
sk-SK
en-US
en-IE
es-CO
uk-UA
es-US

なんと、58言語もあります。もちろん日本語 "ja-JP"もサポート。


長文の認識精度

英語で長文を入れてみてもそもそも自分の発音が、というところがあるので、日本語で試してみます。

private let speechRecognizer = SFSpeechRecognizer(locale: Locale(localeIdentifier: "ja-JP"))!

上記の一文をしゃべって入力してみました。今現在フードコートのテーブルでこれを書いてるので、周りはそこそこ騒がしいです。一発勝負で結果は以下でした。


英語で長文を入れてみても様々自分の発音がどういうところがあるので日本語で試してみます


なんと、間違った箇所は「そもそも」 → 「様々」(さまさま?)だけ


なかなか優秀なんじゃないでしょうか。


パフォーマンス

ちゃんと測ってないですが、体感としてはiOSに従来からある音声入力と同じぐらいです。リアルタイムからほんのちょっと遅れて結果が確定していく感じです。


SFSpeechRecognitionResult

認識結果として SFSpeechRecognitionResult オブジェクトが得られるわけですが、公式サンプルで用いられている `bestTranscription` は最も信頼度(confidence)の高い認識結果で、その他の候補も `transcriptions` プロパティより取得することができます。

@property (nonatomic, readonly, copy) SFTranscription *bestTranscription;
@property (nonatomic, readonly, copy) NSArray<SFTranscription *> *transcriptions;

録音済みファイルの認識

SFSpeechAudioBufferRecognitionRequest の代わりに、SFSpeechURLRecognitionRequest クラスを利用します。どちらも SFSpeechRecognitionRequest のサブクラスです。


実装して試してはないのですが、バッファの代わりにファイルのURL(ローカル/オンライン)を指定するだけで、あとは上述の方法と同じかと思われます。

public init(url URL: URL)    
public var url: URL { get }

(追記)WWDCのセッションで言ってたこと

  • Siri および 音声入力機能と下回りは同じものを使用

Same speech technology as Siri and Dictation

  • 自動でユーザーに適応する
  • 基本的には要インターネット
    • 例外あり(some some languages and device models)

後で調べる

  • シミュレータでも実行できるのか?

*1:古い話かつ余談ですが、音声合成も以前色々と比較してみて結局 AVSpeechSynthesizer が良い、という結論に落ち着いたことがありました:http://qiita.com/shu223/items/223492e4f061032e652e:title:bookmark

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 | 12 |
2016 | 01 | 02 | 03 | 04 | 05 | 06 | 07 |