広告出稿プランニング業務におけるセグメントのマッピングと表示改善
見出し画像

広告出稿プランニング業務におけるセグメントのマッピングと表示改善

電通デジタルでバックエンド開発をしている長内です。

本記事では電通デジタル内で開発されている業務アプリケーション内において、セグメントを取扱う仕組みを改善したのでその内容をご紹介します。

弊社の業務の中に広告出稿のプランニングがあり、業務アプリケーション上から興味・関心に関するセグメントを指定する操作があります。

前提として、この操作に関連する従来の実装ではユーザがアプリケーション上で任意のセグメントを抽出する際、ユーザ入力に対して関連性の低いものを含む大量のセグメント候補のリストに対して目視で選択を行っていました。

当然、我々は大量の要素から任意の1件を目視で探すという行為に非効率さを感じます。

であれば画面上に部分一致判定を行うような抽出用のフォームを用意し、入力した文字列によって目的のセグメントを抽出すればよい、と考えるかもしれませんが、この解決方法では本質的な解決には至れませんでした。

というのもユーザの行動として、複数の選択肢としてのセグメント候補は画面上に出現していてほしいのですが、そのうちどの候補が適切に使えるかどうかはユーザの知識と経験に依存するため、近年の検索エンジンの検索結果のように一見漠然とした関連性を持つ候補が適切な件数で表示されることが求められました。

画像2

方針の決定

結論としては、ユーザの自由入力文字列に対して媒体毎に関連性を持ったセグメント候補に絞って表示するという方法をとりました。

この具体的なマッピング手法については過去記事にある「広告セグメントをfastTextとMagnitudeを使ってマッピングする」を参照ください。これにより目視確認の負担軽減が実現できるようになります。

実のところ本記事で触れている業務アプリケーション側でも、従来実装では上記記事の実装が採用されていました。具体的には事前に作成したマッピングをDBへあらかじめ保持しておき、そのデータをアプリケーション側で読み出し実現していた経緯があります。

しかしその従来の実装ではDBに保持したデータに対し、部分一致したものをそのままズラリと並べていたため扱いづらかったわけですね。

モデルの作成

方針上Wikipediaからモデルを作成する、という流れは同一です。便宜上、これをWikipedia Modelとここでは呼称します。

このWikipedia Modelから、ユーザ入力に対して「媒体毎に関連性を持ったセグメント候補に絞って表示する」という目的のため媒体毎のModelを作成していきます。

これは関連性を持ったセグメント候補を単語の類似性という解釈のもと、アプリケーション上で媒体毎のModelから類義語を取得するためです。そしてその実現のためにはメモリ上に展開できるサイズにまで落とし込んだ媒体毎のModelが必要になります。

さてこのとき、学習などの取り回しは悪くなりますが、Modelの出力フォーマットをKeyedVectorsとすることでModelファイルを直接編集するという力技を使っていきます。

なぜ直接編集などという力技の必要があるかというと、要するにここでやっていることはKeyedVectorsのWikipedia Modelから同じくKeyedVectorsの媒体毎Modelを抽出するという操作で、下図のようなKeyedVectorsのファイルトップに存在する件数と次元数を直接Model作成プログラムの内部から指定する必要があるためです。

これによって媒体毎のModelのファイルサイズが劇的に削減できることを期待し、これは実現されました。

スクリーンショット 2020-11-12 11.33.11

なお媒体によってはセグメントに記号が含まれているケースもあります。

これは例えば「映画/ビジネス」や「映画/音楽」といった階層表現のようなセグメントを指します。

当然この点についてはベクトルをどうするか議論が発生し、結果的に「映画/ビジネス」であれば「映画」と「ビジネス」がそれぞれ持つベクトルの平均を取ることで「映画/ビジネス」のベクトルとする、という決定が下されました。

手法自体について賛否両論ありましたが、手法の実現として容易である、実装コストが少ないという観点からこの方針に舵を切っています。

具体的にはPythonで以下のような実装となりました。

pattern = re.compile(r"[>/・+、 ,._:#|()()-]")
if re.match(r".*[>/・+、 ,._:#|()()-].*", word):
   words = list(
       filter(lambda l: l != "", re.split(pattern, word))
   )
   vector = list(
       map(self.vectorizer, words)
   )
   summary = np.zeros(100)
   for v in vector:
      summary += v
   return summary / len(vector)

モデルの利用

さて、Wikipedia Modelよりはるかにデータサイズの小さい媒体毎Modelが作成されたことでアプリケーションから容易に扱えるようになりました。

具体的にはWikipedia Modelは4.7 GBであるのに対し、媒体毎のModelは大きくても1ファイルあたり5MB程度となっています。データサイズが小さいということは少なくともこの場合においては良いことです。

なぜなら前述の通り、この媒体毎のModelはファイルサイズが小さいため、アプリケーション起動時にメモリへ全て載せられるから、ですね。

一旦整理しましょう。

我々が開発している業務アプリケーションの課題は、ユーザ入力に対して関連性が低いものを含む、大量のセグメント情報を画面上に表示してしまっていたことです。その解決方法として自然言語処理に関連した手法を取り、ファイルサイズの小さい媒体毎のModel作成までを行いました。

この媒体毎のModelはファイルサイズが小さいため、アプリケーション起動時にメモリ上へ読み込んでおけます。

つまるところ、ユーザ入力に対して媒体毎のModelとユーザ入力を用いて関連性の高い単語を返却するmost_similar()を発火させることができるようになりました。

most_similar()について、具体的には以下のような実装が含まれています。

model.most_similar(['カード'])

結果は以下。

[('ICカード', 0.805596113204956),
('スカカード', 0.8045932054519653),
('わざカード', 0.7881333827972412),
('ムシカード', 0.7867932319641113),
('紙カード', 0.7824863791465759),
('etカード', 0.7797060012817383),
('禁止カード', 0.7775223255157471),
('制限カード', 0.7752258777618408),
('キーカード', 0.77522212266922),
('知カード', 0.7744728326797485)]

まとめ

現時点で上記手法によるトライアルを行い、ユーザテストの評価は以前の手法と比較して好評でした。今後全展開を行い、具体的な効果測定を行なっていきます。

効果測定については再現率や適合率などの定量的な指標と、ユーザからの定性的なフィードバックの両軸で評価するつもりです。

評価方法については他の例に漏れず悩ましいところですが、より良い機能となるよう評価方法自体も都度改善を行いつつ進めていきたいですね。