LayerX エンジニアブログ

LayerX の エンジニアブログです。

経費科目推薦機能の機械学習アークテクチャ #ベッテク月間

こんにちは。
LayerXのバクラク事業部 機械学習チームのテックリードを務めております機械学習エンジニアの島越(@nt_4o54)です。
7月はLayerXのエンジニアブログがたくさん出る#ベッテク月間です。LayerXの行動指針の一つである「Bet Technology」を略して「ベッテク」と呼んでいます
昨日は、データチームの@TrsNiumから、「本番同様のデータを扱えるdbtテスト環境をBigQueryで構築する方法」の記事が公開されました!

今回の記事では、先日リリースされた経費科目推薦機能、通称「内訳推薦」機能について、どういう機能なのか、どのようなアークテクチャで構成されているのかを紹介します。 bakuraku.jp

内訳推薦とは

内訳推薦と聞いてもピンとこない方が多いと思いますので、まずは「内訳」という概念について説明させてください。

まず、一般的な経理業務として「仕訳」という作業があります。例えば、社外の人と会食に行った際に経費精算を行うと、「その経費精算はどういう費用でいくらかかったのか」などを帳簿に記載する必要があり、これを仕訳と呼びます。この仕訳を行う際に、「どういう費用なのか」を表すものに「勘定科目」というものがあります。これは国が定めているもので、どれを選択すべきかを理解するには経理に関する知識が必要です。経費精算時に従業員が勘定科目を入力してくれれば経理の方としては楽なのですが、知識のない従業員の方が正確に勘定科目を入力することを期待するのは現実的ではありません。

そこで、「内訳」という概念が登場します。内訳とは、勘定科目を従業員向けに分かりやすくするためのバクラクの機能です。例えば、社外の方との飲食代の場合、税抜10000円以下は会議費、10001円以上は交際費といった勘定科目があります。この場合、「社外の方がいる場合の税抜10000円以下の飲食費」「社外の方がいる場合の税抜10001円以上の飲食費」といった内訳を用意しておけば従業員の方もどれを選択すればいいか迷わずに済みます。また、裏側では「社外の方がいる場合の税抜10000円以下の飲食費」という内訳と「会議費」という勘定科目を紐付けるDBを用意しておけば、経費精算後の仕訳で自動的に勘定科目が補完されるため、経理の方の手間も減ります。

この内訳は便利である一方、大量に経費精算などを行った際にも手入力で選択する必要があり、従業員の方にとっては手間のかかる作業でした。一つの勘定科目に対して複数の内訳を作成することができるので、テナント様によっては内訳が1000以上あるお客様もおり、いかに日本語的に分かりやすいとはいえ、その中から適切な内訳を探すことは非常に大変です。

そこで、今回紹介する内訳推薦機能で内訳を自動で補完できるようになったというのが、今回の記事の本題です。次章からこの内訳推薦機能のアーキテクチャについて解説していきます。

内訳推薦の困難なポイント

内訳は各テナント様ごとに自由に決められる

まず、最初に説明したように内訳というのは各テナント様で自然言語的に自由に決められるので、単純なクラス分類アプローチでは解けません。また、直接勘定科目を当てればいいのでは?という話もありますが、そもそも難しい勘定項目を従業員の方に理解してもらわなくていいようにするというのが内訳の当初の目的なので、間違った勘定科目を推薦した場合に、従業員側で間違っているかどうかの判断が難しくなります。

ニアリアルタイム性が必要

例えば弊社でも社内でイベントがあった際などに、このイベントは新しく作った内訳で経費精算を行ってください、とアナウンスされたことがありました。このように、内訳というのは不定期に変わるものなので、新しい内訳にも素早く対応する必要があります。有り難いことにバクラクの契約テナント数も伸びている状況ですので、新しいテナント様にも素早く適応することが必要です。また、そのテナント様や従業員の方の過去の経費精算がどうであったか、という履歴情報が重要だと考えられるので、特徴量を常に新鮮に保つのも重要です。

内訳推薦のアーキテクチャ

前述したような課題に対して、以下のようなアーキテクチャで実装しました。

まず一つ目の「内訳は各テナント様ごとに自由に決めることができる」問題に対してですが、推薦問題として落とし込むことで解決しました。一般的な推薦問題においては、ユーザに対するアイテム候補のランキング問題として考えますが、ここでは従業員に対してのアイテムを内訳と考え「どの内訳がその経費精算の明細に対して尤もらしいか」を並び替える問題としました。

二つ目の「ニアリアルタイム性が必要」な問題に対しては、多角的なアプローチを行いました。

内訳候補のリアルタイム取得

内訳の鮮度を常に保つためにプロダクトのDBを直接参照することにしました。AmazonのRead replicaを使うことなども候補に入りましたが、リクエスト量的に十分耐えれる範囲だと判断し、SageMaker内部から直接参照することにしました。また、プロダクトからのリクエストパラメータに内訳を含める案もありましたが、前述した通りテナント様によっては1000以上内訳を設定されている方もいることからリクエストに含めるには大きすぎるかなという判断をしました。

特徴量のDaily更新

内訳推薦の特徴量として「そのユーザやテナント様が以前にどういうシチュエーションでどういう内訳を選択したか」というものがあります。一度申請したことのあるパターンであれば、できれば同じ申請はしたくありません。なので、なるべく早くユーザやテナント様がとった行動を特徴量に反映できるようにGO株式会社様の事例を基にバックグラウンドでデータ更新スレッドを動かすようにしました。特徴量はDailyでVertex AI Pipelinesを用いてS3にアップロードを行うpipelineを動かしており、この際にbucketのlatest pathと日付ごとのpathにそれぞれ同じ特徴量をアップロードします。二つのpathにアップロードしているのは、もし問題が起きた際にすぐにfallbackできるようにするためです。

特徴量は、DynamoDBやFeatureStoreなどを活用する方法も考えましたが、カーディナリティやデータサイズが小さく、実装も手軽なことからインメモリで持たせるようにし、Endpoint起動時にダウンロードしてくるようにしました。また、SageMaker Endpointの中では、非同期に定期的にS3のlatest pathを見に行くスレッドを動かしており、特徴量が更新された際にはコンテナ内にある特徴量を更新するようにしています。

このpipelineを現状だとDailyで動かしており、少なくとも次の日の経費精算には特徴量が反映されているようにしました。

モデルのWeekly更新

特徴量としては、上述のように新鮮に保っていたとしても、新しいカテゴリ値などにはすぐさま対応することができません。例えば、新規に契約してくださったテナント様のIDなどをscikit-learnのOrdinalEncoderなどでEncodingしている場合、いつまでもEncoderが更新されなければモデルとしてはずっと未知のテナント様として扱ってしまいます。しかし、Encoderを更新する場合はカテゴリ値の意味が変わってしまうため、モデルも同時に更新する必要があります。そのため、モデルとしてはWeeklyで更新するようにし、同時にEncoderも同じタイミングでS3にアップロードするようにしました。アップロード方法とEndpoint内部での更新の仕方は特徴量の場合と同じです。モデルはWeeklyで更新させているのは、コスト的にもDailyで更新するメリットがほとんどないなと判断したためです。

定期実行管理

特徴量をDailyでモデルをWeeklyで更新するために、Vertex AI PipelinesのScheduler APIを用いて実装を行っています。Scheduler APIを用いた場合のデプロイ戦略については、以下のブログでも触れていますので参照ください。

tech.layerx.co.jp

最後に

今回は、バクラク経費精算で提供が開始された内訳推薦機能について、またそのアーキテクチャについて紹介しました。このような簡単なアーキテクチャであれば、再利用可能な様に開発基盤も整えています。余談ですが、今回の内訳推薦機能は、社内の雑談でCPOの榎本 (mosaさん)から「この内訳自動で入るようにしたいんだよな〜」と話されたことから始まり、データ構造とか聞いているうちに「いや、それ結構簡単にできるんじゃないですか?」となって作ったものです。LayerXのこういう感じでプロジェクトが始まるカルチャーはとてもいいなと個人的に感じています。

このように、バクラクではドメイン知識が必要な場面に対しての意思決定に推薦というアプローチがハマる場所が多々あります。少しでも興味が湧いた方は是非一度カジュアルにお話ししましょう!!

jobs.layerx.co.jp jobs.layerx.co.jp