論文紹介:DYNA-BOLT: DOMAIN ADAPTIVE BINARY FACTORIZATION OF CURRENT WAVEFORMS FOR ENERGY DISAGGREGATION

NILM (Non-Intrusive Load Monitoring)の論文紹介です。

タイトルから分かるように前回紹介したBOLTを発展させたものです。BOLTについては前回の記事をご参照ください。

論文は (PDF) Dyna-Bolt: Domain Adaptive Binary Factorization Of Current Waveforms For Energy Disaggregation から入手できます。

概要

問題設定

以下のようになっており、Semi-supervisedのタスクです。

  • ある建物
    • 主幹計測の波形データをもっている
    • 各家電の個別計測データをもっている(ラベルデータあり)
  • 別の建物
    • 主幹計測の波形データをもっている
    • 各家電の個別計測データをもっていない(ラベルデータなし)

という条件において、 「別の建物の主幹計測データから家電の分離を行いたい」 というタスクです。

問題意識

同じ家電であっても異なる建物同士では電流波形が微妙に違っている、よってBOLTを単純に適用してもうまくいかない というのが問題意識です。

例えば、以下の図において2つの家庭、House3とHouse5の各々のRefrigeratorとFurnace(電気かまど?)の電流波形と電力カーブが示されていますが互いに微妙に違っています。

論文中のFig2

House3の主幹データで学習させたモデルをそのままHouse5に適用しても精度が落ちてしまうことは容易に推測できます。

DYNA-BOLTで何をやったか

同じラベルをもっているけど特徴(波形の形状)が少し異なっているということで、ドメインシフトの問題と捉えることができます。 ただし、本論文によるとDomain Adaptation Neural Network (DANN)の手法を適用しただけでは効果は不十分ということでした。

そこで、Dyna-BOLTではラベルデータを使っての機器の識別タスクも追加することで改善を図っています。

アーキテクチャ

アーキテクチャは以下の図の通りです。

論文中のFig1

Encoder~Binary Activation~Decoder の部分がBOLTと同じ構造になります。 Decoder は 単なる1層の全結合層 $Wx$ であり、その重み行列 $ W $ (次元: $(c, N)$)が c個のActivationに対応するN次元波形を表現し、入力波形とDecoder出力である再構成波形との差が 再構成誤差 $ L_{recon} $ になります。

Dyna-BOLTでは、$ W $ はソースドメイン用の $ W_{source} $ と ターゲットドメイン用の $ W_{target} $ の2種類存在し、入力データの種類によって使い分けます。

$ W_{source} $ と $ W_{target} $ は対象となるドメインが異なっているため別の重み行列となっていますがどちらも同じ家電に対する波形パターンを表すはずなので重みは互いに近くなるべきです。

$ L_{difference} $ はその制約を表現する損失になります。

$$ L_{difference} = \sum_{i=1}^{m} d^2 (W_{source, i}, W_{target, i}) $$

$ L_{domain} $ は Domain Classifier の識別損失です。 Domain Classifierはソースドメインの入力波形とターゲットドメインの入力波形を識別するように訓練されますがやりたいことは建物間の共通の特徴をエンコーダーから抽出したいということです。よって、Gradient Reversalと書いてあるように勾配を反転させて逆伝搬させます。これにより、エンコーダーは異なるドメインの入力を識別しようとするのではなく、共通の特徴をもつように学習されます。

最後に$ L_{class} $ ですが Appliance Classifier による機器識別の損失です。機器のラベルが必要なのでソースドメイン入力に対してのみ計算されるものになります。

これらを足し合わせ、最終的な損失関数$ L $は以下のようになります。

$$ L = L_{recon} + \gamma L_{class} + \lambda L_{domain} + η L_{difference} $$

実験

実験では、REDDデータセットの2家庭を使用しています。

  • House3: ソースデータ、ラベルあり
  • House5:ターゲットデータ、ラベルなし

結果は以下の表のようになっています。

論文中のTable1

Supervised は House3にBOLTを適用し、その後、ラベルデータを使って機器分類した結果です。Logistic RegressionによりBinary Activationを入力データとし、ラベルを使って学習していると考えられます。

そして、Semi-supervisedは House3, House5に関してBOLTを適用した後、House3のラベルデータを使って機器識別を学習した結果です。

どちらのケースにおいてもHouse3のF1スコアはかなり高いけど(0.98~0.99)、House5のF1スコアがとても低い(0.21~0.23)ことがわかります。

理由としては t-SNEによる埋め込みベクトル空間が2家庭間でほとんどオーバーラップしてなく、家庭間の共通の特徴を捉えられてないためです。

以下の図の(a)を見ればわかりますが、House3とHouse5には空間的な重なりがほとんどありません。

論文よりFig3

Vanilla DANNは DANN (Domain Adaptation Neural Network)を適用したものになりますが、House5のF1スコアは0.23→0.32の微増に留まります。

(b)の図を見ると全体的な分布はDomain classifierを導入した効果により似たようになってますが、矢印部分に着目するとHouse3とHouse5とで別の色(家電)になってしまっています。

そこで Appliance classificationのタスクも加えたDyna-BOLTとなります。Dyna-BOLTではHouse5のF1スコアが0.55まで改善しています。

Appliance classificationのタスクについて、MSEロスよりマルチラベルでのクロスエントロピーロスのほうが性能が良いようです。

所感

BOLTの論文で言及されているようにチェーン展開しているファーストフードやスーパーマーケットは店舗間で似たような家電・機器をもっていると考えられ、また、数店舗において正解データ計測をすることは現実的です。

実験の結果からSemi-supervisedとDyna-BOLTのターゲットドメイン家庭での性能の差は明白であり、上述の設定であればDyna-BOLTを使わない手はないでしょう。

論文紹介: BOLT: Energy Disaggregation by Online Binary Matrix Factorization of Current Waveforms

NILM (Non-Intrusive Load Monitoring) の論文の紹介です。

2016年発表という古い論文ですが教師なしアプローチということで取り上げました。

論文リンク先は https://dl.acm.org/doi/abs/10.1145/2993422.2993581 になりますが調べた限り無料では入手できなさそうです..

ちなみに、 BOLT は Binary OnLine facTorization engine から来ています。 響きはカッコいいですが頭文字以外も複数含んでいるのでちょっと強引ですね。。

どんな論文?

ざっくりまとめると、

NILMの論文は電力データ系列を対象とした分離が多いですがこの論文は電流波形を使った分離になります。

個別センサー測定により各家電の正解データを多数集めるのは大変であるので教師なし学習である行列分解 + 事前知識 というアプローチを取っているのは興味深いです。

サブコンポーネントの例

サブコンポーネントは5種類のいずれかのカテゴリに該当します。

  1. 特定の家電の波形 (図2-a)
  2. 複数家電が合成された波形 (図2-d)
  3. 他のサブコンポーネントの存在を前提とする修飾成分 (図2-c)
  4. 正弦波波形 (図2-b)
  5. 他のサブコンポーネントの複製(=類似波形)

論文よりFigure2

3.(図2-c)の例は以下の図3の2つ目の波形になります。 4.(図2-b)である正弦波波形に足されることで他の波形パターンを作りだします。

論文よりFigure3

1つのサブコンポーネントが1つの家電に1:1で対応していると非常に分かりやすいのですが電流波形は複数の任意波形の足し合わせで生成されるという性質上、そう簡単にはいきません。

これらのサブコンポーネントがどの家電に対応するかを推定するのが一つの大きな課題になりますがそれは後の方で説明します。

モデルアーキテクチャ

モデルは非常にシンプルなオートエンコーダーで、BOLTで特徴的なのは最終層の1つ手前のbinary layerの活性化関数がその名の通りSigmoidになっていることです。

論文よりFigure1

Sigmoid出力は各サブコンポーネントのON/OFF状態を意味しており、ONとなっているサブコンポーネント波形の和により元の波形を再現できるという仕組みです。

ここで、binary layer → linear layerの変換行列の重みがサブコンポーネントの波形群を表しています。

行列分解は $ {minimize \atop X, G} || X G - Y || $ という誤差最小化問題で定義できますが

  • Y: 入力波形 (T, N)
    • T: 時間長
    • N: 1波形の次元数
  • X: 入力波形をc次元にする変換 (T, c)
    • c: サブコンポーネント数
    • 上の図のnetwork bodyにあたる
    • c個のN次元波形テンプレートに対する重みと解釈できる
  • G: c次元をN次元波形にする変換 (c, N)
    • 上の図の binary layer → linear layer にあたる
    • c個のN次元波形テンプレートと解釈できる

となります。

すなわち、X は入力波形を c 個のON/OFF状態に変換する役割をもち、G は各サブコンポーネントのN次元波形を表します。 入力波形 Y は c個の ON/OFF状態とサブコンポーネント波形の加重和で表現できます。

実際のニューラルネットでは、1波形単位で行列分解するので

  • X: N次元波形を c次元に縮退するネットワーク
  • G: c次元をN次元に変換する行列

に相当します。

論文では

  • 入力層:4800 (=N)
  • 中間層1: 3000 (活性化関数:Leaky ReLU, alpha=0.5)
  • 中間層2:2000 (活性化関数:Leaky ReLU, alpha=0.5)
  • バイナリ層: 100 (活性化関数:Sigmoid)
  • 出力層:4800

という5層のMLPになっています

また、論文では 入力波形にFFTをかけて実部と虚部をニューラルネットワークへの入力および出力としてますが通常の波形入力でも問題ないと考えられます。

実際にトイデータで試したところ、FFTかけずに入力したほうが分解性能が良かったです。

バイナリコンポーネント群の集約

行列分解によりサブコンポーネントのON/OFF状態の集合、すなわちバイナリコンポーネント群を得られますがこれから実際に求めたい各家電の稼働を推定する処理になります。

すなわち、どのバイナリコンポーネント群がどの特定家電の稼働に関係するかを集約する処理(Re-Aggregation)になります。

SupervisedとUnsupervised の2通りの方法が提案されています。

Supervisedアプローチ

1. Logistic Regression

率直な考え方であり、各家電ごとにロジスティック回帰モデルを用意し

  • 入力データ $ x(t) $:c個のバイナリコンポーネント (c次元)
  • ラベル $ g_i (t) $:家電のON/OFF (0/1)

で教師あり学習を行います。

2. Boolean Re-Aggregation

いわゆる全探索です。

各 $ x_i $ を $ \{ 0, 1 \}^T $ という0,1の時系列データとみなし、 各家電のラベルデータと照らし合わしてF1スコアが最大化する組み合わせを見つけます。

実験結果

評価結果は以下のようになり、ロジステック回帰を使ったほう( $ F1_L $)が 全探索であるグリーディアプローチ( $ F1_B $ )より良いようです。

論文よりTable1

Unsupervisedアプローチ

1. Lower bound on unsupervised Re-Aggregation

手法の説明の中でF1スコアという単語が出てくるように完全なUnsupervisedではないので要注意です。

サブコンポーネントの波形形状からどの家電かを類推できたり、バイナリコンポーネントの時系列変化からどの家電であるかを類推できるという仮説の下、各家電についてGround truthに近しいデータをもっていることが前提となっています。

というように、事前知識を必要としています。

この手法では家電ごとに上記のGround truthを使ってF1スコアが最も高くなるバイナリコンポーネントを1つ選び出します。

つまり、この選ばれたバイナリコンポーネントが対象家電の稼働状態を表すとします。

2. Naive Re-Aggregation

イメージとしては1.の改良版みたいなものになります。

1.で求めたある家電$ a $のコンポーネントをシードコンポーネント $ x_{a, s} $ とし、 それ以外のバイナリコンポーネント $ x_i $ との類似スコアを算出します。

類似スコアは 波形の類似度 $ d_w $ と 時系列パターンの類似度 $ d_a $ から加重平均として計算し、類似スコアが閾値以上となるサブコンポーネント群を含めて $ X_d (a) $ とし、それらの和をその家電の稼働状態とします。

つまり、シードコンポーネントと近い波形であったり近い時系列変化をするバイナリコンポーネントを追加してまとめたバイナリコンポーネント群をその家電の稼働状態を表すものとします。

実験結果

Unsupervised手法の評価結果は以下のようになります。

論文よりTable2

  • Activeは家電が稼働している割合を表しているがActive率が低い家電はSupervisedの結果と比べて軒並み低いFスコアになった

ただし、既存の分離アルゴリズム(ノンパラメトリックFHMM)よりはかなり良い(0.61 vs 0.25)とのことでした。

Naive Re-Aggregation はActive率およびF1スコアが低い一部の家電に対して効果あるように読み取れます。

あらためて BOLTの利点、意義

FUTURE WORKの章でも述べられてますがBOLTの意義は家電のGround truthをたくさん集められない状況を想定していることです。

BOLTが適用しやすいシナリオとしてファーストフード店、ガスステーション、スーパーマーケットといったフランチャイズチェーンでの家電分離について言及されています。

フランチャイズチェーンでは店舗間の所有家電が似たものであると想定できるので、

  • 全店舗で波形データを集めて、Unsupervised学習する
  • 少数の店舗で家電単体のGround truthを測定し、大量データのUnsupervised学習から得られたサブコンポーネント波形パターンを教師あり学習する

といったSelf-supervised + Finetuning的なアプローチが可能になります。

また、どのような家電がどのような波形パターンになるかのカタログデータを保持していればGround truthデータを必要とせずとも分離することが可能になります。

古い論文ですが 大量データでの教師なし学習 + 事前知識によるラベリング(もしくは少数正解データでのファインチューニング) というのは make sense であり、今後どこかのタイミングで日の目を見そうな気はします。

MakeとGPTで英単語帳作る

英語学習として英語での言い回しがすぐに思いつかないとき、これまでGoogle検索しその結果を参考にしてました。

ただ、履歴が物理的にも脳内にも蓄積されず度々過去に調べた単語を検索してしまうこともあり、検索結果を蓄積し定期的に復習ができる単語帳を作ろうと考え、プロトタイピングしてみました。

以前に使ったことのあるノーコードツール Make(旧Integromat)を今回も利用してみます。

シナリオ

ざっとこんな感じです。

各モジュールの役割は以下になります

  • LINE (Watch Event)
    • LINEから入力メッセージ(英語訳させたい単語や文章)を受け付ける
  • OpenAI (ChatGPT, Whisper, DALL-E)
    • GPTに英訳させる
  • LINE (Send a Reply Message)
    • 英訳結果をすぐに確認できるようにメッセージ返信する
  • Google Sheets (Add a Row)
LINE (Watch Event)

Webhookを設定します。

前回からMessaging APIの作成のやり方が変わっていてちょっと時間かかりました。

LINE DevelopersコンソールからMessaging APIチャネルを直接作成することはできなくなりました。 Messaging APIチャネルを作成するには、以下の[LINE公式アカウントを作成する]ボタンからLINE公式アカウントを作成した後、LINE Official Account Manager上でMessaging APIの利用を有効にしてください。

とのこと。

それも踏まえて、手順は以下のようになります。結構長いです..

  1. LINE Developerからプロバイダーを作成する (作成済みならスキップ)
  2. LINE公式アカウントをここから作成する(作成済みならスキップ)
  3. LINE Official Account Manager にログインする
    1. アカウントの作成

      • アカウント名はLINEの友だちリストやトーク画面に表示される名称となり、LINE Developer画面でのチャネル名になります
      • 名前はあとから設定画面で変更することが可能(ただし、変更後は7日間変更できない)
    2. Messaging APIを有効化する

      1. Messaging APIのタブから Messaging API を利用する をクリック
      2. プロバイダーを選択し、同意→OK
  4. LINE Developer上で3.で作成したアカウントのチャネルアクセストークンを作成する
  5. Make上で LINE のWatch Eventモジュールを追加
    1. WebhookのAddをクリック
    2. 作成したチャネルアクセストークンをセット
    3. WebhookのURLをコピーしておく
  6. Makeのシナリオ画面でSaveボタンを押す
  7. LINE DeveloperでWebhook URLをセットする
    1. WebhookURLの編集から5.でコピーしたURLをセットする
    2. 検証ボタンを押すとOKとなるはず(6.でSaveしておかないとOKにならないので注意)
    3. Webhookの利用をオンにする
OpenAI (ChatGPT, Whisper, DALL-E)

LINEでの入力をGPTに投げます。

Message Content がGPTへのプロンプトになっており、以下を設定しました。

次の日本語の単語もしくは文章を英語に翻訳してください

入力された日本語が文章ではなく単語の場合は、例文も合わせて表示してください

### 守ること

- 日常的に使われる表現でお願いします
- 例文は2個表示してください
- 文章の英語訳の場合、英語のみを表示してください 

### 翻訳してほしい単語もしくは文章

{{1.events[].message.text}}

プロンプトの末尾でLINEの入力を挿入します。

Make で OpenAIモジュールを使うには

  • secret key (API Key)
  • Organization ID

が必要です。

OpenAI (ChatGPT, Whisper, DALL-E) を参考にOpenAIの管理画面からコネクションに必要な情報を取得します。

LINE (Send a Reply Message)

GPTの出力結果(英訳)をLINEに返信します

Google Sheets (Add a Row)

LINEへの入力とGPTの出力を日時と共に記録します。 スプレッドシートには "日時"、"日本語"、"英訳" の3つの列が含まれていればOKです

使用例

まず、Make上で SCHEDULING をONにし、Schedule setting を Immediately as data arrives. にしておきます

LINEからいくつか入力してみました。

いちおう使えそうな感触です。

スクリーンショットからはみ出ていますが最後の「タッチ決済」については応答が

**Touch Payment**
例文:
1. スマートフォンでタッチ決済するのが便利です。
2. 彼はタッチ決済を使って買い物をしました。

と、日本語の例文が返ってきてしまったのでプロンプトの改良が必要です。。

Googleスプレッドシートのほうにはこのように自動で記録されていきます。

作ってみて

気になったときにすぐにスマホからサクサク調べられる感は非常に良いなと感じました。

スプレッドシートに記録されていくので、日本語での日記を文章入力して英語日記とすることもできそうです。 英語力アップの1つの方法として英語日記をつけるというのがありますが結構大変なので継続困難になりがちです。まずは日本語で気軽に日記を入力し、その英訳出力を見て英語表現を学ぶことで英語力をつけるというツールとしても成立しそうです。

ColabのGPUの処理パフォーマンスの比較

実験のモチベーション

Colab Proを契約してますがちょっとした実験であっても仕事終わって夜から学習始めて寝る前に終わらないじゃんというのが結構あります。

もちろん、ノートPCの画面を閉じなければ継続できますがあまり気乗りしないし、Colab Pro+ にアップグレードするとバックグラウンドで実行できるけどそのために月5,767円を払うのはちょっと.. といったところです。

普段はクレジット消費をケチってT4を使っていますが、L4やA100のほうが処理が速いはずだし、実際の所、どのGPUを使うのが最も効率が良いかを調べる実験をしました。

各GPUのクレジット消費

リソースモニターのプルダウンから「リソースを表示」で確認できます。

GPU クレジット消費単価 比率
T4 約 1.76 / 時間 1
L4 約 4.82 / 時間 2.74
A100 約 11.77 / 時間 6.69

それぞれのGPUの詳細スペックは NVIDIA GPUの性能比較 から参照できます。

実験

最近ちょっと遊んでみた MAE (Masked Auto Encoder) と SimSiam で試してみました。

それぞれのURLはこちら

1. MAE

事前学習のコードを 各GPUでそれぞれ 50 epoch 分動かしました。

結果はこのようになりました。

GPU 処理に要した時間 想定クレジット消費
T4 7097 sec 3.47
L4 4356 sec 5.83
A100 1610 sec 5.26

クレジット単価比率の6.69倍ほど速くなってませんがA100はやはり速いですね。条件変えたらどうなるか分かりませんが今回 L4 よりも A100 の想定クレジット消費が小さいのには驚きました。

クレジット消費合計はやや大きくなりますが処理時間はかなり短くできるのでA100をチョイスするのはありな気がします。

2. SimSiam

こちらも 50 epoch 分動かしました。

結果はこちら

GPU 処理に要した時間 想定クレジット消費
T4 5360 sec 2.62
L4 2584 sec 3.46
A100 2544 sec 8.32

MAEの場合とは打って変わって、L4とA100はほぼ同じ処理時間でした。T4と比べても2倍程度にしか高速化されていません..

SimSiam は GPUを使わない Data Augmentation の処理が以下のように重めの処理になっており、

    train_transforms = transforms.Compose([
        transforms.RandomResizedCrop(args.img_dim, scale=(0.2, 1.)),
        transforms.RandomHorizontalFlip(),
        transforms.RandomApply([
            transforms.ColorJitter(0.4, 0.4, 0.4, 0.1)  # not strengthened
        ], p=0.8),
        transforms.RandomGrayscale(p=0.2),
        transforms.ToTensor(),
        transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
    ])

きっとこれが処理時間を食っているのでしょう。

どちらの例でもT4が最も想定消費クレジットが少ないですが SimSiamの場合は、L4が最もタイパが良さそうです。

やってみて

A100は確かに速いが条件によってはその恩恵が消費クレジットほどは得られないということが判明しました。

盲目的にL4やA100を選ばず、処理内容に応じてGPUを使い分けるべし、というのが分かってよかったです。

FlaskアプリをHerokuからRenderへ移行

Herokuの有料課金を止めたことにより、機械学習を使ったちょっとしたデモアプリが動かなくなっていたのでRenderというPasSへ移行させました。

先人達の記事のおかげでアカウント作成〜デプロイ開始までサクサク進みました。

詰まった箇所

エイと、Deploy Web Service ボタンをクリックしたらビルドし始めましたが以下のエラーが出てデプロイ失敗しました

Error: module 'inspect' has no attribute 'getargspec'

ググってみるといくつか記事が見つかり、Pythonのバージョンが3.11だとダメなようで Python 3.10にすればよいとのこと

Pythonのバージョンの変更の方法は Setting Your Python Version – Render Docs に書いてあり、Environmentタブから

  • Key: PYTHON_VERSION
  • Value: 3.10.3

のように指定して Save changes and deploy ボタンをクリックすればOK。

2024-04-04からデフォルトは Python 3.11.9 になっているようです。

なぜか Error: module 'inspect' has no attribute 'getargspec' について日本語の情報がヒットしなかったので記事にしてみました。

他のパッケージ起因でも同様のエラーが出てしまい、最終的には Python3.8まで落としたらデプロイ成功するようになりました。。

poger2-liteの開発とリリース

開発のきっかけ

poger mail serviceというPOGの自動集計をする個人サービスをHeroku上で運営していたのですがある日、全機能が動かなくなりました..

理由はHerokuのデータベース自動メンテナンスによるものでした。

We're upgrading your database (MAROON on poger) plan to essential-0. Your database is in read-only mode until the upgrade completes, which takes around 15 minutes. You can monitor the progress of the upgrade from your database's dashboard or with the pg:info command.

この無慈悲なデータベース自動アップグレードにより、Postgresqlの古いアダプタだとデータベース接続できなくなり、あらゆる操作がエラーになってしまうというものでした。

これに対応するためPostgresqlのgem更新しようとしたところ、なんとdocker buildが通らなくなっていました..

理由はベースとなる ruby:1.9.3 というdockerイメージのOSが古すぎたためです。それにより、Dockerfile内の apt update や apt-getのコマンドが通らなくなってしまいました。

安直な急ぎの対策として、新しいUbuntuのベースイメージに ruby1.9.3 + Rails3.2 をインストールしようと試みましたが、インストールはできたものの rails c 等がエラーになってしまいこの方法は断念し、 延命ではなくリニューアルとして代替となる新サービスを開発することにしました。

設計思想

以下の3つの思想を重視し、設計しました

  1. 素早くリリースできる
  2. よりシンプルに
  3. 運用コストをより安く

1点目については言うまでもなく、サービス停止期間を最小限にするためです。

2点目は馬データの登録に多少の手作業が発生していたのをなくすというものです。これまで、自作のpog_horse_selector上で選択した馬データをcsvエクスポートし、それを手作業で少し整えてからデータベースにデータインポートしていました。この手作業をゼロにすることを考えます。また、Postgresql等のデータベースが本当に必要なのかも再考します。

3点目について、これまでのHeroku上の運用ではHerokuの無料プラン廃止により費用が月額$10かかっていました。1ドル150円のレートとして約1,500円/月です。1年だと約18,000円になり無料運営サービスとしては馬鹿にはならないのでもっと安く運用できる仕組みを考えます。

上記3点を考慮し、

  • 開発言語はPython
  • Web管理画面はなし。Job workerだけ動かす
  • データベースは使わない。csvファイルをデータストアとして利用する
  • また、pog_horse_selectorでエクスポートしたcsvファイルを加工することなく利用可能とする
  • 必要なときだけVM上でジョブを動かす。それ以外はVMは停止

という方針になりました

提供機能

主な機能は以下3つです。

  • 集計結果ランキングのメール配信
  • 登録馬の出走情報のメール配信
  • 登録馬のレース結果のメール配信

他に、メイン機能の補助機能として

  • 登録馬の馬名決定を反映させる
  • 登録馬の最新の賞金を取得する

があり、合計5個のジョブから構成されます。

旧サービスでは、特別登録情報 や 馬三郎の記事 からの次走情報もメール配信してましたが今回の開発では必要最低限の機能開発に絞り、非対応としています。

配信メールのサンプル

集計結果ランキング

出走情報

レース結果

スクレイピングツールの技術選定

前サービス(開発言語はRuby)では Selenium を使っていましたが Chromeブラウザのバージョンと Chrome Driverのバージョンと合わせるのが面倒でした。 そのため、Dockerfileでは特定のchromeブラウザのバージョンと特定のchrome driverバージョンをダウンロードするようにしなければなりませんでした

それも踏まえて、今回は Playwright に乗り換えることとしました。 playwright install により必要なブラウザとドライバが楽にインストールでき、Pythonを公式にサポートしているためです。

運用

コスト抑制のため、VMを最低限だけ動かすことにしました。

具体的には、Google CloudのGCEのインスタンススケジュールというのを利用して、必要な時だけVMを稼働させます。

それに加えて、VM上でcronジョブを登録し、インスタンスの起動時刻に合わせてジョブ実行させます。

ただし、インスタンススケジュールを作成してもその時刻ピッタリに起動するわけではありません

VM インスタンスの起動と停止をスケジュールする  |  Compute Engine Documentation  |  Google Cloud

スケジュールされた VM インスタンスは、起動オペレーションまたは停止オペレーションを開始するスケジュール時刻よりも最大 15 分遅れることがあります。特定の時間に VM インスタンスを起動または停止する必要がある場合は、目的の時間の 15 分前までにオペレーションのスケジュールを設定し、各オペレーションは少なくとも 15 分以上の間隔を空けてスケジュールします。

なので、cronジョブのジョブ開始時刻はインスタンス起動時刻から15分以上遅らせて設定する必要があります。

なお、必要なタスクの実行後にシャットダウンするようなcronジョブにしておけばVM稼働時間をより短縮することが可能です。

<登録 job の例>

15 16 * * 1 cd /path/to/poger2-lite; PYTHON_BIN=venv/bin/python ./result.sh > log/result.log 2>&1; sudo /usr/sbin/shutdown -h now

他に注意点というか苦労した点としては、一つのVMには1種類のスケジュールしか割り当てられない点です。

どういうことかと言うと

  • レース結果配信用:月曜日 8時~9時
  • 集計結果配信用:火曜日 15~16時
  • 出走情報配信用:金曜日 16時~17時

のような複数種類のスケジュールを作ってそれぞれを同じVMに割り当てることが仕様上できないということです。

なので、苦肉の策として各メール配信の時刻はすべて同じ時間帯にしています。

<インスタンススケジュール>

起動時刻と停止時刻さえ共通にしておけば、特定の曜日だけでインスタンス自動起動させることは可能です。

運用費用(速報値ベース)

VMのスペック

で動かしています。

また、登録している馬の数は 270 頭です。

運用は始まったばかりですが この条件にて スケジュール3回分の稼働は約4~6円/回でした。6円/1回として1ヶ月分の費用を試算すると、週3回稼働 x 4週間として 1ヶ月あたり 約72円/月 (=6 x 3 x 4)です。

Herokuを利用した場合は$10/月(約1,500円/月)なのでかなりの費用を抑えることに成功しています。

リリースしたもの

週末に数時間ずつ作業し、1ヶ月半ぐらいでリリースに漕ぎ着けることができました。

github.com

オープンソースとしておりご自身でも動かせますので興味あれば使ってみてください!

参考記事

「天声こども語」を要約するMyGPT

せっかくGPT Plusに加入しているので 自分の子ども向けに国語力向上のためのMyGPTを作ってみました。

機能の紹介

機能は非常にシンプルで

  • 天声こども語を撮影した画像から日本語テキストを読み取り、要約文を出力する
  • 要約のポイントも提示する

のみです。

入力画像の例

出力例(比較的うまくいった場合)

ちょっと苦労した点

  1. 回転した画像だとデタラメな日本語が抽出される

    当初、回転した画像にも対応できるように日本語が正しく読み取れなかった場合に、画像を回転させて読み取るようなプロンプトを入れてました。ただ、どこから出てきたのか分からない全く関係のない日本語が抽出されました..

    このため、回転した画像の場合は、再アップロードを促すように変更しました

      デタラメな日本語が抽出される例:入力画像は上と同じ

  2. 天声こども語の撮影画像のみ対象にする

    タイトルを「Tensei Summary Helper」としており、これ以外の任意の文章の要約ができてしまうのも何だかな ということで制限するようにしました。

    具体的には、プロンプトに「天声」「▼」を含むかをチェックするよう追加しました

その他

小学生高学年をユーザーとして想定し、先生が子どもに教えるようなプロンプトを含めていたので公開しようとしたら利用規定違反になってしまいました..。

確かに、OpenAIの利用規約 に

最低年齢 お客様は、13歳以上、又は本サービスの利用に同意するためにお住まいの国で必要とされる最低年齢に達している必要が あります。お客様が18歳未満の場合、本サービスを利用するには、親権者又は法定後見人の許可を得る必要があります。

とあります。

「先生」「子ども」とかの語句はプロンプトから除いたので今は違反してないんじゃないか?と思っていますが過去のプロンプトの影響を受けてしまうようです..

ただ、タイミング?によっては公開できるときもあって基準がどうなっているのかよくわかりません。。

子ども(低学年、幼児)向けの教育用途のMyGPT作成はそれ用の工夫も必要かもしれません

<GPT Storeに公開できたもの>

chatgpt.com

作ってみて

抽出したテキストをよくよく見ると、日本語読み取りが途中までだったり、一部のみのときが頻繁にありました。要約の精度はそれに依存してしまうのでまだまだ改善が必要です。

縦書き文章は難しいのかもしれません

あと、天声こども語を対象にしましたが要約力向上の目的からすると別にそれに限らなくてもいいような気はしました。。

参考記事