🤖

高齢者の膝高からの身長推定

2024/06/17に公開

はじめに

株式会社Rehab for JAPAN 開発2部サイエンスチームの上田です。

栄養評価や必要エネルギー量算出には身長や体重の測定値が必須ですが、高齢者の方では歩行や直立が困難であったり、脊椎が湾曲していたりして身長を正しく測定できない場合があります。このような場合、日本では主にChumlea[1]らやMiyazawa[2]らが提唱する身長推定式が利用されています。これらの推定式は膝高と年齢を説明変数に、身長を目的変数とした重回帰式です。
本記事ではChumleaらの式を例に、重回帰分析による膝高と年齢から身長を推定する式の導出と、同様にLightGBMで回帰分析を行った結果の比較を行います。

ターゲット

  • データサイエンティスト(見習いレベル)
  • データサイエンティストを目指す方
  • データサイエンスに興味のある方

要約

Chumleaらによる推定式

性別 回帰式 R^2 SEE
男性 身長 = 64.19 + 2.03 * 膝高 - 0.04 * 年齢 0.67 3.84
女性 身長 = 84.88 + 1.83 * 膝高 - 0.24 * 年齢 0.65 3.50

における真値y_iと推定値\hat{y_i}から計算される推定値の標準誤差

SEE = \sqrt{\dfrac{1}{n-3} \sum_{i=0}^{n-1}(y_i - \hat{y_i})^2}
を再現できるような人工データを作成して、重回帰分析とLightGBMによる回帰分析を行った結果は次のとおりです。

  • 重回帰モデルの方が汎化性能が高い
  • 説明変数(特徴量)が少ないとLightGBM回帰モデルは過学習しやすい

人工データの準備

利用できそうな膝高のデータがないため、Chumleaらの式に適当な身長と年齢から膝高を計算し、推定される予定の身長に誤差を与えました。

例:男性膝高 = \dfrac{身長 + 0.04 * 年齢 - 64.19 }{2.03}
身長 = 身長 + \alpha{Z}
ここでZは平均0, 標準偏差1に従う正規分布乱数で、重みの\alphaは重回帰式のSEEがChumleaらの式のSEEと一致するように調整しました。

# 適当な身長と年齢データ(年齢は65~90歳)
data
#       height  age
# 0     154.0   81
# 1     167.0   70
# 2     165.3   78
# 3     150.0   84
# 4     148.6   86
# ..      ...  ...
# 995   159.0   81
# 996   166.0   88
# 997   165.4   73
# 998   158.0   88
# 999   162.0   84
# Chumleaらの式による膝高の逆算。大元の身長には誤差を加える(小数点以下1位で丸め)。
import numpy as np
seed = 42
np.random.seed(seed)
n = data.shape[0]

data['knee_height'] = data.apply(lambda x: (x.height + 0.04 * x.age - 64.19) / 2.03, axis=1)
x = data.drop('height', axis=1)
y = round(data.height + alpha * np.random.normal(size=n),1)

学習データとテストデータの分割

今回準備したデータセットはN=1000で、学習用に800件、テスト用に200件としました。

from sklearn.model_selection import train_test_split

x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=seed)

線形重回帰モデルとLightGBM回帰モデルによる学習

Chumleaらは、身長yを目的変数とし、膝高x_1、年齢x_2を説明変数として重回帰式

y = a + b_{1}x_{1} + b_{2}x_{2}
における切片aと回帰係数b_1b_2を最小二乗法により求めています。この時、膝高(cm)と年齢の値の正規化やスケーリングは行っていません。
実際に重回帰分析を行う際には各説明変数の単位や数値としての桁数(粒度)が異なる場合には正規化や各種スケーリングを考慮します。また説明変数が多い場合には分析に必要な適度な説明変数のみを重回帰式に含める必要があるため、「変数増加法」や「変数増減法」など説明変数の様々な選択方法を試すことになります。

今回用いた線形重回帰モデルはscikit-learnのLinearRegressionです。この他にstatsmodelsの重回帰モデルがあり、簡単に各係数の統計量等を表示できて大変便利ですが、今回はChumleaらの式を元にしたデータセットを利用するので各変数の統計量には注目しません。
同じくscikit-learnのLightGBMによる回帰分析では、評価関数としてRMSE(Root Mean Squared Error)を選択しました。

RMSE = \sqrt{\dfrac{1}{n} \sum_{i=0}^{n-1}(y_i - \hat{y_i})^2}
この式は前述のSEEとほぼ同じ形をしており、自由度が異なるだけとなっています。その他のハイパーパラメータはほぼデフォルトのままです。

from sklearn.linear_model import LinearRegression
import lightgbm as lgb

model_lr = LinearRegression()
model_lr.fit(x_train, y_train)

model_lgb = lgb.LGBMRegressor(verbose=-1, random_satate=seed, metric='rmse')
model_lgb.fit(x_train, y_train)

学習データの精度

def see(y_true, y_pred):
    return (sum((y_true - y_pred)**2)/(len(y_true)-3))**0.5

mlr_y = model_mlr.predict(x_train)
lgb_y = model_lgb.predict(x_train)
print(f'MLR: R2 = {model_mlr.score(x_train, y_train):.2f}, SEE = {see(y_train, mlr_y):.2f}')
print(f'LGB: R2 = {model_lgb.score(x_train, y_train):.2f}, SEE = {see(y_train, lgb_y):.2f}')
# MLR: R2 = 0.77, SEE = 3.84
# LGB: R2 = 0.84, SEE = 3.21
モデル R^2 SEE
線形重回帰 0.77 3.84
LightGBM回帰 0.84 3.21

重回帰モデルの方は調整した通りSEE=3.84となりました。
R^2SEEともにLightGBM回帰モデルの方が重回帰モデルより良い結果に見えますが、過学習の可能性があります。

テストデータの精度

LightGBM回帰モデルが過学習を起こしている可能性があるため、事前に分割したテストデータによる汎化性能を見てみます。

mlr_y = model_mlr.predict(x_test)
lgb_y = model_lgb.predict(x_test)
print(f'MLR: R2 = {model_mlr.score(x_test, y_test):.2f} SEE = {see(y_test, mlr_y):.2f}')
print(f'LGB: R2 = {model_lgb.score(x_test, y_test):.2f} SEE = {see(y_test, lgb_y):.2f}')
# MLR: R2 = 0.74 SEE = 3.86
# LGB: R2 = 0.69 SEE = 4.19
モデル R^2 SEE
線形重回帰 0.74 3.86
LightGBM回帰 0.69 4.19

やはり、重回帰モデルでは学習データの予測時と比べて若干落ちるものの精度を維持しているのに対して、LightGBM回帰モデルでは精度がかなり低下しています。
念ため、テストデータで外挿してしまっているか確認すると

print(x_train.agg(['min', 'max']))
print(x_test.agg(['min', 'max']))
#      age  knee_height
# min   65    30.9
# max   90    59.9
#      age  knee_height
# min   65    37.3
# max   90    57.9

テストデータの年齢、膝高はそれぞれ学習データの最小値から最大値の範囲内であることからLightGBMの精度低下は外挿によるものではない事がわかります。つまり過学習しているため汎化性能が落ちていると考えられます。

LightGBM回帰モデルの過学習を抑えた精度

LightGBM回帰モデルの学習時に学習率を抑えることで重回帰モデルと同様にSEE=3.84にした場合の汎化性能を調べてみました。

model_lgb = lgb.LGBMRegressor(verbose=-1, random_satate=seed, metric='rmse', learning_rate=0.0179)
model_lgb.fit(x_train, y_train)

lgb_y = model_lgb.predict(x_train)
print(f'LGB: R2 = {model_lgb.score(x_train, y_train):.2f} SEE = {see(y_train, lgb_y):.2f}')

lgb_y = model_lgb.predict(x_test)
print(f'LGB: R2 = {model_lgb.score(x_test, y_test):.2f} SEE = {see(y_test, lgb_y):.2f}')
# LGB: R2 = 0.77 SEE = 3.84
# LGB: R2 = 0.70 SEE = 4.12
データセット R^2 SEE
学習用 0.77 3.84
テスト用 0.70 4.12

学習率を抑えた結果、学習用データを用いた精度は偶然にも重回帰モデルと一致しました。ただしテストデータを用いた精度はあまり向上しない結果となりました。
この結果から、ハイパーパラメータのチューニングによるテストデータの精度向上は可能性としてはありますが、今回のデータセットを用いた推定モデルとしては素直に重回帰モデルを用いた方が汎化性能が高いモデルを早く得られる事が判明しました。

まとめ

本記事では医療、介護現場などで良く利用される膝高と年齢から身長を推定する式を重回帰モデルとLightGBM回帰モデルにより導きました。結果として重回帰モデルの方が汎化性能が高くなりましたが、理由として

  • 説明変数が膝高と年齢と非常に少なかった事
  • 重回帰モデルの切片、回帰係数の導出にほぼ一意的に解けてしまう最小二乗法を使用

が挙げられます。一方、LightGBM回帰モデルで説明変数が少ない場合には過学習を抑える別の方法として、num_leavesやmax_depthなどを制限してツリー構造を単純化することが考えられます。また今回は外挿による影響を調べませんでしたが機会があればチャレンジしたいと思います。

Rehabではエンジニアの採用募集してます!

脚注
  1. Estimating stature from knee height for persons 60 to 90 years of age. J Am Geriatr Soc 33:116, 1985 ↩︎

  2. Knee-Height 法の方法と問題点, 臨床栄養107巻4号 411-416, 2005 ↩︎

Discussion