AI・機械学習チームブログリレー 7日目担当の高田です。
AI・機械学習チームでは、開発するプロダクトの数が多く、スピード感を持って開発を進めることが求められます。 そのような環境の中では、高速にプロダクトを生むためのあるあるのアーキテクチャであったり、どのプロダクトでも使っているぞというライブラリが存在します。
それらのノウハウを活かして、日曜大工で作った趣味開発のプロダクトを紹介していきたいと思います。
AI・機械学習チームのあるある
アーキテクチャ編
例えばm3.com会員向けのコンテンツ配信設定など、ビジネスサイドでデータの入力を運用するプロダクトがあります。そういったプロダクトでは、Googleスプレッドシートをお手軽な管理画面として提供します。
スピーディーにプロダクトを作って市場の反応を確かめる方針で開発する事も多いAI・機械学習チームでは、スプレッドシートによる簡易的な管理画面はしばしば利用されるアーキテクチャの1つです。
スプレッドシートの入力データはBigQueryに連携するかGoogle Sheets APIで直接ダウンロードできるようにして、ETLパイプラインの流れでGKE上のBatchジョブから利用するアーキテクチャを採用しています。 *1
ライブラリ編
スプレッドシートからBigQueryに連携したデータは、弊社が開発したgokartというPython製のタスクパイプラインツールで使用するためにダウンロードされます。
Pythonでテーブルデータを扱うといえばpandasが有名ですよね。弊社でも利用していますが、元々スプレッドシートに手入力していることもあり
- 欠損値はないか
- 想定外の数値や長過ぎるテキストデータになっていないか
などの、データバリデーション面で不安が残ります。 そこで、去年AI・機械学習チームではgithub.comを導入し、データバリデーションを充実させました。
これにより、管理画面としてのスプレッドシートとデータバリデーションを強化するpanderaという2つの組み合わせで、手入力のデータを安全に扱うことができるようになりました。
趣味プロダクトもスピードが大事
日曜大工的に作る趣味プロダクトではモチベーションを維持しづらいこともあり、思いついたらすぐ開発にとりかかることができることが大切だと思っています。
そこで、「スプレッドシートで管理画面」と「panderaでデータバリデーション」の組み合わせでサクッと作った通知系の趣味プロダクトをいくつか紹介します!
YouTubeライブ開始通知
指定のYouTubeチャンネルでライブ配信が開始したら、Discordに通知する機能です。
私は趣味でオンラインゲームをやっており、ゲーム仲間とDiscordでコミュニケーションを取りながらYouTubeライブ配信を共有して、プレイ動画を見返したり応援したりして過ごしています。
しかし、全員が同じYouTubeチャンネルを登録しているわけでもないので、
- Discordにライブ開始通知をするメリットがある
- 配信チャンネル次第で通知するDiscordチャンネルを変えたいニーズがある
という2点から、この機能を開発することにしました。
管理画面としてのGoogleスプレッドシートには
- YouTubeチャンネルID
- Discordの通知先チャンネルID
を入力しておきます。
panderaによるバリデーション定義です。
import pandera as pa from pandera.typing import Series class YoutubeLiveNotificationSetting(pa.DataFrameModel): youtube_channel_id: Series[str] = pa.Field() discord_channel_id: Series[int] = pa.Field()
自宅のラズパイ上でPythonを実行しています。
- Google Sheets APIでスプレッドシートのデータを取得
- YouTube Live Streaming API の概要 | Google for Developersでライブ中のYouTubeチャンネルを判別する
- 通知ログ用のシートに既に通知履歴があれば終了
- Discordチャンネルに通知
- 通知ログ用のシートに通知履歴を更新
設定内容
通知内容
YouTube Live Streaming APIをポーリングする必要がある点と初期クォータが小さい点が難点ですが、今のところウォッチしているチャンネル数が少ないため満足できています。
ポイ活案件検知
指定の条件を満たすポイ活の案件がスタートしたらDiscordに通知する機能です。
ポイ活とは、ポイントサイトと呼ばれるWebサービスを経由してショッピングサイトで商品を購入したり資料請求をWebフォームで申請したりするなど、特定の条件を満たすと各種ポイントや航空マイルなどに交換できるポイントを獲得する活動を指します。
ポイントサイトは複数あり、同じ条件の案件でもサイトごとに獲得できるポイントの価値が異なることがよくあります。また、同じサイトでも時期によって獲得できるポイント数は変動します。どこ得というサイトではポイントサイトの案件情報を横断検索できるサービスを提供しており、サイトごとに案件の現在の獲得ポイントを一括で調査が可能です。
ちなみに、私が愛飲しているマイプロテインというブランドのプロテインもポイ活でポイントを獲得できます。
ポイントサイトを目視で定期的にウォッチしていると獲得ポイントの相場観が養われるので、最大ポイントに近づいた時だけ通知してもらい、できるだけお得に買い物をしたいところです。
そこで、Googleスプレッドシートには
- query: 検索クエリ
- threshold: 獲得ポイント数、または獲得ポイント率
- threshold_type:
%
または円
を記入しておきます。 ポイントの獲得ルールとしては条件達成で定額のケースと、ECサイト等での購入金額に対する定率のケースが存在しています。 そのため、threshold_typeは定額、定率の両方の場合を想定したフィールドになります。
panderaのDataFrameModelを使ったバリデーションの定義は次の通りです。
from enum import StrEnum import pandas as pd import pandera as pa from pandera.typing import DataFrame, Series class ThresholdType(StrEnum): percent = "%" yen = "円" def __str__(self): return self.value class DokotokuQuery(pa.DataFrameModel): query: Series[str] = pa.Field() threshold: Series[float] = pa.Field(coerce=True) threshold_type: Series[str] = pa.Field(isin=ThresholdType) @pa.dataframe_check def check_threshold_combination(cls, df: pd.DataFrame) -> Series[bool]: def _check_threshold_combination(row: pd.Series) -> bool: if row['threshold_type'] == ThresholdType.percent: return 0 <= row['threshold'] <= 100 elif row['threshold_type'] == ThresholdType.yen: return row['threshold'] >= 0 return df.apply(_check_threshold_combination, axis=1)
設定内容
通知内容
どこ得に検索クエリをリクエストし、取得した検索結果のうち獲得ポイント数・ポイント率を満たす案件のみを選別し、DiscordにWebhookで通知しています。
こちらのプロダクトでも通知ログを記録して、同じ通知を短期間に鳴らさないように処理を入れています。
ANAトクたびマイル通知
ポイ活で獲得したポイントは航空マイルに交換できると前述しました。次に気になるのは興味のある旅行先に航空マイルでお得にフライトの予約ができるチャンスがあれば通知をしてもらえないか?ということですね!!
ANAでは今週のトクたびマイルというサービスで通常より少ないマイル数でフライトを予約できます。
毎週火曜日に対象路線が入れ替わるため、定期的にウォッチする必要があるのですが、確認し忘れてしまう週や確認しても自分が狙っている旅行先の路線が含まれていない空振りの週もあり、徐々に確認する頻度が下がってしまっていました。
そこで、Googleスプレッドシートでは自分が狙っている路線の情報を入力して、条件にヒットした航空券情報があった時だけ通知できるようにしたいと思います。
ユースケースとしては、 「東京から大阪に行きたい!」という発着地点の2拠点が決まっている場合と、「東京が出発地点なら行き先はどこでもいい!」という出発地点だけが決まっている場合があるので、両方のケースに対応したバリデーションを定義しました。
class AnaTokutabiNotificationSetting(pa.DataFrameModel): place1: Series[str] = pa.Field() place2: Series[str] = pa.Field(nullable=True) @pa.dataframe_check def is_different_place(cls, df: pd.DataFrame) -> Series[bool]: return df['place1'] != df['place2']
設定内容
通知内容
まとめ
Googleスプレッドシートをお手軽管理画面として使いながら、panderaによるバリデーションでガッチリとガードする構成で開発スピードが上がる事例を趣味プロダクトを通してご紹介しました。
通知系ツールばかりの紹介にはなりましたが、汎用的に使える手法だと思いますので、皆さんの開発にも取り入れてみたらいかがでしょうか?
We're hiring!
AI・機械学習チームでは、スピード感を持った開発に取り組みたい方、開発速度を向上させるための仕組みづくりに取り組みたい方を募集しています。 少しでも興味がある方は、次のURLからカジュアル面談をご応募ください!