【ダイエット × データ分析】ダイエットデータから2ヶ月後の体重を元トレーナーが 分析/予測 してみた

今回は1回目のため、「ダイエットや筋トレには機械学習が応用できますよ」ということを伝えたいです。 このブログでご紹介する線形回帰以外にもいろいろな機械学習モデルが存在するため、ブログにしていきたいなというふうに思います。
2024.06.05

概要

私はもともとパーソナルトレーナーでした。
その経験から、パーソナルジムで行うダイエットは、トレーナーの経験知識に依存するものが多いと感じています。

もちろん、お客様ごとに体重や体脂肪、その時のトレーニング重量の変動によるデータを考慮して、食事管理やトレーニング方法を提案していきますが、実際の顧客データを機械学習などで分析してお客様に提供するといった事ができるトレーナーは少ないかもしれません。

トレーニング業界のドメイン知識をある程度持つ私が、Google Cloudの最強のデータウェアハウスであるBigQueryを使用して、ダイエットの分析手法をブログにしたいと思います。

今回は1回目なので、デモのデータセットを使用して、このような感じでお客様を分析できるのではないか、という概要をブログにしていきたいと思います。

今回使用するデータ

テーブルの作成

BigQueryに以下のようなコードで、テーブルを作成しました。

CREATE OR REPLACE TABLE
  `muroi-yasunari.diet_analysis.diet_data` AS
SELECT
    ROUND(RAND() * (2.0 - 0.5) + 0.5, 2) AS hours_of_exercise_per_day,
    CAST(RAND() * (3000 - 1500) + 1500 AS INT64) AS daily_caloric_intake,
    CASE
      WHEN i BETWEEN 1 AND 40 THEN 'Low Carb'
      WHEN i BETWEEN 41 AND 80 THEN 'Low Fat'
      WHEN i BETWEEN 81 AND 120 THEN 'Mediterranean'
      WHEN i BETWEEN 121 AND 160 THEN 'Paleo'
      ELSE 'Vegan'
    END AS diet_type,
    CAST(RAND() * (190 - 150) + 150 AS INT64) AS height,
    CAST(RAND() * (100 - 60) + 60 AS INT64) AS initial_weight
  FROM
    UNNEST(GENERATE_ARRAY(1, 200)) AS i

無事BigQueryにテーブルが作成されました。

カラムの説明

  • hours_of_exercise_per_day
    • 1日の運動時間 (単位: 時間)
  • daily_caloric_intake
    • 1日の摂取カロリー (単位: kcal)
  • diet_type
    • 試しているダイエットの種類
  • height
    • ユーザーの身長 (単位: cm)
  • initial_weight
    • ダイエット開始前の体重 (単位: kg)
  • weight_after_2_months
    • 2ヶ月後の体重

今回、パレオダイエット地中海ダイエットなどをバリエーション豊富にするために入れました。
有名なのはローカロリー低糖質ダイエットになりますが、今回はあくまでもランダムに作成したデータからの分析なので、実際のダイエット結果とはマッチしないと思います。

Google Colabと繋いで分析する準備

BigQueryにもPythonノートブックという素晴らしい機能がありますが、今回は無料で使用できるGoogle Colaboratoryを使用していきます。

Google Colabに下記のように入力して、Google Cloud プロジェクトと繋げましょう。

from google.colab import auth  
auth.authenticate_user()

BigQueryのテーブルを引用する

Big Query内にあるテーブルと繋げてみましょう。

from google.cloud import bigquery
import pandas as pd

client = bigquery.Client(project='PROJECT-ID') 

クエリの実行
query = """
    SELECT *
    FROM `PROJECT-ID.diet_analysis.diet_data`
"""
query_job = client.query(query)

分析をしてみた

準備が整ったので、実際にPythonを使用して分析してみましょう。
先に今回使用するクラスや関数をインポートしておきます。

from google.cloud import bigquery
import seaborn as sns
import numpy as np
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import accuracy_score
from sklearn.metrics import mean_squared_error, r2_score

DataFrameを作成して、前処理を行う

前処理といっても、もともと綺麗なデータなので特にnull値や外れ値がないので、今回はほぼデータを変換させません。

df = query_job.to_dataframe()
df.isnull().sum()
df[['hours_of_exercise_per_day']]
df.query('hours_of_exercise_per_day <= 3 & daily_caloric_intake <= 4000 & height <= 200 & initial_weight <= 150', inplace=True)

また、今回は教師あり学習を行うので、目標値2ヶ月後の体重(weight_after_2_months)というカラムでデータフレームに追加したいと思います。

df['weight_after_2_months'] = df['initial_weight'] * (1 + np.random.uniform(-0.1, 0.1, size=len(df)))

無事カラムを追加したテーブルが作成されました。
ちなみに、2ヶ月後の体重はランダムで作成しているので、おそらく現実のダイエット結果とは乖離があります。

文字列カラムの値をエンコード

diet_typeカラムの値が文字列形式なので、数字に変換していきます。

lbe = LabelEncoder()
df['diet_type_encoded'] = lbe.fit_transform(df['diet_type'])

数字だと種類がわからないので、抽出してスクショしておきます。(ついでに元のカラムも削除)

df.loc[:, ['diet_type', 'diet_type_encoded']].drop_duplicates(subset=['diet_type_encoded'])
df.drop(columns=['diet_type'], inplace=True)

データの分割

学習用データとテストデータに分割していきます。
また、最後に元データにマージするためにインデックスを保持しておきます。

x = df.drop(columns=['weight_after_2_months']).values
y = df['weight_after_2_months'].values
x_train, x_test, y_train, y_test, train_index, test_index = train_test_split(x, y, df.index, test_size=0.3, random_state=0)

結果を確認します。

print(x_train.shape, x_test.shape)
print(y_train.shape, y_test.shape)

以下の通り、学習用とテスト用に分割されました。
(140, 5) (60, 5)
(140,) (60,)

線形回帰分析

ここからいよいよモデルを作成して、機械学習を行います。
モデルをトレーニングして、予測までを下記のコードで行います。

Linear_model = LinearRegression()
Linear_model.fit(x_train, y_train)
y_predict = Linear_model.predict(x_test)

モデルの評価

mean_squared_errorr2_scoreを使用して、作成したモデルの性能を評価します。

mse = mean_squared_error(y_test, y_predict)
r2 = r2_score(y_test, y_predict)

print(f'Mean Squared Error: {mse}')
print(f'R^2 Score: {r2}')

以下のような結果となりました。
Mean Squared Error: 19.88869414522818
R^2 Score: 0.858341678389154

この結果だけを考慮すると、モデルの性能は中の上といったところでしょうか。(mseとR2スコアの説明は今回はしません)

結果の考察

予測結果をマージさせたDataFrameを表示

一通り分析が終了したので、元のデータフレームに予測をマージして結果を見ていきましょう。
インデックスの値が異なるので、NaN値を削除して、とりあえず10行だけ取り出します。(インデックスは振り直しません)

# 予測データをテストデータのインデックスに対応させる
y_predict_df = pd.DataFrame(y_predict, index=test_index, columns=['予測データ'])

# 元のデータフレームに予測データをマージ
merged_df = df.join(y_predict_df)

# 結果を表示
cleaned_df = merged_df.dropna(subset=['予測データ'])
cleaned_df

DataFrameから妥当性を推測する

例えばインデックス番号4の人(上から1番目)であれば、「1日の運動時間は30分、摂取カロリーは約3000kcal、身長155cm、ダイエット開始前体重61kg、ダイエットタイプはビーガン」です。
ランダム算出したで2ヶ月後の体重65kgが正解データになりますが、予測結果61kgでした。

まあ、予測は外れているかなと思います。
そもそも「1日の運動時間は30分、摂取カロリーは約3000kcal、身長155cm」という条件であれば、2ヶ月後はおおよそ太ります

ついでにインデックス番号7の人(上から3番目)は、「1日の運動時間は1時間40分、摂取カロリーは約1500kcal、身長182cm、ダイエット開始前体重83kg、ダイエットタイプはビーガン」です。
ランダム算出したで2ヶ月後体重79kgが正解データになりますが、予測結果83kgでした。

これも予測は外れているかなと思います。
そもそも「1日の運動時間は1時間40分、摂取カロリーは約1500kcal、身長182cm、ダイエット開始前体重83kg」という条件であれば、2ヶ月後はおおよそ痩せます

まとめ

今回は、ランダムに抽出したデータを分析した ため、予測結果がうまく得られませんでした。
ただし、このように機械学習を使用して、ある程度のユーザーの情報が取得できれば、教師あり学習により2ヶ月後の体重の予測やどのようなダイエットタイプが痩せやすいかなどを可視化できると思います。

また、今回は予測結果をチューニングせずに結果を予測しましたが、ハイパーパラメータGuridSearchなどの手法を使用し、さらに精度を上げることができます。

今回は1回目のため、ダイエットや筋トレには機械学習が応用できますよということを伝えたかったです。
他にも線形回帰以外にも、いろいろな機械学習モデルが存在するため、ブログにしていきたいなというふうに思います。

もちろん、ダイエットや筋トレを絡めたブログにするつもりです。(今回のような顧客データのダイエット分析などの需要があれば、いつでも私にご相談ください。)