• エンジニアブログ

2024.04.24

千尋の谷と、妄想と(あるいは機械学習コンペ参加記)

データ&リサーチグループ改め、先端技術研究グループ 望月です。

アヴァシスは長野県産業振興機構 上田センター主催の「上田AI活用研究会」に参加しています。
地元・長野県上田市の企業を対象に、AI活用について情報交換しようぜ!って勉強会ですね。
その一環で「機械学習コンペに挑戦しましょう」って取り組みがありました。
今回はその解説をば。

その前に、天の声カムヒア!

「相変わらず人使いが荒いわね?」

はい、天の声こと広報でございます!
唸れ裏拳! 吼えろツッコミ! ビシバシ夜露死苦!

「裏拳はダメ! ゼッタイ! コンプラ的に!
 ていうか、昭和スラングはナウなヤングに通じないので自重なさい」

ヘイ...。

いや機械学習コンペの話って、興味ない人には「地球の言語で話そうかキミ」ってなりやすいので。
前回(「FSS2023で発表した話と、過去の失敗がよぎった話」)同様、キミには知らない人代表として突っ込んでもらいつつ、わかりやすく解説するって寸法よ! アンダスタン?

「...貸しにしとくわ。
 ていうか、アナタに貸しはあっても借りはないから!」

アイシーアイシーわかってますって。(←わかってない)

「どうでもいいけど、その取ってつけたカタカナはどうした?
 アナタは『No Smoking』を『横綱不在』って訳すタイプの英語弱者よね?」
 (「smoking = スモー・キング = 相撲の王 = 横綱」というアクロバティックなミステイク)

いや、子供たちが隣で聞いてた「小学生の基礎英語」(NHK)に影響されまして...。
もちろん望月は英語弱者なのでご安心を!(何が?)
レッツ・ゲット・スターテッド!


1. 機械学習コンペの概要

機械学習コンペとは、提供された問題をAIで解き、その正確さを競うイベントです。
上田AI活用研究会メンバは機械学習プログラミングに無縁の方が多いので、今回はNishika社の練習問題「[Training] 中古マンション価格予測」に参加しました。
タイトル通り、マンションなどの不動産価格を各種条件(地域・築年数・面積・最寄り駅までの時間等)から推論するってお題です。

「なんか難しそうね?」

正直なところ、Pythonプログラミングの経験が全くない人にはハードルが高いですね。
今回はプログラミング経験がある有志3名で参加しました。
あえてチームは組まず、個別に検討してます。


まずはコンペ参加の経緯と結果を簡単にお話します。
機械学習を知らない方でもわかるよう説明しますので、ご安心を。

「頼むわね! プログラミングやAIって、そもそも用語が難しいから」

へいガッテン!

コンペで実施した作戦も解説しますね。
記事本編では大雑把な考え方だけわかりやすく記載して、具体的なPythonコードは最後に補足として載せます。

「おぉ至れり尽くせりね! どうした?」

Nishika社のコンペ運営担当様にご配慮いただいたので、期待に応えないとね。

「どうした?」(←1行ぶり2回目)

このコンペ、練習問題なので終了期日がありません。
そしてコンペの規約では、終了前のコンペについて(所定のフォーラム以外で)知見を共有することができなかったんですね。
このコンペのフォーラムには投稿が一つもないので、事実上「誰も語らない・語れない」という触れ得ざる者ジ・アンタッチャブルだったりします。入門用なのに。マジか!

「マジか!
 『ソフトに詳しい人向けの記事もこのBlogに載せたい』って考えてたのに、このネタではできないのね?」

「企業Blogで(もちろん非営利で)、練習用コンペに関する初心者向け記事を書いてコンペを盛り上げたいんだけど、規約によりできないんですよタスケテ」とNishika社のコンペ運営様に相談しました。
そうしたら問い合わせたその日に規約を変えてくださって、「練習用コンペは終了前でも自由に記事を書いてOK」ってなったんです。
このフットワークの軽さよ! 弊社も見習いたいですね!

「なんと!
 『企業が対外的な規約を変える』って、フツーは審議・承認にすごく時間がかかるでしょ。
 ユーザーのためにすぐ動ける企業は強いわね!」

おかげで、コンペで得られた知見を自由に書けるようになりました。ありがてぇありがてぇ。
まずは概要から説明しますね。



2. 機械学習コンペ参加の経緯と結果

上田AI活用研究会は、顧問に信州大学 工学部 電子情報システム工学科 香山瑞恵教授(2024年4月時点では工学部長)を迎えて活動しました。
2023年度は、香山先生によるAIの解説や、参加企業のAI活用事例紹介などを行っています。

それらの話も大変面白かったのですが。
「業務にAIを適用したい」と考えたら、自分でプログラム作れた方がいいよね、となり。
「せっかくだから自分たちで手を動かして、AI使ってみようぜ!」とトレーニングコンペに参加することに。
メンバは機械学習プログラミング初心者って方が多いので、なかなか難しいのですけどね。

「チャレンジャブルね」

ですよね~。まぁ人生はチャレンジですよ。望月はド安定志向ですが。


で。
望月的には「機械学習のプログラミング経験がない人向けに、簡単に機械学習を実装できるライブラリを(コンペの練習問題に適用して)解説しよう」って意図だったんですね。コンペの成績は置いといて。(←伏線)
AI活用研究会を通して地元企業ズに貢献! それが「ノブオ」の生き様!

「ノブ夫?」

「レスオ」とも言う!

「ねぇノブ夫って誰?」

「ノブレス・オブリージュ」の略な! 「何かを得た者は、相応の責務を負う」ってやつよ!
地元の製造業などの皆さんがAI活用で困っている、だったらその知見を持つ我々がお助けしましょう!
地域に根を張り、社会に必要とされる存在! それが弊社!


2.1. 簡単に機械学習モデルを作れるライブラリを使ってみる

そこで、ですよ。
機械学習モデルを簡単に作れるライブラリ「PyCaret」を、コンペの課題に適用してみました。
機械学習でよく使う以下の処理を、それぞれ1行で実現できてしまうところがステキポイントです。

  • (1)データの前処理(欠損値の処理など)
  • (2)学習(複数モデルで学習し、精度を比較)
  • (3)テストデータを推論

これなら機械学習初心者でも簡単に使えるのではないかと!

「よさそうね!」

まぁ些細な問題はあるんですけどね。

「というと?」

精度が出ませんでした!
具体的にはコンペの順位が「219 / 231 位」! 下から数えた方が早い! 圧倒的に!

PyCaret.png

「えーと、問題の大小で言えば『大』寄りかな?」

「スコアが悪い」こと自体は、大きな問題じゃないんですよ。最初はそんなものです。
ライブラリ自体はイイと思うし、活用すれば性能は出ると思うんですね。
ただ高度にブラックボックス化されているので、困りごとが起きた際に、何が問題なのか・どうすればいいかがわかりにくい。強いて言えばそこが難点かな。
初心者でもとっつきやすいし、うまくハマれば効果がでかいけど、ハマらなかった場合に苦労すると。
なので、コンペには向かない可能性がありますね。


2.2. サンプルコードを使ってみる

はい、この時点でPyCaret活用は断念しました! 人間、諦めが肝心です!

「早っ! ついさっき『コンペの成績は置いといて』とか言ってたのに!」(←伏線回収)

ソコソコの推論結果が出ないと、紹介しても「コレ大丈夫? 仕事に使える?」ってなりそうなんですよね。
精度が出そうな別のモデルを探しましょう!

この手のコンペって、何らかのサンプルコードが用意されていることが多いです。
今回はコンペ主催者が提供したモデルを使ってみます。
「中古マンション価格予測 2023春の部」のサンプルコード(LightGBM)
ベースとしてはイイ感じなので、ここに手を加えていきます。


2.3. 学習データと検証データの分け方を変えてみる

機械学習では、手持ちのデータを学習用データと検証用データに分けて使うことが多いです。
学習用データで学習させたモデルに検証用データを入力して、どの程度精度が出るか確認する、という使い方ですね。
サンプルコードではこの手持ちデータの分割が固定でした。
このため「検証用データ部分は学習に使われない」って問題があります。その分精度は不利だと。

手持ちデータの分割を、簡単な図で説明します。
全部で5つのデータがあったとします。
それを以下の通り学習用データ(□)×4個、検証用データ(■)×1個に分けます。

 □□□□■

先に述べた通り、■は学習には使いません。性能の検証にだけ使います。
これも学習に使えれば、その分性能も上がるはず。もったいないですね。

それを解決すべく、世間には「K分割交差検証」って手法があります。
学習データと検証データの分割パターンをK種類用意し、そのパターンを切り替えて学習・検証を行うって方法ですね。
手持ちのデータをまんべんなく活用できるので、精度が上がります。

分割パターンを5種類用意した場合のイメージが以下の通り。
以下の5パターンについて学習・検証を行うので、全データを学習に使える、って利点がわかると思います。
ただ計算時間も、単純計算で5倍になりますけどね。
この5パターンの検証結果を確認して、もっとも性能が良かったモデルを使います。

 □□□□■
 □□□■□
 □□■□□
 □■□□□
 ■□□□□

詳細を知りたい方は、以下のcodexa社「AIマガジン」の記事が参考になると思います。
サンプルコードもあるので、コンペに挑戦したい!って人には役立つでしょう。
「交差検証(Python実装)を徹底解説!図解・サンプル実装コードあり」


2.4. パラメータの自動調整を行ってみる

機械学習モデルには多様なパラメータがあります。
うまく調整すれば性能が上がりますが、パラメータの種類が多すぎて、手動で試行錯誤するのは現実的ではありません。
「Optuna」というライブラリは、これを自動で調整してくれます。使い方もすごく簡単!
スコアが向上するケースが多いので、最後に使ってみると吉です!

「なぜにファイナル限定?」

計算時間がすごくかかるんですよね。
目安として、通常の10倍以上はかかると思ってください。
これまで1~2時間で終わっていた学習が、丸一日計算しても終わらないとかザラです。

コンペも業務も試行錯誤してナンボ。でもこんなに時間がかかると厳しいですよね。
だから試行錯誤が終わってやり方が確定した後で、最後に1発カマすのが効率的です。


以上をまとめると、こんな感じ。

80819ab9163a3c463f82a74ec67d6f72b07cf847.PNG

(注)上記の「3. 新たな特徴量を追加」は後でお話しします。

これまでの説明では、「不動産価格」に特化した対応は行っていません。
つまりこの機械学習モデルは、表データの学習・推論なら何でも使えます。売上の予測とか。
業務に活用できますし、もちろん他のコンペにも使えます。

このような「そこそこ性能が出る汎用モデル」を1つ持っていると、何かと応用が効くので便利です。
AI活用研究会でメンバに展開したかったのはそこですね。


そんな感じでいろいろ試した結果、「17 / 232 位」(上位7.3%)に入れましたよ!

「おぉ凄いじゃない!」

でしょ? 頑張ったよねアタシ! もういいよね?

「いやいいけどさ、ノブオはどうした...」

まずはこの順位だけAI活用研究会で簡単に話したわけですよ。
後は機械学習を業務に活用できるよう、参加者に解説すればいいよね、順位はもういいよね、と。
それに対する、信大 香山先生のありがたいお言葉がこちら。

「もう少しで一桁順位ですね!」

おふぅ! まだ足りないと? オマエの力はそんなものかと?
さすが先生、必死こいて這い上がった教え子を、千尋の谷に蹴落としますね! 躊躇なく!

その時思い出しましたよ。香山先生の伝説を。
AI活用研究会初回の冒頭で、先生はこう歌いながら登場されたそうです。

  あれもAI  これもAI
  たぶんAI  きっとAI

「参加者に関心を持ってもらう」ためには、手段を選ばないこのプロ根性! さすがです先生カッケェ!
音痴な望月には真似できません! 「やればウケる」とわかっていてもできないでしょう!
(「先生は単にカラオケ好きなのでは」という邪推は却下)

このように教育と研究に全振りした御方ゆえ、シレっと無茶をおっしゃることもありますが。
それは「やればできる」と見込んだうえでのハッパ。
応えようともがけば、必ず自身の糧となります。

この記事は就活を控えた学生さん向けに書いてますが。
学生のみなさんはこのような凄い先生方にすぐ相談できる、ものすごく恵まれた環境に毎日います。
なんせ日本や世界でトップクラスの先生方がうようよ徘徊してる魔境だぜ。天上人だぜオイ。
学生、特にゼミや研究室に所属している方は、ぜひ先生方にどんどん話を聞いてください。
それは貴重な財産になります。必ず。


「シレっとイイ話で纏めようとしてるけど、コンペはどうなったの?」

最終的には「7 / 217 位」(上位3.2%)まで行きました。
(分母が減っているのは、古い投稿を運営側が定期的に削除するため)
やったよ先生! 頑張ったぞオレ! エイドリアーン!

他にも仕込みたいネタはあったのですが、試したらうまく行かなかったので断念。
ま、世の中そんなもんよ!


「一応、最後に順位が上がった手法について、解説をいいかしら?」

「K分割交差検証」を使う際は、ランダム抽出を有効にするとよいです。
偏ったデータから万遍なく学習データを取り出せるので、汎化性能が上がります。それが効きました。

「あの、もう少しわかりやすい説明でひとつ...」

コンペで提供されたデータは、地域毎に情報がまとまっています。
例えば「01.csv」は北海道の物件データ、「02.csv」は青森といった具合。

「ふむ、ごった煮ではない、と」

これを順番に繋げてまとめるので、当然、地域の偏りが出ます。
学習データとして取り出した部分が、たまたま東京や神奈川などの大都市ばかりだった、ってこともあり得ます。
それで学習したモデルは、地方の安い物件を正しく推論できない可能性があるんですよね。
逆もしかり。

「言わんとすることは、わからないでもないかも。なんとなく」

ランダム抽出を有効にすると、全データの中から学習データをランダムに抽出してくれます。
結果、全国の物件をある程度まんべんなく学習できます。
だから、地方や都市部などさまざまな物件に対応しやすくなるんですね。
それがスコア向上の要因ではないかと。
実際のところはわからないので、あくまで妄想ですが。


3. 妄想力が大事です!

この手のコンペでは、精度につながる特徴量を追加すると、スコアが上がることが多いです。
今回は不動産価格の予測なので、以下の仮説を立てられますよね。

  • (a)「最寄駅からの距離(分)」:小さいほど不動産価格が高い
  • (b)「面積(㎡)」:大きいほど不動産価格が高い
  • (c)「建築年」:大きい(=新しい)ほど不動産価格が高い

ってことで、新たな特徴量として以下を考えてみました。
 特徴量 = (-1)×「最寄駅からの距離(分)」 + 「面積(㎡)」 + 「建築年」
結果? うん、まぁ...察してくれ...。

ただ、考え方自体は悪くないと思われます。
「期間限定コンペの話ね、それは終了したからネタバラシするね」という記事を見ると、以下の特徴量を追加して成功した例がありますね。
「もっとも効いた特徴量: "面積 ー 築年数"」
(「築年数 = 現在の年 ー 建築年」なので、こちらは値が小さいほど不動産価格が高い)

でも望月版は、指標としてうまく機能しない可能性があります。
建築年(2020年とか)が他の数値(最寄駅までの時間:10分とか)より大きすぎて、建築年だけでほぼ決まってしまう気がするんですよね。
「実際どうなのか」はコンペの運営しかわからないので、そこは妄想するしかないですけど。


このように、未知の問題に対して仮説を考え、試し、結果を確認するのですが。
だいたいうまくいかないです。世の中そんなもんです。
そして、うまくいかない理由は誰にもわかりません。妄想するしかないのです。
妄想を元に次の仮説を考え、結果を検証する。エンジニアリングはその繰り返しです。
だから妄想が好きな人は、エンジニアに向いている可能性がありますね。

過去の記事(「悩める学生さんの不安にお答えします!」)でも書きましたが。
大学受験までは、「既存の正解に効率よくたどり着く能力」を鍛えてきました。
でも社会人は、正解がない世界で「より優れたやり方」を開拓していく必要があります。
それを助けてくれるのが「妄想力」ですね。
派手な妄想と地道な検証で、世界を変えていきましょう!

「みんなが夢中になれる妄想」を描ける方なら、作家などの道もありますけどね!
それはそれで稀有な才能! その力で、ぜひ世の中に光を与えてください!
特に望月に! 夢と希望を!


4. オマケ

ここでは上で解説した対応について、簡単なコードで解説します。
「アタシもコンペに挑戦したいわ!」って方向けですね。
「プログラムとか難しくてイヤン」って方は、以下は読まなくて大丈夫です!


(1) K分割交差検証

コンペ運営提供のサンプルコードでは、学習データ(train_df)と検証データ(val_df)の分割が固定でした。
以下のように、「取引時点が2019年第4四半期までのデータは学習データ、2020年第1四半期以降のデータは検証データ」と分割しています。

val_min_idx = min(df[df['取引時点'].str.contains('2019年第3四半期|2019年第4四半期', regex=True)].index)
test_min_idx = min(df[df['取引時点'].str.contains('2020年第1四半期|2020年第2四半期', regex=True)].index)
(略)
train_df = feat_df.iloc[:val_min_idx, :]
val_df = feat_df.iloc[val_min_idx:test_min_idx, :]

これをK分割交差検証に変えました。
今回はK=10としましたが、これはテキトーです。

なおスタイルシートの都合で文字のインデントがうまく行かず、下記コードは全角スペースでインデントを付けています。ダセヱ。
あくまで参考として見てください。

import lightgbm as lgb
from sklearn.model_selection import KFold
from sklearn.metrics import mean_absolute_error

# k-分割交差検証を使って学習&予測
N_SPLITS = 10
kf = KFold(n_splits=N_SPLITS, shuffle=True, random_state=0)

for i, (tdx, vdx) in enumerate(kf.split(df_train_X, df_train_y)):
  # 訓練用データと検証用データに分割
 X_train, X_valid, y_train, y_valid = df_train_X.iloc[tdx], df_train_X.iloc[vdx], df_train_y.values[tdx], df_train_y.values[vdx]
  lgb_train = lgb.Dataset(X_train, y_train)
  lgb_valid = lgb.Dataset(X_valid, y_valid)

  # 学習の実行
  model = lgb.train(params, lgb_train, num_boost_round=num_round,
           valid_names=["train", "valid"], valid_sets=[lgb_train, lgb_valid])

  # 検証データに対する予測値を取得
  va_pred = model.predict(X_valid, num_iteration=model.best_iteration)

  # accuracyスコアを計算
  score_ = mean_absolute_error(y_valid, va_pred)
  print(f"fold{i} score is :{score_}")

なおKFold()で"shuffle=True"を指定しています。これが「ランダム抽出有効」ですね。
今回のコンペでは地味に効果的でした。


(2) Optunaによるパラメータ自動調整

まずOptunaをインストールしておきます。

pip install optuna

使うLightGBMをOptuna版に指定するだけです。
下の例では通常版は無効、Optuna版を有効にしています。

# import lightgbm as lgb          # 通常LightGBM(高速)
import optuna.integration.lightgbm as lgb  # Optuna版LightGBM(計算が重い)

これだけでスコアが上がる(ことが多い)のですから、そりゃ笑いが止まりませんわハッハッハ!
(全然終わらない計算表示から目をそらしながら)


こんな単純な対応でもそこそこスコアは伸びるので、気になる方はコンペで試してみると面白いと思います!
では!