Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

LIFULL HOME'S「かざして検索」リリースの裏側

1,888 views

Published on

iOSDC 2018

Published in: Engineering
  • Be the first to comment

LIFULL HOME'S「かざして検索」リリースの裏側

  1. 1. LIFULL HOME’S かざして検索リリースの裏側 iOSDC JAPAN 2018 2018.09.01 LIFULL Co.,Ltd. Hanawa Takuro
  2. 2. ⾃⼰紹介 ! 塙 拓朗 🏢 株式会社LIFULL iOSエンジニア (AWS, GCP, Firebase, ) 🎧 ⾳楽が好き takuro.hanawa
  3. 3. アジェンダ 1. 🤳 建物をかざすだけで検索できる仕組み 2. 🧠 YOLOを使った学習モデルの作成 3. 💻 CoreML+Vision+ARKitの実装 4. 🤔 プロダクトに組み込む際の課題と解決策 5. 🌈 組み込み型の活かし⽅
  4. 4. 🔎 スマホカメラで部屋探し!新体験「かざして検索」の使い⽅ https://www.homes.co.jp/cont/rent/rent_00180/
 * LIFULL、スマホをかざして⾒つける新しい部屋探し https://japan.cnet.com/article/35119662/ ※ (2018.1.30)
  5. 5. 🤳 建物をかざすだけで 検索できる仕組み
  6. 6. • 建物の特徴 : 建物を特定する為に必要な情報 ? 🙇
  7. 7. 建物の特徴を判定するには 物件には様々な種類や⾒た⽬のものがある • マンション、アパート、⼀⼾建て、... • 同じ種類でも⾊や形状が違う ※ かざして検索ではプライバシーの配慮から⼀⼾建ての検索を除外しています
  8. 8. 機械学習でできる 💪
  9. 9. 建物の特徴と範囲を捉える 物体認識(Object Detection) Ren, Shaoqing, et al. "Faster R-CNN: Towards real-time object detection with region proposal networks." Advances in neural information processing systems. 2015. Redmon, Joseph, et al. "You only look once: Unified, real-time object detection." arXiv preprint arXiv:1506.02640 (2015) Liu, Wei, et al. "SSD: Single Shot MultiBox Detector." arXiv preprint arXiv:1512.02325 (2015) 物体の分類(Classification)とその範囲を予測する
  10. 10. iOSで物体認識をするためには ディープラーニングのフレームワークを使⽤して学習モデルを⽣成する • YOLO(Tiny YOLO): darknet, turicreate
 https://pjreddie.com/darknet/yolo/ • MobileNet SSD: tensorflow
 https://github.com/tensorflow CoreMLのモデルに変換する • Core ML Community Tools
 https://github.com/apple/coremltools YOLOよりもパフォーマンスが良いらしいが
 MLModelへの変換がうまくいかなかったため ⾒送り😭 (情報求む)
  11. 11. 🧠 YOLOを使った 学習モデルの⽣成
  12. 12. YOLOのモデルを作成する時に 必要なもの GPUインスタンス • Ubuntu 16.04 LTS, NVIDIA Tesla K80 フレームワーク • darknet: https://github.com/AlexeyAB/darknet
 (Required: CUDA 9.1, OpenCV 3.4.0) データセット • 画像データ, ラベルデータ(Class&BoundingBox) コンパイラの相性的にUbuntu
 (Linux GCC>=4.9)がとっても楽だった🙆
  13. 13. GPUインスタンスを⽴てる
  14. 14. フレームワークのインストール $ sudo dpkg -i cuda-repo-ubuntu1604_9.1.*_amd64.deb $ sudo apt-key adv --fetch-keys http://developer.download.nvidia.com/compute/ cuda/repos/ubuntu1604/x86_64/7fa2af80.pub $ sudo apt update $ sudo apt install cuda-9-1 $ echo 'export PATH=/usr/local/cuda-9.1/bin:${PATH}' >> ~/.bashrc $ echo 'export LD_LIBRARY_PATH=/usr/local/cuda-9.1/lib64:${LD_LIBRARY_PATH}' >> ~/.bashrc CUDAのインストール パスを通す NIVIDIAから取ってきてインストール
  15. 15. darknetのインストール フレームワークのインストール $ git clone git@github.com:AlexeyAB/darknet.git $ cd darknet && vim Makefile GPU=1 CUDNN=0 OPENCV=1 GitHubからクローン(AlexeyABがフォークしたものが/) MakefileをGPUの設定に変更する(OpenCVは任意) $ sudo make コンパイルする virtualenvなどでpythonのバージョンごとに
 環境を切り替えられるようにしておくと🙆
  16. 16. darknetを試してみる フレームワークのインストール $ wget https://pjreddie.com/media/files/yolov3-tiny.weights COCOデータセットの学習済み重みファイルをダウンロード $ ./darknet detect cfg/yolov3-tiny.cfg yolov3-tiny.weights data/dog.jpg layer filters size input output 0 conv 16 3 x 3 / 1 416 x 416 x 3 -> 416 x 416 x 16 0.150 BF 1 max 2 x 2 / 2 416 x 416 x 16 -> 208 x 208 x 16 0.003 BF 2 conv 32 3 x 3 / 1 208 x 208 x 16 -> 208 x 208 x 32 0.399 BF ... dog: 100% (left_x: 129 top_y: 204 width: 249 height: 278) bicycle: 98% (left_x: 229 top_y: 196 width: 330 height: 249) サンプルの⽝の画像をテストしてみる 🐶 OpenCVもインストールしておけば
 テストすると画像として出⼒してくれる 🙆
  17. 17. データセットを作る • 特に⼤きさを揃える必要はない • 画質はある程度良い⽅がいい
 (300x300px以上くらい) • 枚数は学習ログを⾒ながら調整
 最初は200-300枚とかで様⼦を⾒る
 (後ほど説明します) 画像データ img001.jpg, img002.jpg,
  18. 18. データセットを作る ラベルデータ bicycle truck dog custom.names (クラスの情報) 0 0.162500 0.381250 0.203571 0.654167 1 0.839286 0.422917 0.235714 0.570833 2 0.428571 0.414583 0.214286 0.545833 img001.txt (範囲の情報) img001.jpg [class index] [x] [y] [width] [height] path/to/data/img108.jpg path/to/data/img132.jpg ... test.txt (テスト⽤のデータのパス) path/to/data/img001.jpg path/to/data/img002.jpg ... train.txt (トレーニング⽤のデータのパス)
  19. 19. つらみしかない 😭
  20. 20. データセットを作る YOLO⽤のラベルフォーマットが出⼒できる 👏 • OpenLabeling: https://github.com/Cartucho/OpenLabeling • LabelImg: https://github.com/tzutalin/labelImg PASCAL VOC形式なので変換が必要(だけど使いやすい) • React Label: https://rectlabel.com/ トレーニング⽤とテスト⽤のデータセットに分けるスクリプト • https://timebutt.github.io/static/how-to-train-yolov2-to-detect-custom-objects/ おすすめラベルデータ作成ツール
  21. 21. データセットを作る 設定ファイルを作る [net] max_batches = 10000 # batch = 64 # 1 subdivisions = 8 # 1 (GPU ) (batch/subdivisions) angle = 0 # saturation = 1.5 # exposure = 1.5 # hue=.1 # learning_rate = 0.001 # policy = steps # steps = 400000,450000 # scales = .1,.1 # [region] classes = 3 # [convolutional] filters = 30 # filters = (classes + 5) * 5 tiny-yolo-custom.cfg (学習の進め⽅の設定の必要そうなところを抜粋) 学習効率を上げるなら 学習精度を上げるなら
  22. 22. 学習を開始する darknet ├── backup │   ├── custom_1000.weights │   ├── custom_final.weights │   ├── custom.backup │ ... ├── cfg │   ├── tiny-yolo-custom.cfg │ ... ├── darknet ├── data │   ├── custom.data │   ├── custom.names │   ├── custom │   ... ├── train.txt │    ├── test.txt │    ├── img001.jpg │       ├── img001.txt │ ... ... darknetのディレクトリ構成 先ほど作ったデータセットと設定ファイル _{batch}.weight: _final.weight:
 .backup: 定期的に出⼒されるモデル 最後に出⼒されるモデル 
 現在学習中のモデル classes = 3 train = data/custom/train.txt valid = data/custom/test.txt names = data/custom.names backup = backup/ custom.data (データの格納先)
  23. 23. 開始 🎉 $ ./darknet detector train cfg/custom.data cfg/tiny-yolo-custom.cfg darknet19_448.conv.23 ※ darknet19_448.conv.23 は重みの初期値。転移学習させる場合やバックアップから開始する場合はここを変える
 https://pjreddie.com/media/files/darknet19_448.conv.23
  24. 24. 学習ログの⾒⽅ 489: 0.371429, 0.913391 avg, 0.001000 rate, 2.270937 seconds batchesごとのログ出⼒ • 489 : 現在の反復回数
 cfgで設定したmax_batchesの値まで⾏う • 0.371429 : 総損失 • 0.913391 avg : 平均損失
 主にこっちを⾒て0.00Xまで下がれば学習完了でいい
 ある段階から上がってしまう(過学習になる)ことがあるので
 データセットを増やしたり設定ファイルの修正を⾏う • 0.0010000 rate : cfgで設定したleaning_rate
 損失がこの値を下回った場合にパラメータの更新をする • 2.270937 seconds : この処理にかかった時間
  25. 25. 学習ログの⾒⽅ Region Avg IOU: 0.253506, Class: 1.000000, Obj: 0.200767, No Obj: 0.300631, Avg Recall: 0.633333, count: 3 subdivisionごとのログ出⼒ • Region Avg IOU : subdivision内の全ての画像のIOUの平均
 IOU(Union over Intersection)とは範囲を含めた予測の正答率 • Avg Recall : このcount内の平均再現率(recall/count)
 再現率(recall)とは正解データの中で予測が正解だった⽐率 • count : subdivision内の検出されるべきオブジェクトの数
 データセットの⾃分で囲ったラベルの数の合計 • Class, Obj, No Obj : 不明 🙇 https://en.wikipedia.org/wiki/Jaccard_index
  26. 26. 学習モデルを試す $ ./darknet detector test cfg/custom.data cfg/tiny-yolo-custom.cfg backup/tiny- yolo-custom.backup test.jpg layer filters size ... Loading weights from backup/tiny-yolo-custom.backup... seen 32 Done! test.jpg: Predicted in 0.006078 seconds. dog: 78% bicycle: 44% ... 適当な画像でテストしてみる 🐶 学習途中でもbackupやナンバリングされたweightファイル でテストできるのでログを⾒ながら定期的にテストする 😇
  27. 27. YOLOの学習モデルをCoreMLの モデルに変換 • darkflow: https://github.com/thtrieu/darkflow
 darknetのweightからtensorflowのprotbufに変換するツール • tf-coreml: https://github.com/tf-coreml/tf-coreml
 tensorflowのprotobufからCoreMLのmlmodelに変換するツール darknetのモデルから直接CoreMLのモデルへ変換はできないの でKerasやtensorflowのモデルから変換する Keras経由では変換がうまくいかなかった
 のでTensorflow経由がおすすめ 🙆
  28. 28. 🙌
  29. 29. 💻 CoreML+Vision+ARKit の実装
  30. 30. アプリの実装は簡単 🤗 1. ARSessionからキャプチャー画像を取得 🤳
 (AR使わないならAVCaptureSessionで良い) 2. VNImageRequestHandlerに画像を渡す 🧠 3. 予測値をMatthijs Hollemans様のお知恵を借りて 画⾯表⽰に使える値(String, CGRect)に変換する 👼 4. あとはいつものViewやLayerで描画 👏 Matthijs Hollemans: http://matthijshollemans.com/
 iOS+Machine Learning, Deep LearningのコンサルしてたりRay WenderlichのGitHubのオーガナイザーだったりすごい⼈
  31. 31. ARKitのフレームからサンプリ ングバッファを取得する • ARSessionDelegateのsession(_:didUpdate:)でカメラのキャプチャー (CVPixelBuffer)を取得できる • ARFrameにはdisplayTransform(for:viewportSize:)で画⾯上に表⽰する際の 適切なアフィン変換を取得できる func session(_ session: ARSession, didUpdate frame: ARFrame) { // Using portrait only. let transform = frame.displayTransform( for: .portraitUpsideDown, viewportSize: viewportSize) // Start Vision's request processing. presenter.captured( buffer: frame.capturedImage, transform: transform, timestamp: frame.timestamp) }
  32. 32. Visionのリクエストで処理する // Using VNCoreMLModel. let vision = try VNCoreMLModel(for: mlmodel) // Generates Vision's request handler. DispatchQueue.global().async({ in let handler = VNImageRequestHandler(cvPixelBuffer: buffer, orientation: .right) let request = VNCoreMLRequest(model: vision, completionHandler: { request, _ in guard let observations = request.results as? [VNCoreMLFeatureValueObservation], let features = observations.first?.featureValue.multiArrayValue else { return } // Computing bounding boxes(class, rect, score) for YOLO. let predictions = YOLO.computeBoundingBoxes(from: features) DispatchQueue.main.async({ in // Show bounding boxes. }) }) request.imageCropAndScaleOption = .scaleFill // Requests to the vision. try handler.perform([request]) }) • VNCoreMLModelを使うことでMLModelで処理するI/Oの画像サイズ (モデルを作成した時の416x416)や回転などの変換処理が不要になる
  33. 33. Visionのリクエストで処理する • まだObject Detectionに適したVNObservationクラスはないので
 VNCoreMLFeatureValueObservation(回帰分析⽤の特徴データ)から
 ⾃前でクラス/範囲/スコアに変換する(computeBoundingBoxesのところ)
 参考: http://machinethink.net/blog/yolo-coreml-versus-mps-graph/ // Using VNCoreMLModel. let vision = try VNCoreMLModel(for: mlmodel) // Generates Vision's request handler. DispatchQueue.global().async({ in let handler = VNImageRequestHandler(cvPixelBuffer: buffer, orientation: .right) let request = VNCoreMLRequest(model: vision, completionHandler: { request, _ in guard let observations = request.results as? [VNCoreMLFeatureValueObservation], let features = observations.first?.featureValue.multiArrayValue else { return } // Computing bounding boxes(class, rect, score) for YOLO. let predictions = YOLO.computeBoundingBoxes(from: features) DispatchQueue.main.async({ in // Show bounding boxes. }) }) request.imageCropAndScaleOption = .scaleFill // Requests to the vision. try handler.perform([request]) })
  34. 34. Visionのリクエストで処理する • ただしiOS12でVNRecognizedObjectObservation(トラッキングで使う VNDetectedObjectObservationにクラスのラベルが付与されたもの)
 が出たのでこれが使えるかも?
 developer.apple.com/documentation/vision/vnrecognizedobjectobservation // Using VNCoreMLModel. let vision = try VNCoreMLModel(for: mlmodel) // Generates Vision's request handler. DispatchQueue.global().async({ in let handler = VNImageRequestHandler(cvPixelBuffer: buffer, orientation: .right) let request = VNCoreMLRequest(model: vision, completionHandler: { request, _ in guard let observations = request.results as? [VNCoreMLFeatureValueObservation], let features = observations.first?.featureValue.multiArrayValue else { return } // Computing bounding boxes(class, rect, score) for YOLO. let predictions = YOLO.computeBoundingBoxes(from: features) DispatchQueue.main.async({ in // Show bounding boxes. }) }) request.imageCropAndScaleOption = .scaleFill // Requests to the vision. try handler.perform([request]) })
  35. 35. 🤔 プロダクトに組み込む際の課 題と解決策
  36. 36. アプリで機械学習を組み込む上 で問題になること モデルのサイズが⼤きくなりがち 🐖 • 特に今回のようなObject Detection(Tiny YOLO)の学習モデル は30〜60MBとかなり重い • このMLModelをバンドルに⼊れてしまうと使わないユーザー も容量の圧迫を受けてしまう 連続して処理させると処理が重くなりがち 🐢 • YOLO(Tiny YOLO)の学習モデルはiPhoneXで約20FPSで処理さ せることができたが端末への負荷は⼤きい
  37. 37. 学習モデルはダウンロードする • MLModelにcompileModel(at:)というMLModelをコンパイル (.mlmodelc)してくれるメソッドがある let compiledUrl = try MLModel.compileModel(at: modelUrl) let model = try MLModel(contentsOf: compiledUrl) • 通常MLModelをXCodeからバンドルするとそれをコンパイルして ⼊出⼒をラップしたクラスを⽣成してくれるが、ダウンロードする 場合は⾃前で⽤意する // Class for model loading and prediction @available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *) class CustomModel { var model: MLModel init(contentsOf url: URL) throws { self.model = try MLModel(contentsOf: url) } ... }
  38. 38. • ⼀度ダウンロード/コンパイルしたモデルはドキュメントディレクトリ などに保存して以後それを使う // Find the app support directory let fileManager = FileManager.default let appSupportDirectory = try fileManager.url( for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: compiledUrl, create: true) // Create a permanent URL in the app support directory let permanentUrl = appSupportDirectory .appendingPathComponent(compiledUrl.lastPathComponent) do { // If the file exists, replace it. Otherwise, copy the file to the destination. if fileManager.fileExists(atPath: permanentUrl.absoluteString) { _ = try fileManager.replaceItemAt(permanentUrl, withItemAt: compiledUrl) } else { try fileManager.copyItem(at: compiledUrl, to: permanentUrl) } } catch { print("Error during copy: (error.localizedDescription)") } https://developer.apple.com/documentation/coreml/core_ml_api/downloading_and_compiling_a_model_on_the_user_s_device 学習モデルはダウンロードする
  39. 39. 学習モデルのダイエット CoreMLのモデル容量を減らす⽅法が公開されている • Half-Precision Model: 学習モデルの重みで使⽤する浮動⼩ 数点を32bitから16bitに変換する(iOS11.2以上) • Lower Precision Model: 学習モデルの重みで使⽤する浮動 ⼩数点を任意のbit数(16, 8, 4, 2, 1)に変換する(iOS12以上) 参考: https://developer.apple.com/documentation/coreml/ reducing_the_size_of_your_core_ml_app
  40. 40. 学習モデルのダイエット Half-Precision Model • CoreML Tools (v0.7.0以上)をインストールしてUtilクラスの convert_neural_network_spec_weights_to_fp16メソッドに mlmodelファイルを渡すだけ • ただし精度は少なからず落ちるので動作検証が必要 import coremltools # Load a model, lower its precision, and then save the smaller model. model_spec = coremltools.utils.load_spec(‘./custom.mlmodel') model_fp16_spec = coremltools.utils.convert_neural_network_spec_weights_to_fp16(model_spec) coremltools.utils.save_spec(model_fp16_spec, ‘custom_16.mlmodel') convert.py CoreML Tools: https://github.com/apple/coremltools
  41. 41. 学習モデルのダイエット Lower Precision Model • CoreML Tools (2.0b1)をインストールして models.neural_network.quantization_utilsを使う
 (pip install coremltools==2.0b1) • quantize_weightsの引数に渡せるのはモデルと変換するbit数と量⼦化 の⽅法(線形: linear, k平均: kmeans)
 ※ YOLOのモデルだと8bit以下のkmeansの変換ができない 😭 import coremltools from coremltools.models.neural_network.quantization_utils import * model = coremltools.models.MLModel('./custom.mlmodel') # Quantize model weight to 8bit using linear method. linear_quantized_model = quantize_weights(model, 8, "linear") linear_quantized_model.save('./custom_8_linear.mlmodel') # Compare models. compare_models(model, linear_quantized_model, './data') convert.py
  42. 42. 学習モデルのダイエット Lower Precision Model • 検証⽤のメソッドcompare_modelsに両モデルとテスト画像が⼊って いるディレクトリを渡すと性能⽐較ができる
 ※ YOLOのモデルだと性能の情報が全然出ない 😭 $ python convert.py Input name(s) and shape(s): image : (C,H,W) = (3, 416, 416) Neural Network compiler 0: 100 , name = convolution2d_1, output shape : (C,H,W) = (16, 416, 416) ... Analyzing 6 images Running Analysis this may take a while … Output grid: -------------- SNR: 29.3645888188 +/- 2.67386378668 PSNR: 45.9721819368 +/- 11.3184398991
  43. 43. 🙌
  44. 44. サンプリングのフレームレートを制御する • VisionのリクエストにそのままARSession(AVCaptureSession)の画像を渡 しまくると処理が追いつかないのでリクエストのタイミングを制御する 処理はある程度減らす func session(_ session: ARSession, didUpdate frame: ARFrame) { // Requesting vision for the required frames. guard frame.timestamp - timestamp > fps else { return } ... // Start Vision's request processing. presenter.captured( buffer: frame.capturedImage, transform: transform, timestamp: frame.timestamp) // Retain the last timestamp you requested timestamp = frame.timestamp }
  45. 45. 処理はある程度減らす リクエストの平⾏処理の数を制御する • DispatchSemaphorを使ってサブスレッドで平⾏に⾏なっているVisionリ クエスト処理がフレームをドロップしないようにする
 ※ Visionはリクエストが捌き切れなくなるとよしなに処理をドロップする let semaphore = DispatchSemaphore(value: 2) ... semaphore.wait() DispatchQueue.global().async({ in ... let request = VNCoreMLRequest(model: vision, completionHandler: { request, _ in ... DispatchQueue.main.async({ [weak self] in // Show bounding boxes. self?.semaphore.signal() }) }) ... // Requests to the vision. try handler.perform([request]) })
  46. 46. 🌈 組み込み型の活かし⽅
  47. 47. ここまでしなくても
 サーバーでやればいいのに 😂
  48. 48. 最適な予測結果とインタラク ティブな体験が得られる サーバーで処理する場合はユーザーが処理をしやすい写真または動画を 撮って送信しなくてはいけない • ユーザーが意識をして認識しやすいデータを送らなくてはいけない • リアルタイムな描画やインタラクションは不可 🧠
  49. 49. 最適な予測結果とインタラク ティブな体験が得られる 組み込み型では常に処理をしているのでカメラの⼿ブレなどを気にせず に⼀番予測値の良いデータをサーバーに送信できる • ユーザーは意識をしなくても最適な予測結果を受け取れる • 動きのある楽しいUIが作りやすい 🧠
  50. 50. まとめ 1. 🤳 建物をかざしただけで検索できるの仕組みは機械学習と様々 な端末の機能の合わせ技で可能にしている 2. 🧠 学習はとっても時間がかかる。上⼿く⾏うにはログを⾒なが らデータセットと設定ファイルの⾒直しを繰り返す 3. 💻 アプリの実装はキャプチャを取得してVisionに渡すだけ 4. 🤔 モデルはダウンロードさせる。データ容量はCoreML Toolsを 使ってbit数を落とす。Visionの処理回数は必要最低限まで絞る 5. 🌈 組み込み型はユーザーが意識をしなくても最適なデータを取 得できる。インタラクションに富んだUIが作れる
  51. 51. LIFULL HOME’S かざして検索リリースの裏側 ご静聴ありがとうございました 🙇

×