dely engineering blog

レシピ動画サービス「kurashiru」を運営するdelyのテックブログ

iOS版クラシルのフィードを滑らかな動きにするためにやったこと

f:id:takaoh717:20191007184951p:plain

こんにちは、iOSエンジニアのtakao(takaoh717)です

今回はクラシルiOSアプリのフィードのパフォーマンス改善を行った話をご紹介します。
改善を行ったフィードはUICollectionViewで構成されており、レシピ、画像バナー、広告など複数の異なる型のデータを表示しているような画面です。

今回行った変更は以下の内容です。

  • 差分更新ライブラリの導入とデータの管理、更新ロジックの変更
  • セルのサイズ計算を事前に行うよう修正
  • 通信時やログ送信時の重い処理をバックグラウンドスレッドで実行

改善前の課題

改善を行う前は、アプリを動かしていると実際に分かるレベルでパフォーマンスに問題がありました。

  • スクロール自体の挙動が若干重くてスムーズじゃない(指の動きに対して若干ひっかかりがある)
  • ページングの読み込みをしたときにスクロールが止まることがある
  • 更新時に画面がチラつくことがある

差分更新ライブラリの導入とロジックの見直し

まず最初に、差分更新ライブラリの導入を行いました。 これまでは、一部分のみ自前のロジックで差分更新を行って、基本的にはreloadData()を多用しているような状態でした。
何度か差分更新を行うようにしたことはあるのですが、更新タイミングによるクラッシュなどが度々発生し、結局reloadData()に戻すようなことをしていました。 そこで、今回のリファクタリングを気にreloadData()を使用しない状態にしておきたかったので、ライブラリを導入しました。

DifferenceKitの導入

差分更新用のライブラリはDifferenceKitを選択しました。

github.com

選定理由では以下の点が判断基準になりました。

  • パフォーマンスの高さ
  • プロジェクトへの導入コスト、実装コストの低さ

パフォーマンスについては実際に計測して比較はしていないのですが、公式のドキュメントに記載されている内容では、RxDataSourcesIGListKitよりも高速になっているようです。

実装面については、classだけではなくstructにも対応している点や、AnyDifferentiableを使って異なる型のデータを一つの配列で管理可能な点がポイントでした。 Examplesに動くアプリのコードが載っているのでそちらとドキュメントを参考にして実装しました。

実装方法

まず、フィードに表示するコンテンツを表すModelDifferentiableを準拠させます。

extension Model: Differentiable {
    public var differenceIdentifier: String {
        return id
    }

    public func isContentEqual(to source: Model) -> Bool {
        return title == source.title
    }
}

今回の実装ではフィードの1列を1Sectionで表現する構造にしてみました。Sectionのデータ管理をしやすいようにDataSourceは以下のような実装にしました。

// Section毎のデータを保持する配列
var dataSources = [Section]()

// Sectionに該当するデータ型を保持するためのenum
enum FeedSectionModel: Differentiable {
    case hoge
    case fuga
    case piyo
}

// 1Sectionを一つのまとまりとして管理するために定義
typealias Section = ArraySection<FeedSectionModel, AnyDifferentiable>

更新時の実装も変更前と後の配列を用意して渡してあげるだけなので、とてもシンプルな実装になります。 また、interruptに特定の条件を渡しておけば、結果がtrueになった場合に差分更新をせずにreloadData()を行うようにできます。
クラシルはUI上の1つのセルのサイズが大きいため、例えば、リストの途中の位置などで一定数以上のセルの挿入があったりすると、スクロール位置などが大きくずれてしまったりするため、一定の個数以上の更新が必要な場合などはreloadData()を行うようにしました。

let new = dataSource
new.append(ArraySection(model: FeedSectionModel.hoge, elements: newData))

// source: 更新前のデータ、target: 更新後のデータ
let changeSet = StagedChangeset(source: dataSources, target: new)

collectionView.reload(using: changeset, interrupt: { $0.changeCount > 3 }) { data in
    dataSource.data = data
}

パフォーマンスに大きく影響を与えている実装箇所の特定

ベースのリファクタリングが出来た後にどの実装が問題になっているのかを特定するため、InstrumentsのTimeProfilerを使って重い処理を特定する作業を行いました。 使い方はとてもシンプルですが、実際にアプリを動かしながら、カクつくタイミングにメインスレッドで実行している重い処理をひたすら確認していきました。

また、今回のようなMainThreadで実行されている重い処理を見たい場合の確認では以下のような設定を使用していました。

f:id:takaoh717:20190925085850p:plain
Instruments

セルのサイズ計算を事前に行う

スクロール自体の挙動が重くてスムーズな挙動にならない場合はfunc collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath)のサイズ計算が問題になっているパターンがあると思います。 実際にクラシルでもレシピや広告などのデータを取得した後に、func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath)の中で表示するCellのサイズ計算を行っていたため、Instrumentsで見てみるとその中身の関数のときに負荷が跳ね上がっていました。

このような、データの内容によってセルのサイズが可変になる場合、データを取得したタイミングで先にサイズ計算を行い、ModelEntityでサイズを保持するようにしました。

そうすることで、Layoutのサイズを返すときには事前に計算しておいたサイズを返すだけになり、メインスレッド上での処理が減るのでパフォーマンスが向上します。

Viewの描画に直接関わらない重い処理の修正

スクロール途中に急ブレーキがかかるようにスクロールが停止したり、次のページの読み込みが走ったタイミングでカクつきが発生したりしていた原因はViewの描画に直接関係のない重い処理をメインスレッドで行ってしまっていたものでした。 この場合も、どの処理がネックになっているかをまずInstrumentsで確認しました。

  • メインスレッドで実行していた重い処理
    • API通信後のJsonのパース
    • ログの送信
    • サーバーから取得したデータの変換処理

これらの処理をDispatchQueueを使って、適切にバックグラウンドスレッドに引き渡すことによって、読み込み時などにメインスレッドの使用率が急上昇することがなくなり、UIのカクつきが解消されました。

まとめ

UICollectionViewはUITableViewに次いで多くのiOSアプリで使われていると思います。 しかし、適切に実装していないとパフォーマンスの低下がユーザに見える形で分かりやすく出てしまうため、適度にメンテナンスすることが大事だと思います。

delyでは様々なポジションのエンジニアを積極採用中です!ご興味がある方はぜひご連絡ください。

サーバーサイド&SRE改善MTGを始めてチームの生産性があがった話

はじめに

こんにちは、delyでサーバーサイドエンジニアをやっている山野井です。

delyのサーバーサイドチームでは「サーバーサイド&SRE改善MTG」という取り組みを毎週行っています。
この取り組みは個々が日頃開発する上で感じている課題をdelyのサーバーサイドエンジニア、SRE、フロントエンドエンジニア間で共有・議論し、解決を目指すものです。
本記事ではその取り組みについてご紹介したいと思います。

何故この取り組みを行っているのか

以下の2つの課題を解決するためにこの取り組みを行っています。

1.個々で課題や問題だと感じていることを関係者で共有・議論する機会が少ない

delyの開発チームは少数精鋭であり、日頃行う業務に追われているため、日常業務内で感じた課題は個々で解決するか、優先度があがらず手を付けられていない状態になってしまっていました。
個々で問題を抱えている状態は不安やストレスがたまりチームとして健全な状態ではないと思います。

2.分野横断の技術的なFBが少なく分野を跨いだスキルアップが難しい

自分の日常業務に関してのスキルアップは比較的容易ですが、普段作業しない分野においてはどうしてもスキルアップが難しくなってしまいます。
例えばサーバーサイドエンジニアでもフロントエンドのコードを書いたり、インフラの知識が求められます。
しかし、自分の専門外である技術のFBを得られる場がありませんでした。

MTGを行う目的

サーバーサイド&SRE MTGを行うにあたり、上記の2つの課題を解決するために大きく3つの目的を設定しました。

  1. 個々で課題や問題だと感じていることを明確にして参加者に共有する
  2. 課題や問題の明確化や解決策を参加者内で議論する
  3. 解決策をタスク化(緊急度、重要度)して解消につなげる

個人の課題にフォーカスしてみんなで議論することで、今まで一人で解決してきた状態から業務に対する心理的な不安・ストレスの解消を図ることができます。
また、普段違うチームで作業しているからこそ、参加者全員で議論やFBを行うことで、効率的な個人の成長につながると考えています。
ここで議論した結果に基づいて必要であればタスクを作成し、負債の解消につなげます。

どのように行っているか

サーバーサイド&SRE MTGは週に1度の頻度で行っています。
参加者、発表者、議題決め担当者に分かれていて、議題決め担当者のみ参加者内で順番に持ち回り制です。

f:id:yamanoi-y:20190925214326p:plain

サーバーサイド&SRE MTGのおおまかな流れは以下のとおりです。

  1. 各メンバーが自由に議題を出す
  2. 議題決め担当者が議題を決める
  3. 決められた議題を出した人がMTGまでに資料を作る
  4. 資料を元に議論する


ミーティングで議論した内容は議事録としてQiita:Teamに残し、いつでも見返すことができる状態にしています。

今まで扱ってきた議題の例として、アプリケーションの設計の見直しや、開発環境の整備等がありました。

f:id:yamanoi-y:20190925214315p:plain


実際に数ヶ月間この取り組みを行ってきてMTGの振り返り会を一度行いました。
振り返り会では、議題で上がった課題はその後改善ができているのか、また改善ができていないものに関しては何が原因で改善ができていなのか、今後どのようにして改善を進めていくかを議論しました。
この様にしてMTGの振り返り会を定期的に行いMTG自体のブラッシュアップも行っています。

どんな効果があったか

サーバーサイド&SRE MTGをはじめたことで、チーム内にどのような変化が起きたかをヒアリングしてみたところ下記の様な意見がもらえました。

  • 気になる事があった時に1人で抱えてモヤモヤせずに、共有して議論できるのが良い
  • 全体に共有してフィードバックをもらえるので、自分1人で考えるよりも良い案が出る
  • 比較的最近入った人間からすると何が問題になってるかがわからないので、それが可視化されるのは良いなと思う
  • 過去に議論された結果がわかるのも良い
  • 以前は課題感が漠然としていて、それに対して漠然とした不安を抱えていたような気がする。MTGを通して課題の重要度・緊急度・コストがある程度明確に認識できるようになってきたので、それに基づいて優先度を決めて意思決定が出来るようになってきた
  • PRのレビューがしやすくなった
  • 設計の議論がスムーズに行えるようになった(既存のサービスだけでなく、新しいサービスや基盤を作る際にも)
  • 一人で問題を抱え込むストレスがなくなった
  • 入社が遅い人でも迅速に現存する問題点をキャッチアップしてもらえた
  • 引き継ぎ、部署異動しやすい
  • 問題が忘れ去られることがない安心感がある(自分も忘れない)
  • 自分が気づいていなかったことや感じていなかった問題を様々なバックグラウンドをもったエンジニアから教えてもらうことができる

個人的には、

  • 自分が抱えていた問題を全員に共有することができたことでスッキリしたこと
  • 過去にどのような議論が行われたのかを振り返る事ができることによって、似たような問題に遭遇しても参照することができるようになったこと

が良かったと感じました。

まとめ

今回はサーバーサイドチームで行っている取り組みについてご紹介してきました。
参加者からの評判も良く、開発効率・生産性も向上していると感じています。

delyのサーバーサイドチームでは、今後ビジネスを拡大していくにあたって明らかに規模が追いついておらず、新しいメンバーの募集を積極的に行っているところです。
もしご興味があればご応募・ご連絡ください。

speakerdeck.com

SREはじめました

f:id:joe0000:20190924000124j:plain

こんにちは、6月にサーバーサイドチームからSREチームに異動した高山です。

私がSREチームにジョインすることとなり、SREのチームメンバーが増えることをきっかけに、現状のSREチームを見直すという取り組みを行っています。

弊社の開発組織にはもともとSREという職種が存在しています。しかし、実態としては、ソフトウェアも書けるインフラエンジニアがSREという名で活動しているというだけで、「一般的なインフラエンジニアの責務+困った時に頼られるなんでも屋」という曖昧なものでした。

その結果、

  • SREチームが実際には何をすべきチームなのか他のチームに正しい認識をしてもらえていない
  • メンバーによって認識しているSREとしての責務境界がバラバラ
  • 降ってくるタスクが多すぎて足元解決型のチーム
  • 優先順位が定まらず本来SREチームがやるべきことに着手できていない(のでは)

という状況になっていました。

これでは、SREチームの役割や責務範囲が属人的になり、本来のSREが発揮するべきバリューがチームとして効率よく発揮できないという問題が発生してしまいます。また、今後さまざまなバックグラウンドを持つメンバーが増えていくにあたり、さらに責務範囲の属人化が進むことが懸念されました。

そこで、SREチームとしてのミッションや役割、優先順位を明文化することで本来のSREチームとしてのバリューを発揮できるようにチームを見直す取り組みをはじめました。

目次

  • 取り組みについて
    1. SRE本輪読会開催
    2. SREチームミッションの決定
    3. SREチームの役割のスコープ決定
  • これから取り組みたいこと

取り組みについて

取り組み1: SRE本輪読会開催

問題点としてあげた通り、弊社のSREチームは本来のSREチームに対する理解がチームメンバーによって様々でした。 (ここでいう本来のSREチームとは、Googleが提唱しているSREチームのこと)

そもそもSREチームとは、「どのような問題を解決するために」「なにを目的として生まれたチームなのか」ということを全員が理解できていないと、足並みの揃わない、目的がブレたチームを発足することになります。ミッションや役割、優先順位を決めて執行したとしても、全員がその内容を理解できていないと本来時間を費やすべきでないことに時間を費やしたり、正確な意思決定ができなくなったり、納得感のないままミッションを遂行することになってしまうかもしれません。

そこで、SREチーム見直しの第一歩として本来のSREチームを知るべく、SRE本の輪読会を開催することにしました。

www.oreilly.co.jp

輪読会のやり方は以下のような方法で進めました。

### 進め方

頻度: 週1
時間: 1時間
ルール: 1章ずつ持ち回り制で輪読会を開催

### 事前準備

- 担当者
  - 内容のサマリーと気づきをまとめ当日他のメンバーにドキュメントをシェア
  - 読んだだけで理解できなかったところは可能な限りググってまとめる
- 全員
  - 章の内容を読んでくる
  - 章の中で特に議論したい内容を担当者があげたドキュメントに追記

### 当日

- 担当者がまとめてきた内容を各自読む(10分)
- 共有内容と議論したい内容に基づいて議論する(50分)

輪読会をやってみてわかったことは、

  • SRE本から得られる学びがとても多い
  • 輪読会の効果がすさまじい

ということです。

まず、SRE本に書いてあること自体、とても勉強になります。今まで感覚的に「こうあるべき」と思っていたことが、実はそうでもなく、無駄なことに時間や神経をすり減らしていたと気付かされたり、逆に感覚的にこうだと思っていたことがきちんと明文化されて解決策が提示されていたりします。

また、SREという職種は会社ごとに役割にバリエーションがあったり、ふわっとしがちというイメージがありましたが、SRE本を読んでその責任範囲にかなり明確な定義があるということがわかりました。

さらに、輪読会のすばらしい効果を実感しました。 そもそも誰かに読まれることを前提にまとめを作成することで、一人で読むのと比べて理解度が上がりました。 また、メンバーの半分はSRE本をあらかじめ読んだことがあり、理解度の違うメンバー同士で議論することで、新しく読んだメンバーは効率よく理解が深まり、すでに読んでいるメンバーは認識が違っていた箇所や理解が曖昧だった箇所を潰すことでチームで認識を揃えることができました。

取り組み2: ミッションの決定

SRE本には、私たちのような少人数のチームでは真似できなかったり、サービスの特徴によって起こるGoogleならではの問題に対するアプローチや取り組みも含まれています。それゆえ、そのままGoogleのSREチームの概念を自分たちに投影して運用を試みても、無駄があったり、そもそも真似できない部分があります。

なので、自分たちが抱えている問題とGoogleが抱えている問題をきちんと切り分けて考え、自分たちに必要なルールを取捨選択したり、形を変えて自分たちのルールに落とし込んでいく必要があります。

そこで、弊社のSREチームでは共通の認識によって必要なルールを選定する意思決定ができるように、ミッションの策定を行いました。

まずは、こちらが決定したミッションです。

クラシルの信頼性や事業継続性を担保しつつ、ユーザーに価値を素早く提供できるように設計と運用を改善し続ける

信頼性とは:サービスに求められる機能を、定められた条件の下で、定められた期間にわたり、障害を起こすことなく実行する確率

ミッションの決定で行なったこと

  • SRE本の理解 (SRE輪読会)
  • 現状のタスクの洗い出し
  • 他社のSREチームミッションの事例を集めて共通点や違いを分析

まずは、最低限SRE本の1章を理解してミッションを決定するのがいいと思います。 SRE本の中にも出てきますが、1章がSREチームの役割を理解するために一番大事な章となっています。 全てを読んで理解するのは時間がかかるので、まずはこの1章を最低限全員が理解した段階でミッションの決定を行いました。

次に、現状のタスクの洗い出しをして、SREチームがどのような責務を持っているかを明確にし、ミッションを決める際の参考にしました。

最後に、他社のSREチームのミッションの事例を集めて、SREチームが生まれた背景の分析や弊社のチームとの共通点の分析を行いました。

また、この時点でミッションはあとから柔軟に変化させていくという方針で決定しました。 ミッションは最初に最適なものをピンポイントに決めるということはとても難しいです。 あとから形を変えることができるので、悩みすぎずにこれと決めて事業ニーズの変化や次にお話しする役割のスコープ決定などに合わせて都度話し合っていくことが重要かと思います。

取り組み3: SREチームの役割のスコープの決定

取り組みの3つめとして、SREチームの役割のスコープを決定しました。 まず、こちらが弊社のSREチームが現状考えている役割のスコープです。

  • 可用性の担保
  • パフォーマンス(レイテンシなど)
  • リソースの効率的な活用
  • 素早く安全な変更管理
  • モニタリング
  • 緊急対応
  • キャパシティプランニング
  • セキュリティ

スコープの決定で行なったこと

スコープを決定するにあたって、下記の手順を踏みました。

  1. SRE本に挙げられているSREの責務のスコープと、現状の弊社SREチームのタスクのマッピング
  2. マッピングした上で、それぞれのスコープが弊社の今後のSREチームに必要か議論
  3. スコープからはみ出した責務をSREチームが担うべきか議論
  4. 決定したスコープがミッションに沿ったものになっているかを見直す

まずは、SRE本の第1章に登場する「SREの信条」として出てくるスコープと現状のSREチームのタスクをマッピングしました。 現状のタスクがSRE本で挙げられているSREの責任範囲にマッピングできないものは、弊社のSREチームが過度に持っている責務として分類しました。 そして、それぞれのスコープが弊社のSREチームに必要かどうかを議論しました。 ここでは、SRE本で挙げられていたスコープになにかしらのタスクが当てはまる結果となり、どのスコープも必要であるという結論になりました。

f:id:joe0000:20190918184750p:plain

次に、GoogleのSREの責務からはみ出した責務をSREチームが担うべきか議論を行いました。

前述したように、Googleと弊社ではチーム構成も人数も違います。 例えば、SRE本に列挙されている責務のスコープにはセキュリティがありません。 GoogleにはSREチームとは別にセキュリティのスペシャリストチームがいる可能性が高いです。弊社は、セキュリティチームがないのでその責務をSREチームが兼任する必要がある、といったように、GoogleのSREチームの責務に加えて持たなければならない責務についても議論しました。

最後に、最終的に決まったスコープが、SREチームのミッションに沿ったものであるかどうかを確認しました。 必要なスコープがチームのミッションに即していない場合は、スコープを見直すか、チームミッションを見直す必要があります。

f:id:joe0000:20190918182153p:plain

このスコープを決定することで、一気に弊社のSREチームの役割が明確になってきました。SRE本でわかりやすくスコープを切ってくれているため、スムーズに役割のスコープを決定することができました。

これからやっていきたいこと

  • SREチームの役割のスコープを細かいアクションに落とし込む
  • アクションごとの優先順位を決める
  • 他チームへのSREチームのミッションや役割の共有

まとめ

SRE本の輪読会や、ミッションの決定、役割のスコープの決定、MTGの設立などを通して、だんだんとdelyのSRE像がみえてきました。この取り組みにより、チームで足並みが揃い始めていると実感しています!また、SREチームの責務を判断する際の明確な基準が生まれて、意思決定がスムーズになりました。

しかし現時点では、SREチームとしてスタート地点に立ったにすぎません。delyのSREチームでは、そんな発展途上なSREチームを一緒に形にしてくれるSREを募集しています!

データサイエンスチームでの1ヶ月インターンの記録

こんにちは。delyインターンのしょーといいます。 データサイエンスチームで1ヶ月間インターンさせていただきました。
本記事では、インターンで行なってきた事柄を紹介していきます。

目次

1. コホート分析

レシピ詳細画面のUIが変わったことによるリテンションの変化を分析しました。

分析手法

コホート分析を用いました。なぜコホート分析を用いたかというと、リテンションの変化が一目で分かりやすいからです。

roboma.io

今回はGoogleアナリティクスではなく、Pythonを用いてコホート分析を行いました。

まず、以下のデータをSQLで取り出して分析に使用します。

date: 最初にレシピ詳細画面を開いた日
past_days: dateから何日後にアプリを起動したか
user_count: dateの日にアプリを起動した人の中で、さらにpast_days後にアプリを起動した人数
total_count: dateの日にアプリを起動した人数

user_countをtotal_countで割ると、それぞれのリテンションを算出することができます。この値と、date、past_daysを用いてピボットテーブルを作成し、seabornで図を作成しました。

結果

完成した図がこちらです。
(イメージ図となっています、ご了承ください。) f:id:syou818:20190822184050p:plain

こちらを用いてUI変更前との比較を行なった結果、新しいUIの方がリテンションが改善されていることが分かりました。

2. アプリダウンロード数の推移

データレイクごとのアプリダウンロード数のデータを抽出し、それぞれのデータレイクにおいてデータが欠損してないかを調べました。
クラシルのデータレイクは以下を参照ください。

logmi.jp

今回はeternalpose、logpose、firebaseの3つのデータレイクを用いました。

分析手法

それぞれのデータレイクからアプリダウンロード数を、SQLを書いてデータを取り出し、seabornを用いて可視化しました。
日毎に5ヶ月の期間で推移を見ていきました。

結果

可視化したグラフは非公開とさせていただきます、申し訳ございません。

それぞれのデータレイクのアプリダウンロード数を確認したところ、firebaseにおいて、他の2つのデータレイクに比べて所々極端に少なくなっているところがあり、データの欠損が確認できました。このデータの欠損は今後のクラシルのデータ分析にも影響を与えると考えたので、開発チームに伝えてデータの修正をしていただきました。

また、logposeに格納されているアプリダウンロード数が、序盤から中盤まで他の2つのデータベースより少なかったことが分かりました。更に詳細に調査を行った結果、iOSのダウンロード数は正常でしたが、androidのダウンロード数が序盤から中盤まで少なかったため、androidのデータに何か異常が起きていたのだと考えられます。

3. 動画視聴予測モデル作成

ユーザが任意のレシピ動画を見るか見ないかを予測するモデルを作りました。   目標は実際のサービスに組み込めるようなモデルです。

以下用いるデータは、Athenaのeternalposeより取り出しています。

基礎となるデータフレームに至るまで

まず初めのアプローチとして、あるユーザがお気に入りしている動画に含まれている食材のデータを用いることを検討しました。

user_id: あるユーザー
video_cnt: ユーザがお気に入りしているビデオの個数
ingredient_count_list: ユーザがお気に入りした動画に含まれている食材とその個数のリスト

f:id:syou818:20190910212432j:plain

しかしこのまま用いようとすると、ユーザのお気に入りしている食材の情報しか使用できません。また、個々の動画の情報も入れないと良いモデルは作れないだろうと考えました。

そこで、ユーザがお気に入りしている動画で使われている食材のカテゴリを使用することを考えます。しかし、このデータをAthenaから取り出すのにとても時間がかかってしまいました。さらにデータを取り出した後に、カテゴリでなく個々の食材をそのまま使用する方がいいのではないかと考え、結局苦労して取り出したデータは使用しないことにしました。(大きなタイムロスでした・・・)

このようなアプローチを続けていく中で、まずは以下のデータセットを試してみることにしました。

user_id: あるユーザー
video_id: ユーザーが見たビデオ
watch: ユーザーがその動画を見たら1、見ていないなら0
fav: ユーザーがその動画をお気に入りしていたら1、していないなら0
また全食材の列を作り、動画で使われている食材の列に1、使われていない食材の列に0

f:id:syou818:20190910212501j:plain

このうち「fav」と動画の食材を説明変数に、「watch」を目的変数にロジスティック回帰で学習させたところ、99%の正解率が出ました。
しかし考察したところ、

・ユーザー特性が何も入っていないこのモデルになんの意味があるのか?
・そもそもお気に入りしてるかを最初に知れることはありえないんじゃないのか?

となり、大幅な改良が必要であることが分かりました。

学習

これまでのことを踏まえ、以下のデータを特徴量とし、ユーザが動画を見るか推測するモデルを作りました。以下、学習はロジスティック回帰を使用し、グリッドサーチでハイパーパラメータチューニングを行い、k分割交差検証で評価(k = 10)で評価を行います。

・ユーザのアプリインストール日(days)(動画を見た日から何日前かが入っている)
・ビデオのカロリー(calorie)
・ビデオの再生時間(duration)
・ビデオの料理を作るのにかかる時間(cooking_time)
・ビデオに使われている食材1つ1つ(食材が使用されていればその食材の列に1、使用されていなければ0が入る)

f:id:syou818:20190910212528j:plain

テストデータに対して90.4%もの正解率が出たものの・・

f:id:syou818:20190901230733p:plain

上のような混同行列になり、またF1スコアは0.059となりました。これはつまり新規データに対して約6%程度でしか動画を見るか判断できないというものです。混同行列を見たときに、真陰性が7810と、他の要素より飛び抜けています。このことから、今回はとりあえず動画を見てない方に分類してしまえばおおよそ正解してしまうために、正解率は高く出たのだと推察されました。偽陰性が806であることからも上記のことが言えるはずです。

色々悩んだ挙句、どの特徴量を用いればより良いモデルを作れるか検討するために、ランダムフォレストを用いて、今回のモデルの特徴量ごとの重要度を算出してみることにしました。結果は以下のようになりました。値が大きい方がより重要な特徴量であり、範囲は 0 ~ 1 です。

days:0.42, dulation:0.04, calorie:0.03, cooking_time:0.02・・・

これより、daysだけとても重要であることが分かりました。今回ユーザの情報として用いたのはこのdaysだけであり、より良いモデルを作るために、ユーザの特徴量を増やしていけばいいのだと考えました。

精度向上に向けて

次の3つのことを行いました。

①特徴量ごとの分布を正規分布に近づける
学習率は下がってしまうけれども、今回のような予測では、癌予測のように癌である人を確実に当てなければいけないものというよりは、様々なデータに対して予測できる汎化性能の方が重要と考えたためです。正規分布に近づけるアプローチとして、それぞれの特徴量の箱ひげ図と基礎統計量を観察し、外れ値を除去するように、平均値と中央値が近づくように、データを切り取ることをしました。

②データ数を増やす
これまで使用したデータはベルヌーイサンプルによってランダム抽出を行なっていました。データ数を増やす事によって、さらに汎化性能が上がることが予想できます。

③特徴量を増やす
先程までの特徴量に加え、以下の特徴量を追加しました。
・ユーザがお気に入りしている動画の個数
・動画を見たときにユーザがログインしていたか(していれば1、していなければ0とした)
・ユーザがプレミアム会員か(プレミアム会員ならば1、通常会員ならば0とした)

この結果、

テストデータに対する正解率:94.2%

f:id:syou818:20190901233943p:plain

F1スコア:0.76

となり、これまでで最も良い結果が得られました。

このモデルを利用した機能提案

ユーザがアプリを開いたとき、そのユーザーに合ったおすすめ動画を4つ表示させる

ユーザがアプリを開いたときに、API通信をしてdatabaseにアクセスします。そのdatabaseには各ユーザに対して提案する4つの動画が含まれており、このdatabaseを作るのに先程までのモデルを使用します。以下イメージ図です。

f:id:syou818:20190910215259j:plain

databaseの作り方
各ユーザに対して任意の数の動画をモデルに入れます。そこであるユーザーに対して、見ると判断されたビデオが4つ以上あったら、ランダムに4つの動画をそのユーザーに対する推薦動画としてdatabaseに入れます。もし見ると判断されたビデオが4つ未満のときには、4つに満たない分だけビデオの視聴ランキングの上位から選択して追加し、そのユーザーに対する推薦動画としてdatabaseに入れます。このdatabaseは様々な条件を考慮しつつ適当な頻度で更新をかけます。

databaseに登録されていないユーザーに対しては、ビデオの視聴ランキング上位4つを推薦することとします。

4. データサイエンスチームの取り組みに参加した

以上までが、私個人が取り組ませていただいた課題になります。
この他に、データサイエンスチームとしての取り組みに、私も一部参加させていただきました。内容はsakuraさんの記事にうまくまとめられています。

tech.dely.jp

tech.dely.jp

成果報告会を行なった

インターンの成果発表会を1時間ほど行わせていただきました。開発部の他チームからも聴きに来てくださったり、多くの質問をしてくださったりなど、とても貴重な時間でした。

最初どういうバックグラウンドの人が聴きに来てくださるか分からなかったので、どこまで説明するかとても悩みました。また、結構緊張しましたが、終始柔らかいムードで聴いてくださったためとても話しやすかったです。

発表は、使い慣れてるPowerpointがパソコンに入っていなかったため、Jupyter NotebookのRISEを用いて行いました。

qiita.com

終わりに

いかがでしたでしょうか?
この1ヶ月でとても多くの経験をさせていただき、物凄く成長することができました。これにはデータサイエンスチームの方々が、私のどんな質問にも丁寧に答えてくださったり、多くの貴重なお話をしてくださったことに尽きると思います。リソースが限られている中で、私に多くのリソースを割いてくださり本当にありがとうございました。

急成長を続けているベンチャー企業のスピード感を肌で感じつつ、皆がプロダクトのために熱心に取り組んでいる環境で急成長を遂げたい方、是非インターンに挑戦してみてください。

データサイエンスチームの取り組み Presto勉強会

f:id:sakura818uuu:20190826152215p:plain

はじめに

こんにちは。データサイエンスチームのsakura (@818uuu) です。
クラシルの検索改善を担当しています。

データサイエンスチームでは今月 Presto勉強会 を毎日行っていました。
本記事ではその取り組みをご紹介しようと思います。

Prestoとは

Prestoとは、Amazon Athenaで使用されている分散SQLエンジンのことを指します。
※本勉強会ではPrestoで動く「SQL記法」を勉強しています。

概要

Presto勉強会の概要です。

[内容] Prestoの公式ドキュメントを読み知見を深める
[目的] Prestoの知られざる機能などを学び、開発効率化に活かす
[時間] 毎日ランチに行く前の10〜20分
[参加者] データサイエンスチームメンバー(3人〜4人)

f:id:sakura818uuu:20190826150059p:plain
こんなかんじでディスプレイに映しながら話し合いました

Presto勉強会を一言でいうと
「毎日ランチ前にチームでPrestoの公式ドキュメントを読む取り組み」です。

実施内容

f:id:sakura818uuu:20190820175434p:plain
このページの単元を一つずつ読んでいきました

上記のドキュメントに従い1日大体1章ごと進めていきました。
わからないところは実際にAthenaでクエリを書いて試しながら進めていきました。

f:id:sakura818uuu:20190826115411p:plain
json_size関数を試している図 (公式ドキュメントだけでは理解するのが難しかったため)

取り組んでみた感想

複数人で公式ドキュメントを読む機会はなかなかないので貴重な経験となりました。

一番驚いたことは、ドキュメントの読み取り一つでも自分とメンバー間に違いがあったことです。
人によって読み取り方が様々で、
「この一行からそこまで読み取ることが出来るんだ」
「このメンバーだとそういった応用例まで考えているのか」
と技術ドキュメントの読み取り方の勉強としても参考になりました。


また、いくつか今後業務で活かせそうな関数も発見することができました。

f:id:sakura818uuu:20190826113346j:plain:w300
今後活かせそうな関数などをメモしています

もちろん、bool_and()to_iso8601(x) などこれいつ使うんだと思った関数もたくさんありました笑
一通り全ての関数に目を学んだことで「Prestoで出来ること/出来ないこと」を把握できたのがよかったと思います。

おわりに

Presto勉強会で公式ドキュメントを一通り読んだことで、チーム全体でPrestoへの理解が深まりました。
この勉強会で得た知見を業務に活用していきたいと思います。


データサイエンスチームでは、Presto勉強会やサーベイチャレンジなど様々な取り組みを行っています。もしご興味があればご応募・ご連絡ください: )

www.wantedly.com

データサイエンスチームの取り組み サーベイチャレンジについて

はじめに

こんにちは。データサイエンスチームのsakura (@818uuu) です。クラシルの検索改善を担当しています。

データサイエンスチームでは今年の3月から サーベイチャレンジ という取り組みを行っています。 本記事ではその取り組みをご紹介しようと思います。

概要

サーベイチャレンジの概要です。

[内容] 論文を読み、データサイエンスチーム内で共有する
[目的] 料理に関する様々な研究を知る・様々な分野の最先端技術を知る
[作業時間] 基本的に業務時間内に実施
[共有時間] 週1回のチームMTG内で共有及び議論
[参加者] データサイエンスチームメンバー(2人〜5人)

サーベイチャレンジを一言でいうと
「みんなで論文を読み、その知見を共有する取り組み」です。

進め方

サーベイチャレンジの進め方を紹介します。

  1. 各々が読む論文をmendeleyに格納し、チーム内で共有
  2. 論文を読んだ感想をテンプレートに従って記載。(Githubのissueで管理)

f:id:sakura818uuu:20190819170507p:plain
論文を読んだ感想のテンプレート

3.週1回のチームMTGでそれぞれがその週読んだ論文について議論し合う

当初はGithubのみで管理していましたが、論文の管理が少し面倒でした。
そこで文献管理ツールのmendeleyを導入し、簡単に一括管理ができるようにしました。便利です。

実施内容

どんな内容の論文を読んでいるか少しご紹介します。

f:id:sakura818uuu:20190820091307p:plain
実際に読んだ論文1

f:id:sakura818uuu:20190820092454p:plain
実際に読んだ論文2

f:id:sakura818uuu:20190820092334p:plain
今までに読んだ論文の一部

今までに30本以上の論文を議論しています。

取り組みに抱く感想

サーベイチャレンジを始めたことで論文を読む習慣が出来ました。 一人だとすぐ飽きてしまいそうですがチームで共有し議論することで一定のペースを保てて継続出来ているのが良いことだと思います。

また、実際にやってみてサービス開発に活かせるような知見をたくさん発見できたのが驚きでした。論文というと少し学術的な方面によっているのかな・・と思っていたのですがめっっちゃ役に立つ情報が眠っています。

最後に

サーベイチャレンジをすることで継続的に最先端の技術を知ることが出来たり、様々な研究内容をチーム内で共有し議論することができています。
今後も続けていき新たな発見をしていければと考えています。

Xcode11でデバッグ機能がいい感じにアップデートされたので紹介

こんにちは!クラシルiOSアプリを開発しているknchstです。

6月のWWDC19はSwiftUIなどのサプライズもあり、とても盛り上がりましたね!様々なセッションがあったのですが、個人的にいいなと思ったのがXcode11のデバッグ機能についてです。

この記事では以下の項目について紹介します。

  • Device Conditions
  • Environment Overrides
  • Debugging SwiftUI View Hierarchies

Device Conditions

f:id:knchst:20190801105018p:plain
https://developer.apple.com/videos/play/wwdc2019/412/

Thermal state condition

Xcode11から新たに端末の発熱をシミュレートする機能が実装されました。 これにより、端末を実際に発熱させることなく温度状態によるアプリの動作を確認することができるようになります。

Xcode11のメニューのWindowDevices and Simulators内に DEVICE CONDITIONS という項目が追加されていて、ここで設定することができます。

f:id:knchst:20190801114925p:plain

設定できる項目

  • Fair(わずかに高い状態、バックグラウンドフェッチなどが延期される)
  • Serious(高い状態、CPUやGPS、Bluetoothなどの使用量が削減される)
  • Critical(かなり高い状態、あらゆるリソースが最小限になる)

上記の3つをシミュレートすることができます。

Network link condition

通信状態をシミュレートする機能はiOS端末単体ではありましたが、今後はXcodeから直接端末の通信状況をシミュレートすることもできるようになります。

こちらもThermal Stateと同様でXcode11のメニューのWindowDevices and Simulators内に DEVICE CONDITIONS という項目が追加されていて、以下画像赤枠内で設定することができます。

f:id:knchst:20190801112938p:plain

デフォルトで設定できるプロファイルも

  • 100% packet loss
  • Very poor network
  • Edge Network - poor
  • Edge Network - average
  • Edge Network - good
  • Edge Network - best
  • 2G Network - poor
  • 2G Network - better
  • 3G Network - average
  • 3G Network - good
  • 3G Network - bet
  • LTE Network
  • WiFi Network
  • WiFi Network (802.11ac)
  • DSL Network
  • High Latency DNS

と増えて使いやすくなりました。

Environment Overrides

f:id:knchst:20190801103701p:plain
https://developer.apple.com/videos/play/wwdc2019/412/

Appleのプラットフォームには表示に関する設定や様々なアクセシビリティがあり、ユーザーが様々な設定を行うことができます。例えば、

  • ライトモード & ダークモード
  • ダイナミックタイプ
  • アクセシビリティ

などの項目があります。 これらの設定が変更された時に、アプリケーションのレイアウトが崩れないかを確認する必要があります。これらをデバッグする為にEnvironment Overridesという機能が新たに追加されました。

WWDCで行われたデモが以下になります。

f:id:knchst:20190801133322g:plain

Xcodeのデバッグ領域にEnvironment Overridesというボタンが追加されていて、ここから値を変更することができます。変更はリアルタイムに反映されます。また、シミュレーター・実機でも同様に動作します。

Debugging SwiftUI View Hierarchies

f:id:knchst:20190805115656p:plain

SwiftUIが新たに加わりView Hierarchiesのデバッガーも大きくアップデートされました。

Swift Reflection

SwiftUIでは、ビューにあるプロパティが自動でインスペクトされデバッガーに表示されるようになります。実際にデバッガで確認してみると、インスペクタにProfileViewのプロパティの情報が表示されています。

f:id:knchst:20190805123941j:plain

CustomReflectable

新たに追加されたCustomReflectableプロトコルに準拠することによって、独自のプロパティをインスペクタに表示することができます。

f:id:knchst:20190805123757j:plain

SwiftUIとその他のフレームワークのView Hierarchies

SwiftUIのプロジェクトでは既存のUIKitで提供されているUIViewControllerなどのクラスが利用できます。

f:id:knchst:20190805122645p:plain

上の画像のオレンジ色の枠はUIKitで実装されてもので、青色の枠はSwiftUIで実装されたビューになります。

まとめ

SwiftUIの登場によってデバッグもリアルタイム性がでてきて、より効率的な開発ができるようになりました。またDevice ConditionsやEnvironment Overrideなどのハードの機能をシミュレートできる機能を活用することによって再現しにくいランタイムエラーをデバッグすることができるので、リリース後の予期せぬ不具合も未然に防ぐことができます。

この記事で全てを紹介できていないので、詳しく知りたい方は以下をご覧ください。