R言語 Advent Calendar 2025の2日目の記事です。
藤井七冠(当時)が3連敗したことがないという事実
この記事が出たのは2025年の8月のこと。藤井聡太七冠(当時)は王位戦七番勝負の第4局と第5局を永瀬九段に連敗。王位戦第4局時点で通算500戦415勝85敗(勝率.830!!)でしたが、その間3連敗をしたことがないということで話題となりました。
その記事を引用した上記ポストに私は興味を持ちました。たまたま私はこのときに統計検定準1級のマルコフ連鎖を学習したばかりという僥倖(藤井先生の言葉を拝借)を得ており、このポストのとおり勝率0.830の棋士が500局指して3連敗しない確率は12.73%と計算することができました。
統計計算には統計プラットフォームのRを使うのが便利です。Rには統計の計算に特化したライブラリも多いことが美点です。そこでRとRライブラリを使ってどのように計算したかをまとめます。
3連敗する確率をどのように計算するか?
そもそも3連敗する確率を二項分布を使って計算できないのでしょうか?これが10戦目くらいまでだったらなんとか手計算もできるでしょうが、500戦を手計算するなら私は他の作業をします。
「勝ち」と「負け」の2とおりしかないので機械的には計算できるのではないかと思いきや、全ての可能な試合結果の組み合わせは$2^{500}$。これは$10^{150}$に相当するので、余計なところに計算リソースを割くのはやめましょうということですね。
そこで登場するのがマルコフ連鎖の手法です。マルコフ連鎖とは何かについてはここでは詳述しませんが、1点言えることは「3連敗を経験したという状態を記憶しておく必要がある」という観点から、今回の条件は「状態遷移」がマルコフ連鎖の枠組みにぴったり合うということです。
状態を定義する
マルコフ連鎖から確率を求めるにあたってまず行うことは「状態」を定義することです。1戦目から500戦目の直前の状態を次のように定義します(なお千日手や持将棋などの引き分けは考慮しません)。
- 状態0…0連敗中(1戦目or前局勝ち)
- 状態1…1連敗中(前局負け)
- 状態2…2連敗中
- 状態3…3連敗経験(吸収状態)
0連敗とか1連敗とは普通は表現しませんが、便宜上使用します。
今回は500戦して3連敗しない確率を求めたいので、3連敗した段階で状態遷移は終了します。吸収状態とは「一度入ったら二度と抜け出せない状態」のことで、この状態(3連敗)に1度でもなってしまうとそこからは状態が遷移しないことを意味します。
初期状態
まず、1戦目を戦う前の状態を考えます。
1戦目を戦う前は前局がないので(勝ちもしませんが)負けていることがない状態です。ですからこのときは状態0(0連敗中)です。このことから以下の数式(ベクトル)が導けます。
初期状態確率ベクトル $\pi_0 = \begin{pmatrix} 1 & 0 & 0 & 0 \end{pmatrix}$
ベクトルは左から(状態0 状態1 状態2 状態3)を表しています。
1戦目の結果による状態遷移
1戦目、勝った場合は状態0(0連敗中)のまま、状態は変わりません。その確率を$p$と考えます。
1戦目、敗れた場合は状態0→状態1(1連敗中)に状態が遷移します。その確率を$1-p=q$とします。
1戦目が終わった段階でそれ以外のケース(2連敗中、3連敗経験)はあり得ません。ですから状態0→状態2の確率も、状態0→状態3の確率も0です。
以上のことをまとめて数式にすると以下のようになります。
状態0からの遷移確率ベクトル$\begin{pmatrix}P_{00} & P_{01} & P_{02} &P_{03} \end{pmatrix} = \begin{pmatrix}p & q & 0 & 0 \end{pmatrix}$
状態確率ベクトルの$P_{00}$は状態0から状態0に遷移(移動しない)、$P_{01}$は状態0から状態1に遷移することを意味します。
状態1、状態2、状態3からの状態遷移
2戦目以降は状態1、状態2、状態3になる可能性があるので、状態0からと同じようにそれぞれ遷移確率ベクトルを考えます。
状態1からの遷移
状態1(1連敗中)のとき、次局勝つと状態0(0連敗)に、次局負けると状態2(2連敗)になります。
$\begin{pmatrix}P_{10} & P_{11} & P_{12} &P_{13} \end{pmatrix} = \begin{pmatrix}p & 0 & q & 0 \end{pmatrix}$
状態2からの遷移
状態2(2連敗中)のとき、次局勝つと状態0(0連敗)に、次局負けると状態3(3連敗)になります。
$\begin{pmatrix}P_{20} & P_{21} & P_{22} &P_{23} \end{pmatrix} = \begin{pmatrix}p & 0 & 0 & q \end{pmatrix}$
状態3からの遷移
状態3(3連敗経験)から先の連敗はこの問題の場合考慮する必要がありません。先述したように状態3から遷移することはなく、常に状態3となる吸収状態となります。$P_{33} = 1$となり、他の確率は$0$となります。
$\begin{pmatrix}P_{30} & P_{31} & P_{32} &P_{33} \end{pmatrix} = \begin{pmatrix}0 & 0 & 0 & 1 \end{pmatrix}$
これらを組み合わせると遷移行列$P$は以下のように表現できます。
$P = \begin{pmatrix} P_{00} & P_{01} & P_{02} &P_{03} \\ P_{10} & P_{11} & P_{12} &P_{13} \\ P_{20} & P_{21} & P_{22} &P_{23} \\ P_{30} & P_{31} & P_{32} &P_{33} \end{pmatrix} = \begin{pmatrix} p & q & 0 & 0 \\ p & 0 & q & 0 \\ p & 0 & 0 & q \\ 0 & 0 & 0 & 1 \end{pmatrix}$
今回の場合は、藤井七冠(当時)の勝率が常に独立で.830と考え、$p = 0.83, q = 0.17$とします。$500$戦目のときの状態確率ベクトルは以下のように求められます。
$\pi_0 * P^{500} = \begin{pmatrix} 1 & 0 & 0 & 0 \end{pmatrix} \begin{pmatrix} 0.83 & 0.17 & 0 & 0 \\ 0.83 & 0 & 0.17 & 0 \\ 0.83 & 0 & 0 & 0.17 \\ 0 & 0 & 0 & 1 \end{pmatrix} ^{500}$
Rコードにしてみる
ここまで記載したロジックは日本語でも説明できる程度の内容ですから、これをプログラムに記述することはそう難しいことでありません。
しかし、上記の$\pi_0 * P^{500}$という数式をプログラムに記述することで計算させることができれば視覚的にわかりやすく、誤りも少なくなります。それを可能にするのがmarkovchainライブラリです。
まず初期設定をします。
rm(list = ls())
library(markovchain)
# パラメータ設定
n_games <- 500 # 500局
n_wins <- 415 # 藤井七冠500局時の勝ち数
p <- n_wins / n_games # 勝率0.830
q <- 1 - p # 負ける確率0.170
# 状態の定義 状態0: 0連敗中, 1:1連敗中, 2:2連敗中, 3:3連敗経験 (吸収)
states <- c("0", "1", "2", "3")
# 初期状態確率ベクトル
pi_0 <- c(1, 0, 0, 0)
names(pi_0) <- states
# 遷移行列の定義
transMatrix <- matrix(c(
p, q, 0, 0,
p, 0, q, 0,
p, 0, 0, q,
0, 0, 0, 1
), nrow = 4, byrow = TRUE)
rownames(transMatrix) <- states
colnames(transMatrix) <- states
markovchainライブラリを使ったRでの確率算出
ここで、states(状態)とtransitionMatrix(遷移行列)をオブジェクト作成します。これにはnew()関数を使います。
【注意】この記述をする前に、markovchainライブラリの読み込みをしておきましょう。
install.packages("markovchain") # インストールされていない場合はインストール実行
library(markovchain)
# Markovchainオブジェクトの作成
p_matrix <- new("markovchain",
states = states,
transitionMatrix = transMatrix)
new("markovchain")によって、行列$P=\begin{pmatrix} p & q & 0 & 0 \\ p & 0 & q & 0 \\ p & 0 & 0 & q \\ 0 & 0 & 0 & 1 \end{pmatrix}$がマルコフ連鎖の計算(べき乗)に対応するようにp_matrixという名前で登録されました。あとは、数式を記述するだけで状態ベクトルの計算ができるようになります。
> pi_0 * (p_matrix ^ 500)
0 1 2 3
[1,] 0.1061082 0.01811318 0.003092006 0.8726866
pi_0 * (p_matrix ^ 500)
という記述だけで、500戦目に状態0~状態3の状態にいる確率がそれぞれどれくらいか算出できました。上の出力例で言えば、状態3の確率は一番右側の列に表示されており、500戦して3連敗を経験する確率が$0.8727$とわかります。つまり、500戦して3連敗しない確率は
$1-0.8727 = 0.1273$
と算出できます。
行列が出てきた瞬間に文系民族(私もそうでした…)はおののきますが、計算を簡単にしてくれる便利なツールなのでこんなに容易に記述ができ、確率を計算できるという僥倖を得られるのです(←しつこいし文脈がおかしい)。
ENJOY!