エブリーエンジニアブログ エブリーエンジニアブログ

エブリーでインターンをしています

f:id:nanakookada:20210812114528p:plain

はじめに

はじめまして。 2021年2月から、インターンとしてデータ&AIチームでデータエンジニア業務に携わっている金安です。 入社からの約半年間、データに関わる多種多様なタスクを経験させていただきました。 ここではエブリーでのデータ分析の様子を紹介するとともに、業務を通して学んだことを整理しようと思います。

いきさつ

私は大学で情報処理技術・人工知能技術について勉強しており、アカデミックな研究の道と、ものづくりに携わるエンジニアとで進路に迷っていました。 そのような中で、何となく参加した逆求人イベントでエブリーのCTOとお話しする機会があり、インターンでエンジニアとして就業することになりました。 プログラミング歴も浅く、実務経験も全くない私を採用していただき感謝しています。

現在はエブリーで週2日業務に従事しながら、大学で量子熱力学・量子情報科学分野を研究しています。 熱力学に登場するエントロピーと情報理論におけるエントロピーは本質的に等価であることからもわかるように、両者の間には接点があります。 これに注目し、情報理論の知見を物理学に持ち込むことで、非平衡熱力学など未完成の理論の発展に貢献する、というのが当分野の一つの目標です。 私の研究は、近年発見された「熱力学不確定性関係」という不等式にスポットが当てられています。 これはエネルギーと精度の関係を示唆すると解釈することができ、実用的には、量子コンピューターの性能限界の理解に繋がります。 総じて分野横断的な研究であり、情報が物理世界で果たす役割を新たな視点から探ることができます。

エブリーのデータ分析基盤

エブリーでは、毎日大量のデータを集計し、加工・分析して得た知見を施策に反映させるデータ分析のOODAループが回っています。 OODAは、Observe, Orient, Decide, Act の頭文字で、意思決定プロセスにおける4つのステップにあたります。

第一ステップは観察(Observe)で、データを集計・分析し、事実を集めることに相当します。 これをもとに、仮説構築(Orient)を行います。 これは今後の施策の方向性を決める非常に重要なプロセスです。 仮説を立てたら、具体的にどのような施策を行うかを決定(Decide)します。 最後に施策を実行(Act)します。 以上のステップを何度も繰り返すことで、変化し続ける状況の中で、適切な判断を下し、迅速に行動に移すことが可能になります。 これがOODAループです。

このデータによるOODAループを回す根底には、DIKWモデルが前提としてあります。 DIKWは Data, Information, Knowledge, Wisdom の頭文字で、下図のようなピラミッド構造になっています。

DIKWモデルのイメージ
DIKWモデル

(出典:The Knowledge Pyramid: A Critique of the DIKW Hierarchy)

集めてきただけの生のデータ(Data)は、乱雑で整理されていないうえに重複や欠落が含まれている可能性もあり、それだけでは意味を持ちません。 これをクレンジングし、何らかの基準で整理して初めて、情報(Information)として意味を見出すことができるようになります。 さらにここから、特定の観点で情報を凝集させたり、統計的な数量を算出したりすることで、より抽象度の高い知識(Knowledge)や知恵(Wisdom)に昇華させることができます。 これらは施策を行う上での判断の根拠となり、OODAループを回し、データから価値を生み出すことに繋がります。

エブリーでは、これに則ったデータ分析基盤が構築されています。 大量の生データが、Google BigQuery や Amazon S3 といったストレージに集約されます。 これを、databricksというデータ分析プラットフォームを通して加工・分析・凝集し、目的ごとに多様なテーブルを作成しています。 さらにこれをRedashというツールで集計・可視化し、組織の意思決定に役立てています。

最近体験した業務とその振り返り

ここでは、私がインターン生として最近取り組んだタスクについて振り返ってみます。

エブリーが様々な施策やサービスを運用する中で、「施策の定量的な検証・可視化をより高速に行いたい」という課題がありました。 これを達成するため、Redash上で高度な統計処理を行い、よりカジュアルに統計情報を処理できる可視化基盤を作成する、という目標が立てられました。 RedashではもともとPythonスクリプトを実行できますが、通常はRedashのコンソール上で直接コードを入力して実装します。 しかしこの方式には、実装コードがGitHubで管理されない、テストが行われていないなどの問題がありました。 そこで、統計処理用のスクリプトをモジュール化したものを、Redashサーバにデプロイすることを検討しました。

このタスクは、直接データに触れるよりは、サービスを運用するインフラ構成の把握が主だったのですが、周辺知識がほぼ0だった私にとっては、次から次へと学びを得られて非常に新鮮でした。 Redashのサーバが構築されているdockerコンテナに触れてみたり、そのサーバからS3上に通信してファイルを受け渡してみたり、Pythonライブラリが0から作られる様子を見てみたりと、新しい体験の連続でした。

特に印象的だったのが、チームリーダーからの「GitHubでmergeすると、どうしてそのコードが本番環境で動き始めると思う?(意訳)」という言葉です。 私はそこで初めて、GitHub上からサーバにコードをデプロイしている、circleCIというツールの存在を知りました。 確かに言われてみれば、git上でコードのバージョン管理をしても、全然違う場所にあるサービスのコードが勝手に書き換わるわけはないのですが、そんなことは考えたことも疑問に思ったこともありませんでした。

このRedashサーバへのモジュールデプロイは、客観的には特別難易度の高いタスクではないと思いますが、本当に有意義な体験ができたと感じています。

おわりに

エブリーでは日々実力不足を痛感させられていますが、同時に成長を実感することもあり、業務は非常に充実しています。 今後ともよろしくお願いします!


エブリーでは、エンジニア以外にもインターン生を募集しています。 興味を持たれた方は、こちらをご覧ください。

A/Bテストにおける評価指標選定の話

f:id:nanakookada:20210805165140p:plain

はじめに

 はじめまして。2021年4月にエブリーに入社した山西と申します。
 データサイエンティストとしてデータ関連部門に所属後、DELISH KITCHENアプリ改善施策のA/Bテストに約3ヶ月間従事してまいりました。
 今回はその実業務の中での体験も踏まえ、A/Bテストにおける評価指標の選定プロセスや苦労したポイントなどを紹介していきます。

A/Bテストについて

 DELISH KITCHENのアプリでは機能の改善に向けたUIやアプリ内動線等の変更を定期的に実施しております。
 このようなアプリ機能の変更はユーザーの体験を多かれ少なかれ変化させるものであるため、ユーザー視点、および事業視点で良い効果が見込めるものを見極めたうえでユーザー全体へ展開するのが望ましいといえます。
 そこでエブリーでは、プロダクトマネージャーがアプリ機能変更施策を企画し、データサイエンティストがA/Bテストによりそれらの効果を事前検証することで、変更を施すべきか否かの意思決定への還元を行っています。

 A/Bテストは、文字通りAパターン(変更前)とBパターン(変更後)の結果を比較する手法です。
 「とあるアプリ機能の変更施策」の文脈においては、一部ユーザー(テスト群)のみに介入した結果の効果を、変更を施さなかったユーザー(コントロール群)と比べて検証する手法とも言い換えられます。
 あらかじめ検証期間を定めて行うのが一般的であり、その期間内でテスト群がコントロール群よりもどの程度良くなったか(または悪くなったか)を評価指標を用いて統計的な見地から判断する、という流れをとります。
 以下、その流れを簡潔にですが整理します。

A/Bテストの流れ

 プロダクトマネージャー、データサイエンティストが連携しつつ実施することとなります。
 データサイエンティストが主体となるのは2~6のフェーズとなります。

f:id:yama24every:20210804164939p:plain
A/Bテストの流れ

1. 施策の企画
 プロダクトマネージャーが施策の案を企画し、データサイエンティストへ共有、内容の精緻化を行う。

2. 評価指標の選定
 施策の効果を測るために事前設定しておく指標を選定する。
 ※ とある機能の継続率や訪問回数などのユーザーのエンゲージメントを測る指標や、CTRやCVRなどのビジネス上注視すべき指標が例として挙げられる。

3. 対象ユーザーの抽出
 アプリ利用ユーザーをランダムに抽出した後、コントロール群(施策を実施しないグループ)とテスト群(施策を実施するグループ)に分ける。

4. 施策の実施
 テスト群のユーザーに対してアプリ機能の変更を適用する。

5. 結果の集計
 アプリのログを各評価指標へと集計するSQLクエリを作成。
 コントロール群とテスト群それぞれに対して算出した評価指標値の有意差検定や信頼区間の推定を行う。
 各集計値の比較結果を表や時系列グラフで表現し、ダッシュボードにまとめる。

6. 結果の報告
 プロダクトマネージャーにダッシュボードを共有すると共に結果を報告する。
 有意差の有無や効果量の結果レポート、及びそこから得られる考察を述べつつ議論する。

7. 意思決定
 A/Bテストの結果を省みて、施策を全ユーザーに適用するか否かの判断、または追加施策の実施の有無について判断を下す。

 本記事では以後、2.の評価指標を選定するプロセスについて掘り下げていきます。

評価指標の選定

 前述の通り、施策の効果による「良し悪し」をA/Bテストによって見極めるためには、その目的にあった評価指標を設定する必要があります。
 エブリーでは現在、以下の指標分類フレームワークをその選定基準として採用しております。

指標の分類

 これらの分類は書籍『A/Bテスト実践ガイド』にて提唱されているものです。
 要点を整理し紹介いたします。

  • ゴール指標

    • ビジネスにおける最終的な目標を直接反映した指標。
    • 施策に携わるステークホルダー間で広く受け入れられるようなシンプルなものであるのが望ましい。
  • ドライバー指標

    • ユーザー体験等を元にした、ビジネス目標を間接的に表現する指標。
    • 短期的に観察することが出来、その変動が捉えやすいという特徴を持つ。
    • ゴール指標の代理指標としての役割も担う。
  • ガードレール指標

    • 施策が問題なく進行しているか確認するための指標
    • 予期せぬバグ等でユーザー体験が悪化してないかを確認する指標

 また、優れたオンライン実験を設計するためには各指標が『短期的(実験期間内で)に測定可能であり、計算可能であり、十分に敏感(分析感度)で即時的(即時性)に反応するものでなければならない。』ことが書籍『A/Bテスト実践ガイド』で指摘されています。
 つまり、計算するためのログが存在し集計可能であること、それらが有限である検証期間内に測定可能であり、さらにその変化が観察できることを確かめたうえで、ゴール、ドライバー、ガードレールの枠組みに照らし合わせながら評価指標を選んでいくことになります。
 このようなA/Bテストに向けた評価指標に求められる観点は、ビジネスでの報告目的で用いられる指標の観点とは一致しないケースが多々あることにも留意する必要があります。

指標の実装

 施策の目的と指標分類のフレームワークを照らし合わせながら評価指標を洗い出し、それらが集計可能であること(元となるログが存在していること)を確認した後は、各々の評価指標を集計するためのSQLクエリを作成します。
 その後、ダッシュボード上にこれらのクエリを登録し、表やグラフとして可視化します。
 このようにして、A/Bテストの結果をダッシュボードを軸に報告、考察および意思決定へ還元する準備が整います。

f:id:yama24every:20210804165130p:plain
ダッシュボード可視化の例

大変だったこと

 次に、施策の目指す向きをうまく表現する指標を選定したり、それを実装へ落とし込むために元となるログ周りを理解したり、さらにプロダクトマネージャーと連携したりする過程で大変だったことについて紹介します。

指標の選定作業

ゴール指標の代理指標の設定

 本来は上記の指標の分類にて挙げたゴール指標で施策の良し悪しの意思決定が出来れば理想ですが、実際には代理指標(指標の分類でいうところのドライバー指標)を設定し、これを主要な指標として観察していく場合も多々あります。
 主な理由として以下2点が挙げられます。

  • ゴール指標が遅行指標(数値として現れるには一定の期間が必要な指標)であり、施策実施期間終了まで、すなわちビジネスにおける意思決定のタイムリミット時点までに十分な観察が行えない。

  • ゴール指標には関連する因子が多くあることが想定されることから、施策によってその一部に介入を行っただけではほとんど変化しない恐れがある。

 この際には代理指標としての妥当性(擬似相関になっていないか、因果関係まで整理できているか)の模索に、多くのリソースを割かなければなりません。

施策の影響範囲を見極めた集計

 ひとえに施策の効果を測るといっても、集計対象とするユーザーをどう選ぶかでその意味合いは大きく変わります。
 例えば「アプリトップ画面→機能A→機能B→機能C」と遷移するアプリ内動線の中で機能Cに介入するA/Bテストを実施する場合、機能Cを利用したユーザーのみ集計するのか、それより前の動線のユーザーも集計対象に含めるか、を注意深く設定しないと求めたいものと乖離する場面が多々ありました。
 求めたいものに応じてどう集計するか、以下2パターンに整理します。

「使った人がどう反応するか」を知りたいとき

 機能の使い心地に介入するような施策を打った場合は、その機能が利用されない限り効果を測ることが出来ないため、機能利用実績があるユーザーのみを集計対象とするのがふさわしいといえます。
 ユースケースとしては「ユーザーが必ず1回は利用または訪問するであろう機能のデザインに対して介入する施策を打ったとき、2回目以降の利用が伸びるか否か」を判断したいとき等が挙げられます。

「使わなかった人がどれだけ使ってくれるようになったか」も含めて知りたいとき

 アプリ内動線の追加や、デザイン変更等で機能の利用を促す施策を打った場合はその機能の獲得傾向に興味があるため、注目する機能を利用していないユーザーも集計対象となります。
 このケースではまず施策検証期間内に、アプリトップ画面にアクセスしたA/Bテスト対象ユーザー全体を対象に集計することが多くなります。
 しかし、「注目する機能があまり使われていないときに指標を平均値として集計したい」場合は、単なる全アプリアクセスユーザーの平均値を出すと0に非常に近い値が出てしまい意味のある解釈が難しくなってしまいます。
 その際には集計対象を「該当機能により近いアプリ内動線を利用したユーザー」に絞ったうえで平均値を算出する場面もありました。

 このようにして「どのユーザー集団に対して平均を取り、そこから何を知りたいのか」をイメージしながら意味のある指標に落とし込むにはアプリのUI/UX側の深い理解が必要だと感じました。
 アプリ内の機能、動線にどのようなものがあるか把握しておくことはもちろん、各機能のアプリ利用傾向を普段からモニタリングしておくことで、求める分析軸に対して適切な指標を設計する目を養っていきたいと思いました。

ログ周りの理解

ログの仕様および状態の確認

 評価指標を実装するためには当然その元となるログの存在が前提となるため、選定時点でそれらの仕様を頭に入れておく必要があります。
 また、アプリ内OSの特定のバージョンでのバグ発生等により、ログが汚染され評価指標として正しく集計できなくなっている場合は、事前にその影響を排除する措置を取ったうえで指標をデザインする必要があります。
 しかし、複雑なクライアント・サーバーサイドの実装背景を咀嚼したり、大量に存在するアプリ内動線から必要なログを洗い出したり、さらにそれらが正しく記録されているかをモニタリングしたりするのは中々に骨が折れる作業でもあり、入社後しばらくは評価指標一つの集計クエリを書くだけでも多くの時間を費やす日々が続いてしまいました。
 こうした経験から、アプリ内のユーザーの動きがログとしてどのように反映されているか、そのログが何処に格納されているか、さらに仕様変更や更新、バグ等にも目を張り巡らせておくこともデータサイエンティストの重要な役割であることを実感いたしました。

実装したい評価指標に対応するログが存在しない場合の対処

 もし評価指標の実装に必要なログが存在しない場合は、以下のいずれかの選択肢を取ることになります。

  • クライアント・サーバーサイドで新たに実装してもらう

  • 代替、近似できるような既存ログを探す

 これらのうちどの方向に舵を切るかは、当該指標のビジネス的な重要性、意思決定までのタイムリミットから逆算した施策の実施期間、他部署のリソース等の間のトレードオフ関係の熟慮ののちに各ステークホルダーと相談することによって決定されることとなります。
 データ側の視点から、こうした調整を円滑に進めることの難しさも実感しました。

プロダクトマネージャーとの連携

わかりやすく説明する能力

 流れの説明にて解説した通り、A/Bテストの狙いはその分析結果をプロダクトマネージャーへ報告し、そこから意思決定につなげる洞察を得ることです。
 それを円滑に行うため、「データサイエンスの見地をわかりやすく、かつ誤解無く説明する」能力がデータサイエンティスト側に求められることとなります。
 評価指標を解釈する観点、有意性の解釈の仕方、懸念事項などの多くの情報をどのようにしてまとめれば簡潔にアウトプットできるか、試行錯誤の毎日です。

評価指標についてのすり合わせ

 指標の分類の項でも述べたように、プロダクトマネージャーが追跡するKGIなどのビジネス指標とA/Bテストとして設定する評価指標は必ずしも一致しない場合が多くあります。
 例えば、一般的にKGIとして利用されるビジネス指標の多くは短期的に変化を観察するのが困難であり、そのままA/Bテストで追っていくとすると多くの場合ゴール指標の代理指標の設定にて説明した懸念に直面するため、この場合はより効果検証にふさわしい代理指標を主に見ていくことになります。
 そのため「なぜA/Bテストとしてその評価指標を選定したのか」「どう解釈すれば良いか」等の認識をプロダクトマネージャーとデータサイエンティスト側でしっかりと認識を合わせておく必要があります。

最後に

 「コントロール群とテスト群を比較する」というコンセプトだけ聞くと単純明快な印象を受けがちなA/Bテストですが、それを良質な意思決定へつなげるためには一筋縄にはいかない工夫が必要であることをこの数ヶ月間の業務で改めて認識しました。
 分析要件とアプリ側のドメイン知識を相互に照らし合わせつつ、試行錯誤も繰り返しながら最適な指標を見定める力を付けていきたいと思います。
 最後までお読みいただきありがとうございました。

参考文献

Ron Kohavi,Diane Tang,Ya Xu,大杉 直也.(2021)「A/Bテスト実践ガイド 真のデータドリブンへ至る信用できる実験とは (Japanese Edition) 」

Alex Deng, Xiaolin Shi. Data-Driven Metric Development for Online Controlled Experiments: Seven Lessons Learned. In Proceedings of the 22nd ACM SIGKDD International Conference on Knowledge Discovery and Data Mining, KDD, 2016

Apache SparkのSparkSQLのstack関数を用いてデータを横持ちから縦持ちにする

f:id:nanakookada:20210729160653p:plain

はじめに

はじめまして。 データストラテジストの田中です。普段は『DELISH KITCHEN』レシピ視聴実態の可視化やオーディエンス配信のレポート作成、サービス好意度の分析などの業務を行っています。

サービス好意度など定性的な要素が多い分析ではWEBアンケート調査のデータを活用していますが、WEBアンケート調査のローデータは質問内容がカラムとして横持ちで存在することが多いのが特徴です。
今回はデータベースでも扱いやすいよう「Apache Spark環境下で横持ちのデータを縦持ちにする」TIPSをお伝えします。

stack関数を用いて縦持ちにする

Spark SQLにてデータを横持ちから縦持ちにするにはstack関数を使用します。
stack関数については、Apacheより公式ドキュメントが提供されていますので、詳細は下記リンクをご覧ください。

Apache公式ドキュメント

サンプルデータ

横持ちのサンプルデータとして、アンケートデータに近い形のものを用意しました。

+------+------+---+------+--+--+-----------+--+
|sample|gender|age|  area|Q1|Q2|         Q3|Q4|
+------+------+---+------+--+--+-----------+--+
|   AAA|     1| 40| Tokyo| 1| 5|    特になし| 0|
|   BBB|     2| 15| Shiga| 2| 6| アプリを見て| 1|
|   CCC|     1| 20| Osaka| 3| 7|   広告を見て| 0|
|   DDD|     1| 55|Nagoya| 4| 8|       null| 1|
+------+------+---+------+--+--+-----------+--+

各ユーザID(sample)毎に付帯情報(gender 〜 area)と質問内容(Q1 〜 Q4)の回答結果が1行ずつ積まれています。今回はユーザIDと付帯情報に紐づく形で、質問内容を縦持ちにしたい場合の実装例を提示いたします。

実装方法

ドキュメント通りですとstack関数はSELECT stack(n, col1, col2 ...)と記述し、「col1, col2 ...」を「n」行で分割するといった仕様になります。
サンプルデータではSELECT stack(4, Q1, Q2, Q3, Q4)と記述しても良いのですが、実際のケースシナリオを想定した場合、汎用的に使えるよう質問内容のカラム情報を動的に取得できることが望ましいです。汎用性を加味した実装例を以下に提示します。

実装例

import org.apache.spark.sql.functions._

val result = data.columns.filter(x => x.contains("Q")).map{
  v =>
    data.select($"sample", $"gender", $"age", $"area",expr(s"stack(1,'$v',$v) as (q_id, q_value)"))
}.reduce(_ unionByName _)

display(result)

質問内容がスケールすることを考慮し、columnsを使用しカラム名を取得、filterを使用し質問内容のカラム名のみ抽出を行っています。 抽出したカラム名の値をmapにて、1つずつ読み出し、縦積みにしています。$vのようにカラム名を指定すると回答結果だけが縦積されるため、カラム名も'$v'で取得する形にしています。
最後に質問内容のカラム情報を1つずつ縦積みしたものをreduce(_ unionByName _)で結合しています。

出力結果

各ユーザIDと付帯情報に紐づく、質問内容のカラム名q_idと回答結果q_valueの一覧を出力することができました。

+------+------+---+------+----+------------+
|sample|gender|age|  area|q_id|     q_value|
+------+------+---+------+----+------------+
|   AAA|     1| 40| Tokyo|  Q1|           1|
|   BBB|     2| 15| Shiga|  Q1|           2|
|   CCC|     1| 20| Osaka|  Q1|           3|
|   DDD|     1| 55|Nagoya|  Q1|           4|
|   AAA|     1| 40| Tokyo|  Q2|           5|
|   BBB|     2| 15| Shiga|  Q2|           6|
|   CCC|     1| 20| Osaka|  Q2|           7|
|   DDD|     1| 55|Nagoya|  Q2|           8|
|   AAA|     1| 40| Tokyo|  Q3|      特になし|
|   BBB|     2| 15| Shiga|  Q3|   アプリを見て|
|   CCC|     1| 20| Osaka|  Q3|    広告を見て|
|   DDD|     1| 55|Nagoya|  Q3|        null|
|   AAA|     1| 40| Tokyo|  Q4|           0|
|   BBB|     2| 15| Shiga|  Q4|           1|
|   CCC|     1| 20| Osaka|  Q4|           0|
|   DDD|     1| 55|Nagoya|  Q4|           1|
+------+------+---+------+----+------------+

また別のサンプルデータを元に、Databricksのdisplay関数でプロットを作成しました。
縦持ちのテーブルのメリットは下記プロット結果のように質問全体での足し上げがしやすいことと、その他2軸以上のグラフを作成するときにも便利なことです。

サンプルデータ

f:id:blacktiger126:20210729144746p:plain
サンプルデータ

プロット結果

f:id:blacktiger126:20210729140324p:plain
サンプルプロット

最後に

実装自体はシンプルですが、stack関数の実装例が少ないと感じたため取り上げてみました。
最後まで閲覧いただきありがとうございました。

DELISH KITCHENチラシの郵便番号・地域名・店舗名検索実装について

f:id:nanakookada:20210721185210p:plain はじめまして。DELISH KITCHEN開発部でバックエンド開発等に携わっている南です。

今回は2021年4月の中旬にリリースされた、「DELISH KITCHENチラシの郵便番号・地域名・店舗名検索実装」の裏側をお話したいと思います。

f:id:takahiro_minami:20210720132515p:plain
DELISH KITCHEN チラシ

検索エンジンによる、郵便番号・地域名・店舗名検索

DELISH KITCHENチラシにはもともと郵便番号検索機能がありましたが、今回、その郵便番号検索の入力欄に郵便番号・地域名・店舗名、いずれの文字をいれても検索できるよう機能拡張しました。 1つの入力欄で郵便番号・地域名・店舗名検索をできるようにするにあたり、今回はElasticsearchを用いました。

f:id:takahiro_minami:20210720132442p:plain
Elasticsearch

n-gram vs. 形態素解析

検索エンジンで用いる際は、文字列をどのようにトークン化するかが重要になってきます。 検索エンジンのトークン化といえば半角スペース区切り、形態素解析、n-gramあたりが主流です。

昔の話ですが、カーナビの目的地住所検索ではn-gramを使用していると聞いたことがありました。 そこで最初にn-gramによるトークン化を試してみましたが、本来1位付近に表示したかった店舗が、他の店舗に埋もれてしまうという悪い結果に終わりました。

地域名・店舗名で検索される方は、短いワードで検索することが予想されます。 その短いワードに「海、川、木、山」や「東、西、南、北」など地域名に頻出するワードが含まれていると、大量の結果が返ってきてしまいます。 (昔のカーナビの住所検索は、住所を8-9割入力してようやく絞り込みができたな・・・、ということを思い出しました。)

n-gramでは良い検索体験を得られないことが分かったので、DELISH KITCHENチラシの郵便番号・地域名・店舗名検索では形態素解析することにしました。

郵便番号・地域名・店舗名検索と形態素解析

まず郵便番号は数字&記号であるため、形態素解析ではなくもっとシンプルな解析器を用いました。これについては後述いたします。

地域名・店舗名は、いずれも日本語の非分かち書きではありますが、文章ではありません。 形態素解析をするというより、辞書を充実させて形態素解析器に名詞判定してもらいトークン化する作戦です。 辞書の優劣が結果の優劣に直結してきます。

郵便番号の解析器

郵便番号検索は日本語を含まないため形態素解析も辞書も不要です。 ただし郵便番号検索では、例えば 「106-6238」とハイフン付きの7桁で検索するユーザーと「106」と3桁で検索するユーザーへの対応が求められます。

そこで「106-6238」のハイフンを「106 6238」(半角スペース)に置換したのち、半角スペース区切りでトークン化する解析器を用意しました。 indexに [106, 6238] とリストとして情報をもたせておくことで、106-6238(106 AND 6238) で検索されても106のみで検索されても〒106-6238の店舗を検索結果に含めることができます。

{
    "settings": {
        "analysis": {
            "char_filter": {
                "hyphen_to_space" : {
                    "type" : "mapping",
                    "mappings" : ["-=>%"]
                }
            },
            "analyzer": {
                "postal_code_analyzer": {
                    "type": "custom",
                    "tokenizer": "standard",
                    "char_filter": [
                        "hyphen_to_space"
                    ],
                    "filter": [
                        "split_delimiter"
                    ]
                }
            }
        }
    }
}

地名辞書データの作成

エブリーにはデータ分析業務を行っているData&AIチームがあり、地名の読み仮名データを過去に作成していたため、それを活用して地名辞書を作成しました。 ただし漢字一文字の地名は、細かくトークン化されてしまいn-gramのようになってしまう恐れがあるため削除しました。 また長すぎる住所は、辞書の1単語としてふさわしくないためこちらも削除しました。

検索対象となる店舗の住所の大半は市街地になります。「漢字2-5,6文字の地名さえ網羅できればよいだろう」くらいの気持ちで辞書を作成しました。

...

左曽,左曽,サソ,地域
巨勢,巨勢,コセ,地域
布佐,布佐,フサ,地域
布勢,布勢,フセ,地域
布太,布太,フダ,地域
布施,布施,フセ,地域
布木,布木,フキ,地域
布瀬,布瀬,フゼ,地域
布良,布良,メラ,地域

...

店舗名辞書データの作成

こればかりは、人力で作成するほかなかったため、DELISH KITCHENのデータベースから店舗名一覧を取り出して1つ1つ読み仮名を振っていきました。 幸いにも膨大な数ではなかったので手作業でこなすことができましたが、単純作業というわけにはいきませんでした。

たとえば店舗名が「エブリー商店」だった場合、「エブリー商店」で検索するユーザーもいれば、「エブリー」のみで検索するユーザーもいるでしょう。 そこで「エブリー」で1単語、「商店」で1単語、辞書作成することにしました。そうすることで「エブリー」でも、「エブリー商店」でも検索できるようになります。

店舗名は凄くユニークな店舗名もあれば、一般名詞や人名の店舗名もあります。1つずつ店舗名を確認し「どうのように検索されるだろうか?」「自分ならどんな検索をするだろうか?」と考えながら辞書作成を行いました。

kuromoji_iteration_mark filterに注意

リリース直前に「代々木」で検索できないという報告があがりました。 これはkuromoji_iteration_markをfilterに設定していたことが原因でした。iteration_markとは踊り字、つまり代々木の「々」を意味します。 kuromoji_iteration_markを設定すると、検索エンジンが踊り字を前の漢字に変換してしまいます。「代々木」で検索すると、「代代木」に変換されます。「代代木」という単語は地域名辞書には存在しないため、「代/代/木」とトークン化されていたのが不具合の原因でした。

地域名辞書に「々」を含む地域がいくつあるか確かめてみたところ170個ほど存在しました。 さほど多くはないのですが、幸運にも「代々木」という有名な地域名があったため気がついてくれた方がいました。安易にkuromoji_iteration_markを使うと辞書とマッチしなくなるため注意しないといけません。どうしてもkuromoji_iteration_markを使わなければならない場合は、辞書に「代々木」と「代代木」の両方を含めないといけません。

...

久々知,久々知,ククチ,地域
久百々,久百々,クモモ,地域
久野々,久野々,クノノ,地域
代々木,代々木,ヨヨギ,地域
佐々木,佐々木,ササキ,地域
佐々生,佐々生,サソウ,地域
佐々礼,佐々礼,サザレ,地域

...

最後に

今回は、郵便番号・地域名・店舗名検索公開に至るまでに悩んだことや躓いたことの地道な活動をまとめてみました。

検索結果の良し悪しにゴールはありません。今後、提携する店舗が増えていけば、それに伴った調整も必要になりますし、ユーザーの声にあわせた調整も必要なります。良い検索結果を返し続けるためにも、絶え間なく改善活動を続けていきたいと思っています。

社内でkubernetesの輪読会を開催しました

f:id:rymiyamoto:20210714113901p:plain

社内でkubernetesの輪読会を開催しました

はじめに

こんにちはMAMADAYS バックエンド担当エンジニアの宮本です。 今回は私の所属している開発チームでkubernetes(以下k8s)の輪読会を行ったので、その内容を紹介していきます。

MAMADAYSのサービスやバックエンドシステムの全体像については MAMADAYSのサービスとバックエンドシステムのお話 にて紹介していますので、よろしければご覧ください。

経緯

現状MAMADAYSのバックエンドシステムはAWSのEKS上で運用されています。 しかしk8s周りを触っているのが特定のメンバーのみとなっており、チーム内で知識にばらつきがありました。 またそのメンバーも体系的な学習を行っているわけではなく、十分な理解がない状況でもありました。 そのためメンバー内のSREから「チーム全体でk8sへの理解を深める必要があるのではないか」という意見が出され、チーム全員で輪読会を行う流れになりました。 私のいるバックエンドチームはweb開発も行っており、web担当メンバーも自主的に参加し実施されました。

輪読会とは

参加しているメンバーが同じ書籍を事前に読んできて、その内容について意見を交わす会です。 事前に決められた担当者が本の内容を要約し、他のメンバーが理解できるような形で発表を行います。 複数人で同じ書籍をそれぞれの視点から読み解くため、個人では理解が難しい部分をフォローしあうことで、よりメンバー間での知見が深まるようになります。

社外も含めた輪読会は実績もなくハードルが高いため、今回はチームメンバーの知識のベースアップと実際に業務で用いている部分を見比べながら行うことに重きを置くようにしました。 またコロナの感染状況も考慮して、Zoomを使ったオンライン開催となりました。

利用した書籍

インプレス社から出版されている Kubernetes完全ガイド 第2版 を用いて行われました。

f:id:rymiyamoto:20210714111531j:plain
Kubernetes完全ガイド 第2版

本書はk8sに関する機能でアプリケーションエンジニアが利用する可能性が高いものを網羅的に解説されており、様々なユースケースが紹介されています。 体系的に説明されていて図による視覚的な理解を得やすく、サンプルが添付されているためこちらを利用することとなりました。

運用

輪読会の進め方は様々ありますが、今回は知識のベースアップが目的のためチームメンバーが週一回入れ替わりで担当。事前に対象となるページを決めて、その内容をスライドにまとめて議論していく方針を取りました。 このときの進行役としては発案者であるSREのメンバーが執り行ってくれました。

全体の流れとしては以下のとおりです。

  1. 開催1週間前までに対象となるページと発表者を決める。
  2. 各メンバーが対象箇所を読んでおく、また発表者はさらにその箇所をスライドにまとめる。
  3. 当日にスライドを使って発表(30分程度)し、残り時間で質疑応答

業務を圧迫しないように、毎週金曜日開催で水曜日の時点で間に合わない場合はスキップも可としています。 緩くですが確実に進めれるようにしました。

実際やってみて

輪読会は週1ペースで全19回にわたり緩く行われ、読破までには6ヶ月弱かかりました。 本書に書かれていることはもちろんためになりましたが、さらに輪読会をする上で得た経験としては以下のとおりです。

良かったこと

メンバーによってはベースの知識の差で理解度のブレがありましたが、メンバー内でしっかりと深堀りをしていくことで埋め合わせができました。 チーム全体の知識のベースアップができたのはとても大きかったです。

また書籍の内容については新しい発見もあり、プロダクトで生かせる機能が数多くありました。 例を上げると、Pod起動時のヘルスチェックを Startup Probe で行うことができ早速導入しました。

大変だったこと

書籍のボリュームが多く、読破までの時間がかかってしましました。 ですが、下手に章を飛ばしたりすることなく全体的に学ぶことができました。

また業務や参加メンバーのスケジュールによっては調整が必要でした。 なのであまりかっちりとした予定は組まずに緩く進めるのは重要だったと思います。

最後に

以上、社内でのk8s勉強会の簡単な報告となります。 なんとなく理解している状況は継ぎ接ぎの対応で済ましてしまうため、しっかり時間をとって学ぶことは大変有意義となりました。 k8を新規に採用したり運用している場合は、体系的に学ぶことで今後のユースケースに対応しやすくなると思います。

社内でのk8sの勉強会を検討している方に参考になれば幸いです。