Zwift라는 게임이 있습니다. 일반 로라나 스마트 로라에 자전거를 올려서 페달을 밟으면 전 세계인들과 경쟁하게 되는 온라인 자전거 게임입니다. 작년부터 큰 인기를 끌고 있어서 한동안 와후 키커라는 비싼 스마트 로라를 사서 꽤 즐겁게 즐겼었는데요, 로라를 집안 사정으로 팔아버리게 되면서 마땅히 Zwift를 즐길 방법이 없어졌습니다.
집안을 둘러봐도 그나마 제일 비슷하게 생긴게 아내가 다이어트를 위해 얻어온 헬스 자전거, 숀리 엑스 바이크라고 하는 이름을 가진 십몇만원 한다는 실내용 자전거입니다. 하지만, 아무런 센서류가 없기 때문에 이대로는 Zwift 플레이가 불가능합니다.
때마침, 올해 시즌에 쓰려고 새로 산 페달형 파워미터인 가민 벡터2가 도착했습니다. 파워값만 제대로 나오면 Zwift 플레이에 아무 문제가 없다는데 착안하여 일단 이걸 끼워서 Zwift를 돌려봤습니다. 잘 됩니다. 싸이클에 비해 페달이 너무 앞쪽에 있어 힘 싣기 어렵고 그런 어려움들은 좀 있지만, 어쨌든 Zwift를 즐길 수 있습니다. ㅋㅋㅋ
스마트 로라와 같이 지형에 따라 자동으로 부하를 넣었다 뺐다 하지는 못하지만, 까짓거 손으로 조절하면 됩니다. 업힐 만나면 단수를 올려주고, 평지 만나면 내려주고 등등... ^^
전체적으로 할만 합니다만.... 십몇만원짜리 자전거에 백몇만원짜리 파워미터라니 뭔가 너무 언밸런스합니다.
그래서, 여러가지 방법을 생각을 해봤는데요, 자전거 센서들 중에 아무래도 가장 싼 축에 속하는 케이던스 센서나 속도 센서를 이용해서 파워값을 연산해서 얻어내어 Zwift 클라이언트에 쏴주면 파워미터가 없어도 별 상관 없이 게임을 즐길 수 있을 것 같습니다. 실제로도 Zwift에서도 인증 받은 로라나 트레이너 기기들은 속도 센서만 붙여도 vPower라고 하는 가상 파워 계산 루틴을 통해 게임을 즐길 수 있게 해줍니다.
물론 비싸거나 유명한 기종들만 대상이고, 이런 숀리 엑스 바이크 같은 변방의 헬스 바이크를 등록해줄리는 만무합니다.
그래서, 일단 이 실내 자전거의 부하 그래프를 얻어보기로 했습니다. 속도 센서와 파워미터를 모두 자전거에 달고 열심히 페달을 밟아 (속도 : 파워) 데이타를 얻어냅니다. 아주 느린 속도에서부터 아주 빠른 속도까지 대략 10초 정도 간격으로 LAP을 나눠가며 자전거를 돌립니다. 가장 많이 쓰이는 파워 구간인 100W~400W 까지 모두 고르게 나오도록 하려면 자전거의 부하 스위치를 8에 두어야 하네요. (10단 중 8단이니 꽤 힘듭니다만, 원래 자전거는 고통으로 타는겁니다^^)
데이타를 정렬해서 그래프로 그려봅니다. 가로가 속도(mi/h), 세로가 파워(W)입니다. 부하 그래프를 구할 때에는 이상하게들 km단위가 아닌 마일 단위를 쓰네요. 그래서 저도 역시 한번 그렇게 해봅니다.
그래프의 모양새는 아주 정밀하지는 않지만 어느 정도 괜찮은 부하 그래프를 얻을 수 있을 것처럼 생겼습니다.
요즘 딥 러닝이 핫하다고 하니 한창 이야기들이 많은 Tensorflow를 써서 간단하게 선형 회귀 분석을 해봅니다. 아래의 식을 이용합니다. x가 속도, y는 파워.
y = a*x + W
분석하니 아래와 같은 값들이 나옵니다.
W = -88.4703598, a = 13.56646538, LOSS=594.065
그래프로 그려보면 아래와 같습니다. 괜찮네요, Tensorflow.
사실, 위의 그래프 정도만 해도 쓸만은 한데, 한가지 눈에 띄는 점은 bias 값이 지나치게 크다는 겁니다. (여기서는 W) 이대로 쓰면 페달을 돌리지 않아도 마이너스 파워가 나오는 결과를 초래하게 됩니다.
그래서, 0점의 데이타 (0mi/h : 0W) 를 추가해서 그래프가 0점을 지나도록 해줘서 어떤 형태의 그래프를 얻어야 하는지 한번 살펴봅니다. 대략 아래와 같은 모양이 나와야 할 것 갈습니다.
가만 보니 뭔가 지수함수적으로 생겼으니 수식을 아래와 같이 바꿔줍니다.
y = W + a*x + b*x^2 + c*x^3
Loss 함수를 그래프의 값과 실제의 값과의 차이분들을 모아 그 면적을 구하도록 해서 해당 값을 손실 값으로 간주하도록 하고, 이 손실이 최대한 적은 쪽으로 학습을 하도록 합니다. 아주 세밀세밀하게...
소스는 아래와 같습니다.
# -*- coding: utf-8 -*- import numpy as np import math import matplotlib.pyplot as plt import tensorflow as tf
num_points = 1000
# 비교를 위한 랜덤 테스트 데이타 그냥 만들어 둠. vectors_set2 = [] for i in xrange(num_points): speed = np.random.random()*30.0 # mi/h power = 0.0 + speed*8.76 + speed*speed*0.006 + speed*speed*speed*0.040 # W vectors_set2.append([speed, power])
speed_data2 = [v[0] for v in vectors_set2] power_data2 = [v[1] for v in vectors_set2]
# 스트라바에서 가져온 실제 속도vs파워 데이타들. # 형식은 [ speed , power ]
# from activity 870293015 vectors_set = [ [35.5/1.6, 258], [48.1/1.6, 345], [45.9/1.6, 369], [41.2/1.6, 223], [33.1/1.6, 210], [37.6/1.6, 222], [44.1/1.6, 343], [39.1/1.6, 255], [28.9/1.6, 139], [29.6/1.6, 156], [30.8/1.6, 178], [36.6/1.6, 234], [41.4/1.6, 275], [49.2/1.6, 323], [35.6/1.6, 196], [36.4/1.6, 197], [37.5/1.6, 200], [34.0/1.6, 191], [33.5/1.6, 191], # 아무래도 0,0 값은 꼭 있어야 bias 계산을 제대로 할 듯 해서 몇개 추가! [0,0], [0,0], [0,0], [0,0], [0,0], [0,0], [0,0], [0,0],
speed_data = [v[0] for v in vectors_set] power_data = [v[1] for v in vectors_set]
# 실 데이타만 일단 그림 plt.plot(speed_data, power_data, 'ro') plt.xlabel('Speed(mi/h)') plt.ylabel('Power(W)') plt.show()
# 파워커브의 식은 보통 이렇다. (y=파워, x=속도) # y = W + a*x + b*x^2 + c*x^3 W = tf.Variable(tf.random_uniform([1], 0, 0.0001)) a = tf.Variable(tf.random_uniform([1], 0, 0.0001)) b = tf.Variable(tf.random_uniform([1], 0, 0.0001)) c = tf.Variable(tf.random_uniform([1], 0, 0.0001)) y = W + a*speed_data + b*speed_data*speed_data + c*speed_data*speed_data*speed_data
# 이걸 최소로 줄이는 방향으로 트레이닝 시키자!!! loss = tf.reduce_mean(tf.square(y-power_data))