LIVESENSE Data Analytics Blog

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

LivesenseDataAnalyticsBlog

MENU

DigdagとEmbulkで行うDB同期の管理

データプラットフォームグループの松原です。 弊社各サービスのデータ分析基盤であるLivesense Analytics(以降LA)の開発、運用を行っています。
今回はLAで行っている分析のためにサービス側のデータ(テーブル)を、Redshiftへ同期を行う処理について紹介します。

概要

LAではデータウェアハウスとしてRedshiftを運用しており、社内から比較的自由に利用できる様にしています。
LAで取り扱っているデータはアクセスログが中心ですが、分析を行う利用者からはLA由来のデータ以外にも自分たちのサービスのデータを用いて分析を行いたい、という要望がよく出てきます。
サービスのデータには個人情報を含むものも少なくありませんが、分析基盤として社内にデータを解放するためにはそのような情報は削る必要があります。
そこで個人情報をマスキングしたサービス側データを利用できるよう、Redshiftに同期しています。

システム構成(概要)

大まかなシステム構成としては、下図のようになっています。

f:id:livesense-analytics:20190227151830p:plain:w650

多くのテーブルを同期しているので、カラム単位のマスキング処理や加工処理はEmbulkで行わず、同期元のデータベースであらかじめ行っておきます。

Digdagの選定理由

BigData-JAWS勉強会でAirflowのことを話してきましたにもあるように、すでにAirflowを運用していますが、ここではDigdagを選定しています。
主な選定理由としては以下があります。

  1. 構築や運用の容易さ
    Digdagはサーバーモードで動かしても、JavaとPostgreSQLの動く環境があれば運用を開始できます。
    また、Airflowと比べると、今のところ構築・アップグレードなどの運用は容易です。
  2. やりたいことがシンプル
    今回はsh(embluk)、redshift、httpオペレータで用途として足りそうで、それぞれのタスク間の依存関係も複雑では無いです。
    そのためタスク間の依存関係はYAMLで宣言的に書いたほうがメンテンスしやすいと考えました。
    また、DAG自体も複雑で無いため、どこまで実行できたか・どこで失敗したのかの可視性は求めていないのでダッシュボードも簡易なもので問題ありませんでした。

これらの理由により、Airflowで統一するより用途によりツールを使い分けた方が運用が容易なのでは、という結論になりました。

規模感

Redshiftへ同期しているデータに関する情報ですが以下のとおりです。

同期元のデータベース: 13個
同期を行っているテーブル数: 280テーブル
同期対象のデータベースの種類: 2種類(MySQL、PostgreSQL)
同期先: 2種類(Reshift、Redshift Spectrum)

これらのテーブルは、毎日業務開始前に前日のデータを同期しています。
同期はデータの増分を追加するのではなく、全量差し替えています。

Digファイルの依存関係

現時点では同期対象のテーブル数が280テーブル程度あるため、テーブル個別のデータ変換処理はなるべく行わないようにしています。
embulkやdigファイルを共通化しておくことで、同期対象テーブルの追加などの依頼があった場合にはテーブルとカラムの情報が書かれたファイル(下記の例だとtest1_tables.dig)のみを変更すれば良いようにしています。
また、テーブル個別のデータ変換処理はできるだけ行わないようにしていますが、変換が必要な場合はAirflow側で処理を行うようにしています。

2つのDBと同期を行う際のdigファイルの依存関係は、下図ようになります。

それぞれのファイルは以下のような役割で分けています。

ファイル名 概要
test1.dig Workflowを表すdig
test1_secret.dig 同期元への接続情報を保持しているdig
test1_tables.dig 同期対象のTable,Columnを定義しているdig
from-mysql-to-redshift.dig Embulkの実行・Redshiftへの処理を定義しているdig
in-mysql-out-s3.yml.liquid Embulkの設定ファイル
create.sql 一意になるテーブル名を作成
copy.sql EmbulkでS3においたファイルを、create.sqlで作成したテーブルにCOPY
swap.sql 既存のテーブルとSWAPし既存のテーブルを削除

スケジュール設定、通知・エラー処理などを省略すると、主だったdigファイルは以下のようになります。

--test1.dig
_export:
  mysql_database: MySQLの接続先DB名(ex. tes1)
  la_schema: redshift上のスキーマ名
  !include : digdag/environment/env.dig
  redshift:
    host: redshiftのホスト名
    database: redshiftのDB名
    user: la_importer
    ssl: true
    schema: ${la_schema}
    strict_transaction: false
    connect_timeout: 600s

+task:
  _export:
    !include : digdag/secret/test1_secret.dig

  for_each>:
    !include : digdag/media/test1_tables.dig
  _do:
    !include : digdag/flow/from-mysql-to-redshift.dig
--digdag/media/test1_tables.dig
mysql_table_info:
  - table: table1
    column: id, created_at, updated_at
  - table: table2
    column: id, created_at, updated_at
--digdag/flow/from-mysql-to-redshift.dig
_export:
  la_table: ${mysql_table_info.table}
  s3_file_path: mediadb/${la_schema}/${la_table}

+from-mysql-to-s3:
  sh>: embulk run -b /home/digdag/embulk_bundle /home/digdag/dag/embulk/in-mysql-out-s3.yml.liquid
  _export:
    mysql_table: ${mysql_table_info.table}
    mysql_select: ${mysql_table_info.column}

+mysql_redshift_create_temptable:
  redshift>: digdag/flow/queries/create.sql

+mysql_redshift_copy_2_temptable:
  redshift>: digdag/flow/queries/copy.sql

+mysql_redshift_swap_table:
  redshift>: digdag/flow/queries/swap.sql

運用上行っていること

以下のようなことを行って、運用コストを下げています。

  • Digdagで同期するテーブル情報の作成・Redshift用のCreate Table文(SORTKEY, DISTKEYは手動で設定)の作成のスクリプト化
  • タスクのログをS3に出力し、(shオペレーターから呼び出した)EmblukのログをLambdaでパース処理を行いエラー内容をSlackへ通知
  • Redashで同期済みデータのヘルスチェック
  • Errbotとmogを用いてChatOps(DAGの実行)
  • Redshift上に同期したが、1年以上利用されてないテーブルの割り出しと、今後も同期するのかの確認(不要テーブルを同期対象から外す)

これらを含めると、全体では以下のような構成になります。
f:id:livesense-analytics:20190220113717p:plain:w650

まとめ

もともとは Airflow を用いたデータフロー分散処理にあるバッチ処理と同様にRakeとwhenever(Cron)で運用されていたものをDigdagに置き換えました。
置き換えを行うことで、メンテナンスコストはだいぶ下げることが出来たのではないかと思います。
現在はデータソースがどこにあるのかと処理の複雑さによって、AirflowとDigdagのどちらを使うのかを使い分けています。

今後やっていきたいこと

今後もDigdagを用いた同期の改善の他にも、以下のようなこともやってきたいと考えています。

  • DigdagとAirflowの連携の強化(同期完了をトリガーとしてAirflowのDAG実行)
  • マスキング処理にProxySQLを使うことで、任意のタイミングでの同期の実行
  • CDC(Change Data Capture)を用いた変更内容の(ほぼ)リアルタイムの同期

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

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

 前回の私のブログでは、前編として「UXデザインが総論賛成、各論疑問になる理由」と「プロジェク設計で意識したい3つの条件の<その1>サービス・ドミナント・ロジックを中核とするサービスかどうか」について述べさせて頂きました。 analytics.livesense.co.jp

今回は、中編として<その2>潜在ニーズを探る必要性の有無、について述べさせて頂きます。

前編でも述べさせて頂きましたがUXDのプロジェクト設計で意識したい3つの条件は

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

になります。今回は3条件の中の2つ目です。

4.1 潜在ニーズ有無による特徴

 プロジェクト設計で意識したい2つ目の条件として、「潜在ニーズを探る必要の有無」を挙げさせて頂きました。 ここでは、まず、潜在ニーズの有無による新規ソリューション案(事業案)の特徴について考えてみます。

 一般的に「顕在ニーズ」を対象にしたソリューション案(事業案)は、購買欲求が高く自明な顕在ニーズを対象にしているので早期に市場が立ち上がることが期待できます。しかしながら、デメリットとして、競合が多く、差別化を作り難く、価格競争になりがちな傾向があります。

 一方、「潜在ニーズ」を対象にしたソリューション案(事業案)は、近未来における社会やユーザーの変化に対応したサービスにより差別化を作り易く、先行利益を得やすいです。しかしながら、デメリットとして、サービス市場が立ち上がるまで安定した需要が見込めず事業成長の不確実性が高くなる傾向があります。

 この様な特徴を考慮して「潜在ニーズを探る必要の有無」により、そのソリューション案(事業案)の種を探す工数や、説得材料を揃えて検証する工数を大きく変えて設計した方が良いと考えてます。図4.1.1に示したデザインプロセスにおけるダブルダイヤモンドで例えると、前後のフェーズで共に設計を変える必要があるのです。

f:id:livesense-analytics:20180926172204p:plain
図4.1.1 デザインプロセスにおけるダブルダイヤモンド

4.2「問題の発見・収束」における設計の違い

 ダブルダイヤモンドの前半である「問題の発見・収束」のステップについて、新規事業や新規サービスを検討する場合について考えます。対象となる問題が「顕在ニーズ」「潜在ニーズ」のどちらなのか?と、その新規性が「機能」もしくは「体験価値」の場合で考えてみます。

4.2.1 「顕在ニーズ」の場合

 差別化要素となる新規性が機能に有り、その機能が解決するのが「顕在ニーズ」の場合、どの様な問題(顕在ニーズ)を対象にするのかが自明なのでダブルダイヤモンドの前半である「問題の発見・収束」フェーズに掛ける工数は少なくすることができます。

 潜在ニーズを探る必要が余り無いのは、検討しているサービスの事業領域において世の中のサービスに不満が多く存在し”なんとなく不満なサービス”が世の中に溢れている状況になっている時、とも言えます。

 例えば図4.2.1 の様に「事業やサービス領域において、既存技術や新規技術で解決できる顕在ニーズが一定規模以上で存在している」状況などを挙げることができます。 ※図4.2.1 の丸角四角は、個別の顕在ニーズを表し、その大きさはニーズを持つ人の規模を表現しています。青塗りの丸角四角は、すでにソリューションが世の中に存在しているニーズ、白塗りの丸角四角は、まだソリューションが世の中に存在していないニーズ、を表します。

 高度成長時代に技術イノベーションにより産業成長が続いていた時は、【Ⅰ】【Ⅱ】を繰り返していたとも言えます。ですので比較的に技術的な発展による新興サービスが該当することが多いです。また、既存事業等で解決したい問題が明らかになっており、その解決案の目処がたっている時も該当すると言えます。

 この様な状況下では、従来の「定量的なマーケットリサーチの判断基準」で判断することができます。ダブルダイヤモンドの左側でユーザーのニーズについて深いリサーチをかける必要は余りありません。定量的に把握可能な自明な顕在ニーズに対して、素早く製品・サービス開発を行い市場に投入した方が事業成果を得やすいことが多いからです。

f:id:livesense-analytics:20180531115416p:plain
図4.2.1 潜在ニーズを探る必要が余り無い状況

4.2.2 「潜在ニーズ」の場合

 差別化要素となる新規性が機能で有るものの、機能により解決される一定規模感の有る顕在ニーズが見当たらない場合や、機能に差別化する要素が乏しく差別化要素となる新規性を体験価値に求める場合は、潜在ニーズの探索が必要な場合が多いです。また、自明になっていない問題(潜在ニーズ)を対象にするので「問題の発見・収束」フェーズに掛ける工数を多く設計する必要が有ります。

 潜在ニーズを探る必要のあるのは、事業環境面では検討しているサービスの事業領域において世の中のサービスに大きな不満は無く、”なんとなく良いサービス”が世の中に溢れている状況になっている時、とも言えます。

 例えば図4.2.2の【III】【Ⅳ】の様に「事業やサービス領域において、既存技術で解決できるニーズはほぼソリューションが存在しており」、【III】「既存技術で解決できる未解決の顕在的なニーズは少数意見のみ」の状況や、【Ⅳ】は【III】に加えて「新規技術で解決できる未解決の顕在ニーズも少数意見のみ」の状況を挙げることができます。

 この様な状況は、成熟期の事業領域に多く、従来のマーケットリサーチの判断基準(一定数量のある未解決なニーズを対象にサービス開発を行う)だけでは、新規事業の芽を見つけ出すことは非常に困難です。この時、様々なアプローチ方法がありますが、その一つに潜在ニーズを探る手段があります。

 潜在ニーズを探す場合、ダブルダイヤモンドの左側で、"今まで、顕在ニーズとして少数の人しか必要としていないと思われていたけど、実は多くの人が求めていた潜在ニーズ"を探しだすことを目的とします。

 ここで述べる潜在ニーズとは、厳密な定義とは少しずれてしまうかもしれませんが、ウォンツとニーズにおける”ウォンツ”、ジョブ理論における”本質的に片付けたいジョブ”、UXDにおける”本質的に求めている未発見の体験価値”、等と重なる概念として考えております。

f:id:livesense-analytics:20180531115653p:plain
図4.2.2 潜在ニーズを探る必要のある状況

4.3 「解決案の発見・収束」における設計の違い

 次にダブルダイヤモンドの後半である「解決案の発見・収束」のステップにおいて考えてみます。「解決案の発見・収束」のステップでは、前半で対象とした「顕在ニーズ」「潜在ニーズ」のどちらの解決策を探索するのかにより必要な工数は異なります。

 「顕在ニーズ」を対象にした解決策は、顕在ニーズを対象にしているので想定価格や購入意向率を客観的に評価することができます。よって評価と検証がしやすいことから工数を少なくすることができます。

 しかしながら「潜在ニーズ」を対象にした解決策は、まだ無消費の市場であることが多いため客観的な評価はありえません。解決策はステップを経た検証が必要です。またその説得材料を揃えるにも不確実性が高く限られた材料になるので時間が掛かります。数ターンのアジャイルなプロトタイプの検証を通して、想定価格や購入意向率等を、その「世界観」や「体験価値を実感できるストーリー」のプロトタイプを用いて検証し徐々に確度を上げていく必要があります。 またその際は、不確実性が高い検証になりますので、有力な一つの「潜在ニーズを対象にした解決策」に絞るよりも、複数案を対象に検証す進め、その中から選別するプロセスを推奨します。

 更に最終的な検証はサービスのリリース後も継続されます。将来起こるであろう「社会の行動や価値感の変容」と共にサービスが世の中に浸透することになるからです。ですので、今回のプロジェクトで、どこまでの範囲を検証対象とするのか?を事前に確認して設計する必要が有ります。

 また複数の候補案が有り、「顕在ニーズ」「潜在ニーズ」が混在している場合は特に注意が必要です。同じ設計で同時並行的に進めようとすると「潜在ニーズ」を対象にした検証が時間不足で不十分になりがちで、中途半端な結果となり最終候補案として見送られてしまうことが多いからです。その必要性により十分な工数を確保して検証することを推奨します。

 この様に、事業としての成果を出すためには、一見同じ様なテーマのリサーチプロジェクトでも、「潜在ニーズ探索の必要性有無」により、大きく設計を変える必要があると考えてます。

5. 中編のまとめ

 以上、UXDのプロジェクトを選定する時に意識する3条件の2つ目である <その2>潜在ニーズを探る必要性の有無、について実践時に心掛けてきたことを中心にまとめてみました。

 次回は、後編として最後の条件である

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

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

階層ベイズによる小標本データの比率の推定

こんにちは、リブセンスで統計や機械学習関係の仕事をしている北原です。今回は階層ベイズを使った小技の紹介です。推定にはStanを使います。

Webサービスに限らないかもしれませんが、CVRやCTRなど比率データを扱うことって多いですよね。弊社の求人サービスは成果報酬型であるため、各求人の採用率などを知りたいこともよくあります。しかし、求人別だとバイト求人や転職求人の応募数はそれほど多くないので、採用数を応募数で単純に割っただけでは極端な採用率になりがちです。今回は、このような分母の値が小さい比率のデータを、階層ベイズを使って計算する方法を紹介します。


応募数が少ないときの採用率計算の問題

まず、応募数が少ない求人の採用率計算が必要な理由と、このようなサンプルサイズが小さいデータの比率計算の問題について説明します。
その問題をふまえて、今回どのような推定を行いたいかを説明します。

弊社の成果報酬型求人サービスでは、応募だけでなく採用してもらうことで売上が発生します。そのため、応募率だけでなく採用率も重要になってきます。例えば、あまりに採用率が低い求人があれば、その求人にマッチした求職者に応募してもらうような施策を考えたり、採用方針を企業に問い合わせたりする必要があります。また、採用率が高い求人があれば、その理由を調べて他の求人の採用率を上げる施策に活かすこともできます。

採用率は採用数を応募数で割ることで計算しますが、バイト求人にしても転職求人にしても、求人ごとに集計すると安定した比率を得るだけの十分な応募数がある求人は少ないです。集計期間を長くとれば計算に使える応募数が多くなることもありますが、求人は入れ替わることも多いし、集計期間を長くすればするほど古い情報を反映した採用率になってしまうので好ましくありません。また、応募数が少なかった求人についても状況に応じて適切な対策を打ちたいので、応募数が少ない求人を無視するということもしたくありません。

当然ながら、採用率を計算するときの分母にあたる応募数が少ないと、算出された採用率の信頼性は低くなります。例えば、応募数200件で採用数100件の求人であれば採用率50%と言われてもあまり違和感がないですが、応募数2件で採用数1件だと採用率50%と言われても疑わしいですよね。特に応募数が少ないときに、極端な採用率が算出されてしまいやすいという問題があります。

それでは、全求人の平均採用率が20%であることがわかっていたらどうでしょう。多くの人は、各求人の応募数や採用数だけでなく全体的な傾向も考慮して、応募数が増えたときの採用率を推測するのではないでしょうか。例えば、応募数2件で採用数1件の求人の採用率は、応募数が増えていけば50%より低くなりそうですが、全体採用率20%よりは高そうなので、だいたい30〜40%ぐらいかなと考えませんか。一方、応募数200件で採用数100件の求人場合だと、分母の値も比較的大きいので採用率は50%より少し低めの45〜49%ぐらいかなと考えませんか。

このように全体的な傾向も考慮して採用率を計算できると便利です。今回はベイズ推定を使って、このような計算を行います。

主観的な事前分布を設定したベイズ推定

それでは、全求人の採用率傾向を考慮した各求人の採用率推定の考え方について説明します。

ベイズ推定では主観的な情報を事前分布として設定することができます。そのため、全求人の採用率の傾向を事前分布として設定すれば、全体傾向を考慮して各求人の採用率を推定することができそうです。

ベイズ推定では、データが少ないと事前分布の影響を強く受けますが、データが多くなってくると徐々に事前分布の影響が薄れてきます。そのため、応募数が少ないと、推定採用率はデータから単純に計算された採用率よりも全求人の採用率に近い値になります。一方で、応募数が多くなると、全求人の採用率よりデータから計算された採用率に近い値になります。その結果、前節で例として説明したような、人が直感的に判断したときと同じような推定採用率が得られることが期待できます。

事前分布の検討

今回の肝となる事前分布をどうするかについて説明します。

どのような事前分布を設定するのがよいかについては様々な考え方があるのですが、ここでは全求人の採用率傾向を表すことができ、計算が収束する分布を選ぶことにします。全求人の採用率傾向についてはデータを調べて検討することにしましょう。

ここからはサンプルデータを使って具体的に見ていきます。

サンプルデータ(クリックすると展開されます)


このサンプルデータは、ある条件で抽出した求人の応募数と採用数を模擬したデータになっています。1行が1求人に対応し、N、R、G列は推定に使うデータ、idとrateは参考のために付与したデータになっています。Nは応募数、Rは採用数、Gは地域や職種など何らかのグループを表すものと考えてください。rateは採用率で、idは説明の際に参照しやすいようにつけました。全体の採用数を応募数で割り、全体採用率を計算するとおよそ0.1766になります。全391求人のうち、応募が10件以上ある求人でも53しかなく、ほとんどの求人では採用率を計算しにくいことがわかると思います。

全体傾向を事前分布にするためには、採用率がどのような確率分布になるかを考える必要があります。まずは各求人の採用率をヒストグラムにして分布を見てみましょう。
f:id:livesense-analytics:20181004181021p:plain:w360
採用率0%が飛び抜けて多く、その他33%、50%、100%が多いことがわかりますが、これだけだと応募数が増えたときにどのような確率分布になるかわからないですね。知りたいのは応募数が多くなったときの採用率の分布なので、応募数が一定数以上の求人の採用率分布を見てみましょう。
f:id:livesense-analytics:20181004181227p:plain:w360f:id:livesense-analytics:20181004181239p:plain:w360f:id:livesense-analytics:20181004181243p:plain:w360
応募数が多い求人だけを見てみると、右裾の長い分布になっているように思えます。加えて、採用率0%や100%の求人もなくなっていることから、応募数が十分多いと採用率0%や100%の求人は0件になるのではと予想されます。そこで、このような特徴をもつ確率分布を探すとベータ分布が見つかります。これ以降はベータ分布を事前分布として使うことにしましょう。なお、採用率推定の事前分布としてベータ分布が常によいわけではありません。例えば、応募数が多いのに採用率100%の求人が多い場合はガンマ分布など別の分布を使うこともあります。

事前分布を活用した比率の推定

事前分布に使う確率分布も決めたので、実際に採用率を推定します。ここでは、前節で検討した事前分布の有無による違いを見るため、事前分布を使わないときと、事前分布に固定パラメータのベータ分布を使ったときの2種類の結果を調べます。

まず、採用率は二項分布にしたがうことだけを仮定したシンプルなモデルで推定してみましょう。Stanでは明示的に事前分布を指定しないと一様分布を指定したときと同じになります。モデルとコードは以下のようになります。
f:id:livesense-analytics:20181004182305p:plain:w480

Rコード(クリックすると展開されます)

library(rstan)
library(tidyverse)

dat <- read_csv("sample.csv")

stan_dat <-
  list(M = nrow(dat),
       N = dat$N,
       R = dat$R)

fit <- stan(file = 'binom.stan', 
            data = stan_dat,
            seed = 123)


Stanコード(binom.stan)(クリックすると展開されます)

data {
  int M;
  int<lower=0> N[M];  // 応募数(分母)
  int<lower=0> R[M];  // 採用数(分子)
}
 
parameters {
  vector<lower=0, upper=1>[M] theta;  // 採用率
}
 
model {
  R ~ binomial(N, theta);
}


推定結果のサマリは以下のようになります。
推定結果のサマリ(クリックすると展開されます)

単純に計算したときのように0%や100%のような推定採用率にはならないことがわかります。一方で、採用率の全体傾向がまったく考慮されていないため、応募1件採用0件の求人の採用率期待値がおよそ33%になるなど、採用率としては好ましくない結果も見られます。

では、採用率の事前分布としてベータ分布を設定したモデルで推定してみましょう。ここではベータ分布のパラメータに\(\alpha=3\)と\(\beta=13\)を使います(理由は後述します)。固定パラメータを使うのでプレート図は同じで、モデルとコードは以下のように事前分布の部分が追加されます。

f:id:livesense-analytics:20181004182611p:plain:w480

Rコード(クリックすると展開されます)

library(rstan)
library(tidyverse)

dat <- read_csv("sample.csv")

stan_dat <-
  list(M = nrow(dat),
       N = dat$N,
       R = dat$R)

fit <- stan(file = 'binom_beta.stan', 
            data = stan_dat,
            seed = 123)


Stanコード(binom_beta.stan)(クリックすると展開されます)

data {
  int M;
  int<lower=0> R[M];  // 応募数(分母)
  int<lower=0> N[M];  // 採用数(分子)
}
 
parameters {
  vector<lower=0, upper=1>[M] theta;  // 採用率
}
 
model {
  R ~ binomial(N, theta);
  theta ~ beta(3, 13);     // 事前分布
}


推定結果のサマリは以下のようになります。
推定結果のサマリ(クリックすると展開されます)


まず、採用がないケースについて見てみると、応募1件採用0件の求人は採用率期待値がおよそ0.176、応募2件採用0件の求人は採用率期待値がおよそ0.166となっています。応募数が1〜2件程度と少なく不確実性が高いので、全体採用率0.1766よりやや低い値として算出されています。また、応募1件採用0件より応募2件採用0件のほうが採用率はより低い可能性が高いため、採用率も低めになっていることが確認できます。さらに、id=234の求人のように応募10件採用0件になると、採用率が低い可能性がさらに高まるので、採用率期待値はおよそ0.116と推定されています。逆に採用が多いケースについて見てみると、応募1件採用1件の求人の採用率期待値はおよそ0.235、応募3件採用3件の求人ではおよそ0.314と推定されています。データが少なく不確実性が高いことを考えると、これらの推定結果は直感的によさそうに思えます。

応募が多いほど推定もより正確になるので採用率の分布の幅はより狭くなります。例として、単純に計算した時の採用率が等しいid=302とid=308の二つの求人について見てみましょう。id=302は応募4件採用1件、id=308は応募72件採用18件の求人です。推定された各々の求人の採用率の分布は以下のようになります。

f:id:livesense-analytics:20181004183102p:plain:w360

id=302は応募数が少ないので事前分布の影響を強く受けており、さらに不確実性も高いので分布の幅がid=308と比較して広いことがわかります。一方で、id=308は応募が比較的多いため事前分布の影響が弱く、さらに不確実性も低いので分布の幅が狭いことがわかります。

目論見どおりベータ分布を事前分布として設定することで、直感的によさそうな採用率が推定されているように見えます。しかし実は、推定結果がベータ分布のパラメータ\(\alpha\)、\(\beta\)の値に強く依存するという問題があります。そのため、ベータ分布のパラメータ\(\alpha\)、\(\beta\)を別の固定値に設定すると、全く異なる推定採用率になってしまいます。採用率のヒストグラムを見ても、応募の多い求人が十分にないため適切なパラメータ値まではよくわかりません。そこで、階層ベイズを使うことでベータ分布のパラメータも推定するようにします。

階層ベイズによる比率の推定

事前分布をうまく設定することができれば、直感的によさそうな採用率を推定できることがわかりました。しかし、その事前分布のパラメータをどのように設定すればよいかわからないという問題があることもわかりました。ここではこの問題に対処するため、事前分布のパラメータにも事前分布を設定する、いわゆる階層ベイズを使うことで、採用率の事前分布のパラメータも推定します。

採用率の事前分布には今までと同じようにベータ分布を使いますが、ベータ分布のパラメータ\(\alpha\)、\(\beta\)にも事前分布を設定します。ここでは、後で扱いやすいように以下のような再パラメータ化を行います。\(\mu\)はベータ分布の期待値です。
\begin{eqnarray*}
\mu &=& \frac{\alpha}{\alpha + \beta} \\
\kappa &=& \alpha + \beta
\end{eqnarray*}

モデルとコードは以下のようになります。
f:id:livesense-analytics:20181004183644p:plain:w480

Rコード(クリックすると展開されます)

library(rstan)
library(tidyverse)

dat <- read_csv("sample.csv")

stan_dat <-
  list(M = nrow(dat),
       N = dat$N,
       R = dat$R)
 
fit <- stan(file = 'hier.stan',
            data = stan_dat,
            seed = 123)


Stanコード(hier.stan)(クリックすると展開されます)

data {
  int M;
  int<lower=0> R[M];  // 応募数(分母)
  int<lower=0> N[M];  // 採用数(分子)
}
 
parameters {
  vector<lower=0, upper=1>[M] theta;  // 採用率
  real<lower=0, upper=1> mu;          // 事前分布(ベータ分布)の期待値(= a / (a + b))
  real<lower=0> kappa;                // a + b
}
 
transformed parameters {
  real<lower=0> a;        // 事前分布(ベータ分布)のパラメータ
  real<lower=0> b;        // 事前分布(ベータ分布)のパラメータ
  a = mu * kappa;
  b = kappa * (1 - mu);
}
 
model {
  R ~ binomial(N, theta);
  theta ~ beta(a, b);     // 事前分布
}


このようにすることで、モデルとデータに合った、事前分布のパラメータ(の分布)が推定されます。ベータ分布のパラメータに固定値のような強い制約を課すのではなく、分布のような緩やかな制約を使っているところがポイントです。採用率の事前分布の条件が緩和されたことで、データに応じてパラメータが自動的に推定されるようになります。階層ベイズの場合も事前分布の事前分布を変更すると推定結果は影響を受けるものの、パラメータがモデルとデータから大きく逸脱した値をとる確率は低くなるため、階層化せず事前分布のパラメータ固定値を変更した時ほど大きな影響は受けることは少ないです。そのため、このようなケースで階層ベイズを使うと恣意的に推定結果を操作するのが難しくなるので、結果の客観性が高まるというメリットもあります。

推定結果は以下のようになります。

推定結果のサマリ(クリックすると展開されます)


前節と同様に推定結果を確認すると、階層ベイズでも直感的によさそうな採用率が推定されているように見えますね。前節で使ったベータ分布の固定パラメータは、この階層ベイズのパラメータを参考にして決めました。なお、ハイパーパラメータの有効サンプルサイズが小さいところは気になるので、必要に応じてサンプリング数を増やすなどの対応が必要です。

グループの違いを考慮した階層ベイズによる比率の推定

採用率は職種や地域によって大きく異なることが知られています。そこで最後に、職種や地域のようなグループごとの採用率の違いも考慮した推定をやってみましょう。

基本的には今までやってきたことと同じように、採用率の事前分布の事前分布のパラメータにさらに事前分布を設定します。グループごとに採用率を推定することもできますが、職種や地域によっては応募が少ないため全体傾向すら推定できないグループがあることが多いです。そこで、求人の採用率の事前分布にグループごとの採用率を使い、グループごとの採用率(の期待値)の事前分布にグループ別採用率の分布を設定します。このように階層を増やすことで、応募が少ないグループの採用率の推定にはグループ別採用率の分布が利用されるため、推定がしやすくなります。なお、ここではデータの分布の傾向については確認しませんが、グループごとの採用率分布も、応募の多いグループ内の求人の採用率分布も、前節までに見てきた全求人の採用率分布とほぼ同じような傾向になっているので、引き続き事前分布にはベータ分布を使います。

モデルとコードは以下のようになります。モデルが複雑になり計算が不安定になりやすいので、ハイパーパラメータ\(\kappa_g\)と\(\kappa_i\)に半正規分布を設定して安定化を図っています。また、Stan実行時にオプションadapt_deltaを0.99に設定しています。
f:id:livesense-analytics:20181004184637p:plain:w480

Rコード(クリックすると展開されます)

library(rstan)
library(tidyverse)

dat <- read_csv("sample.csv")

stan_dat <-
  list(M = nrow(dat),
       K = dat$G %>% unique %>% length,
       N = dat$N,
       R = dat$R,
       G = dat$G)

fit <- stan(file = 'hier2.stan', 
            data = stan_dat,
            seed = 123,
            control = list(adapt_delta = 0.99))


Stanコード(hier2.stan)(クリックすると展開されます)

data {
  int M;                            // データ数
  int K;                            // グループIDの最大値
  int<lower=0> N[M];                // 応募数(分母)
  int<lower=0> R[M];                // 採用数(分子)
  int<lower=1, upper=K> G[M];       // グループID
}

parameters {
  real<lower=0, upper=1> theta[M];  // 採用率
  
  vector<lower=0, upper=1>[K] mu;   // グループ別の採用率事前分布のパラメータ(期待値)
  vector<lower=0>[K] kappa;         // グループ別の採用率事前分布ののパラメータ(alpha + beta)
  
  real<lower=0, upper=1> mu_g;      // グループの事前分布のパラメータ(期待値)
  real<lower=0> kappa_g;            // グループの事前分布のパラメータ(alpha + beta)
}

transformed parameters {
  vector<lower=0>[K] a;             // グループ別の事前分布(ベータ分布)のパラメータ
  vector<lower=0>[K] b;             // グループ別の事前分布(ベータ分布)のパラメータ
  
  real<lower=0> a_g;                // グループの事前分布(ベータ分布)のパラメータ
  real<lower=0> b_g;                // グループの事前分布(ベータ分布)のパラメータ
  
  a = mu .* kappa;
  b = kappa .* (1 - mu);
  
  a_g = mu_g .* kappa_g;
  b_g = kappa_g .* (1 - mu_g);
}

model {
  R ~ binomial(N, theta);
  for (i in 1:M) {
    theta[i] ~ beta(a[G[i]], b[G[i]]);  // 採用率の事前分布(パラメータはグループ別)
  }
  mu ~ beta(a_g, b_g);                  // グループの事前分布(採用率の事前分布のパラメータの事前分布)
  kappa ~ normal(0, 100);
  kappa_g ~ normal(0, 100);
}


結果は以下のようになります。
推定結果のサマリ(クリックすると展開されます)


応募数と採用数が同じでも、グループによって推定採用率に違いがあることが確認できます。例として、推定採用率が比較的高いG=18に所属しているid=41の求人と、推定採用率が比較的低いG=24に所属しているid=35の求人について見てみましょう。どちらも応募1件採用0件ですが、推定採用率の期待値はそれぞれ0.215、0.1と異なっており、id=41のほうが高いことがわかります。期待通り、グループの違いが推定採用率に反映されていることがうかがえます。

一方で、データがほとんど変わらないのにパラメータが増えモデルが複雑になっているため、収束しにくくなっていることもわかると思います。ハイパーパラメータ\(\kappa_i\)の事前分布への依存性も高く、頑健性は高くありません。実務で利用する場合は、グループごとの違いが反映されるメリットと、計算が不安定化するデメリットを比較してどのモデルを使うかを決めることになると思います。また、より客観的にモデル選択をしたいのであれば、WBICを使う方法もあります。

最後に注意点を述べておこうと思います。今回のような推定結果を利用するときに気をつけなければいけいないことは、結果が事前分布や全体傾向に依存しているところです。事前分布を変更すると異なる推定値が得られることはよくありますし、データの抽出条件によって全体傾向が違うため抽出条件ごとに異なるモデルが必要とされることもあります。また、商品設計が変更されたり時間が経過したりしてデータの全体傾向が変わると、個別のデータの特徴には変化がなくとも事前分布が全体傾向に適合しなくなる可能性があるので注意が必要です。例えば、求人メディアの場合では、異なる採用率傾向をもつ求人が増えると全体傾向が変わる可能性があるので、事前分布の再検討が必要になるかもしれません。

まとめ

今回は階層ベイズを使って採用率などの分母が小さい比率を推定する方法を紹介しました。ポイントは以下の二点です

  1. 個別に見るとデータ量が少なくてまともな推定が難しそうでも、全体傾向などをうまく利用すると推定が可能になることがある
  2. 階層ベイズを利用することで事前分布のパラメータ(の分布)を自動的に推定できることがある

似たような方法は比率だけでなく別の小標本データにも応用できます。弊社にはベイズ統計に理解のあるディレクターもいるため、今回のようなベイズ推定結果を活用した取り組みも行われています。

なお、元ネタは統計モデリングの社内勉強会で階層ベイズを説明するときに使ったものです。この勉強会に参加したアナリストにもエンジニアにも受けがよかったのでブログ記事にしてみました。今後は小ネタでも役立ちそうなものがあれば紹介していこうと思います。

Argo によるコンテナネイティブなデータパイプラインのワークフロー管理

データプラットフォームグループの野本です。主に機械学習基盤の構築やそれにまつわるアプリケーションの開発をしています。 以前までの記事で現在 Kubernetes を利用して機械学習基盤の構築を進めているという紹介をしましたが、機械学習システムに付きものだと思われるワークフローのジョブ管理に Argo という Kubernetes 上で動作するワークフローエンジンを導入し使いはじめまてみました。まだ色々試している段階でもあるのですが現状でどんな感じで使っているのか紹介してみようと思います。

ワークフローエンジンの選定に関して

現在機械学習基盤では先に紹介した以前の記事 や マルチコンテナ構成による機械学習アルゴリズムとアプリケーションの疎結合化 のような形で機械学習システムの構築を進めています。特に後者の具体例のように各アプリケーションを疎結合にうまく動かせるように出来るのが理想です。 これらを踏まえ以下のような点を重視してワークフローエンジンを検討しました。

コンテナによる実行が手軽に出来る

  • 各ジョブで利用する言語をジョブに適したものだったり担当者が得意なものに柔軟に変えることが出来る
  • 複数のシステムでコアとなるコンテナをライブラリ的に利用したい

ワークフローを宣言的に記述出来る

  • ワークフローの定義をファイルとしてバージョン管理したい
    • UI での設定よりも再現性がある
    • GitOps のような自動化を導入しやすい

出来るだけ手軽に導入 / 運用出来る

  • 依存するミドルウェアなどが極力少ない
  • 学習コストが低い

人員的なリソースや基盤構築のフェーズにもよると思いますが、現状は以上のようなことを念頭にワークフローエンジンの導入を考えていたところ、Argo - The Workflow Engine for Kubernetes というプロダクトと遭遇しました。

Argo とは

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

Argo とは Kubernetes 上で動作するコンテナネイティブなワークフローエンジンです。Kubernetes の CRD (Custome Resource Definition) として実装されており、標準の Deployment などの Kubernetes リソースと同等に yaml ファイルなどでワークフローの定義を管理出来るようになっています。

コンテナネイティブなワークフローエンジン

  • 各ジョブは Pod により実行される
  • ジョブ内 sidecar コンテナを利用可能
  • Kubernetes の他のリソースと同様に ConfigMap や Volume を参照可能
  • ダッシュボード用の argo-ui という Deployment の存在
    • 現状は実行済みワークフローの参照のみ

Kubernetes の CRD として実装されている

  • インストールされるのは workflow-controller と argo-ui の Deployment と argo-ui 用の Service、ConfigMap / Secrets で他のミドルウェアなどは必要としない
  • kubectl コマンドで他のリソースと同等に扱える

yaml によるワークフローの定義

  • 各ジョブはKubernetes 標準の Pod の Spec とほぼ同等の記述で設定出来る
  • 並列処理などかなり柔軟なワークフローの設定が可能

選定基準のところと対になるような記述となりましたが、Argo はちょうど今求めているようなワークフローエンジンであり、より手軽にコンテナベースのワークフローを構築出来そうだと判断し導入を進めることとしました。

ちなみにデータ分析基盤チームの方では AirFlow を本格的に利用しています

導入してみた

現在は主に機械学習アプリケーションのワークフローとして利用しています。一例として以下のようなケースで利用しています。

  1. Python によりデータ分析基盤からデータを取得し後続のアルゴリズムによる計算のタスクに渡す形に整形
  2. R によるアルゴリズムで計算し結果を出力
  3. Python により出力結果を実データに変換しデータベースに登録

これら各タスクは全て独立したコンテナで動作しているため、共通の Volume をマウントすることにより各タスク間のデータの受け渡しを行っています。

また、このワークフローを定期実行するために Kubernetes の CronJob を利用しています。現状の Argo では単独で定期実行などのイベントによるトリガーが実装されていません。なので、CronJob による Job で argo submit を実行することにより定期実行を行っています。失敗時などの再実行は Kubernetes v1.10 より実装された kubectl create job <Job Name> --from=cronjob/<CronJob Name> にて手軽に実行出来ます。

どのように運用しているか

実際にどう設定してどのように動かしているのか、簡単なサンプルをベースに説明してみます。

ワークフローの設定

以下のようなフローを構築してみます。

  1. Python 可変長の値を生成 (コンテナイメージ: task-python:1.0)
  2. R により 1 で生成した数値を処理して出力 (コンテナイメージ: task-r:1.0)
  3. Python により 2 で出力した数値を処理する (コンテナイメージ: task-python:1.0)

また、2, 3 は 1 で生成した数値の個数分並列に実行させてみます。 Argo の設定ファイルは以下のようになります。

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: workflow-sample-
spec:
  entrypoint: workflow-sample-steps
  arguments:
    parameters:
      - name: max
        value: 5
  templates:
    - name: workflow-sample-steps
      steps:
      - - name: task1
          template: task-python-pre
          arguments:
            parameters:
            - name: max
              value: "{{workflow.parameters.max}}"
      - - name: task2-3
          template: nested-steps
          arguments:
            parameters:
            - name: result1
              value: "{{item}}"
          withParam: "{{steps.task1.outputs.parameters.results}}"

    - name: nested-steps
      inputs:
        parameters:
          - name: result1
      steps:
      - - name: task2
          template: task-r
          arguments:
            parameters:
            - name: param1
              value: "{{inputs.parameters.result1}}"
      - - name: task3
          template: task-python-post
          arguments:
            parameters:
            - name: param1
              value: "{{steps.task2.outputs.parameters.result}}"

    - name: task-python-pre
      inputs:
        parameters:
          - name: max
      container:
        image: task-python:1.0
        command: [sh, -c]
        args: ["python pre-task.py {{inputs.parameters.max}}  > /tmp/results"]
      outputs:
        parameters:
          - name: results
            valueFrom:
              path: /tmp/results

    - name: task-r
      inputs:
        parameters:
          - name: param1
      container:
        image: task-r:1.0
        command: [sh, -c]
        args: ["Rscript task.R {{inputs.parameters.param1}} > /tmp/result"]
      outputs:
        parameters:
          - name: result
            valueFrom:
              path: /tmp/result

    - name: task-python-post
      inputs:
        parameters:
          - name: param1
      container:
        image: task-python:1.0
        command: [sh, -c]
        args: ["python post-task.py {{inputs.parameters.param1}}"]

設定ファイルの解説

  • ワークフロー / コンテナを templates として定義する
    • ワークフローテンプレート (step)
      • name: workflow-sample-steps
      • name: nested-steps
    • 実行コンテナテンプレート (container)
      • name: task-python-pre
      • name: task-r
      • name: task-python-post
  • step は入れ子で定義出来る
    • ここでは task1 で複数の数値を生成、以降のタスクを並列に実行させるため入れ子の step を定義している
  • 各タスクの入出力はファイル経由で行っている
  • task1 の出力は [1,2,3,4] のような JSON の配列になっていて、withParam により配列をパースし以降の処理に 1 つずつ渡すことで並列処理を行うようになっている
  • spec 直下の arguments に設定しているグローバルなパラメータ(ここでは max)は argo submit workflow.yml -p max=30 のように実行時に値を上書き出来る

各ジョブの実際の内容は DB へのアクセスやデータ分析基盤である RedShift へのアクセス、GCS へのバックアップなどの処理がありますが、ワークフローとしては先程上げた導入例とほぼ同じような流れとなっています。

上記ワークフローを実際に実行した際のダッシュボードは以下のようになっています。

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

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

各ジョブがどのように実行されどのくらい時間がかかったか、その際のログ出力などが確認出来ます。

CronJob の設定

CronJob でワークフローを定期実行するにあたり、以下のような運用を行っています。

Argo クライアント

Kubernetes クラスタ内から argo コマンドを実行するために Argo クライアント専用のコンテナを作成しています。複数のアプリケーションで Argo を利用することになりますが、Argo 本体とのバージョンの整合性を保ちやすくするなどの理由で共通のコンテナとしています。

ワークフロー定義ファイル

先程設定したワークフロー定義の yaml ファイルはジョブのコンテナに含める運用にしています。Kubernetes の ConfigMap に設定したり GCS に置いておくなどの方法もありそうですがジョブのコンテナ (= ジョブと同一のリポジトリ) に配置しておくことでジョブのロジックと一緒に管理出来るの方が今のところ扱いやすいためこのような方法を取っています。 今回はワークフローの定義(workflow.yml)は Python のコンテナに同梱するような構成で進めてみます。

これらを踏まえ CronJob の設定は以下のようになります。

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: argo-cronjob-sample
spec:
  schedule: "0 * * * *"
  jobTemplate:
    spec:
      backoffLimit: 0
      template:
        spec:
          restartPolicy: Never
          volumes:
            - name: workdir
              emptyDir: {}
          initContainers:
            - name: init-workflow
              image: task-python:1.0
              command: ["sh", "-c"]
              args: ["cp /workflow.yml /mnt/work/workflow.yml"]
              volumeMounts:
                - name: workdir
                  mountPath: /mnt/work
              securityContext:
                runAsUser: 0
          containers:
            - name: exec-workflow
              image: argo-cli:1.0
              command: ["sh", "-c"]
              args: ["argo submit /mnt/work/workflow.yml"]
              volumeMounts:
                - name: workdir
                  mountPath: /mnt/work

CronJob 実行時の処理の流れは

  1. initContainerstask-python コンテナにある workflow.yml を取り出しマウントした volume に配置
  2. containers によるメイン処理にて 1 で展開した定義ファイルを引数に argo submit

となっています。改めて workflow.yml をどこで扱うかは悩ましいのですがよりアプリケーションに近いところで管理しておくことでワークフローと処理内容を一緒に参照出来る形にしています。

まとめ

このように Argo は当初から欲しかった

  • コンテナによる実行が手軽に出来る
    • Kubernetes ベースの実装のため Kubernetes のリソースとの親和性も高い
  • ワークフローを宣言的に記述出来る
  • 出来るだけ手軽に導入 / 運用出来る

という部分の要求は十分に満たしています。また現時点で使っている機能はほんの一部で、 argo リポジトリの example にあるようにかなり多様で柔軟なワークフローを構築出来る点も魅力です。

反対に、使ってみて現時点での課題と思うところは以下です。

  • 柔軟で複雑な処理が書ける反面 yaml も複雑になりがち
    • 今回は使っていないが dag での実行も実装されているのでこれを利用すればより簡潔 / 柔軟に記述出来そう
  • ダッシュボード (Argo UI)
    • 現状実行済みワークフローの参照程度。定義済みのワークフローの表示や再実行など出来るようになると良さそう
    • 実行済みワークフローが沢山あると描画がめちゃくちゃ重い
  • 実行済みワークフローの削除が自動で行われず自前で管理しなければならない
    • CronJob の 実行済み Job のように古いものは自動で消えて欲しい気もする
  • やはりスケジュール実行 / イベントトリガーが欲しい

スケジュール実行やイベントトリガーは Argo Events というプロジェクトが開発中で Calendar(Cron) や Webhook の機能が見受けられます。 また、Argo CD / Argo CI というプロジェクトも進行中で、 Argo による手軽なワークフローを CI / CD に利用出来るのはかなり便利になるのではと思っています。

「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>改善・改良を目的とした既存事業・既存サービス

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