👋

身近なデータで試すPythonの機械学習! その2 お住まいの地域の不動産取引価格 続編 3 -価格予測アプリ編-

に公開

こんにちは!これまでのシリーズでは、
1. 不動産データを用いた機械学習モデルの基本フロー
2. LightGBMモデルの学習・評価・保存
3. Optunaによるハイパーパラメータ自動最適化
と、ステップを踏んで不動産価格予測モデルの構築と改善に取り組んできました。
理論やJupyter Notebook上での分析も重要ですが、やはり「実際に使える形」にしてみると、より達成感があり、機械学習の面白さを実感できるのではないでしょうか。
そこで今回は、これまで作成してきた予測モデルを活用し、Pythonの標準GUIライブラリであるTkinterを使って、簡単な不動産価格予測デスクトップアプリケーションを作成する方法をご紹介します!ユーザーが物件情報を入力すると、予測価格を表示するシンプルなアプリです。

今回のプロセスの流れ

1. Tkinterアプリの基本構造: tk.Frame を継承したクラスを作成し、アプリの骨組みを作ります。
2. UI要素の作成と配置:
    ◦ 物件情報を入力するためのラベルとエントリー(入力欄)。
    ◦ 予測を実行するためのボタン。
    ◦ 予測結果を表示するためのラベル。
3. モデルとスケーラーの読み込み: 前回の記事で保存した学習済みLightGBMモデルとスケーラーをアプリ起動時に読み込みます。
4. 予測ロジックの実装:
    ◦ ユーザーが入力した値を取得。
    ◦ 取得した値をモデルが学習した際の特徴量の形式(Numpy配列、標準化)に変換。
    ◦ モデルを使って価格を予測。
    ◦ 予測結果をUIに表示。
5. エラーハンドリング: ファイルが見つからない場合や、不正な入力があった場合の基本的なエラー処理。

コード紹介:TkinterでGUIアプリ開発

それでは、Tkinterを使った不動産価格予測アプリのコードを見ていきましょう。

  1. 必要なライブラリのインポートと定数定義
import tkinter as tk
from tkinter import ttk, messagebox, StringVar, DoubleVar # GUI部品、メッセージボックス、Tkinter用変数
import numpy as np # 数値計算
import pickle      # モデル読み込み用 (joblibも可)
import os          # ファイル存在確認用

# --- 定数定義 ---
# 重要:これらのファイルパスは、ご自身の環境に合わせて修正してください
MODEL_FILE_PATH = 'lgbm_optimized_model.joblib' # 前回保存したモデル
SCALER_FILE_PATH = 'lgbm_optimized_scaler.joblib'# 前回保存したスケーラー

# モデル学習時の特徴量の順番 (非常に重要!)
# この順番と、UIで入力データを取得してNumpy配列にする際の順番を一致させる必要があります。
EXPECTED_FEATURE_ORDER = [
    '最寄駅:距離(分)', '面積(㎡)', '間口',
    '延床面積(㎡)', '前面道路:幅員(m)', '築年数'
]

まず、必要なライブラリをインポートします。tkinter の他に、モデルの読み込みに pickle (または joblib)、数値処理に numpy を使用します。
MODEL_FILE_PATH と SCALER_FILE_PATH には、前回Optunaで最適化して保存したモデルとスケーラーのファイルパスを指定します。
EXPECTED_FEATURE_ORDER は、モデルを学習させた際の特徴量の「順番」を正確にリスト化したものです。GUIから入力された値をモデルに渡す際、この順番が異なっていると、全く見当違いの予測結果になってしまいます。前回の記事で FEATURE_COLUMNS として定義したものを参照してください。

  1. アプリケーションクラスの定義
    GUIアプリケーションの主要なロジックは HousePricePredictorApp クラスにまとめます。
class HousePricePredictorApp(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.master = master
        self.master.title("不動産価格予測アプリ")
        self.master.geometry("500x400")
        self.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

        # 入力用変数の初期化 (DoubleVarで数値入力を期待)
        self.dist_var = DoubleVar()
        self.area_var = DoubleVar()
        self.maguchi_var = DoubleVar()
        self.total_area_var = DoubleVar()
        self.road_width_var = DoubleVar()
        self.age_var = DoubleVar()

        self.prediction_result_var = StringVar() # 予測結果表示用
        self.prediction_result_var.set("予測結果はここに表示されます")

        # モデルとスケーラーの読み込み
        self.model = None
        self.scaler = None
        self._load_model_and_scaler() # 起動時に読み込み

        # UIウィジェットの作成と配置
        self._create_widgets()

コンストラクタ (init) では、ウィンドウの基本的な設定、入力値と結果表示用のTkinter変数 (DoubleVar, StringVar) の初期化、そして後述するモデル読み込み関数とUI作成関数を呼び出しています。

  1. モデルとスケーラーの読み込み (_load_model_and_scaler)
    アプリ起動時に、学習済みのモデルとスケーラーをファイルから読み込みます。
def _load_model_and_scaler(self):
        """学習済みモデルとスケーラーを読み込む"""
        try:
            # ファイル存在チェック
            if not os.path.exists(MODEL_FILE_PATH):
                raise FileNotFoundError(f"モデルファイルが見つかりません: {MODEL_FILE_PATH}")
            # ... (スケーラーも同様) ...

            # ファイルから読み込み (pickleを使用、joblib.loadでも可)
            with open(MODEL_FILE_PATH, 'rb') as f_model:
                self.model = pickle.load(f_model)
            with open(SCALER_FILE_PATH, 'rb') as f_scaler:
                self.scaler = pickle.load(f_scaler)
            print("モデルとスケーラーの読み込みに成功しました。")
        except FileNotFoundError as e:
            messagebox.showerror("エラー", str(e)) # エラーダイアログ表示
            self.master.destroy() # アプリ終了
        except Exception as e:
            messagebox.showerror("エラー", f"モデル読み込み中にエラー: {e}")
            self.master.destroy()

ファイルが見つからない場合や読み込み中にエラーが発生した場合は、エラーメッセージを表示してアプリを終了するようにしています。

  1. UIウィジェットの作成と配置 (_create_widgets)
    ユーザーインターフェースを構成する部品(ラベル、入力ボックス、ボタンなど)を作成し、ウィンドウ上に配置します。ここでは grid レイアウトマネージャを使用しています。
def _create_widgets(self):
        """UIウィジェットを作成し配置する"""
        main_frame = ttk.Frame(self, padding="10") # メインのフレーム
        main_frame.pack(fill=tk.BOTH, expand=True)

        ttk.Label(main_frame, text="住宅情報を入力してください:", font=("メイリオ", 12, "bold")).grid(
            row=0, column=0, columnspan=4, pady=(0, 10), sticky="w"
        )

        # --- 入力フィールド ---
        input_fields_config = [
            ("最寄駅:距離(分)", self.dist_var, 1, 0), # (ラベル, 変数, 行, 列)
            ("面積(㎡)", self.area_var, 1, 2),
            ("間口(m)", self.maguchi_var, 2, 0),
            ("延床面積(㎡)", self.total_area_var, 2, 2),
            ("前面道路:幅員(m)", self.road_width_var, 3, 0),
            ("築年数(年)", self.age_var, 3, 2),
        ]

        for i, (label_text, var, r, c) in enumerate(input_fields_config):
            ttk.Label(main_frame, text=label_text, font=("メイリオ", 9)).grid(
                row=r, column=c, padx=5, pady=5, sticky="w"
            )
            entry = ttk.Entry(main_frame, textvariable=var, width=12)
            entry.grid(row=r, column=c + 1, padx=5, pady=5, sticky="ew")
        # ... (ボタンと結果表示ラベルの配置は上記コード参照) ...

入力項目ごとにラベルと ttk.Entry (入力ボックス) を作成し、grid で位置を指定しています。textvariable には、コンストラクタで初期化した DoubleVar オブジェクトを指定し、入力値と変数を連動させます。

  1. 予測実行ロジック (_predict_price)
    「価格を予測」ボタンが押されたときに実行される関数です。
def _predict_price(self):
        """入力値から価格を予測し、結果を表示する"""
        if self.model is None or self.scaler is None: # モデル未読み込みチェック
            messagebox.showerror("エラー", "モデルまたはスケーラーが読み込まれていません。")
            return

        try:
            # 入力値を取得 (DoubleVar.get()でfloat型として取得)
            # EXPECTED_FEATURE_ORDERの順番に合わせてデータを取得・整形
            input_values = [
                self.dist_var.get(),
                self.area_var.get(),
                self.maguchi_var.get(),
                self.total_area_var.get(),
                self.road_width_var.get(),
                self.age_var.get()
            ]
            
            # 簡単な数値チェック (DoubleVarがエラーを出す前に基本的なチェック)
            for i, val in enumerate(input_values):
                # DoubleVar.get() は不正な入力の場合TclErrorを発生させるので、
                # ここでは主に空でないことや、より詳細なバリデーションが必要な場合にカスタムする
                pass # DoubleVarが数値変換を試みるので、ここでは追加のチェックは省略

            # Numpy配列に変換し、モデル入力形式 (1, n_features) に整形
            X_test = np.array([input_values]).reshape(1, -1)

            # 標準化 (学習時と同じスケーラーを使用)
            X_test_std = self.scaler.transform(X_test)

            # 予測実行
            y_pred = self.model.predict(X_test_std)
            
            # 予測結果を万円単位で丸めて表示
            predicted_price_man = np.round(y_pred[0] / 10000) 

            self.prediction_result_var.set(f"予測価格は約 {int(predicted_price_man):,} 万円です")

        except tk.TclError: # DoubleVar.get() で数値変換に失敗した場合など
             messagebox.showerror("入力エラー", "有効な数値を入力してください。")
             self.prediction_result_var.set("入力値に誤りがあります。")
        except ValueError as e: # その他の値エラー
            messagebox.showerror("入力エラー", str(e))
            self.prediction_result_var.set("入力値に誤りがあります。")
        except Exception as e: # その他の予期せぬエラー
            messagebox.showerror("予測エラー", f"予測中にエラーが発生しました: {e}")
            self.prediction_result_var.set("予測中にエラーが発生しました。")

ここでのポイントは:
• DoubleVar.get() で各入力ボックスから数値を取得します。
• 取得した値を EXPECTED_FEATURE_ORDER で定義した正しい順番でリストに格納します。
• Numpy配列に変換し、reshape(1, -1) でモデルが期待する2次元配列の形式にします。
• 読み込んだスケーラー (self.scaler) を使って入力データを標準化します。
• モデル (self.model) の predict メソッドで価格を予測します。
• 結果を見やすく整形(万円単位、カンマ区切り)して、結果表示用ラベルの変数 (self.prediction_result_var) にセットします。
• try-except ブロックで、数値変換エラーや予測中のエラーを捉え、ユーザーにメッセージを表示します。

  1. メイン実行部分
    最後に、アプリケーションを起動するためのコードです。
def main():
    root = tk.Tk() # メインウィンドウ作成
    app = HousePricePredictorApp(master=root) # アプリケーションクラスのインスタンス化
    app.mainloop() # イベントループ開始

if __name__ == "__main__":
    main()

アプリの実行と画面イメージ

このPythonスクリプトを実行すると、以下のようなウィンドウが表示されるはずです。

各入力欄に数値を入力し、「価格を予測」ボタンを押すと、予測結果が表示されます。

まとめと今後のステップ

今回は、Tkinterを使って、これまで作成してきた不動産価格予測モデルを実際に操作できるデスクトップアプリケーションに仕立て上げる方法をご紹介しました。GUIを通じて自分のモデルが動くのを見ると、また新たなモチベーションに繋がるのではないでしょうか。

このアプリは非常にシンプルですが、ここからさらに発展させることも可能です。

• デザインの改善: フォント、色、レイアウトなどを工夫して見栄えを良くする。
• 特徴量の追加: モデルが対応していれば、さらに多くの特徴量を入力できるようにする(例:ドロップダウンで市区町村を選択)。
• グラフ表示: 例えば、入力された築年数と予測価格の関係などを簡易的にグラフ表示する。
• ファイルからのデータ読み込み: CSVファイルなどから物件情報を一括で読み込んで予測する機能。
• Webアプリケーション化: Tkinterはデスクトップアプリですが、FlaskやDjangoといったフレームワークを使えば、同様の機能をWebアプリケーションとして公開することも可能です。

機械学習モデルを構築するだけでなく、それを「使う」ためのインターフェースを作ることも、データサイエンスの重要な側面の一つです。ぜひ、このサンプルアプリが皆さんの参考になれば幸いです。

Discussion