2019 年度研究助成報告書

高齢女性の下腿のデジタル画像による低骨格筋肉量判定の可能性

1度，下肢筋力の指標として膝伸展筋力，上肢筋力の指標として握力を調査した。
2）SMI
SMI の計測は，BIA 法（Bioelectrical Impedance Anal-

中口拓真（PT）1），石本泰星（PT）2），桑田一記（PT）1），
1）

1）

1）

福本祐真（PT） ，田津原佑介（PT） ，近藤義剛（PT）

ysis：以下，BIA 法）で計測した。計測は排尿後，運動前に統一し，計測時間は午前 9 〜 11 時の間に実施した。
SMI の算出法は，四肢骨格筋量を身長の二乗で除した

1）

貴志川リハビリテーション病院

値を SMI と定義した。AWGS における女性のサルコペ

2）

2

赤ひげクリニック

ニアの基準である 5.7 kg/m 未満を低 SMI 群，それ以外を正常群とした。

キーワード：高齢者，骨格筋量，機械学習

3）下腿のデジタル画像

はじめに画像とし，下肢に整形外科的疾患を有さない場合や両側下肢に整形外科的疾患を有する場合は健側下腿外側の高齢期にみられる骨格筋量，筋力，身体機能の低下は下肢に整形外科的疾患を有する場合は，膝伸展筋力の強サルコペニアと呼ばれ 1），骨折リスクの増加 2）3），心疾い側を撮影した。対象者は裸足となり，膝関節から遠位

4）

5），生活の質の皮膚が観察できるように衣服を調整した。次に，対象の低下 7）および生命予後 8）との関連が報告されている。

者は昇降台上で端座位をとり，足底が床に接触した状態

また，高齢者においては，外科的手術の前にサルコペニで下腿が床面に対し垂直となるように座面を調整した。

患，呼吸器疾患 ，日常生活能力の低下

6）

アの評価をすることが重要であり後の合併症を予測する

9），サルコペニアは術10）。

撮影者は対象者の非撮影側下肢ができる限りカメラに映らないように，膝屈曲位で保持させた。撮影時の注意点

サルコペニアの診断においてアジア作業グループ

として，カメラレンズの中央が撮影側下腿の腓骨頭と外1）（Asian working group for sarcopenia；以下，AWGS）

果の中央で撮影側の腓骨と垂直になるように目視で調整

は地域在住高齢者のためのサルコペニア診断基準を発表

し，下腿とカメラのレンズとの距離は 50 cm として非しており，握力や歩行速度と併せて骨格筋指数（Skeletal

伸縮性のメジャーで距離を計測した。デジタルカメラは

Muscle Index；以下，SMI）を計測することでサルコペ三脚に固定し三脚に取り付けられた水準器で水平を確認

ニア診断することを推奨している。SMI の計測にはコした後に撮影をした。また，デジタル画像を撮影する際，生体電気インピーダンス法 12），は，窓のブラインドを下ろし，外の光が入らないようになどが存在する。これらは，特殊な機器を必要とし，運した。撮影場所については，リハビリテーション室内で搬においても労力を有することから，実際の臨床現場に実施し，下腿以外の周辺環境が撮影者や対象者によっておいて広く用いられているとは言い難い。下腿周径によ異ならないようにすべて同じ環境で撮影した。撮影にはるスクリーニング検査も報告されているが，人が接触すデジタルカメラを使用した。

ンピュータ断層撮影

11）

る検査法であるため，新型コロナウイルスなどの感染リスクが存在するため，非接触型の検査法が望ましい。そのため，本研究では，下腿の画像を使用して，低 SMIを予測できるか予備的に検証した。

3．データ解析
すべての対象者属性データと SMI について平均値と標準偏差を調査した。低 SMI 判定についてのディープ
ラーニングフレームワークは，先行研究でも使用してい対象および方法た Residual Network（以下，ResNet）とした。ResNet

1．対象の出力層は正常群，低 SMI 群と 2 値化した。最適化手対象は，2020 年 1 月 1 日〜 6 月 30 日まで貴志川リハ

法は確率的勾配降下法，バッチサイズは 32 とした。活ビリテーション病院の回復期リハビリテーション病棟に性化関数には Rectiﬁed Linear 関数，損失関数はクロス入院していた 65 歳以上の女性とした。除外基準は，神エントロピーとした。

経学的疾患を有する者，下腿に圧痕を残す浮腫のある
2

すべてのデータを教師データと検証用データ，独立者，BMI 25 kg/m 以上（肥満）である者，立位保持がしたテストデータにランダムに分け，テストデータを不可能な者とした。本研究は，貴志川リハビリテーショ

30% とした。教師データの学習回数は 10 回として予測ン病院の倫理委員会の承認（承認番号：4）を得ている。

モデルを作成し，テストデータの予測モデルにおける C統計量，感度，特異度を求めた。解析にはプログラミン

2．調査・測定項目

グ言語 Python の PyTorch を使用し，C 統計量，感度，1）対象者属性

特異度は統計ソフト R 4.0 を使用した。本研究で用いた対象者の属性として，年齢，身長，体重，BMI，疾患名を診療録から調査した。歩行能力の指標として歩行速解析コードを Appendix にて公開する。

2

2019 年度研究助成報告書

表1
項目

対象者属性
正常群（n=26）

低 SMI 群（n=26）

年齢（year）

75（10.6）80（6.8）

身長（cm）

153.3（0.6）

146.3（0.6）

体重（kg）

53.5（4.1）

42.4（5.27）

BMI（kg/m ）22.8（1.4）

19.6（2.1）

SMI（kg/m2）

5.78（0.3）

4.76（0.4）2

歩行速度（m/sec）

1.07（0.39）

0.84（0.34）

膝伸展筋力（kgf/weight）

0.33（0.09）

0.31（0.07）

握力（kg）

18.3（5.97）

14.7（7.1）

平均値（標準偏差）BMI：Body Mass Index
SMI：Skeletal Muscle Index

結果析した時点ですぐに予測として使用できない点である。

研究期間内に 52 名（正常群 26 名，低 SMI 群 26 名）

この点については，ウェブアプリケーションなどを開発の同意が得られた。対象者の基本属性を表 1 に示す。デすることで容易に使用可能になると考える。2 つめとしジタル画像による低 SMI を判定するテストデータの低て，本研究における対象者は入院中の運動器疾患患者で

SMI の判定精度［推定値（95％信頼区間）］は，C 統計量 0.91（0.83‒1.00），感度 0.93（0.70‒0.97），特異度 0.90あり，今後は入院歴がない地域在住高齢者を対象とした（0.70‒0.97）であった。

下腿のデジタル画像と下腿周径における予測精度の比較研究の実施が必要であると考える。最後に，本研究ではは行えていないため，検証する必要がある。

考察
入 院 中 の 運 動 器 疾 患 を 有 す る 高 齢 女 性 を 対 象 に，結論

2

AWGS の低 SMI 基準である 5.7 kg/m 未満を低 SMI 群

本研究では，入院中の運動器疾患を有する高齢女性にとして，機械学習のアルゴリズムである ResNet により対し低 SMI の判定を下腿のデジタル画像を用いて行っ下腿の画像を用いた予測精度の判定を行った。その結た。その結果，低 SMI を高い精度で判定できる可能性

果，C 統計量，感度，特異度は高くスクリーニング検査を示した。しかし，サンプルサイズが小さいことや理想として使用できる可能性を示した。

条件のみでの検証であり，解釈には注意が必要である。

機械学習の予測モデルに関して，教師データでは精度が高く検証用データの精度を低下させる過学習が懸念さ文れている。本研究ではサンプルサイズが小さいため過学

1）Chen LK, Woo J, et al.: Asian Working Group for
Sarcopenia: 2019 Consensus Update on Sarcopenia Diagnosis
and Treatment. J Am Med Dir Assoc. 2020; 21: 300‒307.
2）Bischoﬀ-Ferrari HA, Orav JE, et al.: Comparative performance of current definitions of sarcopenia against theprospective incidence of falls among community-dwelling
seniors age 65 and older. Osteoporos Int. 2015; 26: 2793‒
2802.
3）Schaap LA, Schoor MN, et al.: Associations of Sarcopenia
Definitions, and Their Components, With the Incidenceof Recurrent Falling and Fractures: The LongitudinalAging Study Amsterdam. J Gerontol A Biol Sci Med Sci.
2018; 10: 1199‒1204.
4）Bahat G, IIhan B, et al.: Sarcopenia and the cardiometabolic syndrome: A narrative review. Eur Geriatr
Med. 2016; 7: 220‒223.
5）Bone AE, Hepgul N, et al.: Sarcopenia and frailty inchronic respiratory disease. Chron Respir Dis. 2017; 14:85‒99.
6）Malmstrom TK, Miller DK, et al.: SARC-F: a symptomscore to predict persons with sarcopenia at risk for poor
functional outcomes. J Cachexia Sarcopenia Muscle. 2016;
7: 28‒36.

習が発生しやすく，結果の解釈は慎重に行う必要がある。
また，先行研究13）14）

では，SMI ではなくサルコペニアを予測している。そのため，サルコペニア診断基準である歩行速度と握力を調査した。対象者属性において，AWGS では，0.8 m/sec 未満がサルコペニアを診断する基準のひとつであり，本研究では正常群と低 SMI群の両方で AWGS の基準を上回っていた。また，握力に関しては AWGS の診断基準は，18 kg 未満である。
本研究では，正常群が 18.3 kg と AWGS の基準をわずかに上回っているが，両群とも握力が低下した対象者であった。膝関節伸展筋力に関しては，両群ともに低値であった。本結果は上記のような身体機能を有する入院中の女性運動器疾患患者に適応できるものであるため，その他への一般化は現状困難である。よって今後は様々な疾患や地域在住高齢者を対象とした調査が望まれる。
本研究では，いくつかの限界がある。機械学習のアルゴリズムを使用した結果であるため，回帰式のように解献2019 年度研究助成報告書

7）Beaudart C, Emmanuel B, et al.: Validation of the SarQoL,
a speciﬁc health-related quality of life questionnaire forSarcopenia. J Cachexia Sarcopenia Muscle. 2017; 8: 238‒
244.
8）Stefanie L, Mirko P, et al.: Validation of the FNIH
sarcopenia criteria and SOF frailty index as predictorsof long-term mortality in ambulatory older men. AgeAgeing. 2016; 45: 602‒608.
9）Riccardo A, Barbara Le: When Reporting on OlderPatients With Cancer, Frailty Information Is Needed.
Ann Surg Oncol. 2011; 18: 4‒5.
10）Vicente V, Neda A, et al.: Sarcopenia Adversely Impacts
Postoperative Complications Following Resection orTransplantation in Patients With Primary Liver Tumors.
J Gastrointest Surg. 2015; 19: 272‒281.
11）Goodpaster BH, Kelley DE, et al.: Skeletal muscle
attenuation determined by computed tomography is
associated with skeletal muscle lipid content. J ApplPhysiol. 2000; 1: 104‒110.

12）Giuseppe S, Marina D, et al.: Measurement of leanbody mass using bioelectrical impedance analysis: aconsideration of the pros and cons. Aging Clin Exp Res.
2017; 29: 591‒597.
13）Yang M, Xiaoyi H, et al.: SARC-F for Sarcopenia
Screening in Community-Dwelling Older Adults: Are 3
Items Enough? Medicine. 2018; 9730: 11726.
14）Yang M, Xiaoyi H, et al.: Screening Sarcopenia in
Community-Dwelling Older Adults: SARC-F vs SARC-F
Combined With Calf Circumference (SARC-CalF). J Am
Med Dir Assoc. 2018; 19: 277.e1‒277.e8.

発表実績【原著論文】
1）中口拓真，石本泰星，他：高齢女性の下腿のデジタル画像による低骨格筋肉量判定の可能性― Convolutional Neural
Network とエッジ検出を用いた分類による予備的研究―．
理学療法学．2021; 48(3): 279‒286.

Appendix
import os
import time
import copy
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, random̲split
from torchvision import datasets, models, transforms
import optuna
import matplotlib.pyplot as plt
from cnn̲ﬁnetune import make̲model
import csv
import calc̲img
device = torch.device("cuda:0" if torch.cuda.is̲available() else "cpu")
def plot̲result(phase,x,y̲tloss,y̲tacc,y̲eloss,y̲eacc):
plt.clf()
plt.subplot(211)
plt.plot(x,y̲tloss,"-",color="b",label='Train')plt.plot(x,y̲eloss,"-",color="r",label='val')
plt.title("Loss")
plt.legend()
plt.subplot(212)
plt.plot(x,y̲tacc,"-",color="b",label='Train')plt.plot(x,y̲eacc,"-",color="r",label='val')
plt.title("Accuracy")plt.legend()
plt.tight̲layout()
plt.draw()
returndef prepare̲dataset(dataset, val̲split=0.3):
print(dataset.class̲to̲idx)
dataset̲size = len(dataset)train̲size = int(dataset̲size * 0.6)val̲size = int(dataset̲size * 0.2)
test̲size = dataset̲size - train̲size - val̲size
print('train size: ' + str(train̲size))print('val size: ' + str(val̲size))3

42019 年度研究助成報告書

print('test size: ' + str(test̲size))train, val, test = random̲split(dataset, [train̲size, val̲size, test̲size])change̲list = [transforms.RandomHorizontalFlip(p=0.5),transforms.RandomVerticalFlip(p=0.5)]calc̲mean̲std̲class = calc̲img.calc̲mean̲std()mean, std = calc̲mean̲std̲class.calc(train, BATCH̲SIZE, NUM̲WORKER)
if not GRAYSCALE:
print('input images are RGB !')data̲transforms = {'train': transforms.Compose([transforms.RandomChoice(change̲list),transforms.ToTensor(),transforms.RandomErasing(p=0.3,scale=(0.02,0.33),ratio=(0.3,3.3)),transforms.Normalize(mean,std)]),'val': transforms.Compose([transforms.ToTensor(),transforms.Normalize(mean,std)]),'test':transforms.Compose([transforms.ToTensor(),transforms.Normalize(mean, std)])}
else:
print('input images are GrayScale !')data̲transforms = {'train': transforms.Compose([transforms.Grayscale(num̲output̲channels=3),transforms.RandomChoice(change̲list),transforms.ToTensor(),transforms.RandomErasing(p=0.3,scale=(0.02,0.33),ratio=(0.3,3.3)),transforms.Normalize(mean,std)]),'val': transforms.Compose([transforms.Grayscale(num̲output̲channels=3),transforms.ToTensor(),transforms.Normalize(mean,std)]),'test':transforms.Compose([transforms.Grayscale(num̲output̲channels=3),transforms.ToTensor(),transforms.Normalize(mean, std)])}
train.dataset.transform = data̲transforms['train']val.dataset.transform = data̲transforms['val']
test.dataset.transform = data̲transforms['test']
return {'train':train, 'val':val, 'test':test}
def create̲dataloader(image̲datasets):
train̲dataset̲loader = DataLoader(image̲datasets['train'], batch̲size=BATCH̲SIZE,
shuﬄe=True, num̲workers=NUM̲WORKER)
valid̲dataset̲loader = DataLoader(image̲datasets['val'], batch̲size=BATCH̲SIZE,
shuﬄe=True, num̲workers=NUM̲WORKER)
test̲dataset̲loader = DataLoader(image̲datasets['test'], batch̲size=BATCH̲SIZE,
shuﬄe=False, num̲workers=NUM̲WORKER)
dataloaders̲comb = {"train": train̲dataset̲loader,"val": valid̲dataset̲loader,2019 年度研究助成報告書"test": test̲dataset̲loader,}return dataloaders̲comb
def train̲model(model, criterion, optimizer, num̲trials, num̲epochs=25):
since = time.time()best̲model̲wts = copy.deepcopy(model.state̲dict())
best̲acc = 0.0
x=list(range(num̲epochs))y̲tloss=[]; y̲tacc=[]y̲eloss=[]; y̲eacc=[]for epoch in range(num̲epochs):
print('Epoch {}/{}'.format(epoch + 1, num̲epochs))
print('-' * 10)for phase in ['train', 'val']:if phase == 'train':model.train()else:
model.eval()running̲loss = 0.0
running̲corrects = 0
for inputs, labels in dataloaders[phase]:
inputs = inputs.to(device)labels = labels.to(device)
optimizer.zero̲grad()# forward
with torch.set̲grad̲enabled(phase == 'train'):outputs = model(inputs)
̲, preds = torch.max(outputs, 1)
loss = criterion(outputs, labels)# backward + optimize only if in training phase
if phase == 'train':loss.backward()
optimizer.step()running̲loss += loss.item() * inputs.size(0)running̲corrects += torch.sum(preds == labels.data)epoch̲loss = running̲loss / dataset̲sizes[phase]
epoch̲acc = running̲corrects.double() / dataset̲sizes[phase]print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch̲loss, epoch̲acc))
if phase == 'train':y̲tloss.append(epoch̲loss)y̲tacc.append(epoch̲acc)else:
y̲eloss.append(epoch̲loss)y̲eacc.append(epoch̲acc)
if epoch == num̲epochs-1:
ﬁg, axes = plt.subplots(2,1)
plot̲result(phase,x[:epoch+1],y̲tloss,y̲tacc,y̲eloss,y̲eacc)
ﬁg.saveﬁg("res"+str(num̲trials)+".png")
if epoch̲acc > best̲acc:
best̲acc = epoch̲acc
best̲model̲wts = copy.deepcopy(model.state̲dict())
time̲elapsed = time.time() - since
print('Training completed in {:.0f}m {:.0f}s'.format(time̲elapsed // 60, time̲elapsed % 60))
print('Best val Acc: {:4f}'.format(best̲acc))

5

62019 年度研究助成報告書

model.load̲state̲dict(best̲model̲wts)
return model, best̲acc, ﬁg
def test̲model(model):print("=" * 10)model.eval() # Set model to evaluate moderunning̲corrects = 0
for i, (inputs, labels) in enumerate(dataloaders['test'],0):
inputs = inputs.to(device)labels = labels.to(device)outputs = model(inputs)
̲, preds = torch.max(outputs, 1)
running̲corrects += torch.sum(preds == labels.data)with open('result.csv', 'w', newline='') as f:writer = csv.writer(f)writer.writerow(['y','pred'])for i in range(len(preds)):
writer.writerow([str(labels.data[i].item()),str(preds[i].item())])
epoch̲acc = running̲corrects.double() / len(dataloaders['test'].dataset)
print('Testing completed')
print('Acc: {:.4f}'.format(epoch̲acc))
returndef objective(trial):
lr =trial.suggest̲loguniform('lr', 1e-6, 1e-4)
beta1 =trial.suggest̲uniform('beta1', 0.8, 0.95)
beta2 =trial.suggest̲uniform('beta2', 0.9, 0.99)
eps =trial.suggest̲loguniform('eps', 1e-9,1e-7)
model̲ft = make̲model('resnet18', num̲classes = NUM̲CLASSES, pretrained=True, input̲size = (512,512))
model̲ft = model̲ft.to(device)
criterion = nn.CrossEntropyLoss()
optimizer̲ft = optim.Adam(model̲ft.parameters(),
lr=lr, betas=(beta1, beta2), eps=eps, weight̲decay=0, amsgrad=False)model̲ft, best̲acc, ﬁg = train̲model(model̲ft, criterion, optimizer̲ft, trial.number, num̲epochs=NUM̲EPOCHS)
trial.set̲user̲attr(key="booster", value=model̲ft)
return best̲acc
def callback(study, trial):
if study.best̲trial.number == trial.number:study.set̲user̲attr(key="best̲booster", value=trial.user̲attrs["booster"])
returndata̲dir = './'
BATCH̲SIZE = 32
NUM̲WORKER = 4
NUM̲CLASSES = 2 # クラス数
NUM̲EPOCHS = 10 # エポック
NUM̲TRIALS = 10 # 試行数
GRAYSCALE = False # grayscale -> True
image̲datasets = prepare̲dataset(datasets.ImageFolder(os.path.join(data̲dir, 'dataset')))dataset̲sizes = {x: len(image̲datasets[x]) for x in ['train', 'val', 'test']}dataloaders = create̲dataloader(image̲datasets)
study = optuna.create̲study(direction='maximize')ﬁnetuned̲model = study.optimize(objective, n̲trials=NUM̲TRIALS, callbacks=[callback])
best̲ft̲model = study.user̲attrs["best̲booster"]print('Testing the best model ...')
test̲model(best̲ft̲model)
torch.save(best̲ft̲model.state̲dict(), 'best̲ﬁnetune̲ResNet18.model')