0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

生成 AI を、誰が使っても同じ結果に — ランニングのコーチをワークフローで固めた

0
Posted at

要点(3行)

  • AI に頼めば、たいていのことは便利にこなせる。
  • ただ、同じことを頼んでも、使い手しだいで返ってくる結果は変わってくる。
  • 誰がやってもいい結果になるよう整える——そのやり方を、ランニングのコーチづくりで実際に試した記録。

ランニングのコーチアプリを作るなら、いまはこう作るのが早い。ChatGPT に「あなたはランニングのコーチです。今日やる練習を組んでください」と頼んで、自分の練習記録を渡す。それらしいメニューが返ってくるし、使える人にはそれでいい。

ただ、使いこなすには、地味に手間がある。走るたびに、結果を貼り付け直す。返ってきた内容が正しいのか、もっともらしい間違い——ハルシネーションを疑って、別に裏を取る。プロンプトの書き方が甘いと、答えもぶれる。

これは、最近の仕事でもよく見る形だ。AI に慣れた人は、何気なく的確に指示して、いい結果を引き出す。でも慣れていない人だと、指示が甘かったり、返ってきたものを確かめる手が回らなかったりして、同じようには使えない。同じ道具でも、腕前で結果が変わってしまう。

だから最近よくやるのは、誰が使っても同じところに着地するように、ワークフローのほうを組んでおくことだ。プロンプトの巧さや、確かめる根気に、結果を預けない。生成 AI のうまみは活かしつつ、外れては困るところは、こちらで固めておく。

今回のランニングコーチも、その形で作ってみた。RunAdvisory という iOS アプリだ。

土台にしたのは、ダニエルの理論だ。『ダニエルズのランニング・フォーミュラ』——ランニングの練習強度を体系立てて決めるための、版を重ねて読まれてきた本で、その中心に VDOT という考え方がある。ざっくり言えば「今の走力を、一つの数字にしたもの」だ。この数字さえ決まれば、ゆっくり走るイージーはこのペース、速く刻むインターバルはこのペース、と練習ごとの適正な速さが芋づる式に出てくる。ただ、その VDOT を出すには、本来 5000m やレースを全力で走って測る必要がある。

RunAdvisory がやることは、単純だ。ふだん使っている練習記録アプリ(Strava)のデータから今の走力を読み取って、理論に沿って、今日やる練習を一つ返す。その全力走は、要らない。走るたびに最新の記録で測り直すから、走力が変われば、数字もそのぶん動く。

「今夜は 5:00 の組に入れ。今週インターバルが溜まってるから 1 個落として温存。終わったら流し 2km 足せ」

クラブはペース別の「組」に分かれて走る。今夜どの組に入り、今週の負荷を見て一本落とすか、締めに軽く流すか——今日ぶんが、この一言に畳まれている。表もプランも、理論も見せない。渡すのは、今日やること一つだけだ。作ったのは、まるごとぼく自身のためだった。全力走はできれば避けたいし、VDOT を測り直すのは続かない。理論は分かっていても、練習に落ちてこない——ぼく自身が、その手前で止まっていた。

肝心なのは、この「今日やること」を、AI に書かせて出していないことだ。ダニエルの理論を、検証できる決定的なロジックに固めて持つ。ペースの処方は、本の式と表のとおり。だから誰が使っても、同じ入力なら同じ答えが返るし、なぜその数字かも後からたどれる。本の外に出たのは一点だけ——ダニエルは「今の走力は分かっている」前提で始まるので、その VDOT を普段の記録から推定するところだけ、自前で組んだ。

ここからは、その「誰が使っても外れない」を、どう組んだかの話だ。理論をどう固めたか、固めたものをどう確かめたか、そして AI をどこに残したか。

AI に丸投げすると使う人の腕前で結果がぶれる。判断を検証できるロジックに固め、AI を言葉の読み取りだけに残すと、誰が使っても同じ結果になる。


理論を「表引き」ではなく「式」で解く

書籍には、VDOT ごとに「イージーはこのペース、インターバルはこのペース」という早見表が載っている。実装では、その表を引いて間を補間するのではなく、表のもとになった式——ダニエルの表を支える、Gilbert の酸素コスト式——を、そのまま解析式で置いた。

oxygenCost(v) = -4.60 + 0.182258·v + 0.000104·v²      // v = m/min(Gilbert 酸素コスト式)
percent(t)    = 0.8 + 0.1894393·e^(-0.012778·t)
                   + 0.2989558·e^(-0.1932605·t)        // t = 分(%VO2max の低下曲線)
VDOT          = oxygenCost / percent

表を引くやり方もある。ここで式にしたのは、どのペースも同じ 1 本の理屈から出したかったからだ。あとで「なぜこの数字なのか」を、原典までたどれる。

5000m を 20:00 で走る人なら、この式から VDOT は 49.8 と出る。E / M / T / I / R——イージー・マラソン・しきい・インターバル・リピティション、遅い側から速い側へ並ぶ 5 つのペースは、VDOT に係数(0.72〜1.05)をかけた値を、同じ式で速度へ解き直して求める。印刷版の表とも、ずれなく重なる。

土台がここまで合うと、逆に「合わないところ」がくっきり見えてくる。


固めたものを、原典と突き合わせて確かめる

式も処方も、本のとおりに実装してある。核はちゃんと動く。抜けるのは、その周りの細部だ。原典に書いてあるのに、まだコードに落ちきっていない条件が残る。この「書いてあるのに落ちている」を拾うのが、地味で、効くところだ。

そこで、変更の節目ごとに、複数の AI を点検役に立てて、実装を原典と突き合わせる。観点は 6 つに割ってある——VDOT の科学 / 各ペースの導出 / 強度の判定 / 負荷と時期配分 / メニューの照合 / 抜けの批評。それぞれが担当のコードを読み、原典の式・数値・章と照らしていく。

一度の監査で、原典との抜けや取りこぼしが 22 件挙がった。重いものが 5 件、中くらいが 10 件、軽いものが 7 件。

直す順番は、コードの綺麗さでは決めない。その抜けで、走る人の体に何が起きるかで決める。いちばん上に来たのは、暑さの補正が入っていなかったことだった。真夏の夜のクラブ練でも、涼しい日と同じペースを出してしまう。そのペースは速すぎて届かないし、無理に食らいつけば故障につながる。だから、理屈として大きい抜けよりも、体にこたえる抜けを先に直す。

抜けを挙げるところまでは、点検役の AI の仕事だ。人が受け持つのは、その先——挙がった抜けに、いま見たような優先順位をつけるところだ。機械が漏れなく拾い、人が体の目で順を決める。


①「自分のイージー、6:30 で合ってる?」

ぼくの記録でアプリを試していて、表示されたイージーの幅に引っかかった。体感では 6:30/km あたりが気持ちいい。なのに、出てくる範囲がどうもしっくりこない。「これ、本当に合ってる?」

当時の実装は、イージーの帯を、原典の第 4 章に載っている数字で出していた。合っているはずだった。でも、原典を読み直すと、勘違いがあった。第 4 章の数字は「イージーとはこういうゾーン」という広めの定義で、実際に処方に使う帯は別にあった。表 5.2 に載っている、少し狭い帯だ。定義の帯と、処方の帯。似て見えて、別物だった。

処方に使う帯へ直すと、体感の 6:30 とも素直に噛み合った。「合ってる?」という素朴な引っかかりが、実装の勘違いを一つ掘り当てた格好だった。

// DRF Table 5.2 printed E prescription band (about 63-73% VO2max),
// narrower than the chapter 4 zone definition (59-74%).
easyPaceRangeSlowFraction = 0.63
easyPaceRangeFastFraction = 0.73

原典の実測と Gilbert 式の検算で裏を取ってから直す。理論書を相手にするときは、「どこに何が書いてあるか」だけでなく、「どの記述をどの計算に使ってよいか」まで合わせないといけない。ここを取り違えると、正しく書いたつもりの実装が、一つ静かにずれる。


② 短距離型と長距離型を、1 本の VDOT で測らない

もっと大きな取りこぼしは、走力を 1 本の数字で測っていたことだった。

当初は、全部の実績から最も高い VDOT を 1 つ選び、そこから E / M / T / I / R もマラソン予測も導いていた。これ自体はダニエルの入門的な指示どおりで、本にも「距離ごとに VDOT は食い違うが、いちばん高い値を全ペースに使ってよい」とある。

ただ、読み進めると、ダニエル自身の使い方はもう少し細かい。目標レースに近い距離の実績から VDOT を取り、スピード寄りのペースは短い距離の速さで合わせる。速さの根拠と持久の根拠を、同じ 1 本には縛っていない。実在のランナーも、短い距離が速い人と長い距離が強い人に分かれる。

1 本の最高 VDOT だけで押すと、弱い側を過大に処方してしまう。1500m は速いがハーフは並、という人に、マラソンペースや T ペースを 30〜40 秒/km 速く、数ヶ月渡し続ける——故障への近道だ。

そこで、ダニエルの扱いに寄せて、証拠を持続時間で 2 系統に分けた。分け方の細かさは本に指定がないので、まずは 2 つに。

  • 15 分より短い、全力に近い走り → インターバル / リピティション(I / R)の基準に
  • 15 分より長い走り → イージー / マラソン / しきい(E / M / T)と、マラソン予測の基準に

「全力に近い走り」といっても、測定テストを課すわけではない。普段の練習に混じる短く速い区間を、そのまま速さの証拠として使う。

速さの証拠と、持久の証拠を、別々に使う。片側の証拠が欠けているときは、保守側へ寄せて埋める。持久からスピードを補うときは VDOT を 2.0、スピードから持久を補うときは 5.0 引く——欠けている側ほど、安全側へ大きく下げる。この 2 系統への割り方も割引の数字も、本に載っているわけではない。ダニエルの向きに沿って、こちらで決めた設計だ。

この取りこぼしを再点検で見つけたのが午前、2 系統に分ける実装を出したのが同じ日の昼だった。監査で「走る人に何が起きるか」が言語化できていると、直しは速い。


③ 理論は幅で、指示は一点で

①も②も、原典に実装を合わせる話だった。三つ目は毛色が違う。理論はそのまま正しくて、どう手渡すかだけを決めた話だ。

イージーのペースには、ダニエル自身が「幅」を認めている。「会話できる速さなら、この範囲のどこでもいい」。理論としては、それでいい。

でも、メニュー作りに慣れていない人に「この幅で走って」と幅のまま渡すと、たいてい迷う。「で、結局いくつで走ればいいの?」。幅は、親切なようで、実行の手前で人を止める。

だから、今夜のメニューとして渡すときは、一点に決める。帯の真ん中を出す。イージーは速い側に寄せても効果が増えるわけではないから、真ん中なら外さないし、自信のない人でも踏み出せる。「幅がある」という理論はそのまま指標の画面に残しつつ、実行の場面では迷いどころを消す。理論を捨てたわけではない。理論に従ったうえで、使う人が悩まなくていい形にした。


AI に残すのは、言葉の読み取りだけ

理論を固めて、原典と突き合わせて確かめた。最後に、AI をどこに残すかだ。

このアプリが出す判断は、理論から決定的に決まる。同じ入力なら、同じ答え。テストで固定できるし、なぜその数字かを後から追える。

そのうえで、もう一つ引き受けたい困りごとがある。ランニングクラブに行くと、その日のメニューが貼り出され、「自分の走力に合った組に入って」と言われる。走り慣れていれば選べる。でも始めたばかりだと、どの組が自分に合うのか、見当もつかない。ここを、アプリに肩代わりさせたい。

ただ、その組分けとメニューは、自然文で配られる。

「土曜、5 組の変化走。7:30 組は 500m + 700m を、2 時間まで」

変化走は、速い区間とつなぎの区間を交互に、休まず続けて走るメニューだ。クラブによって、メニューの書き方も組み方もさまざまで、この一文もそのひとつにすぎない。ここでやりたいのは、二段の読み取りだ。まず、これは「どの強度の練習か」。次に、その強度なら「自分はどの組に入るのが妥当か」。前半の読み取りだけを、自分のサーバー(VPS)で動く言語モデル(LLM)に渡す。使ったツールは Codex CLI だ。後半、その強度と走力を突き合わせて組を決めるのは、これまで固めてきた決定的なロジック——エンジンのほうだ。理解はモデル、判断はエンジンだ。

読み取りで効くのは、平均で均さないことだ。変化走を一本の平均ペースにならすと、速い区間もつなぎも混ざって、ただの緩いジョグに見えてしまう。だから均さず、区間ごとに分けて扱う。この 7:30 組なら、速い区間はマラソンペース、つなぎはイージー、と区間ごとにほどける。休まず続ける変化走を、かたちだけ見て「インターバル」と決めつけることもしない。逆に、1000m を何本、という本物のインターバルなら、その強度(I)を落とさずに残す。速いところを、緩いところで薄めない。この線引きを、プロンプトで LLM に手渡しておく。

「変化走」「余裕を持つ」「長走」「2時間走」など、休まず走り続けるメニューは、
疾走/つなぎが交互でもインターバル(I)にしないでください。連続走として表現し、
components で 500m/700m などの構造を残してください。
intensity は必ず E, M, T, I, R, REST のどれか 1 文字です。"E〜M" のような
合成表記は使わず、変化走なら速い区間=M・つなぎ=E のように 1 つ選んでください。

モデルの出力には、読み取った強度や構造に加えて、どのくらい確からしいかの「確信度」も付く。ただ、その出力は形が揺れて、指示しても「E〜M」のような中間表記を書いてくることがある。だから、出力を確かめる工程で、寛容に捨てるところと、厳格に拒むところを分ける。

  • 合成された強度のように、一部だけおかしい区間は、その区間だけ落として先へ進む。1 個の不備で解析全体を捨てない。
  • 選ばれた組が無い・確信度が壊れている、といった芯の欠けは、丸ごと突き返す。芯が無ければ通さない。

「どこまで許して、どこから拒むか」。この線引きが、生成を実運用に乗せるときの勘所になる。

中継も、公開ポートを開けずに済ませた。アプリからの解析依頼は、データベースに 1 行積むだけ。VPS のワーカーが、外向きの接続でそれを取りに行き、結果を書き戻す。VPS 側は受信ポートを一切開けない。理解を外部のモデルに預けても、経路は手元に置いておく。


判断を決定的なエンジンに寄せたのは、誰が使っても同じ答えに着地させて、その根拠を後から原典までたどれるようにするためだ。AI に預けたのは、言葉の読み取りだけ。走力の推定も、処方したペースも、その人のデータの外には出さないし、学習にも回さない。

やってみて分かったのは、「AI を使う場所を最低限に絞る」は、口で言うほど自動では決まらない、ということだ。処理の流れを一度ぜんぶ分解して、従来どおり硬く設計すべきところと、AI に任せていいところを、一つずつ切り分ける。その判断ができて初めて、AI は最低限のところに収まる。しかも、固めた側も油断できない。原典と突き合わせるレビューの観点を先に決めておかないと、正しく書いたつもりのところに、静かに穴が開く。

生成 AI に「あなたはダニエルです」と頼めば、すぐに動く。今回やっていたのは、その手軽さを、誰が使っても同じように効く形に組み直す作業だったのかもしれない。ランニングに限った話では、たぶんない。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?