BASE開発チームブログ

Eコマースプラットフォーム「BASE」( https://thebase.in )の開発チームによるブログです。開発メンバー積極募集中! https://www.wantedly.com/companies/base/projects

機械学習にアノテーションを活用して、商品検索の関連キーワード機能を作る

DataStrategyの齋藤(@pigooosuke)です。

ネットショップ作成サービス「BASE」は60万店舗のショップが利用しており、ショッピングアプリ「BASE」のユーザーは、新着商品、キーワード検索、関連商品、商品特集などを介して気になる商品を見つけることができます。今回、新機能として、検索ワードに関連するキーワードを表示することで、ユーザーの興味のありそうな商品にたどり着ける動線を機械学習を活用して実装しました。

DataStrategyチームは発足して間もなく、サービスドメインに適応した単語辞書がなかったので、新規で作成するところから始まりました。機械学習におけるデータセットのアノテーションについての知見が共有される機会が少ない印象もあり、折角なので今回私達が行ったデータ作りから実装までの流れをご紹介します。

概要

f:id:HifiroleLorum:20181016175341p:plain

今回、どんなキーワードも意味的に近ければ、サジェストしても良いとはせず、ホワイトリストに登録されたキーワードのみをサジェストし、サービス品質を保てることを最低条件としています。また、関連キーワードにはいくつか定義が存在しますが、今回は類義語をサジェストする関連キーワードの開発を行いました。

大まかな手順は以下となります。

  1. 「BASE」で過去検索されたキーワードのデータからキーワードのリストを取得
  2. 前処理として、不適切な検索キーワード、優先度が低いキーワードを一定のルールで削除
  3. 有用なキーワードに対して、アプリに表示可能な単語かをアノテーション
  4. チェックがOKだったキーワードを登録したMeCab辞書を活用し、商品のタイトル・紹介文を分かち書きに分解
  5. 商品情報(タイトル+説明文)に対してWord2vecを通し、類似度の高い単語を調査し、関連キーワードとして表示

f:id:HifiroleLorum:20181016175427p:plain

Word2vecについて

文字の通り、word(文字)をvector(ベクトル:n次元)で表現するために深層学習を行うモデルです。それぞれの単語はその周辺に出現する単語によって決められているという仮説に基づいて学習を行います。今回の記事のテーマではないので詳細は割愛します。

# 学習語彙での類似度の高い単語を出力
model.wv.most_similar(positive='ワンピース', topn=6)
> [('フレアワンピース', 0.8115994334220886),
>  ('ロングワンピース', 0.7681916952133179),
>  ('ミニワンピース', 0.7396647930145264),
>  ('aラインワンピース', 0.7346851825714111),
>  ('vネックワンピース', 0.7129236459732056),
>  ('レースワンピース',0.7060198783874512)]

アノテーション

実際にアノテーションを進めていく前に、きちんとルールを決めておくことがかなり重要です。 いくらルールを厳格に決めておいても、「このケースはOKで通してみたけれど、アノテーションを進めて全体感がつかめてくると、あれはNGのケースだった。」という手戻りは少なからず発生してしまいます。 ましてや、集団でアノテーションを実施する時の難易度はかなり高くなると感じています。 今回は、(幸いなことに?)アノテーション作業を一人で行ったので、可能な限りルールの統一が出来たと思います。

参考までに、今回設定したルールとしては下記の通りになります。 今回は、各キーワードについて3つのラベルをつけていきます。

NG単語ラベル

アプリで表示するに耐えうる表現・ユーザーのUXを損なうことのない表現かをチェックしていきます。 OKとなった(ホワイトリストに登録された)キーワードのみアプリ上で表示されます。

  1. 基本的に最小商品単位になりうるものはOK
  2. 修飾語が3語以上つながっているものはNG(バックロゴ付き薄手コート:バック,ロゴ付き,薄手
  3. ブランド名が含まれているものは、社内ルール的にNG(ルイヴィトン,ディズニー
  4. 年号・数字・期間限定のキーワードが含まれているものはNG(例外で、季節感のあるものはOKとしているサマーサンダル,スプリングコート
  5. 色を表すものはNG(全色対応など、候補のノイズになる可能性が高いため)
  6. 口語表現がついているものはNG(ゆったり,もこもこ
  7. 単語単体で何を検索しているのか不明瞭なものはNG( ,クーポン,名刺

ゆらぎ表現ラベル

単語表現のゆらぎは検索エンジンにはつきものです。類似表現が出てきた時、両単語を表示することはせず、どちらかの単語のみを表示します。これをゆらぎ表現として別途登録して入出力制御に活用します。 例:Pコート,ピーコート,キャミソール,キャミなど。

  1. ゆらぎ表現は検索件数が多いものを正解とする
  2. 鞄でいう、〇〇バッグ,XXバックのように語尾が統一されないジャンルが存在するが、検索実績が多いキーワードを正解とする

カテゴリーラベル

これは個人の主観が大きく入るのと、全体のバランスを見つつゼロからラベルをつけていくので、大きく手戻りが発生する作業なかなか難しい作業です。こちらのラベルも出力制御に活用します。BASEで設定しているカテゴリーを参考にしつつ作業を進めました。

  1. 各単語について、何のカテゴリーなのかを手動でラベリング(コート,,
  2. カテゴリー階層は最大3階層まで

アノテーションデータはこのように構成されています f:id:HifiroleLorum:20181016175504p:plain

入出力の制御(アノテーションラベルの活用)

f:id:HifiroleLorum:20181016175514p:plain

Word2vecは学習に使用された語彙に対して、それぞれベクトルが設定されています。上図のようにアノテーションで属性が判明している単語もあれば、まだ調査していない未知語も入力として入ってくる可能性があります。未知語はシャットアウトすれば良いという判断もありますが、対応語を増やすため未知語でも入力を認めることにしました。ただ、Word2vecにより大量の文章を学習しており、未知語の関連語として何が出力されるのかブラックボックス化されてしまう懸念がありました。この不透明さを解消するために活用したのがこれまでアノテーションしてきたラベルです。

NGフィルタは、ホワイトリストに登録された単語のみを出力するように配置されています。ゆらぎ表現フィルタは、ホワイトリスト内の単語を片寄せするために配置されています。カテゴリーフィルタは、出力するカテゴリーを統一させ、出力ノイズを減らすために設置されています。カテゴリーフィルタは、特に未知語や学習が不十分な語彙に対して大きく影響を与えます。

下でカテゴリーフィルタ設定前後を比較しています。

# カテゴリー補正前
model.wv.most_similar(positive='ポット', topn=6)
>  [('ピッチャー',0.6643306016921997),
>   ('ティーポット',0.62236487865448),
>   ('ボウル',0.6212266087532043),
>   ('鉢',0.5690277814865112),
>   ('ティーカップ',0.5430924296379089),
>   ('花器',0.5429580211639404),

# カテゴリー補正後
model.wv.most_similar(positive='ポット', topn=6)
>  [('ピッチャー',0.6643306016921997),
>   ('ティーポット',0.62236487865448),
>   ('ボウル',0.6212266087532043),
>   ('ティーカップ',0.5430924296379089),
>   ('コーヒードリッパー',0.5426920056343079),
>   ('トレイ',0.5033057332038879),

花器といった、容器ではあるものの、キッチン周りにはふさわしくない園芸向けのキーワードの出現が抑制されるようになっています。結果、類似度の閾値制御だけでは弾くことが難しいケースもカテゴリーを設定することによって解決することができました。

まとめ

今回は、アノテーションを機械学習の制御に活用してみたという内容をお送りしました。機械学習の学習結果をそのまま信用して活用するのではなく、アノテーションの結果を活用することで品質向上を行うことができました。ただ、アノテーションを闇雲に行うのではなく、どう活用していくのか事前の課題設定をきちんと立てることも重要そうですね。

BASEでは一緒にネットショップ作成サービスを開発・改善するエンジニアを募集してます。 機械学習のチームでは、様々なデータや技術を使ってECならではの開発を続けています。 ご興味のある方はぜひ遊びにきてください!!

jobs.binc.jp

BASE BANK コーポレートロゴ誕生のデザインプロセス

f:id:yoshiokachang:20180925185620j:plain こんにちは、BASEのDesign Groupに所属している吉岡です。

ネットショップ作成サービス「BASE」のデザインや、2018年1月に設立されたBASE株式会社の100%子会社であるBASE BANKの株式会社立ち上げにデザイナーとして携わっています。

BASE BANK株式会社は、「銀行をかんたんに、全ての人が挑戦できる世の中に」をミッションとし、現在は関連事業の立ち上げを行っています。代表はBASE社と同じく鶴岡裕太が務め、メンバーはBASEと兼務している者もいます。オフィスも同じフロアにあり、カルチャーや思想なども含めてBASE社とかなり近いと言えます。

先日、BASE BANK社のロゴを作成しましたのでデザインプロセスの経緯を書き留めておきたいと思います。この記事を通じてBASE BANKがどのような会社なのか伝われば嬉しいです。

デザインの経緯

はじめに仮置きされていたロゴは下記画像の右のロゴでした。 f:id:yoshiokachang:20180925184738j:plain

左側がBASEのコーポレートロゴで、BASEロゴに合わせて、タイポグラフィをアウトライン化して入れたものでした。

'SE'に対して、BANKの'NK'がフォントボディが大きく、上手くはまりません。

綺麗にデザインしよう、ということでここからロゴの作成をスタートします。代表の鶴岡に時間をもらいながらヒアリング/調整を行なっていきました。

まず、BASE BANKの立ち位置と印象を考える

ヒアリングしたところの概要をまとめると、

  • 「銀行をかんたんに」をミッションとするので安心感を与えるロゴにしたい
  • ただ、固すぎないような自由なデザインも欲しい
  • 印象としてはストリート感が少し欲しい

という内容でした。

それを受けてデザインした初稿がこちらです。 f:id:yoshiokachang:20180925184921j:plain

なんとなく、Aが良いかな...という感じで、もう少し安定感のあるフォントが良いという流れに。ただ、この段階では、しっくりくる提案ができていないような感触がありました。

迷うロゴデザイン

ここから2週間ほどかけて3回ほどアップデートしていくのですが、なかなか良い形で決めることができませんでした。

フィードバックとしては、

  • BASEとの親和性が感じられない
  • ロゴの立ち位置が中途半端になっているのでフォントを変える意味がなさそう

などでした。

この時点でスタートから既に3週間は掛かっていました。しかし、BASE社のタイポグラフィやテイストに捉われすぎて、壊せていない部分があったので、今まで作ったロゴは全て壊してしまおうという結果に。

他のプロジェクトと並行して行なってきたのですが、この段階でロゴの作成に注力していきます。

コンセプト設計へ立ち返る

BASE BANK のロゴはどういう立ち位置なのか。パーソナリティとしてどういう人なのか?を定めて、それに対しロゴを考えるフローに戻しました。

BASE BANKは何をする会社なのか?

MTGやブレストで上がった内容から、BASE BANKはどういう会社なのかまとめました。

  • もっと銀行を簡単にしたい
  • ITとデータを活用する
  • 多くの人にチャレンジしてほしい
  • 成長をフォローしていく
  • 新しく立ち上げた会社

パーソナリティを決める

どういう性格なのか、を決めます。

  • 革新的(銀行を簡単にする、新しく立ち上げた会社)
  • クール(ITとデータの活用)
  • 安心感(チャレンジして欲しい、成長をフォローしていく)

以上の3点を踏まえた上で、ロゴを再考しました。

ラフスケッチしていく

まずは手書きでラフスケッチしていきます。 パーソナリティを考慮し、すっきりとしつつ、安定感のあるようなロゴの形を簡単にスケッチしていきます。一旦思いつくままに書き込みます。

あまり綺麗ではありませんが...100~150個位は書いたかと思います。 後々の閃きにも繋がってくるので、どんなアイデアでも書き留めることが重要です。 f:id:yoshiokachang:20180925184959j:plain

データに落とし込む

手書きラフから、イメージに合うものを選び、Illustratorで描いていきます。手書きでは問題なさそうな案も、データにしてみるとイメージが違った..などの案はこの段階で絞り込んでいきます。 f:id:yoshiokachang:20180925185038j:plain

ベースと、方向性の決定

f:id:yoshiokachang:20180925185103p:plain

brandon grotesqueという割と新しいフォント。1930年代のgeometricフォントの歴史的なものを継承しつつ、ディテールに新しさを感じます。可読性もあり、安定感と革新性を感じられるものを選びました。

このベースを元にデザインの方向性を決定しました。

洗練された印象を持たせる白をベースに、フォントの持つ安定感は残し、角丸は消す

BASEとの親和性を持たせるためにスクエアにこだわる

成長をフォローしていくイメージで、斜めのラインモチーフを入れる

ロゴだけに注力し始めて2週間...出来上がったアウトプットがこちらです。安定感のあるボディと革新的な部分を共存させるようなイメージで作り上げました。 f:id:yoshiokachang:20180925185255j:plain

ここから、角度をつけた部分やディテールを調整していきました。

角度をつけた部分が急に見えるので30度から15度に変更したり、安定感が欲しい...ということで'A'のバー位置を下げたり、もう少し落ち着かせたい...角度をつけるロゴは1つに限定する、などの変更をしました。

スタートから1ヶ月半、遂に完成したロゴはこちらです...! f:id:yoshiokachang:20180925185205j:plain アウトプットとしては、単にロゴだけなのですがBASE BANK社のこれから成し遂げたい事や思いを全て詰め込んでロゴを作れたかな..と思います。会社について、ひたすら考えることができた1ヶ月半でした。

文字領域と四角の領域は黄金比率1:1.618を使ってレイアウトを組んでいます。

全てをグリッドに合わせ過ぎると、小さく見える文字があるので若干調整しつつ、斜めの角度を0.5%単位、線幅など最終0.4ptの細かい調整をしています。

最後に

コーポレートのロゴを作成する、という経験はインハウスデザイナーでもなかなかできないので良い経験になりました...!自ら手を上げれば、色々なデザインに携わることができる環境だと思います。

この記事でBASE BANKのチームで一緒に働くことに興味が出たという方は以下の募集からご連絡ください!

jobs.binc.jp

TerraformでNGTのポータブル環境を作った

はじめまして、BASEでSREに所属している浜谷です。現在は主にAWSを使用したインフラ構築と運用を担当しています。
そこで今回は前回好評だったBASEビール部部長が語ってくれた「Yahoo!の近傍探索ツールNGTを使って類似商品APIをつくる」のインフラ環境の構築についてお話をしようかと思います。

1. 背景

BASEでは機械学習の環境以前に今本番で何が動作しているのか、又その全体を把握するにはAWSのコンソールにログインして調査する方法しかありませんでした。 AWSの運用をしているとよくある事かと思います。
そんな中BASEではシステム全体構成図の見直しやサーバの一覧を自動化したりとインフラの見える化を進めています。
そこで次はインフラの構成管理を行っていこうといった流れがあり、まずはData Strategyチームの環境をTerraformで構成管理しようと相成りました。

2. 機械学習の環境

f:id:jhamatani:20180912101224p:plain

環境をサクッと説明するとS3に画像がアップロードされたら、SNSにイベントを送付します。 SNSからSQSやLambdaに振り分けて情報を蓄積し、蓄積したデータを元にECSのDockerで機械学習した結果をAPIで返すといったインフラ環境となります。 類似商品APIって何と詳細が気になった人はBASEビール部部長の記事を読んでみてください。

3. そもそも構成管理は必要?

運用をしていく為に構成を把握する事は必要不可欠です。 ただ私自身構成管理ツールの必要性をあまり感じていませんでした。

というのはSIerの出身なので設計書、手順書、運用のドキュメントは納品物なので時間を掛けて作るのは当たり前でした。 SIerのエンジニアはoffice製品でのドキュメント作りや成果物の承認に時間を割くことが多く、その上構成管理のツールまで必要なのって感じている人は少なくないと思います。 またドキュメントでの構成管理は構築を始めるまでに時間が掛かるのは当然といった考え方が前提としてあります。

しかし、BASEの行動指針の一つに「MOVE FAST」があります。 このMOVE FASTを実現するためには、時間が掛かるのは当然といった考え方を捨てる必要があります。 そこで構成管理ツールは必要不可欠なのです! 構成管理ツールを使用するとインフラをコードで管理できるので設計から構築までを一気通貫に実現できます。

4. なぜTerraformなの?

現在BASEではAWSをメインに利用しているので、AWS CloudFormationの方が良いかもしれません。
機械学習の環境においてGCPのBigQueryを使用する場面が今後発生してくるかと考えています。
その際にマルチプラットフォームに対応した構成管理ツールを選ぶ必要がありました。

5. コードでのインフラ管理

AWSを管理コンソールで構築しているとGUIベースで確認するか、ドキュメントベースで環境を把握する必要があります。
しかし、Terraform等の構成管理ソフトを使用するとコードベースで管理できます。

VPCをTerraformでコード定義すると以下のようになります。

resource "aws_vpc" "ds-image-processing-vpc" {
  cidr_block = "10.1.0.0/16"

  tags {
    Name = "ds-image-processing-vpc"
  }

  # We explicitly prevent destruction using terraform. Remove this only if you really know what you're doing.
  lifecycle {
    prevent_destroy = true
  }
}

コードで管理することでGitHubを利用した管理が出来ます。
GitHubはアプリエンジニアだと日常的に使用しますが、インフラエンジニアにとっては少し敷居が高く感じます。 私もBASEに入社するまでは殆どGitHubは使用していませんでしたが、今では普通に使用してレビューなどの履歴も残すことが出来てとても便利に感じています。 今まではインフラ構築のレビューをシステム構成図や設計書ベースでのレビューをしていたので、別途レビュー票を起こしたり等の手間が大幅に減りました。

f:id:jhamatani:20180912101551p:plain

またインフラ構成の変更をAWSのコンソールでを漏れなくチェックするのは非常に困難です。
しかし、GitHubを使用することで差分の確認をすることで、何を変更したのかも一目瞭然です。

f:id:jhamatani:20180912101546p:plain

6. 実際にTerraformを使ってみよう!

それではいよいよ実際にTerraformを使用してみましょう。
今回はハンズオンとして、RDSのAuroraとEC2のLinuxインスタンスを実際に作ってみます。

①まずはTerraformを実行出来る環境の準備をします。
 Terraformのダウンロード(URL:https://www.terraform.io/downloads.html
  ※バージョンは適時ダウンロードしたファイルに読み替えてください。
 ダウンロードしたファイルを展開して、環境変数にパスを通す。

$ mv terraform_0.11.4_linux_amd64.zip /usr/local/bin
$ unzip terraform_0.11.4_linux_amd64.zip
$ ls -l terraform 
 ※実行権限があることを確認
$ env | grep PATH 
 ※PATHに/usr/local/binが通っていることを確認
$ mkdir -p <作業ディレクトリ> 
$ cd <作業ディレクトリ> 

②GitHubにサンプルを用意しましたので、ダウンロードします。
 GitHubのリポジトリ:https://github.com/baseinc/Hands-on-Terraform
 Terraformのドキュメント:https://www.terraform.io/docs/providers/aws/

$ git clone git@github.com:baseinc/Hands-on-Terraform.git
$ terraform init
$ vi terraform.tfvars

 必要に応じて以下の内容を書き換えてください。

aws_access_key = "<AWSのアクセスキー>"
aws_secret_key = "<AWSのシークレットキー>"
aws_region = "ap-northeast-1"

main_aurora_root_user = "<auroraへのアクセスユーザ>"
main_aurora_root_password = "<auroraへのアクセスパスワード>"

external_ip = "<EC2にアクセス出来るIP>"
host_ssh_key = "<EC2にアクセスする際のキーペア名>"

③準備ができたので、Terraformを実行するだけです。

  • 実行前の動作確認(DryRun): $ terraform plan
Plan: 20 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

 上記の様に「20 to add」と表示されればOKです。
 20個のリソースがコマンド一つで作成されます。

  • 実行:$ terraform apply
Plan: 20 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

 「yes」と回答します。

aws_iam_role.db-main-aurora-monitoring: Creating...

 リソースの作成が開始します。

④リソース作成完了までは少し待ちます。

aws_rds_cluster_instance.db-main-aurora-instance.0: Still creating... (12m50s elapsed)
aws_rds_cluster_instance.db-main-aurora-instance[0]: Creation complete after 12m57s (ID: db-main-aurora-instance-0)

Apply complete! Resources: 20 added, 0 changed, 0 destroyed.

 これでAWS上にAuroraとLinuxのインスタンスが立ち上がりました。  どうですか、サクッと作れてしまいましたね。
 リソースが出来たかどうか、実際にAWSコンソールにログインして確認してみましょう!

f:id:jhamatani:20180912101538p:plain f:id:jhamatani:20180912101543p:plain

6. リソースを作った後は。。。

こんなに簡単に作れてしまうと、次から次へとリソースを作ってしまいますね。
そして放置なんてことになったら、AWSに多大な貢献をしてしまうことになりかねません。
ですので不要になったらきちんとお片付けを実施します。

  • 削除 :$ terraform destroy

コマンドを実行して削除完了と思ったら。。。。

Error: Error running plan: 1 error(s) occurred:

aws_route.db-vpc-route-external: aws_route.db-vpc-route-external: 
the plan would destroy this resource, but it currently has lifecycle.prevent_destroy set to true. 
To avoid this error and continue with the plan, either disable lifecycle.prevent_destroy or adjust the scope of the plan using the -target flag.

あれ?なんでだろう。。。エラーが出てしまいます。
理由は削除保護が有効になっていたからです。 VPCやDB等は間違えて消してしまうと想定外の影響が出てしまう可能性がある為、簡単に消せない様に削除保護prevent_destroy = trueをリソースに定義していました。
ただ、今回は消してしまいたいのでリソース上の定義を全てprevent_destroy = falseに変更して再度実行してみましょう。

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

「全てのリソースを削除しますが、良いですか」と聞かれます。
「yes」と回答します。

…
aws_subnet.db-vpc-subnet-d1: Destroying... (ID: subnet-0bae4cb4032d56f93)
aws_subnet.db-vpc-subnet-a1: Destroying... (ID: subnet-0a9d2118812674f12)
aws_subnet.db-vpc-subnet-c1: Destruction complete after 0s
aws_subnet.db-vpc-subnet-d1: Destruction complete after 1s
aws_subnet.db-vpc-subnet-a1: Destruction complete after 1s
aws_security_group.db-main-aurora-security-group: Destruction complete after 2s
aws_vpc.db-vpc: Destroying... (ID: vpc-0790adf505d9e6ac3)
aws_vpc.db-vpc: Destruction complete after 0s

Destroy complete! Resources: 20 destroyed.

今度は消えましたね。 また必要になったら、$ terraform applyで作り直しましょう。

まとめ

Terraformを使用することで新規作成から削除までコマンドで簡単に出来るようになりました。 今後、既存の本番環境のTerraform化や新規にプロダクトを開発している子会社のBASE BANKもTerraformを利用したりしているので、また機会があれば続きを書ければなぁと思います。

ランサーズ、ReBuild、BASEの3社合同のイベントで、「PHPバージョンアップと決済テストを支えたユニットテスト」について話しました

こんにちは!BASE Product Division サーバーサイドエンジニアの東口(@Khigashiguchi)です。主にEコマースプラットフォーム「BASE」の決済領域の開発や、BASE BANKというBASEの子会社にて金融事業の立ち上げを行っています。

さて、2018/8/7(火)に、ランサーズ、ReBuild、BASEの3社でエンジニア向けの勉強会「レガシーコード改革!UT/CIでWebサービスの技術的負債を解消する取り組み」を開催しました。

www.wantedly.com

そこで「PHPバージョンアップと決済テストを支えたユニットテスト」というタイトルで発表させていただきました。

f:id:khigashigashi:20180816100445j:plain

「レガシーコードを取り扱うためにどのようなユニットテストを書いていったか」について実体験ベースでの取り組みと得られた課題について話しています。 参加者の方からも、「実体験ベースでの知見が参考になった」といったフィードバックもいただけました。

登壇資料

この発表は、PHPカンファレンス関西2018にてLT発表させていただいた、テストを書いたことがないエンジニアがテストを書けるようになるまでやったことの詳細版として、以下のポイントについて言語化させていただきました。

  • テストがないコードに対して、どういう考え方でテストを書いていったか
  • チームとしてどうやってテストを広めようとしたか
  • 結果としてどのような課題があったか

実際にコードレベルでの説明にまで落とし込んでいるので、同じ課題を持つ方にとって一つの有益な知見として参考になれば幸いです。

また、今回はイベントタイトルが「レガシーコード改革!UT/CIでWebサービスの技術的負債を解消する取り組み」というものなので、「レガシーコード」をテーマとした発表になりました。
そのため、「レガシーコード」というテーマで発表するに当たり、主に下記の2点の書籍を改めて読み直して発表のコアとなる部分の言葉をお借りさせていただいています。

www.shoeisha.co.jp www.shoeisha.co.jp

とても参考になるので、今回の資料を入り口にそちらもご参考にしていただければと思います。

まとめ

Yahoo!の近傍探索ツールNGTを使って類似商品APIをつくる

はじめまして、BASEビール部部長の氏原です。BASEのData Strategy Groupで機械学習エンジニアをしています。 今回初登場ということで、暑いときにいいサワーエールのお話でも......といきたいところですが、ここは開発ブログということなので仕方ありません。開発のお話をしましょう。

現在私は商品の画像に基づいて、その商品に似た商品を類似商品として提示するAPIの開発を行なっています。今回はこのAPIをYahoo!さんのNGT(Neighborhood Graph and Tree for Indexing)を使って作成したことについて書いてみようと思います。

f:id:beerbierbear:20180814180938p:plain:w800

背景

BASE株式会社はネットショップ作成サービス「BASE」を運営しています。ここで作成されたショップはそれぞれ別のWEBサイトとして公開されていますが、ショッピングアプリ「BASE」では作成されたショップを横断して商品を検索し購入することができます。しかしショップの数は公称で50万店舗、非常に多くの商品がありますのでユーザーさんが興味を持つであろう商品をいかにして探しやすくするかはサービスにとって喫緊の課題と言えます。その取り組みの一環として、私は商品詳細ページにある関連商品の改善を目的に類似商品APIの作成を行っています。

全体の概観

類似商品APIは以下の3つの構成要素で成り立っています。

  1. 画像の特徴量抽出
    • 新しい商品画像がサービスに登録された際に特徴量を計算して保存する
  2. 特徴量のindexing
    • 関連商品として出す商品の画像を選別して近傍探索用のindexを作成する
  3. API
    • 検索元商品の画像から近傍の画像を探索しその画像に対応する商品を類似商品として返す

ではそれぞれについて解説していきましょう。

画像の特徴量抽出

BASEには毎日結構な量の商品が新しく登録されていきます。既存の商品の画像を差し替えたりすることもあります。それらの新しい画像の特徴量は随時計算しておかないといけません。

特徴量抽出の全体構成

BASEでは商品画像をS3に保存しています。S3ではファイルが登録されると、そのことをイベントとして通知できます。特徴量抽出ではそれを利用して以下のようにシステムを組んでいます。

f:id:beerbierbear:20180814175019p:plain:h300

S3, SNS, SQS

S3で画像が登録されると、そのイベントをまずSNSに投げます。そしてSNSはそのイベントをそのままSQSに投げます。

一旦SNSを通しているのは、画像登録を契機に何かしらの処理を行いたいという要望は今後他にも出てくることを想定しているためです。SNSであればsubscriberを増やせばイベントをbroadcastできます。

こうしてS3に登録された画像はイベントとしてSQSに溜まっていきます。

ECS

SQSに溜まったイベントを取りに行くのがECS配下で稼働しているServiceです。ここではSQSからイベントをpollingして、取得したイベントから画像を取得して一枚づつ特徴量を計算してDBに保存していきます。ECSはAuto Scalingと組み合わせればSQSが溜まってきたときにServiceを増やすのが簡単です。

画像の特徴量

みなさん、画像の特徴量といえば何を思いつきますか?SIFTとかHOGでしょうか。最近ですとDeepLearningでしょうか。今回、画像の特徴量の抽出にはMobileNetを利用しました。

いちからMoblieNetを作るのではなく学習済みモデルをそのまま利用しました。Kerasのやつですね。ホント楽になりましたね。

from keras.applications.mobilenet import MobileNet

model = MobileNet(weights='imagenet',
                  include_top=False,
                  input_shape=(224, 224, 3),
                  pooling="max")

include_topはFalseにしてクラス分類のネットワークは外して特徴量を抽出する部分だけ使います。画像はもう単純に224×224にリサイズして使います。これで224×224のRGB画像からfloat32の1024次元のベクトルが得られます。

from keras.applications.mobilenet import preprocess_input
from keras.preprocessing import image

import boto3
import keras.applications.mobilenet
import numpy as np

import io

# S3から画像を取ってくる
s3 = boto3.resource('s3')
img_object = s3.Object(bucket, object_key)
response = img_object.get()
# 画像を読み込む
img_data = io.BytesIO(response["Body"].read())
img = image.load_img(img_data, target_size=(224, 224))
# numpyのarrayにしてMobileNetの前処理をする
x = image.img_to_array(img)
xs = preprocess_input(np.array([x])
# 特徴量を計算する
vec = model.predict(xs).flatten()

特徴量を保存するDB

上で得られた特徴量はDBに保存しておきます。 今回特徴量を保存するのにはAuroraを利用しました。1024次元のfloat32のベクトルを保存するのに容量あまり気にしないでもいい場所が欲しかったためです。でもRDSとか単にベクトル保存する場所としては機能過多ではあります。必要な機能を考えると単なるKey-Value Storeでいいんですが、ここは今後も要検討です。

特徴量のindexing

画像登録に連動してDBに特徴量を保存できるようになりましたが、サービスで使うにはある画像の特徴量vectorの近傍にあるvectorがどれなのかを知ることができるようにしなくてはいけません。これを実現するために今回はNGTを利用しました。

NGTとは

Yahoo!さんの説明をそのまま引用させていただきます。

NGTは任意の密ベクトルに対して事前に登録した(同次元の)ベクトルから最も距離が近いベクトルの上位数件(k件)を高速に近似k最近傍探索(k-Nearest Neighbor Search)するためのソフトウエアです。 高次元ベクトルデータ検索技術「NGT」の性能と使い方の紹介

超高速に近傍のベクトルを探せるソフトです。本当に超高速です。350万件の1024次元のベクトルを登録してみたところ、メモリを15G程度食いますが近傍1000件取ってくるのに数十msくらいしかかかりません。これならキャッシュを併用すれば十分使えると判断しました。 python wrapperもあるので使うのも簡単です。

index作成の全体構成

ECS Taskを利用したバッチ処理でNGTのindexを作成しています。

f:id:beerbierbear:20180814175146p:plain:h300

ECS

indexの作成は日次バッチで行うのでCloudWatch Eventをdailyで投げるようにして、ECSがそれを受け取ってTaskを実行するようにしました。 TaskはAuroraからindexingする特徴量を取得してNGTに登録します。このとき全部の特徴量を使うのではなく、古い商品は登録しないなどある程度の取捨選択をしています。

NGTはpython wrapperを使ってこんな感じです。(適当に簡略化してます)

from ngt import base as ngt

ngt_index = ngt.Index.create(b"any/where/you/want/to/save/index", 1024)

# 結果は大きいのでサーバーサイドCursorつかう
conn = MySQLdb.connect(..., cursorclass=MySQLdb.cursors.SSCursor)
cursor = conn.cursor()
cursor.execute(......) # 画像の特徴量とかとってくる

# 大きくて全部持ってこれないから一個づつ処理
for row in cursor:
    image_id, vec = row
    oid = ngt_index.insert_object(vec)
    # NGT内でのobject idと画像のIDの紐付けは自分で覚えておく必要あり
    ...

# indexの作成
ngt_index.build_index(num_threads=8)

S3

作成されたNGTのindexはS3に保存しています。indexは毎日作成され、ある程度の期間保存して古いものは捨ててます。

API

NGTのindexが日々作成されるようになりましたので、今度はそれを利用する部分を用意しましょう。

API提供部分の全体構成

f:id:beerbierbear:20180814175216p:plain:h150

構成としてはAPI Gatewayを入り口としたECSの二段構えになっています。 これは類似商品を取得するという機能と類似画像を取得するという機能を分離させておくことで、類似画像APIを利用した他の機能の開発を簡単にするためです。例えば、現在BASEの商品の画像検索APIも開発中ですが、これはこの仕組みにそのまま乗っかっています。

f:id:beerbierbear:20180814175234p:plain:h300

類似商品API

ここは以下のような役割を受け持ちます。

  1. API Gatewayから商品IDを受け取る
  2. 商品IDを対応する画像IDに変換する
  3. 画像IDを類似画像APIに投げ、類似画像のIDを受け取る
  4. 類似画像のIDを対応する商品IDに変換する
  5. 類似商品のIDをAPI Gatewayに返す

やってることはシンプルです。 構成のところには出ていませんが、毎度類似画像APIを呼ばなくてもいいようにElastiCacheを設置して、類似画像APIを呼ぶ前にそっちを確認しています。

類似画像API

ここはNGTの薄いwrapperになってます。やっているのは画像IDや画像の特徴量そのものを受け取り、近傍の画像のIDを返すだけです。 ここはindex作成が終わったときに新しいindexを読むように再起動されます。この再起動はECSの仕組みを使って複数動いているECS Serviceを少しづつ再起動させていくことでサービスとしては無停止で行われるようになっています。

実際にどう変わるか

では、構築したAPIで類似商品がどの程度改善したかみてみましょう。

f:id:beerbierbear:20180814175959j:plain:h600

この洋服の関連商品は以前のロジックだと以下のようになってました。

f:id:beerbierbear:20180814180047j:plain:h600

うん、服ですらないですね。 正直どうしてこれらが関連商品に上がってきたのか謎です。名前だろうか。

で、それが今回のAPIで以下のように改善しました。

f:id:beerbierbear:20180814180241j:plain:h600

おお!服だ! ......まあそれだけで感動できるほど前のが悪すぎたという話もあります。 雰囲気もなんとなく似てますかね。ファッション系は概ね良い感じに類似商品を出せるようになりました。

まとめ

私の所属しているData Strategyチームは最近できたばかりで、まっさらなAWS環境を渡してもらってインフラ含めて一から類似商品APIを構築するというなかなか楽しい経験をさせてもらいました。まだまだ精度を上げたいところではありますが、そこそこ良いものができたのではないかと思っています。 実際関連商品のタップされる率は今までの2倍ほどに伸びており一安心というところです。

今回構築した類似商品APIでは今のところ画像しか見ていません。そのため少々コンテキストから外れたものが出てくることもあります。そこで現在商品のタイトルや説明も考慮するようにAPIをアップデートしようとしているところです。もっと精度を上げて、私がいいビールを探せるように......いえ、ユーザーさんがいい商品をみつけられるようにこれからも頑張ります。

開発した成果は、ショッピングアプリ「BASE」で見ることが出来ます。個別の商品を見るビューをスクロールして「関連する商品」をぜひご覧下さい。

P.S.

長くなるんで省きましたが、実は今回作ったAPIのAWSの構成管理はTerraformで書いて1発で構築できるようにしています。その話はまたの機会にしたいと思います。