Upgrade to Pro — share decks privately, control downloads, hide ads and more …

日本最大級の求人検索エンジン「スタンバイ」を支える技術

 日本最大級の求人検索エンジン「スタンバイ」を支える技術

スタンバイは、700万件以上の様々な求人情報をまとめて探せる求人検索エンジンです。 スタンバイは求人のクロールなどにSpark、検索エンジンにはElasticsearch、機械学習での処理にChainer、などの様々な技術で構成され、運用しています。 日本語での検索システム構築の観点でも多くのノウハウがあるので、このセッションではそれらを中心にどのように検索システムを作っているのかを紹介します。

Shinsuke Sugaya

May 26, 2018
Tweet

More Decks by Shinsuke Sugaya

Other Decks in Technology

Transcript

  1. Copyright (C) 2018 BizReach, Inc. 自己紹介 ビズリーチ AI室 菅谷 信介

    ・機械学習基盤やアルゴリズム開発、検索システム開発など ・Apache PredictionIO/Portalsのコミッタ ・オープンソース全文検索サーバFessの開発 ・N2SM, Inc.でプロダクトディレクター 活動内容
  2. Copyright (C) 2018 BizReach, Inc. 構成図 求人データ 利用者 APIなど 検索サーバ

    入力サジェストAPI 検索ログ 検索エンジン周辺 更新バッチ (機械学習など) モデル 追加データ Spark Kinesis クローラ 生成
  3. Copyright (C) 2018 BizReach, Inc. 構成要素 検索エンジン:Elasticsearch+プラグイン 画面やAPI周辺:Scala, Play Frameworkなど

    クローラ周辺:Spark, Akkaなど 入力サジェストAPI:MapReduce, Fess Suggestなど 機械学習関連:Python, Chainer, scikit-learnなど
  4. Copyright (C) 2018 BizReach, Inc. スタンバイで扱うデータ例  タイトル:Javaエンジニア  勤務地:東京  説明文:世界120カ国以上でサービス展開…  年収:500万円~

     勤務時間:裁量労働制  雇用形態:正社員  ・・・ キーワード検索:タイトル、説明文、勤務時間などを全文検索 勤務地検索:勤務地を緯度経度に変換して位置情報検索 スタンバイでは… 求人情報
  5. Copyright (C) 2018 BizReach, Inc. LuceneだとAnalyzerで単語に分解 転置インデックス 文書ID 文字列 1

    東京スカイツリー 2 東京タワー 単語 文書IDリスト 東京 1, 2 スカイツリ 1 タワー 2 文書データ(検索対象) インデックス(索引) 検索 文書群を取得 検索対象の文字列から単語の索引を作る
  6. Copyright (C) 2018 BizReach, Inc. 「東京スカイツリーの①番出口」 CharFilter (文字単位で変換) TokenFilter (単語単位で変換)

    Tokenizer (単語に分割) 「東京スカイツリーの 1番出口」 「東京」「スカイツリー」「の」「 1番」「出口」 「東京」「スカイツリ」「 1番」「出口」 Analyzer CharFilter (文字単位で変換) CharFilter (文字単位で変換) TokenFilter (単語単位で変換) TokenFilter (単語単位で変換) Analyzerとは (今回説明するAnalyzerは) Luceneで提供されるテキストを解析する機能
  7. Copyright (C) 2018 BizReach, Inc. インデックス 検索 Analyzer Analyzer 検索対象

    検索時 インデクシング時 Analyzerが使われる場所 Analyzerはインデクシング時および検索時に適用される
  8. Copyright (C) 2018 BizReach, Inc. インデックス設計 適切なスコアリングと検索漏れがないようにする →形態素解析+よみがな+bi-gram(同義語) たとえば、説明文のフィールド(job_content)のマッピング定義は… (マルチフィールドでも良いが、他フィールドもマージするならcopy_to)

    "job_content": { "type": "text", "analyzer": "kuromoji_analyzer", "copy_to": ["bigram_content", "reading_content"] }, “reading_content”: { "type": "text", "analyzer": "kuromoji_reading_analyzer", }, “bigram_content”: { "type": "text", "analyzer": "bigram_analyzer", },
  9. Copyright (C) 2018 BizReach, Inc. キーワードのクエリー ブーストしたOR検索(boolのshould):  フレーズのブーストしたmulti_matchで検索 (左側)  minimum_should_matchでフレーズ以外も検索

    (右側) { "bool": { "should": [ { "multi_match": { "query": "${word}", "type": "phrase", "fields": [ "job_title^0.8", "job_content^0.5", "bigram_content^0.1", "reading_content^0.1" ] } }, ...右に続く... { "multi_match": { "query": "${word}", "minimum_should_match": "2<-25%", "fields": [ "job_title^0.01", "job_content^0.01" ] } } ] } }, ...
  10. Copyright (C) 2018 BizReach, Inc. 勤務地のクエリー ジオサーチ:  function_scoreで距離の近さでスコアをブーストする (左側)  filterで範囲で絞り込む

    (右側) “bool”:{ “must”:[ "function_score": { "functions": [{ "exp": { "location_point": { "origin": { "lat":${lat}, "lon":${lon}}, "scale": "${distance}km", "offset": "0.1km", "decay": 0.5 } } }, “filter:[ { "geo_distance": { "distance": "${distance}km", "location_point": { "lat": ${lat}, "lon": ${lon} } } }, …
  11. Copyright (C) 2018 BizReach, Inc. Elasticsearchプラグイン さまざまな機能をプラグインとして拡張可能 Javaで作れる (作る際には既存のプラグインなどを参考にすると良い) https://github.com/elastic/elasticsearch/tree/master/plugins

    Elasticsearchのバージョンに依存する →次のバージョンで動くとは限らない… org.elasticsearch.plugins.Pluginを継承して実装する →拡張したい機能でOverrideしてコンポーネント追加したりする
  12. Copyright (C) 2018 BizReach, Inc. public class ExampleRestHandlerPlugin extends Plugin

    implements ActionPlugin { @Override public List<RestHandler> getRestHandlers(final Settings settings, final RestController restController, final ClusterSettings clusterSettings, final IndexScopedSettings indexScopedSettings, final SettingsFilter settingsFilter, final IndexNameExpressionResolver indexNameExpressionResolver, final Supplier<DiscoveryNodes> nodesInCluster) { return singletonList(new ExampleCatAction(settings, restController)); } } ExampleRestHandlerPlugin Pluginを継承する 拡張したい機能を追加する (この場合はRESTのAPI)
  13. Copyright (C) 2018 BizReach, Inc. public class ExampleCatAction extends AbstractCatAction

    { ExampleCatAction(final Settings settings, final RestController controller) { super(settings); controller.registerHandler(GET, "/_cat/example", this); controller.registerHandler(POST, "/_cat/example", this); } @Override protected RestChannelConsumer doCatRequest(final RestRequest request, final NodeClient client) { final String message = request.param("message", "Hello from Cat Example action"); Table table = getTableWithHeader(request); table.startRow(); table.addCell(message); table.endRow(); return channel -> { try { channel.sendResponse(RestTable.buildResponse(table, channel)); } catch (final Exception e) { channel.sendResponse(new BytesRestResponse(channel, e)); } }; } ExampleCatAction 応答するパスを指定 応答する内容を記述 /_cat/example のレスポンス
  14. Copyright (C) 2018 BizReach, Inc. 独自のSimilarity 課題:収集した求人にはSEO対策をしているものがある 対応: ・そのような求人はスコアを下げる  →マッチしたキーワードの頻出回数が高すぎるもの等

    ・独自のSimilarityを実装する  →SimilarityはLuceneのスコアリングに関するコンポーネント ・Elasticsearchプラグインで独自のSimilarityは追加可能 ・BM25Similarityなどを継承して作ると楽
  15. Copyright (C) 2018 BizReach, Inc. サーチテンプレート 課題:検索クエリーをどのように管理するのが良いか? 対応: ・業務ロジックと検索クエリーは切り分けたい  →標準でもmustacheでサーチテンプレートを利用可能

    ・でも、ちょっとしたロジックも書きたい ・Script-based Search Templateプラグインを導入  →サーチテンプレートでスクリプト言語を使える ・Velocityプラグインを導入  →サーチテンプレートをVelocityで記述 https://github.com/codelibs/elasticsearch-sstmpl https://github.com/codelibs/elasticsearch-lang-velocity
  16. Copyright (C) 2018 BizReach, Inc. 辞書の動的更新 課題:再起動などをなしに辞書ファイルを再読込したい 対応: ・辞書ファイルはインデックスのオープン時に読み込み ・辞書ファイルが変わると単語(term)が変わるのでヒットしなくなる

     →スタンバイでは更新量が多いので自然淘汰されるはず ・Analysis JaやAnalysis Synonymプラグインを導入  →辞書がリロードされる https://github.com/codelibs/elasticsearch-analysis-ja https://github.com/codelibs/elasticsearch-analysis-synonym
  17. Copyright (C) 2018 BizReach, Inc. 検索結果の並び替え 課題:同じ求人名や媒体名が並んで表示されないようにしたい 対応: ・検索結果の上位部分だけでも改善する ・DynaRankプラグインを導入

     →ヒットした上位N件を並び替える  →似たものが連続表示されないようにする ・Minhashプラグインを導入  →求人タイトルのビット列をインデックスに入れる  →DynaRankではこのビット列で類似度を判断して並び替える https://github.com/codelibs/elasticsearch-dynarank https://github.com/codelibs/elasticsearch-minhash
  18. Copyright (C) 2018 BizReach, Inc. オンラインインデックス更新 課題:無停止でインデックスの設定・マッピングを変更したい 対応: ・Indexing Proxyプラグインを導入(コーディネートノード)

     →更新リクエストを別途ファイルに保存  →インデックスのデータ更新は保存したファイルから更新  →設定・マッピング更新時には新規インデックスを作成して切り替え https://github.com/codelibs/elasticsearch-indexing-proxy
  19. 更新リクエスト 検索リクエスト Indexing Proxyの概要 Index 1 File Index alias Elasticsearchクラスタ

    index 更新リクエストはファイルに保存する 定期的にファイルからインデックスへ取り込む Indexing Proxy
  20. 更新リクエスト 検索リクエスト 設定・マッピングの更新時 Index 1 File Index alias Elasticsearchクラスタ index

    新しい設定でインデックスを作成 新インデックスへ再インデクシング 新しいインデックスにもファイルから取り込み Index 2
  21. Copyright (C) 2018 BizReach, Inc. 職種・業種推定 課題 • 様々な求人サイトから収集しているため、横 断的な職種・業種がない

    内容 • 自然言語処理で求人の特徴を抽出 • その特徴を学習して職種業種を推定 • Chainerを利用 求人内容から職種・業種を推定
  22. Copyright (C) 2018 BizReach, Inc. 年収推定 課題 • 年収情報のニーズが高いが、年収非公開求 人などがある

    内容 • 自然言語処理で求人の特徴を抽出 • その特徴を学習して年収を推定 • Chainerを利用 求人内容から年収を推定
  23. Copyright (C) 2018 BizReach, Inc. 機械学習まわりの構成 本番環境 モデル 更新バッチ 求人取得

    取得 情報更新 学習用 機械学習 バッチ 保存 モデルを作成する バッチ起動時に モデルを読み込みする 1. 更新対象の求人群を取得 2. 各種モデルを予測値を取得 3. Update APIで部分更新 学習フェーズ 予測フェーズ
  24. Copyright (C) 2018 BizReach, Inc. 今後 オートスケーリングによるノード数の増減 →Shard Allocationを駆使して… Learning

    To Rankによる検索結果の最適化 →現状はシンプルなモデルで対応しているので、その改善… Word2Vecなどの応用 →HR領域の単語ベクトル: https://github.com/bizreach/ai まだまだ面白いことができるのでエンジニア募集中!