LIVESENSE Data Analytics Blog

リブセンスのデータ分析、機械学習、分析基盤に関する取り組みをご紹介するブログです。

LivesenseDataAnalyticsBlog

MENU

「UXデザインが総論賛成、各論疑問になる理由」とプロジェクト設計で意識したい3つの条件【前編】

 テクノロジカルマーケティング部 データマーケティンググループにてUXリサーチャー/UXコンサルタントをしている佐々木と申します。普段は、UXデザイン(以下、UXDと略記)に関するプロジェクトを事業部横断で支援する業務についております。

 リブセンスでは、サービスの戦略レイヤーや主要機能と結合度の高い領域について機械学習を適用することに挑戦しており、データマーケティンググループは、UX担当とアナリティクス担当が並存しているグループになります。

グループとしての取り組みは、こちらの記事等もご参照下さい。 「競争優位性構築のための人間中心機会学習〜CVRからUXへ〜」

analytics.livesense.co.jp

 私は、UXDを社内に普及する立場であり、かつ、UXDがビジネスに必要な要素であると強く認識しておりますが、全てのプロジェクトにUXDを多くの時間を掛けて取り組むことが適切な姿であるとは考えておりません。今回、まず社内で陥りがちな「UXDが総論賛成、各論疑問?に陥りやすい理由」を述べ、次いでUXDのプロジェクトを選定する時に意識したい条件について”学びほぐし”も兼ね整理してみます。

1.UXDが総論賛成、各論疑問?に陥りやすい理由

 まず、多くの企業では、UXDの業務は「総論賛成、各論疑問?(反対するまでではない)」の状態になっている事が多いと推察します。私自身も新規事業や新商品・サービス開発を担当する当事者として、そのような場面を何度か経験してきました。また支援する立場になり周囲の話を聞いても「UXDの必要性は理解してもらえたが、どこまでの工数と時間を掛けるのが適切なのかが判断できず、実際のプロジェクトでは見送られている事も多い」と聞きます。ここでは、まずその理由と対応の考え方について整理します。

1.1 UXDが総論賛成の理由

  プロジェクトマネジメントやサービスデザインの分野でも多く述べられておりますが、ここでは図1.1に示した様に事業を「ヒトの視点(ユーザー要件)」「モノの視点(技術要件)」「ビジネスの視点(ビジネス要件)」で整理して考えます。この時、事業が成功する条件は、その3つの円が重なる中央の★印に辿りつけた時になります。その時「ヒトの視点(ユーザー要件)」が満たされることは必須なので、UXD(人間中心設計、以後HCDと略記)は反対はされずに総論賛成になり易いと理解しています。

f:id:livesense-analytics:20180606173055p:plain
図1.1事業を構成する3要素

1.2 UXDが各論疑問になる理由

 次に、総論賛成になりながら各論疑問になる理由は「UXD(HCD)のアプローチのみが事業が成功するアプローチでは無く、UXDの選定と適切な設計ができていないから」だと考えてます。

1.2.1 UXDが事業が成功する唯一のアプローチではないから

 事業が成功するためのアプローチは様々です。例えば

  • ビジネス視点で、流行りのビジネスモデルを起点に、自社が強みとする経営資源(アセット)と組み合わせて新事業・新サービスに取り組む
  • モノ視点で、技術イノベーションを目指し、今流行りのブロックチェーンやAI(深層学習)等を軸に新規事業・新サービスに取り組む
  • ヒト視点で、ユーザーの未解決の課題を発見しその課題を起点に新規事業・新サービスに取り組む

等、が挙げられます。実際は、3つの視点を個別に進めるというより、平行しながらその比重を変えて進めることになります。

 サービスデザインの分野でも図1.2.1の様に、3つの視点で平行して調査を進めサービスをデザインします。実施のプロジェクトでは、3つの視点で平行しながら、時には戻りながら、多様なアプローチで進めることを推奨しております。

 また私の経験では、サービスデザインの領域外のプロジェクトにおいても図1.2.1の様に、3つの視点で平行しながらプロジェクトを進めることが多いです。その場合も可能な限り初期の段階からユーザー要件の理解を深めることは効果的ではありますが、初期より深いユーザー理解をすることだけが唯一絶対の方法ではありません。3つの視点を平行しながらバランスをみてユーザー理解に掛ける工数を見極める必要があります。

f:id:livesense-analytics:20180523162712p:plain
図1.2.1.サービスデザインにおける3つの視点と5つのステップ ※千葉工業大学 安藤教授「サービスデザインとUXDそしてデザインプロセス」資料より

1.2.2 UXDの選定と設計ができていないから

 前節で3つの視点を平行しながらバランスを見てプロジェクトを進捗することをお勧めしましたが、ユーザー調査の必要性や工数を見極める為にプロジェクトの対象となるUXの範囲を意識することも有効です。UXの範囲の一事例として、図1.2.2に示した4つのUXの期間モデルを用いて対象となる範囲について考えてみます。

f:id:livesense-analytics:20180612140514p:plain
図1.2.2 UXの期間モデルと異なる視点  ※2011年2月:Use experience Paper,HCD Value:”UX白書の翻訳と概要”資料を元に作成

 図1.2.2には、UX白書でも述べられている時間軸で変化した4つのUXの期間モデルと、それぞれの期間モデルを業務の中で述べやすい担当者の関係を整理してみました。この図に示した様にUXと一言で言っても、語る人により、該当する範囲の認識がずれていることが多くあります。

 例えば、営業の方や、SEO担当者が述べるUXは、実際の経験をする前の”想像された経験”である「予期的UX」であることが多いと言えます。営業の方は、まだ実現していないサービスや製品に対するユーザーの声であることが多いと思います。また、SEO担当者は、ネットを通じて情報検索をする際に、求める情報に行き着く前の行動や心情が対象になりますので「予期的UX」が主な対象になると言えます。

 次いで、UI/UXデザインの担当者は、ユーザーインタフェイスを使っている瞬間である「瞬間的UX」、サービスデザインの担当者は、"瞬間的UX"が連続したエピソードとしてサービス設計をすることが多いので「エピソード的UX」、ブランドデザインの担当者は、各種サービスの”エピソード的UX”を積み重ねることによりブランド価値の向上を目指すことが多いので「累積的UX」を中心に語られる傾向があるといえます。

 よって、UXに知見がある人々が集まったプロジェクトであっても、今回のプロジェクトで対象がどの範囲のUXが該当とするのか?を確認する必要があります。また、確認した結果、全てのUXが対象だとしても、第一歩として取り扱うのはどの範囲なのかを選定し、ステップを分けてプロジェクトを設計できると、より適切な工数を導き出すことがし易くなります。

 このUXの範囲を不明確なままにしてプロジェクトを設計すると、全てのUXが対象になり、ユーザー調査に必要な工数が重くなる傾向があります。またブラックボックスになりますので、必要性の判断がより難しくなり、UXのプロジェクトがより疑問となり易いと考えています。

 UXDの選定や工数を判断する為の材料として図1.2.2の4つのUXモデルの事例を紹介しましたが、その他にも1.2.1節で述べた、ビジネス視点・モノ視点・ヒト視点のバランスをみることや、次に述べる重視したい3つの条件、等も考慮して検討することになります。今、弊社はその知見を実務ベースで蓄積している状況です。

2.UXDのプロジェクト設計で意識したい3つの条件と事例

 先にUXDを取り組むにはプロジェクト選定と設計が肝になると述べましたが、ここではUXDのプロジェクトを選定し設計する際に意識したい3つの条件と、その事例を紹介します。

現在、私たちが取り組んでいる中で意識している条件は3つあり

  • <その1>サービス・ドミナント・ロジックを中核とするサービスかどうか
  • <その2>潜在ニーズを探る必要性の有無
  • <その3>改善・改良を目的とした既存事業・既存サービス

になります。

2.1 <その1>サービス・ドミナント・ロジックを中核とするサービス

 まず始めに、サービス・ドミナント・ロジックを中核とするサービスを挙げます。 ここでは、図2.1のグッズ・ドミナント・ロジックとサービス・ドミナント・ロジックの概念を用いて説明します。これは、HCDがその一部の概念として採用されているサービスデザインの核となる考え方です。

f:id:livesense-analytics:20180522163949p:plain
図2.1 サービス・ドミナント・ロジック ※富士通総研「企業の競争力を高めるICTの新たな活用法とマネジメント 第2回」の図を元に作成

 近年のビジネスは、インターネットの普及や通信・流通のインフラの発展に伴い、 「モノ」を購入し所有することに価値を置いたビジネスから、所有せずシェアする等の「利用体験」に価値を置き費用を支払うビジネスに流行がシフトしていると言われております。

 この従来の「モノ」売り主体のビジネスをグッズ・ドミナント・ロジックと言い、「モノ」と「サービス」は単独で扱われており、お金と交換して所有することによりビジネスが成立します。

 これに対してサービス・ドミナント・ロジックは、「モノ」は「サービス」の構成要素の一つで一体であると考え、顧客はサービスを利用する体験そのものに価値を感じ、対価を支払うことによりビジネスが成立します。

 この分類を用いて、該当する事業がグッズ・ドミナント・ロジックの事業・サービスなのか?、サービス・ドミナント・ロジックの事業・サービスなのか?を判断し、後者のユーザーの体験価値を差別化要因とする事業・サービスを、UXDを用いる対象とした方が成果をあげやすいプロジェクトを選定できます。

 また、現状は前者のグッズ・ドミナント・ロジックの事業であっても、今後目指す理想的な事業の姿としてサービス・ドミナント・ロジックを志向する場合もプロジェクトの対象とすることができます。ただしこの場合は、新しい事業を推進する為に事業構造改革が必要になりますので、より難易度が高くなります。

 弊社はWEBサービス事業を展開しており、基本的に全てこのサービス・ドミナント・ロジックの条件に該当するサービスになります。よって、追加の条件として、前述したUXの範囲や、後述の2条件も含めて、プロジェクトの選択や設計をします。

 例えば、対象とするUXの範囲の事例では、「ブランドやユーザーがサービスを利用するコンテキストは既に明確になっており、UI/UXに代表される「使いやすさ」を重視したUXリサーチを対象とする」のか「WEBサービス外までの領域まで含めて新たなブランドコンセプトとなる体験価値を見つけることを重視したUXリサーチを対象にする」のか等が考えられます。プロジェクトの目的を明確にすることにより更に詳細な検討が可能になります。

2.1.1 サービス・ドミナント・ロジックに必要なリサーチ手法

 サービス・ドミナント・ロジックの事業にUXDのアプローチが有効な理由の一つに「従来成果を挙げてきたリサーチ手法が限界になっている」ことを挙げます。

 前者のグッズ・ドミナント・ロジックの事業・サービスにおいては、顧客の購買履歴を中心とするマーケットリサーチを行うことにより、効率的な事業判断を行えてきたとも言えると思います。

 しかし後者のサービス・ドミナント・ロジックの事業においては、従来のマーケットリサーチのみでは限界があります。あるサービスを利用した体験は、人によって得る体験価値は異なるため、従来のマーケット(購買履歴)を中心とした購買特性の調査のみでは差別化要因となるユーザーの体験を確認することはできないからです。サービスを利用する利用文脈(コンテキスト)や体験価値まで踏み込んだ調査(UXリサーチ)がより重要になります。

 また、同じUXリサーチでも、前述した様に対象となるUXの範囲が異なると手法を変えることが多いです。例えば、UI/UXデザインの「使いやすさ」を対象とするリサーチ、WEBサービス外の体験価値まで含めて対象とするリサーチ、では手法を変えて用います。後者の方がより広範囲の深いユーザー理解が必要なので時間と工数が掛かる傾向があります。既に述べていることと重複しますが、どの様なUXを対象とするかによって、リサーチの手段や必要な期間は変化しますので、プロジェクトの選定や設計をする際に重要になります。

3. 前編のまとめ

 以上、「UXDが総論賛成、各論疑問になる理由」、及び、UXDのプロジェクトを選定する時に意識する3条件の一つ目である <その1>サービス・ドミナント・ロジックを中核とするサービスかどうか、について述べさせて頂きました。

 前述しておりますが、弊社は、基本的に1つめの条件であるサービス・ドミナント・ロジックを中核とするサービスに該当しますので、実務では残り2つの条件を追加で検討することによりプロジェクトの選定と設計をすることが多いです。

 次回以降に社内で取り組んでいる事例と共に、残る2条件

  • <その2>潜在ニーズを探る必要性の有無

  • <その3>改善・改良を目的とした既存事業・既存サービス

について述べさせて頂きます。

機械学習システムの信頼性エンジニアリング

こんにちは、データプラットフォームグループの田中 (@yubessy) です。機械学習基盤のプロダクトマネージャをしています。

いきなりの質問で恐縮ですが、機械学習(ML)システムの開発プロジェクトでこんな経験をされた方はいらっしゃいますか?

  • MLエンジニア: Webエンジニアに「コードの品質が低くシステムを本番運用できない」と言われた
  • Webエンジニア: MLエンジニアに「開発と本番でデータが違って精度検証ができない」と言われた

どちらかに心当たりがあるなら、同じチームにもう一方に当てはまる人がいるかもしれません。 MLシステム開発には様々な経験をもつメンバーが関わるため、システムの信頼性について重視する観点が分かれることは珍しくありません。

システム開発において信頼性は重要な要素です。 特にMLシステムには高い不確実性がつきまとうため、通常にくらべて信頼性の実現がより困難になります。 この記事では、MLシステムが安定して価値を提供するために求められる信頼性と、それに対するシステム開発・運用の観点からの扱いについて考察し、実際にリブセンスで行っている取り組みをご紹介します。

MLシステムの信頼性をどう捉えるか

システムの信頼性についてはサイト信頼性エンジニアリング (SRE) の考え方がとても参考になります。 SREの原則の中でもサービスレベルの概念は特に有用です。 サービスレベルに基づく信頼性エンジニアリングでは、信頼性をサービスレベル指標 (SLI) によって定義し、保証すべきサービスレベル目標 (SLO) を定め、SLIとSLOに基づいて方法論を展開します。 例えばWebサービスならリクエストに対するレイテンシやエラー率、バッチジョブならスループットや復旧時間などでSLIを定義し、その目標値をSLOとして設定します。 この手法はWeb開発で注目されていますが、広く一般のシステムにも適用できると考えられます。

MLシステムにサービスレベルの概念を導入するには、まずその信頼性を定義する必要があります。 通常のシステムでは人が書くコードが入出力を決定する関数となりますが、MLシステムではデータを入力とする学習アルゴリズムがこの関数(モデル)を生成します。 このためシステムに変更がなく正常に動作しているように見えても、データの変化により内部的に精度が下がっている、といったことがMLでは起こり得ます。 このようにMLシステムはデータに強い依存性をもつため、通常と異なる種類の信頼性についても考慮する必要があります。 私自身の経験から、MLシステムの信頼性は次のように区分すると捉えやすいと考えています。

  1. 通常と同じ指標で定義でき、同じ手段で実現できるもの
    • システムを構成するML以外のコンポーネントの信頼性
    • アプリケーションやインフラのセキュリティ
  2. 通常と同じ指標で定義できるが、ML特有の手段が必要なもの
    • 予測モデルを内包するWeb APIのレイテンシやモデル学習のジョブの復旧時間
    • MLを利用する機能のエンドポイントに対するリクエストのエラー率
  3. ML特有の指標と手段が必要なもの
    • 分類モデルの誤り率や回帰モデルの誤差などの精度指標
    • ノイズに対する頑健性や学習を繰り返した場合の結果再現性

このうち2については1と同一視されがちなため、特に注意が必要です。 簡単な例としてWeb APIのレイテンシの向上を図る場合、一般的なアプリケーションならばI/O処理の効率化などが有効ですが、MLモデルではパラメータ数の調整による計算量の削減といった手段が必要なこともあります。

MLシステムの信頼性が問題になるとき

さて、これらの信頼性はどんなときに問題になるのでしょうか? その説明のため、個人的な経験をもとに架空のケースを考えてみました。 以下では求人情報サービスにおける次のようなシステムを想定としています。

  • 各求人のCTRをロジスティック回帰で予測し、求人の検索結果の並び替えに利用する
  • モデルの学習と結果の予測は一連のバッチジョブとして1日1回夜間に実行される
  • 出力データはアプリケーションのDBに格納され、最後に成功したジョブの結果が常に利用される

ケース1: エラーに対する過剰な反応

ある日、予測精度の改善のためにパラメータの調整を行いました。 オフライン検証ではこの調整により平均的な精度は向上するものの安定性が多少低下することがわかりました。 そこでジョブの学習ステップの後に検証ステップを追加し、精度が以前のモデルより低い場合にはジョブを途中終了させて予測とデータ更新が行われないようにしました。

リリースから数日後のジョブ実行で、精度が以前のモデルを下回りジョブが途中終了しました。 これをジョブ管理システムが異常終了として検知し、運用担当者にアラートが通知されました。 運用担当者は直ちにジョブの再実行を試みましたが、ジョブは再び途中終了しました。 最終的に運用担当者はシステムを開発したMLエンジニアに連絡を取り、アプリケーションからは前回の予測結果が利用されるため数日間ジョブが失敗しても問題ないことを知らされました。

ケース2: 効果的でない手段の適用

ある日、開発環境で精度検証を行ったところ、本番環境と全く異なる異常な予測値が得られました。 このままではモデルの改善に支障をきたします。 開発用DBの主なデータは日次で本番用DBと同期されていたためデータに大きな問題はないと考え、コードの中で環境条件が影響する箇所を確認することにしました。 しかしコードの中では到るところでデバッグ出力のON/OFFなどに環境情報が使用されていました。 そこで数日間かけて大規模なリファクタリングと単体テストの追加を行い、環境差異を局所化した上で再度精度検証を実行しましたが、やはり結果は異常なままでした。

後日改めて精度検証を実行してみると、今度は本番との差異が全く見られませんでした。 そこで改めてデータの同一性を厳密に確認したところ、次のことが判明しました。 開発用DBの初期化と本番からのデータ同期は毎朝行われ、その直後に精度検証を実行すれば本番と同じ結果が得られます。 ただし開発用DBはアプリケーション開発と共用であり、開発中に不自然なダミーデータが混じりこむと、それが学習データの中の外れ値となってモデルに破壊的な影響を及ぼすようです。

MLシステムの信頼性エンジニアリング

上のようなケースはMLシステムの開発に関わった方ならある程度馴染みがあるかと思います。 では、こうしたケースはどんな原因から発生し、どうすれば解決できるでしょうか? 様々な方策が考えられますが、私自身は以下のような方針を重視しています。

サービスレベルについての認識を関係者間で一致させる

信頼性エンジニアリングにおいては、過度の可用性や耐久性を追求するのではなく、求められる水準に対して必要十分な手段をとることが大切です。 特にMLシステムでは、信頼性を通常と同じ指標で定義できても、通常と同等の水準を保証するのが困難なこともあります。 このような場合にMLシステムを通常と同じように扱おうとして過度のコストをかけてしまわないよう、開発・運用に関わるメンバーが必要十分なサービスレベルを認識しておく必要があります。 場合によっては前述のような信頼性についてSLI/SLOを明示することも有効と考えられます。

ケース1では、MLエンジニアと運用担当者の間でサービスレベルの認識が一致していなかったと思われます。 MLエンジニアはモデルの安定性が低いことを考慮した上で、実際のCTRは数日では大きく変動しないため過去の予測結果が使われても問題ないと考えたかもしれません。 一方運用担当者はMLジョブのサービスレベルを売上計上ジョブなどと同じよう捉え、ジョブの途中終了がサービス上の損失に繋がると考えたかもしれません。 結果的にはアラートの仕組みやそれに対する対応は過剰なものでしたが、根本的な問題はジョブの復旧要否や復旧時間について共通認識を築けていなかったことです。

システムに求める信頼性に応じた手段を適用する

システムの信頼性を実現するには、そのシステムの性質に合った手段を選ぶ必要があります。 通常のシステムでは可読性やテスタビリティのようなコード品質が信頼性の実現に大きな役割を果たします。 MLシステムでもコード品質は重要ですが、前述の2,3番目のような信頼性をコード品質だけで担保するのは困難です。 例えばコードレビューが「人の目を利用して信頼性を実現する」手段だとすれば、MLシステムではオフライン検証などの「データを利用して信頼性を実現する」手段が有効になります。

ケース2では、コードのバグが原因であるという誤った仮説に基づいて、リファクタリングや単体テストの追加といったコード品質の向上に多くの労力が割かれました。 もちろん元々のコード品質が良好なら他の原因にいち早く気づけた可能性もあるため、これは決して無駄な行為ではありません。 しかし一度問題が発生した後では、数日間かけてコード品質を向上させるよりも、まずデータの同一性をより厳密にチェックするべきだったと考えられます。 特にMLの場合、通常の開発では問題にならない小さなデータの差異がモデルに大きな影響を及ぼすことがあるため、大部分のデータが一致しているだけでなく件数・値域・分布などあらゆる面からデータを検証することが必要です。

信頼性の実現手段を適用できる仕組みを整備する

目的に応じて手段を適用すべきとわかっていても、それが簡単にできるわけではありません。 複数のMLシステムが継続的に開発・運用されるような現場では、手段の適用を支える仕組みの構築にも注力すべきです。 例えばモデル開発に利用するデータは、一元的に管理され開発者が簡単に利用できることが理想的です。 さらに、同じシステムの一部であっても、MLモデルとデータ入出力のような各機能には異なる性質の信頼性が求められます。 これらを疎結合化し、独立して実行できるようにすれば、コードレビューの分担やモデルの精度検証もやりやすくなります。

ケース2でも、モデル検証に利用するDBとアプリケーション開発に利用するDBが異なっていれば問題自体が起きなかったかもしれません。 またMLモデルの部分が他と独立化されていれば、モデル学習の実行前にデータの異常を検知する処理を導入するなどの手段が取れたかもしれません。

リブセンスでの取り組み

実際のMLシステム開発の現場では、それぞれの環境に応じて試行錯誤が行われていると思います。 リブセンスでのMLシステム開発においても、数年間にわたり成功と失敗が積み重ねられてきました。 そのような経験をもとに、現在社内で実践しているいくつかの取り組みについてご紹介します。

システムの開発段階に応じたフェーズの導入

サービスレベルの認識を合わせることはとても重要ですが、ひとつひとつのシステムに細かく運用上のルールを定めるのもそれはそれで手間がかかります。 そこでMLチームでは、システムに対して求められる信頼性と試行錯誤の自由度を把握しやすいよう、実環境で運用されるMLシステムを次のいずれかのフェーズに区分しています。

フェーズ 開発・運用の目的 サービスレベル 信頼性エンジニアリングの注力点
コンセプト検証 コンセプトの実現性の検証 SLI/SLOを定めない インフラやデータの整備と提供
技術検証 最適な技術手段の検証 個別に判断 システム設計やトラブル対応の協力
実運用 継続的な価値提供 SLI/SLOを定める 安定性・メンテナンス性の維持

例えば前述の予測システムならば、「CVRによる検索結果の並び替えがユーザ行動に影響するか」といった検証を目的とする段階では、コンセプト検証フェーズとしてSLI/SLOを定めず一部のユーザ・求人に限定してリリースすることが考えられます。 コンセプト検証がうまくいったとしても、精度が安定せずユーザへの価値提供に懸念があるような場合、技術検証フェーズではフェールオーバー方式の導入など運用課題の解決を目的とした検証と改善を行います。 こうして継続的に価値を提供できるようになれば、実運用フェーズに移行してSLI/SLOを設定し、ジョブの復旧体制やアラートルールを整備するといった要領です。

細かい条件にはまだ考慮の余地がありますが、目安としてのフェーズを決めることで、個別にルールを定める手間を省きつつサービスレベルの共通認識を形成しやすくなります。 これにより運用負荷を抑えつつ試行錯誤のプロセスを効率化しやすくなると感じています。

データ分析基盤・ML基盤を活用した再現性の担保

リブセンスでは全社データ分析基盤としてLivesense Analyticsを開発・運用しており、MLシステムの入力ソースは本番・開発ともできる限りここに一元化しています。 このため環境毎にデータが異なり再現性が担保できないといった問題は起こりにくくなっています。

また最近ではML基盤をGoogle Kubernetes Engine (GKE)上に構築し、各MLシステムを移行・集約しています。 この基盤では開発環境でも本番と同スペックのプリエンプティブルインスタンスを活用するなど、コンピューティング環境の差異もなるべく小さくなるよう工夫しています。

この話題については以下の記事でも解説しています。 analytics.livesense.co.jp

コンテナ技術によるコンポーネント化の推進

リブセンスでは現在、新しいシステムや既存システムのリプレース実装を開発する際に、MLのモデル部分をアプリケーションから独立したコンポーネントとして切り出すようにしています。 各コンポーネントは単一のコンテナとして動作するよう設計しており、データはファイルとして共有ボリュームやクラウドストレージを経由して受け渡しています。

この話題については以下の記事でも解説しています。 analytics.livesense.co.jp

各コンポーネントが独立していると、それらをまとめるワークフローエンジンが必要になります。 そこで最近ではKubernetesのCustom Resourceとして動作するワークフローエンジンであるArgo Workflowを試験的に導入しています。 Argo Workflowを用いると複数コンテナから構成されるワークフローが構築しやすく、上記のような構成の利点を活かせると考えています。 この話題については今後機会があればご紹介したいと思います。

Open source container-native workflow engine for Kubernetes | Argo

さいごに

リブセンスでは数年前に機械学習をサービスに導入し始めました。 初期の模索の段階を経て、現在ではMLシステムの信頼性を支える仕組みが徐々に整ってきています。 今後もこの中で得られた知見を共有していきたいと思います。

参考資料

分析基盤のバッチ処理構成を考える

データプラットフォームチームの橋本です。 日頃は分析基盤LivesenseAnalytics(LA)の保守・運用を担当しているエンジニアです。最近は専ら、バッチ処理のリファクタリングを懇々と進めていました。今回はその内容をまとめてみます。

分析基盤のバッチ処理

LAのバッチ処理基盤についてはこのへんを見ていただくとして、私が着手しているのはその中でもairflow-workerと呼称しているバッチ処理の集まりになります。その昔、Airflow導入以前に、cronでバッチ処理を運用していた頃から使い続けているrakeタスクの集まりで、類似の分析基盤を保守しているプロジェクトではよくある感じのバッチ処理集になっています。AirflowはDAGで設定された定時、ないしは任意のタイミング(ユーザーからのキック)で、airflow-workerのrakeタスクを起動します。この時、起動されるrakeタスクのIOは、概ね以下のようになります。

f:id:livesense-analytics:20180611112817p:plain

ところで、このairflow-worker。かつて、cronで運用されていた時代から引き継いだタスクの集まりで、チーム内では追加・修正の頻度が最も多いコードの1つなのですが、その分歴史と修正の積み重ねも厚く、多くの問題を抱えるようになっていました。具体的には、

  • 処理がrake直書きになっていて、テストが書けない
  • コンポーネントの配置やディレクトリ構造、命名に一貫性がなく、見通しが悪い
    • コードリーディングで繰り返される grep -r
    • 違う名前で機能が重複する実装
  • DRYじゃない・繰り返されるコード
    • task_env = ENV["task_env"] || "development"
    • エラーハンドリングと通知
  • dryrunできない・実装を逐一書かねばならない

などなどです。もともと素朴な定時処理から始まっているコードであるがゆえ、リリースの即時性に重きをおいて運用してきたため無理もないのですが、このまま可読性・保守性を犠牲にして拡張を続けると、

  • 新規参入エンジニアの立ち上がりコスト増加・属人化
  • 拡張コストの増加
  • バグの温床化

といったデメリットが考えられたので、思い切って大規模なリファクタリングを敢行しました。

リファクタリングの方針と改善のポイント

まず次の2点を大まかな方針としました。

  • 細かい問題点はたくさんあるが、大きな問題を優先して期間内に終わらせる
  • 理想形には近づけるが、完璧にしない

リファクタリングの作業自体は重要な仕事ですが、理想形を目指し続けると際限がないので、とにかく大きな問題点をザックリ改善し、今後の継続的なコード改善への足がかりにする方針としました。 上記のIOの図の通り、定時のバッチ処理は概ね

  • 日時やタスク名、スキーマ名などのパラメータを受けてタスクがキックされる
  • テンプレートからクエリを発行したり、日付の範囲をとったAPIに投げるパラメータを作る
  • 実際に処理を行うマネージドサービスのAPIを呼び出す、あるいはクエリを投入する

の3ステップで構成されています。そこで、バッチ処理のコンポーネントを図のように3つに分割しました。

f:id:livesense-analytics:20180611124506p:plain

改善のポイントは

  • コンポーネントの構成を整理
  • rakeタスクの処理を別のクラスへ委譲:rakeタスクとLATaskクラス
    • バッチ処理のインターフェースとロジックを分離する
    • ロジックに対してユニットテストを導入する余地を作る
  • APIアクセスをFacadeにまとめる
    • 可能な限りAPIアクセスをdryrunできるようにする
  • 分割しにくい部分や広域なスコープで共通化された処理等は、思い切ってUtilsとして切り出す

としました。以下、その内容をもう少し具体的に見ていきます。

クラスとパッケージの構成

f:id:livesense-analytics:20180530163538p:plain

クラス構成はザックリ上記の様な感じにしました。モジュールは大きく4つに分けて、以下の構成にしました。

  • API
    • API呼び出しを行うFacadeのモジュール
    • dryrunの機能をここに集約する
    • このパッケージのクラスは状態を保持しないようにする
  • LATask
    • 処理の実装を持つクラスのモジュール
    • 親クラスにエラーハンドリングやロギング、環境変数の参照などを集約する
  • rakeタスク
    • rakeタスクは各々のタスクに対応するLATaskクラスへ処理を委譲する
    • LATaskのインスタンスを呼び出し、パラメータを渡して処理を実行するだけ
  • Utils
    • バッチ処理全体で頻発する処理や、分割しきれなかった塊をとりあえず置いておくモジュール
    • 状態を保持しない、関数の集まりにしておく

以下、rakeタスクからの処理フローを、サンプルのソースコード上で追いかけてみます。

rakeタスク

require 'la_tasks/foo/foo_task'

namespace :foo do
  desc 'Foo関連のタスク'
  task :foo_task, %i(schema table column) do |task, args|
    LATask::Foo::FooTask.new(task).foo_task(
      args[:schema],
      args[:table],
      args[:column]
    )   
  end 
end

rakeタスク上では引数のチェックすらせず、LATaskクラスに処理を丸投げします。この様にrakeタスクの名前と実装を切り離しておくと、後から実装の差し替えや改名等がしやすくなります。

LATaskクラス

LATask - 親クラス

require 'path/to/messenger'

module LATask
  class Base
    def initialize(task_name)
      @messenger = Messenger.new
      @task_env = ENV['TASK_ENV'] || 'development'
      fail "Wrong TASK_ENV: #{@task_env}" unless ['production', 'development'].include? @task_env
      @task_name = "#{task_name}:#{@task_env}"
    end
        
    protected
        
    def exec
        notify("Task Start - #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}")
        yield
        notify("Task End - #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}")
      rescue => e 
        error_action(e)
        @messenger.alert("#{@task_name}: #{e}", e)
    end
        
    def error_action(e)
      # 何かあればoverrideして使う
    end 
        
    def alert(message, e)
      @messenger.alert("#{@task_name}: #{message}", e)
    end
  end
end

LATaskクラスの親クラスには、ロギングやエラーハンドリング、通知、環境変数チェック等、全タスク共通で頻発する処理をシンプルにまとめておきました。これにより、子クラスの実装がかなりスッキリしました。

LATask - 子クラス

require 'api/aws/sqs'

class LATask::Foo::FooTask < LATask::Base
  def initialize(task_name)
    super(task_name)
    @sqs = API::AWS::SQS.new
  end

   def foo_task(schema, table, column)
    exec do
      # ここに処理本体を記述
      alert("task_env is #{@task_env}")
      @sqs.send_msg(@task_name)
    end
  end

  def bar_task
    exec do
      # ここに処理本体を記述
    end
  end

  private

  def private_func
    # このタスク限定の処理など
  end
end

LATaskの子クラスに、タスクの実装を書きます。この時、execに渡すプロック内で全てのタスクの処理を記述することにより、ロギング、エラーハンドリングを親クラスの実装に一元化できます。パラメータのチェックを行う場合も、ここに記述します。また、タスクのロジックをrakeタスクにベタ書きせず、クラスに切り出した事で、各々の関数に対してテストが書けるようになりました。

APIクラス

API - 親クラス

require 'utils/utils'                                                                                                 

module API                                                                                                             
  include Utils
  def check_dry_run_then(param)                                                                                        
    if dry_run?                                                                                                        
      puts "### DRY_RUN ###"
      pp param
    else
      yield
    end
  end                                                                                                                  
end
equire 'api/api'

module API 
  module AWS 
    include API 
    require 'aws-sdk-core'
  end 
end

APIの親クラスに、dryrunの処理を実装しています。こうすることで、LATaskのロジック本体からdryrunに関連する処理を取り除けるようになり、コードが簡潔になりました。

API - 子クラス

require 'api/aws'                                                                                                     
require 'yaml'                                                                                                         
                                                                                                                       
class API::AWS::SQS                                                                                                    
  include API::AWS                                                                                                     
                                                                                                                       
  def initialize(region = AWS_REGION)                                                                                  
    @sqs = Aws::SQS::Client.new(                                                                                       
      region: region,                                                                                                  
      access_key_id: AWS_ACCESS_KEY_ID,                                                                         
      secret_access_key: AWS_SECRET_ACCESS_KEY                                                          
    )                                                                                                                  
    @conf = YAML.load_file('config/aws.yml')[task_env][:sqs]                                                    
  end                                                                                                                  
                                                                                                                       
  def send_msg(data)                                                                                                   
    check_dry_run_then("[sqs.send_msg] #{data}") do                                                                     
      @sqs.send_message(data)                                                      
    end                                                                                                                
  end                                                                                                                  

  private

  AWS_REGION = "ap-northeast-1"
end

check_dry_run_thenのブロックで、APIにアクセスしています。dryrunの際にはこのブロックの処理が実行されず、代わりにcheck_dry_run_thenの引数で与えられる文字列が表示されます。他のAPIアクセスについても同様に記述しておくことで、簡単にdryrunが実装できます。

Utilクラス

module Utils
  def dry_run?
    !ENV['DRY_RUN'].nil?
  end
end

システム横断でよく使われる処理をまとめた関数群ですが、なるべく使わないようにしています。

リファクタリングを途中までやってみて

下記の様なメリットを感じつつあります。

  • 全体が一貫性を持った構成、書き方で統一できた(可読性・保守性の向上)
  • テストを書く余地ができた
  • ディレクトリ構成でコンポーネントが一目瞭然(もうgrepしない)
  • LATaskクラスにシンプルなロジックを記述するだけでも、概ね全部のタスクにdryrunモードが実装できそう
  • エラーハンドリングとロギングが集約できた

当初、課題として挙げていた部分はまずまず解決できそうな見込みです。一方で、

  • まだイマイチな実装が細々とたくさん残ってる
  • 本当は思い切って名前を変えて、タスクの分類体系も再編したい
  • 想定通りとは言え、分解しきれずUtilsに残ってしまった塊

といった点が、課題として見えてきました。

まとめ

サンプルにある通りかなりシンプルな実装ですが、コードの見通しの良さを保ったまま課題としていた事を概ね克服しつつあるので、良かったと感じています。

  • 実装の難易よりも、課題をシンプルに解決できる事が大切
  • 課題の本質を、しっかり見定めることが同様に大切

という学びとリファクタリングの経験を得られました。新しいことも奇抜なこともないリファクタリングでしたが、よい学びと経験が得られたと感じています。

BigData-JAWS勉強会でAirflowのことを話してきました

データプラットフォームチームのよしたけです。

さて先日のBigData-JAWS 勉強会 #12にて、「リブセンスのデータ分析基盤を支えるRedshiftとAirflow」というタイトルで発表させていただきました。

Airflowについては、このブログでも Airflow を用いたデータフロー分散処理 でご紹介させていただきましたが、今回、弊社での活用事例を交えてお話させていただきました。

発表後のQAや懇親会で、Airflowの導入を検討しているが実際どういうところが大変なのか? とか運用上のつらみ、とかそういうところを気にされている方が多かった印象でした。

AirflowはPythonでデータフローを記述するため、柔軟に何でもできるという強みがあり、反面、やりすぎると改修が大変になり運用しづらくなるデメリットもあるように思います。 私たちはDAG側にはロジックを盛り込まずRake側でロジックを組み上げていくような運用をしているため、比較的変更、修正はカジュアルに回せていると思いますが、 反面、Airflowの豊富なオペレータや機能が使いこなせていないというジレンマもあったりします。 このあたりの加減は今後も改善を進めていきたいなと思っています。

競争優位性構築のための人間中心機械学習〜CVRからUXへ〜 

テクノロジカルマーケティング部データマーケティンググループにてデータサイエンティスト兼UXアーキテクトをしている新保と申します。普段は機械学習を中心としたデータ活用の推進や新規機能のユーザ体験の設計をしています。ここ1年程リブセンスではサービスの戦略レイヤーや主要機能と結合度の高い領域に対して機械学習を適用していくことに挑戦しており、今回はそれらを実際にどのように行っているかをご紹介したいと思います。

パッチ型機械学習の成功体験とその限界

 本題に入る前にリブセンスのデータ活用の歴史について少しお話しします。以前に別のメンバーが投稿した記事に詳しい説明がありますがリブセンスでは2014年にデータ活用の専門組織を立ち上げてから現在に至るまで機械学習のビジネス活用を継続的に行っています。初期の頃は既存サービスの枠組みの中でサービスとの結合度が出来る限り低く、かつ利益インパクトが大きい領域に機械学習を適用することからスタートし、比較的早い段階で成功を収めています。2015年にはレコメンドシステムやアトリビューションモデルなどが主要サービスに本格導入され、当時の全社売上50億円規模に対して安定的に年間1〜2億円程度の利益インパクトを残せるようになりました。一方で成功を収めたことでより高次の新しい課題が見え始めたのもこの時期です。課題の1つは機械学習の開発環境の整備です。こちらについては過去の記事に詳しく解説されていますので

マルチコンテナ構成による機械学習アルゴリズムとアプリケーションの疎結合化 - LIVESENSE Data Analytics Blog

などを御覧ください。

もう1つの課題はデータ活用(機械学習含む)によってCVR向上を通して利益貢献に結びけることに成功した一方で、既存サービスにパッチを当てるような機械学習の使い方ではその成果がサービスの競争優位性構築に結びつけるまでには至らなかったことです。そこで利益貢献を維持しながらデータ活用によってCVR向上よりも大きな成果、サービス自体の競争優位性構築に貢献することが2016年以降のグループのテーマになっていきます。これは現時点で100のサービスをを105にするためではなく中長期的に200、300に延ばしていくために機械学習を始めとするデータ活用を進めて行くということです。

パッチからデザインへ

この方針のもと2016年以降はアルゴリズムの洗練化
を継続しつつ、ユーザ体験の設計段階まで踏み込むプロジェクトを継続的に立ち上げています。これは機械学習がサービスと切り離された機能として提供されるのではなく、サービスの中核を担う行動デザインとインタラクションデザインのレイヤーから関わることを意味します。少し前にGoogle Design blogの記事「Humuman-centerd Machine learning と類似した取組みをリブセンスにおいても約1年前から行っており、次のようなプロセスでプロジェクトを回しています。

f:id:livesense-analytics:20180319193618p:plain

このプロセスのポイントは「既存のデータから機械学習で出来ることを考える」から「コンセプトを先に決めて必要なデータをすべて取る」という方式に変更した点です。Webサービスにおいて機械学習はあくまでユーザへの価値提供を実現するための強力な選択肢の1つであると考え、機械学習ありきではなくユーザへの価値提供を最上位におき、提供価値の具体化と行動デザインのフェーズで機械学習の活用を検討している点で人間中心な機械学習と言えるでしょう。

人間中心の機械学習の実施プロセス

 ここからはフェーズ①〜②においてリブセンスで実際に行っている具体的なプロセスをご紹介します。基本的にはISO9241-210にて定義されているHCDのプロセスをベースにしています。まだまだ試行錯誤の日々ですが現在我々のチームでは次のステップで進めています。

  1. ターゲットセグメントの決定
    まず最初にターゲットになりうるユーザを対象に幅広くデプスインタビュー行い、定性分析によるユーザセグメンテーションを行います。セグメントの軸には様々な方法がありますが我々のチームでは行動パターンの類似性で切り分けた後にメインターゲットとするセグメントを決定しています。

  2. ユーザの価値抽出
    ターゲットセグメントに属するユーザの価値感の抽出分析を行います。デプスインタビューで得た発言録からユーザの行為と意図を抽出して分析を行うことでユーザのもつ本質的欲求を分析します。手法はいくつかありますが、時間がないときは上位下位関係分析、余裕があるときはKJ法やKA法などを用いることが多いです。その後As-isのカスタマージャーニーマップを作成して現在のユーザ行動やタッチポイントごとの課題を可視化します。期間と予算の関係上分析のデータソースとしてはstep1で行ったデプスインタビューの結果を再利用することが多いです。

  3. コンセプト設計とユーザ体験の可視化
    提供するユーザ体験のコアとなるコンセプトをプロジェクトメンバー全員で出し合います。この段階では提供価値にフォーカスしたいため一旦技術的なことは忘れます。次にコンセプトアイディアを「ユーザへの提供価値の大きさ」と「機械学習を使うことによる付加価値大きさ」の二次元マップ上に配置します。ここで高い提供価値を与えるアイディアの機械学習依存度が高い場合はデータサイエンティストとや機械学習エンジニアと実現可能性について検討を行います。ただしこの時点での確認はアイディアが荒唐無稽すぎたり、AIがなんとかするといった抽象的すぎるものになってないかなどの確認であって、具体的なアルゴリズム検討には踏み込みません。また機械学習依存度の低いアイディアの提供価値が一番大きいと判断された場合や機械学習を使うことにより効果が小さい場合は機械学習を使わない選択をすることも多いです。 仮コンセプト確定後は構造化シナリオ法を用いてユーザのサイト上における行動シナリオを可視化します。

    f:id:livesense-analytics:20180320103257p:plain

  4. コンセプト評価とプロトタイピング
    ユーザインタビューによってコンセプト評価を行い、フィードバックを反映してコンセプトを修正していきます。例えば昨年度実施した転職会議の新機能のコンセプト設計では計3回のコンセプト修正と評価を行っています。定性だけで確信が持てない場合はコンセプトが固まった時点で定量アンケート調査で量的な担保を取る場合も多いです。また、プロトタイピングツールを使用してコンセプトをUIに落とし込んで実際にユーザ使ってもらった上での感想も聞いています。プロトの段階でアルゴリズムを開発するとコストが掛かりすぎるためレコメンドシステムであれば予めユーザに好みを聞いておいて人手で選んだ求人を見せたり、チャットボットであればUI上はボットに見せて裏側では人がやりとするようにして極力アルゴリズムを開発せずに擬似的な体験を提供してユーザ評価を行うようにします。   

  5. 開発プロジェクトへの展開
    サービスリリースに向けてデザインプロセスに関わったメンバーを中心に開発プロジェクトを立ち上げます。この段階で機械学習エンジニアをプロジェクトメンバーに本格的にアサインして具体的なアルゴリズムの検討を開始します。

このプロセスでは機械学習の利用を前提とせず提供価値に着目していますが、一方で最初から機械学習の活用アイディアがあるケースもよくあるかと思います。その場合は調査からスタートすると時間がかかりすぎるのでstep1〜step3をプラグマティックペルソナで代替して高速にアイディアの検証とピボットを繰り返していくことで効率的にプロジェクトを進めていく方法をとるのが良いのではないかと考えています。

以上がリブセンスで行っている機械学習を活用したおおまかな体験設計のプロセスです。直近の事例では就活会議での就活生と企業のマッチング機能をこのプロセスを採用して設計しています。アルゴリズム改善のみでマッチング精度を上げるのではなく、就活会議に訪れたユーザが適性な企業とのマッチングに至るまでに必要なユーザの体験設計を最初に行い、ユーザ体験がユーザに刺さるかどうかを事前検証後に必要な機能・アルゴリズム・データを収集・開発しています。就活会議における具体的なサービス設計の事例についてはまた別の機会にご紹介できればと思います。