ミツモア Tech blog

「ミツモア」を運営する株式会社ミツモアの技術ブログです

マッチングアルゴリズムに機械学習を導入した話 アプリ導入編 (3/3)

※ こちらはミツモアAdvent Calendar 2021の9日目の記事です。

こんにちは ミツモアのエンジニアの髙橋(@yumit414)です。

ミツモアは「確定申告めんどくさい…」「リモートワークが増えてエアコンを綺麗にしたい」「子供の卒業式の記念に写真を撮りたいな」といった生活のあらゆるシーンであなたにぴったりの専門家を無料で探せるサービスですので、ぜひ気軽に使ってみてください!

meetsmore.com

7日目 古田さん(@crazysrot)
マッチングアルゴリズムに機械学習を導入した話(1/3) モデル作成篇
8日目 藤井さん(@yuki_meetsmore_87910)
マッチングアルゴリズムに機械学習を導入した話 モデル今後の展望編 (2/3)

に引き続き、本日は実際に機械学習結果を取得しマッチングアルゴリズムに組み込むまでの流れについてお話しさせていただこうと思います。

背景

ミツモアでは不用品回収やエアコンクリーニングなどを希望する依頼者に対して、それを解決するプロを自動応募システムによってマッチングさせています。

現在その自動応募システムのアルゴリズムをロジックベースから機械学習に置き換えるプロジェクトが実施されております。
自動応募について詳しくは 古田さんの記事に記載してありますので、ご参照ください。

Vertex AI

今回使用するVertex AIは、GCPが提供しているAIサービスです。
必要なパラメーターを投げるとendpointを取得してくれて、そのendpointに学習済みモデルが必要な値を渡すと、計算した結果を返してくれます。

Vertex AIエンドポイントの作成

VertexAIで既存のコンテナを使用しエンドポイントを提供する場合、モデルのライブラリとしては主にTensorFlow/XGBoost/scikit-learnの3つの選択肢があります。 なおカスタムコンテナを使用する場合は他のライブラリも使用可能です。
今回はXGBoostのモデルを提供したので、XGBoostについて記載します。

modelファイルの作成

Vertex AIにXGBoostモデルをデプロイする場合、"model.bst"という名前でモデルファイルを生成する必要があります。

save_name = "フォルダのパス/model.bst"
bst.save_model(save_name)

上記のようなコードで作成したモデルを保存してください。

VertexAIへのデプロイの流れ

VertexAIへは簡単にモデルをデプロイすることが可能です。

1. CloudStorageへのモデルの登録

CloudStorageへ先ほど作成したmodel.bstを保存してください。 なおこの際作成予定のエンドポイントと同じリージョンにする必要があります。 CloudStorageへのアップロードの方法はここでは省略。

2. モデルのインポート

VertexAIのモデルタブの以下の画面からインポートをクリックし、モデルをインポートします。

Vertex AIインポート

モデルインポート

Vertex AIインポート2

XGBoostのライブラリのバージョンに対応したコンテナを選びます。 先ほどのStorageのパスを指定し、モデルのインポートが完了します。

Vertex AIインポート3

3. エンドポイントへのデプロイ

エンドポイントタブでエンドポイントの作成を押し、 後は手順に従えばエンドポイントが作成できます。

エンドポイント作成

実装の流れ

参考: https://cloud.google.com/vertex-ai/docs/predictions/online-predictions-automl

1. エンドポイントの取得

(import)

import * as aiplatform from '@google-cloud/aiplatform'
import * as config from 'config'
import { ClientOptions } from 'google-gax'

import * as path from 'path'

以下のようにしてエンドポイントを取得します。特に難しい点は無いです。 credentialファイルを取得して、適切なディレクトリに配置する必要があります。

const endpointId = 'YOUR_ENDPOINT_ID'
const project = 'YOUR_PROJECT_ID'
const location = 'asia-northeast1'

// Specifies the location of the api endpoint
const clientOptions: ClientOptions = {
  apiEndpoint: 'asia-northeast1-aiplatform.googleapis.com',
  projectId: project,
  // credential
  keyFilename: path.join(
    config.get('vertexApiCredentialPath'),
    'vertex-api-credential.json'   
  ),
  //  もしくは、
  //  credentials: JSON.parse(
  //    fs.readFileSync(
  //      path.join(
  //        config.get('vertexApiCredentialPath'),
  //        'vertex-api-credential.json'
  //      ),
  //      'utf-8'
  //    )
  //  )
}

// Imports the Google Cloud Prediction Service Client library
const { PredictionServiceClient } = aiplatform.v1

// Instantiates a client
const predictionServiceClient = new PredictionServiceClient(clientOptions)

// get endpoint
const endpoint = predictionServiceClient.endpointPath(
  project,
  location,
  endpointId
)
2. instancesの作成

学習済みモデルが計算するのに必要な特徴量を、指定された順序の通りに詰めた配列がinstanceです。
そのinstanceの配列 = instancesをエンドポイントに投げると1 instance毎の計算結果を配列で返してくれます。

例えば特徴量順序が

  1. 依頼月
  2. 依頼者とプロの距離
  3. プロの提示する金額

だとすると、instanceは

const instance = {
  listValue: {
    values: [
      { numberValue: 12 }, // 依頼月
      { numberValue: 0.5 }, // 依頼者とプロの距離
      { numberValue: 25000 }, // プロの提示する金額
    ]
  }
}

という形に成型する必要があります。かなり冗長です。
ちなみに[12, 0.5, 25000]という形だとapiからはエラーが返ってきます。

Error: 3 INVALID_ARGUMENT: Failed to parse input instances.

(そもそもTypeScriptだと型エラーになります。)

よってinstancesは、

const instances = [
  {
    listValue: {
      values: [
        { numberValue: 12 },
        { numberValue: 0.5 },
        { numberValue: 25000 },
      ],
    },
  },
  {
    listValue: {
      values: [
        { numberValue: 12 },
        { numberValue: 0.6 },
        { numberValue: 22000 },
      ],
    },
  },
 ...
]

になります。 ここで1点注意が必要なのは特徴量の値で、

  • valuesの中に{ numberValue: null }が入っているlistValueが存在
  • valuesの中に{ numberValue: null }が入っていないlistValueが存在

この2点が同時に起こると、apiからエラーが返ってきます。

Error: 9 FAILED_PRECONDITION: "Prediction failed: Could not initialize DMatrix from inputs

おそらくnullのものは認識されず、他のlistValueと次元が異なってしまうのが原因のようです。 従って、nullが入る可能性がある場合はforなどで回してNumber.NaNに置き換える処理が必要になります。

3. 計算結果の取得

requestをendpointに投げて結果を取得します。

// create request obj
const request: aiplatform.protos.google.cloud.aiplatform.v1.IPredictRequest = {
  endpoint,
  instances,
  parameters: null,
}

// Predict request
const [response] = await predictionServiceClient.predict(request, {
  timeout: 2000,
})

response.predictionsに、

[
  { numberValue: 0.12 },
  { numberValue: 0.22 },
  ... 
]

といった形で計算結果(mlResult)が入っています。 (1 instanceにつき1 mlResultが返ります。)

ミツモアでは様々なパラメーターを特徴量として利用し、取得したmlResultを自動応募のマッチングに利用しています。

機械学習によってより良いマッチングを実現し、最高の見積もり体験を全国の依頼者様にお届けするため、これからもミツモア一同誠心誠意取り組んで参ります!

最後に

現在、事業拡大を進めておりエンジニア・デザイナー・PdMを積極採用中です! ぜひWantedlyのリンクからカジュアル面談をしましょう。TwitterのDMでもお待ちしております。

www.wantedly.com