Save 37% off PRO during our Black Friday Sale! »

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

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

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

Bc430116d109e77cebbc8bf573ff34b2?s=128

Shinsuke Sugaya

May 26, 2018
Tweet

Transcript

  1. 日本最大級の求人検索エンジン 「スタンバイ」を支える技術 株式会社ビズリーチ 菅谷信介

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

    ・機械学習基盤やアルゴリズム開発、検索システム開発など ・Apache PredictionIO/Portalsのコミッタ ・オープンソース全文検索サーバFessの開発 ・N2SM, Inc.でプロダクトディレクター 活動内容
  3. Copyright (C) 2018 BizReach, Inc. 今日話すこと スタンバイの検索機能について スタンバイの機械学習について(簡単に…) (クロールまわりは話しません…)

  4. スタンバイ

  5. ひとことでいうと

  6. 「求人検索エンジン」

  7. Copyright (C) 2018 BizReach, Inc. スタンバイとは 800万件以上の様々な求人情報をまとめて検索 国内の全業種・全職種・全雇用形態を対象 求人情報はクロールで収集 or

    入稿 Web、iOS、Androidで利用可能 https://jp.stanby.com/
  8. Copyright (C) 2018 BizReach, Inc. 検索の全体像    検索ワード 求人サイト クローラ

    検索エンジン 利用者
  9. Copyright (C) 2018 BizReach, Inc. 構成図 求人データ 利用者 APIなど 検索サーバ

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

    クローラ周辺:Spark, Akkaなど 入力サジェストAPI:MapReduce, Fess Suggestなど 機械学習関連:Python, Chainer, scikit-learnなど
  11. Copyright (C) 2018 BizReach, Inc. クロールまわりは… クローリングハック あらゆるWebサイトをクロールするための実践テクニック

  12. 検索エンジン

  13. Copyright (C) 2018 BizReach, Inc. スタンバイ的には…  クラスタでノードを管理しやすい  プラグインで拡張しやすい Elasticsearchとは 分散型RESTful検索/分析エンジン

    Apache Luceneを利用
  14. Copyright (C) 2018 BizReach, Inc. Elasticsearchの構成 データノード コーディネートノード (検索用) コーディネートノード

    (更新用) 検索API Spark/更新バッチ Elasticsearch 5.6 検索系 更新系
  15. Copyright (C) 2018 BizReach, Inc. スタンバイで扱うデータ例  タイトル:Javaエンジニア  勤務地:東京  説明文:世界120カ国以上でサービス展開…  年収:500万円~

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

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

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

    検索時 インデクシング時 Analyzerが使われる場所 Analyzerはインデクシング時および検索時に適用される
  19. Copyright (C) 2018 BizReach, Inc. 全文検索まわりの話は… 『ITSearch+』で検索システム関連を連載中 https://news.mynavi.jp/itsearch/article/bizapp/3154

  20. スタンバイのインデックスを考える

  21. 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", },
  22. 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" ] } } ] } }, ...
  23. 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} } } }, …
  24. Elasticsearchを拡張する

  25. Copyright (C) 2018 BizReach, Inc. Elasticsearchプラグイン さまざまな機能をプラグインとして拡張可能 Javaで作れる (作る際には既存のプラグインなどを参考にすると良い) https://github.com/elastic/elasticsearch/tree/master/plugins

    Elasticsearchのバージョンに依存する →次のバージョンで動くとは限らない… org.elasticsearch.plugins.Pluginを継承して実装する →拡張したい機能でOverrideしてコンポーネント追加したりする
  26. 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)
  27. 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 のレスポンス
  28. 検索課題と対応

  29. Copyright (C) 2018 BizReach, Inc. インデックスのバックアップ 課題:定期的にインデックスのバックアップを取得したい 対応: ・標準でスナップショットを取得する仕組みがあるので利用する ・分析や学習などではリストアされた環境を利用

    本番環境 snapshot restore 確認環境 分析/集計 機械学習 バッチ 1日以内:毎時で保存 2週間以内:1日単位で保存 それ以上:1ヶ月に1つを保存 日次でリストア
  30. Copyright (C) 2018 BizReach, Inc. 独自のSimilarity 課題:収集した求人にはSEO対策をしているものがある 対応: ・そのような求人はスコアを下げる  →マッチしたキーワードの頻出回数が高すぎるもの等

    ・独自のSimilarityを実装する  →SimilarityはLuceneのスコアリングに関するコンポーネント ・Elasticsearchプラグインで独自のSimilarityは追加可能 ・BM25Similarityなどを継承して作ると楽
  31. Copyright (C) 2018 BizReach, Inc. 辞書ファイルの配布 課題:Elasticsearchクラスタの全ノードに簡単に配布できないか? 対応: ・(NFSとか使えるなら、そこをマウントでも良い…) ・REST的にAPIでファイルを配布したい

    ・ConfigSyncプラグインを導入  →1回のHTTPリクエストで全ノードのファイルを配布できる https://github.com/codelibs/elasticsearch-configsync
  32. 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
  33. Copyright (C) 2018 BizReach, Inc. 辞書の動的更新 課題:再起動などをなしに辞書ファイルを再読込したい 対応: ・辞書ファイルはインデックスのオープン時に読み込み ・辞書ファイルが変わると単語(term)が変わるのでヒットしなくなる

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

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

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

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

    新しい設定でインデックスを作成 新インデックスへ再インデクシング 新しいインデックスにもファイルから取り込み Index 2
  38. 更新リクエスト 検索リクエスト インデックスの切り替え File Index alias Elasticsearchクラスタ 新しいインデックスを作成後、 aliasを新しいインデックスに張り替える 古いインデックスは削除する

    Index 2 Index 1 index
  39. 機械学習

  40. 機械学習で何をしているのか?

  41. Copyright (C) 2018 BizReach, Inc. 職種・業種推定 課題 • 様々な求人サイトから収集しているため、横 断的な職種・業種がない

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

    内容 • 自然言語処理で求人の特徴を抽出 • その特徴を学習して年収を推定 • Chainerを利用 求人内容から年収を推定
  43. 他にもいろいろと…

  44. Copyright (C) 2018 BizReach, Inc. 機械学習まわりの構成 本番環境 モデル 更新バッチ 求人取得

    取得 情報更新 学習用 機械学習 バッチ 保存 モデルを作成する バッチ起動時に モデルを読み込みする 1. 更新対象の求人群を取得 2. 各種モデルを予測値を取得 3. Update APIで部分更新 学習フェーズ 予測フェーズ
  45. 今後

  46. Copyright (C) 2018 BizReach, Inc. 今後 オートスケーリングによるノード数の増減 →Shard Allocationを駆使して… Learning

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

  48. Copyright (C) 2018 BizReach, Inc. まとめ スタンバイの検索まわりの機能の紹介 →Elasticsearchプラグインで機能拡張して対応 機械学習の取り組みの紹介 →Elasticsearchと機械学習の更新バッチの構成

  49. THANK YOU