친구의 IRONMAN 70.3 목표 시간 달성을 위해, 기존 레이스 기록 79개를 예측하는 오픈 모델을 만들었습니다.
요약
사용자의 훈련 데이터를 기반으로 IRONMAN 70.3 레이스 기록을 예측하는 오픈 모델 'Race Card'를 개발했습니다. 이 시스템은 Strava/Garmin 데이터를 받아 6,000일의 시뮬레이션을 실행하고, 목표 시간 달성 가능성을 코스별로 분석합니다. 이를 통해 단순히 어려운 코스를 지적하기보다 구체적인 개선 포인트를 제시할 수 있습니다.
핵심 포인트
- 훈련 데이터 기반으로 70.3 레이스 기록 예측 모델 개발
- TabPFN을 활용하여 예상 범위 및 시뮬레이션 실행
- 코스별 목표 시간 달성 확률과 주요 개선점을 정량적으로 분석
이 글은 Hacktoberfest Weekend Challenge: Build for a Friend에 제출하는 내용입니다.
제 친구 Sam은 IRONMAN 70.3에서 5시간 이내로 완주하고 싶어 합니다. 아직 어느 코스인지 결정하지 못해서, 이것이 상당히 중요한 세부 사항입니다.
70.3는 수영 1.9km, 사이클 90km, 하프 마라톤으로 구성됩니다. 거리는 변하지 않지만, 레이스를 치르는 장소는 다릅니다. Weymouth의 자전거 코스는 약 1,043미터를 오르막으로 가집니다. Westfriesland는 55미터가 오르막입니다. 어느 곳을 선택하는 것이 도움이 될 수 있는지 알 수 있습니다.
저는 Weymouth에서 세 번 레이스를 치른 적이 있어 의견이 부족하지 않았습니다. 하지만 그 조언을 건네기 전에, 몇 가지 숫자를 얻어보는 것이 가치가 있을 것 같다고 생각했습니다.
제가 만든 것 (What I Built)
Race Card는 사용자의 훈련 데이터를 활용하여 어떤 70.3이 목표 시간 달성에 가장 좋은 기회를 제공하는지 계산해 줍니다.
Strava 또는 Garmin에서 내보낸(export) 데이터를 주면, 각 종목별 예상 범위(range)를 예측하고, 8개의 실제 코스를 기반으로 6,000일의 시뮬레이션 레이스 일정을 실행합니다. 이를 통해 목표 시간보다 낮은 기록을 달성할 기회를 각각 알 수 있을 뿐만 아니라, 어떤 종목을 개선하면 가장 큰 차이를 만들지 파악할 수도 있습니다.
모든 것은 노트북에서 실행됩니다. Prior Labs의 TabPFN이 예측을 담당하며, Google의 Gemma 4가 Ollama를 통해 로그를 읽고 계획을 작성합니다. 둘 다 오픈 모델입니다. 사용자가 기록한 데이터는 기기를 벗어나지 않습니다.
Sam의 훈련은 Sam의 것이므로, 저는 먼저 제 데이터를 가지고 테스트했습니다. 만약 제가 내일 레이스를 치른다면, 이 시스템은 다음과 같이 예측합니다:
- Erkner, La Quinta, Türkiye, Westfriesland (모두 평지): 약 4시간 48분, 5시간 미만 달성 확률 85~86%.
- Weymouth: 5시간 6분, 달성 확률 31%.
- Swansea, 가장 언덕이 많은 곳: 5시간 8분, 달성 확률 24%.
거의 모든 격차는 사이클 종목에 있습니다. Weymouth에서는 자전거가 3.4% 빨라지거나 달리기가 6.5% 빨라진다면, 5시간 미만 달성이 동전 던지기(coin flip) 수준이 된다고 예측합니다. 이는 단순히 'Weymouth가 어렵다'고 말하는 것보다 Sam과 나눌 훨씬 유용한 대화 주제입니다. 그는 이미 자신의 훈련 데이터로 레이스를 치러봤습니다. 더 자세한 내용은 아래에서 다룹니다.
데모 (Demo)
실시간: iamrobertmoore.github.io/race-card
이것은 80초짜리 투어이며, 제가 하나하나 설명해 드릴게요:
몇 가지 재미있게 만져볼 수 있는 것들이 있습니다:
- 드롭다운에서 코스를 선택하거나, '모든 코스 순위'의 아무 행이나 클릭해 보세요. 링과 결승 시간이 해당 코스로 애니메이션됩니다.
- Weymouth에서 자전거 슬라이더를 드래그해 보세요. 속도를 3% 올리면 확률이 31%에서 47%로 올라가고, 자전거 시간은 5분 감소합니다.
- 'Checked before trusted(신뢰하기 전에 확인)'까지 스크롤하여 TabPFN이 동일한 레이스에서 네 가지 더 단순한 추측 모델과 어떻게 비교되었는지 확인해 보세요.
- '모든 79개 구간 보기'를 열어 과거에 예측된 모든 레이스 구간을 살펴보고, 얼마나 벗어났었는지도 확인해 보세요.
이 라이브 페이지는 제가 학습시킨 것을 사용합니다. 의도적으로 업로드 상자가 없는데, 만약 본인의 데이터를 사용하고 싶다면 아래 코드를 사용하여 본인 기기에서 실행해야 합니다.
코드
GitHub logo iamrobertmoore / race-card
어떤 IRONMAN 70.3이 목표 시간 달성에 가장 적합할까요? TabPFN + Gemma 4를 본인 노트북에서 Strava 내보내기 데이터로 사용해 보세요.
Race Card
어떤 IRONMAN 70.3이 목표 시간 달성에 가장 적합할까요? Race Card는 사용자의 자체 트레이닝 내보내기 데이터를 읽고, 레이스 당일의 모든 구간을 범위로 예측하며, 목표 시간을 밑돌 확률에 따라 실제 70.3 코스 8개를 순위 매깁니다. 그런 다음 그중 어떤 구간이 이 확률을 가장 많이 움직이는지 보여줍니다.
이것은 두 개의 열린 모델과 함께 사용자의 노트북에서 실행되며, 기록한 데이터는 어디에도 업로드되지 않습니다.
자체 Strava 내보내기로 구축된 라이브 데모: https://iamrobertmoore.github.io/race-card/
저는 70.3에서 5시간 미만을 목표로 하는 친구 Sam을 위해 이것을 만들었습니다. 그는 어떤 코스를 선택해야 할지 정하지 못했기 때문에, 제가 먼저 제 데이터로 테스트해 보았습니다. 이후 그는 자신의 내보내기 데이터를 가지고 자신의 노트북에서 실행했으며, 현재 자전거를 타고 있습니다.
제 데이터로 발견한 것들
- 만약 제가 내일 레이스를 한다면: 평지 코스에서는 약 4시간 47분 (86% 확률…
MIT 라이선스이며, 모델을 다운로드하지 않고도 테스트를 실행할 수 있습니다.
구축 과정 (How I Built It)
TabPFN이 예측을 수행합니다 (TabPFN does the predicting)
TabPFN은 테이블 기반 파운데이션 모델(tabular foundation model)입니다. 이 모델에 테이블을 제공하고, 사용자가 직접 트레이닝 루프를 설정할 필요 없이 특정 열을 예측하도록 요청할 수 있습니다. 각 종목별로 저는 세션당 한 행의 데이터를 입력했습니다: 거리, km당 등반 높이, 지난 날짜로부터 경과된 일수, 직전 7일 및 42일간의 훈련 부하, 그리고 그것이 레이스였는지 여부입니다. 이 모델이 예측하려고 하는 것은 속도(speed)입니다.
여기서 유용한 점은 단일 숫자가 아니라 분포(spread)를 제공한다는 것입니다. 저는 각 코스의 각 구간에 대해 예상되는 속도의 5번째 백분위수부터 95번째 백분위수까지의 범위를 사용하며, 이 페이지는 해당 범위에서 6,000개의 레이스 날짜를 추출합니다. 5시간 이내로 끝나는 비율이 '링(ring)'에 표시된 확률이 됩니다. 슬라이더를 드래그하면 브라우저가 그 시뮬레이션을 다시 실행합니다.
또한 빠릅니다. 4,484개 세션으로 구성된 전체 카드도 GPU 없이 CPU에서 약 20초가 걸립니다. 백테스트(backtest)는 몇 분 정도 더 소요됩니다.
신뢰하기 전에 확인해 보기 (Checking it before trusting it)
Sam에게 예측을 전달하기 전에, 이것이 얼마나 좋은지 알고 싶었습니다. 그래서 저는 레이스 날짜 전까지 기록했던 훈련 데이터만을 사용하여 과거 레이스를 예측하도록 만들었습니다. 그 이후에 무슨 일이 일어났는지 들여다보지 않았습니다.
79개 레이스 구간 중 62개가 모델의 80% 범위 안에 들어왔습니다. 이는 78%로, 정직한 80% 범위가 포착할 것이라고 기대하는 수준과 비슷합니다. 평균적으로 놓친 비율은 6.9%였으며, 저를 너무 빠르거나 너무 느리게 예측하려는 일관된 경향은 없었습니다.
그다음에는 아마도 가장 먼저 물어봤어야 할 질문을 했습니다: TabPFN이 실제로 단순한 방법보다 나은가? 저는 동일한 79개 구간을 예측하는 네 가지 다른 방법을 시도했으며, 모두 같은 기준(cut-off)을 사용했습니다:
| Same 79 legs | Typical miss |
|---|---|
| TabPFN | 6.9% |
| ... | |
| A win, but not a thrashing. TabPFN은 79개 중 48개 구간에서 나무(trees)보다 더 가까웠다. 또 다른 유용한 차이점은 이 나무들이 나에게 하나의 숫자를 주는 반면, TabPFN은 전체 페이지가 의존하는 범위(range)를 제공한다는 것이다. |
한 가지 실수는 비교적 꾸준히 저질렀다. 나는 2018년, 2019년, 그리고 2022년에 Weymouth 레이스에 참가했는데, TabPFN은 매번 내 자전거 기록을 너무 빠르게 예측했다. 7%에서 11% 정도 과대평가했었다. 코스가 얼마나 오르막이 있는지(climbs)는 알지만, Weymouth의 언덕과 바람이 실제로 나에게 어떤 영향을 미치는지까지는 모른다. 나는 이 경고를 페이지에 넣었다: 언덕 코스의 자전거 예측은 낙관적으로 간주하라. 물론 그렇게 한다.
Gemma가 로그를 읽다
모델이 레이스 노력이 무엇인지 학습하려면, 어떤 세션이 레이스였는지 알아야 한다. Strava는 그것을 기록하지 않으며, 4,574개의 활동(activities)에 수동으로 태그하는 것은 내가 이 작업의 일부로 만들고 싶지 않은 것이었다. Sam도 분명히 나에게 감사하지 않을 것이다.
이것이 Gemma 4의 역할이다. 이것은 노트북에서 Ollama를 통해 활동 제목을 읽고 레이스를 찾아낸다. 나는 이미 카드를 위해 내 목록을 수동으로 확인했기 때문에, 이 작업에 맞춰 점검할 것이 있었다. 세 번 만에 끝났다.
첫 번째 실행에는 47분이 걸렸고 약 3분의 2를 맞혔다. Gemma 4는 기본적으로 답변하기 전에 생각한다. 어려운 질문에는 유용하지만,
세 번째 실행에 이르러서는 생각하는 것을 멈추고, 앱이 작성한 제목을 건너뛰었으며, 같은 날의 다른 세션들을 컨텍스트로 제공했습니다. 또한 Sufferfest 세션, 브릭(bricks), 그리고 "오픈 워터 수영(Open Water Swim)"과 같이 계속해서 틀리는 것들도 명시해 주었습니다.
그 버전은 3분도 안 되는 시간에 1,005개의 제목을 읽어냈고, 제가 참가한 181개 레이스 중 166개를 찾아냈으며, 오경보(false alarms)는 6개였습니다. 저는 이 점수를 매긴 것과 동일한 목록에 프롬프트를 조정했기 때문에, 이것은 독립적인 테스트가 아닌 최상의 경우의 결과입니다. 여전히 Challenge Roth의 세 다리(three legs)를 모두 놓쳤는데, 이는 간과하기에는 상당히 큰 레이스입니다.
Gemma는 카드에 메모도 작성합니다. 이 메모는 Race Card가 이미 계산한 숫자들만 가져옵니다. 만약 그 사실들에 없는 숫자를 메모에 넣으면, 그 메모는 폐기되고 다시 시도합니다. 이것이 제 노트북에서 13초 만에 작성된 내용입니다:
로버트, 70.3 웨이마우스(Weymouth) 기억나? 서브 5를 달성할 확률은 31%로, 70.3 에르크너(Erkner)의 86%보다 낮아. 자전거 구간에서 3%만 줄일 수 있다면, 네 확률은 47%까지 올라갈 거야. 저 자전거 구간을 완벽하게 해내자!
코스는 실제입니다
등반 수치는 IRONMAN 자체의 코스 경로 또는 finishers.com에서 가져왔습니다. 저는 금요일에 확인했습니다. 만약 특정 실행의 등반 정보가 게시되어 있지 않다면, 페이지에는 그렇게 표시되며 임의로 꾸며내기보다는 평지(flat)로 취급합니다.
제가 처음에 틀렸던 점들
- Strava의 내보내기(export) 데이터는 "Distance"라는 이름의 두 개의 열을 가지고 있습니다. 첫 번째는 킬로미터(km) 단위이고, 수영 기록만 해당하면 쉼표가 포함된 미터(m) 단위입니다. 제 초기 파서(parser)는 2,192미터짜리 수영 기록을 2,192km로 잘못 처리했습니다. 정말 물속에서 정신없는 아침이었습니다.
- 한 레이스 수영 기록은 시속 6.4km로 나왔습니다. 이는 1,500미터 세계 기록 페이스보다 빠른 속도입니다. 유감스럽게도 이것은 GPS 오류였습니다. 이제는 상식적인 속도 범위 밖에 있는 모든 데이터는 모델이 볼 기회 전에 걸러집니다.
- 가장 최신 TabPFN은 다운로드를 하려면 계정과 승인된 라이선스를 요구합니다. 저에게는 괜찮지만, 제가 "그냥 실행해 봐, Sam"이라고 말하고 싶었던 의미는 아니었습니다. TabPFN v2의 가중치(weights)는 계정 없이 Hugging Face에서 다운로드되므로, 이 버전이 사용됩니다.
- Gemma가 남긴 첫 번째 메모에는 "안녕 Sam"이라는 문구와 함께 제 수치가 담겨 있었습니다. 제가 하지 않겠다고 말했던 바로 그 행동입니다. 이제는 해당 수치가 누구의 훈련을 기반으로 하는지 명시적으로 알려주며, 페이지에 붙은 메모는 저에게 주소지어져 있습니다.
- 메모에는 48%라고 적혀 있었는데 슬라이더에는 47%였습니다. 두 번의 시뮬레이션, 서로 다른 무작위 추출(random draws)을 통해 나온 결과였고, 그 값은 47.5% 근처에 머물렀습니다. 이제는 동일한 추출값을 사용하며 백분율에 대해 동의합니다.
- 제 첫 번째 디자인은 베이지색과 녹색이었고 연금 명세서처럼 보였습니다. 지금은 고리(rings)가 있는 어두운 색상입니다.
전달하기 (Handing it over)
저는 일요일 아침 Sam에게 링크를 보냈습니다. 그는 제 수치들을 살펴본 후, 자신의 내보내기 데이터와 자신의 노트북으로 직접 실행해 보았습니다. 그게 제가 실제로 이 데이터를 구축한 기준이었습니다. 그의 수치는 그의 기기에 남아 있었지만, 그는 저에게 어떤 내용이었는지 말해주었고 그것이 "매우 통찰력이 있다"고 했습니다.
Weymouth는 그에게 더욱 가능성이 낮아 보입니다: 5시간 이내 기록 달성 확률은 11%로, 제 것이었던 31%와 비교됩니다. 하지만 그는 아직 레이스를 바꾸지는 않고 있습니다. 자전거에 노력을 기울여 격차를 줄일 수 있는지 확인하고 싶어 합니다. 만약 계획대로 되지 않는다면, 그의 승률이 더 좋은 코스를 목표로 할 것입니다.
그것이 제가 그가 스스로 결정할 수 있기를 바랐던 부분이었습니다. 저는 그에게 Weymouth에서 포기하라고 말하려 한 것이 아니었습니다. 단지 그것을 선택하는 것이 그에게 어떤 비용을 요구하는지, 그리고 노력이 어디에 집중되어야 하는지를 알게 하고 싶었을 뿐입니다.
오픈 혁신이 중요한 이유
이것은 단순히 유용한 숫자표가 아니라 누군가의 훈련 기록이기 때문입니다.
Strava 내보내기 파일에는 집 문 앞에서 시작하는 심박수, GPS 트랙, 그리고 15년간의 아침 일과가 담겨 있습니다. 샘에게 레이스 선택을 도와주기 위해 이 모든 것을 서비스에 전송해달라고 부탁하는 것은 저에게 불편했습니다. 오픈 모델을 사용하면 그럴 필요가 없습니다. TabPFN과 Gemma 모두 노트북에서 실행되기 때문에 데이터가 그곳에 머무릅니다. 저는 샘의 파일을 본 적이 없습니다. 제가 아는 것은 그가 저에게 말하기로 선택한 숫자뿐입니다.
그는 계정, API 키 또는 구독도 필요하지 않습니다. TabPFN v2는 가입 없이 다운로드할 수 있으며, Gemma는 Ollama를 통해 오프라인으로 실행됩니다. 실행하는 데 비용이 들지 않으며, 그의 레이스 전에 가격을 올리거나 기능을 끌 수 있는 사람이 아무도 없습니다.
또한 내부를 볼 수 있다는 것은 제가 이를 제대로 테스트할 수 있었다는 의미였습니다. 저는 제 자신의 레이스에 대해 백테스팅(backtested)을 했고, 모델이 잘못 예측한 부분을 찾아 페이지에 기록했습니다. 이는 폐쇄형 API로는 훨씬 어렵습니다.
물론 트레이드오프(trade-off)가 있습니다. 최첨단 모델(frontier model)이라면 Gemma 4가 노트북에서 실행하는 것보다 더 많은 레이스를 더 빠르게 찾았을 것입니다. 로그를 읽는 것은 폐쇄형 모델이 이겼을 가능성이 높습니다. 하지만 저는 15년간의 활동 기록을 다른 사람의 서버로 전송해야 했을 것이고, Gemma는 그 어떤 것도 기기를 벗어나지 않게 하면서 181개 중 166개를 찾아냈습니다.
나의 에이전트 세션
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기