Rで疎行列の演算を確認します。
本ポストはこちらの続きです。

疎行列とは
疎行列(そぎょうれつ、Sparse Matrix)とは、成分のほとんどが0である行列のことです。
対義語は、成分の多くが0以外の値で埋まっている「密行列(みつぎょうれつ、Dense Matrix)」です。
密行列(Dense Matrix)の例 \[
\begin{pmatrix}
1 & 2 & 3 \\
4 & 5 & 6 \\
7 & 8 & 9
\end{pmatrix}
\] すべての場所に0では無い数値が存在しています。
疎行列(Sparse Matrix)の例 \[
\begin{pmatrix}
0 & 0 & 0 & 5 \\
0 & 2 & 0 & 0 \\
0 & 0 & 0 & 0 \\
1 & 0 & 0 & 0
\end{pmatrix}
\] ほとんどが0で、疎らに0以外の数値が存在しています。
疎行列の作成
2000行 × 2000列の要素数400万のうち、1%の4万要素のみを0以外の数値で埋める疎行列を作成します。
seed <- 20251128
set.seed(seed)
# 1. 行列のサイズを設定
rows <- 2000
cols <- 2000
# 2. 全要素数と0以外の要素数(1%)を計算
n_total <- rows * cols
n_nonzero <- n_total * 0.01 # 4,000,000 * 0.01 = 40,000個
# 3. 全てが0の行列を作成
mat <- matrix(0, nrow = rows, ncol = cols)
# 4. ランダムに埋める場所(インデックス)を選択
# matrixはメモリ上ではベクトルとして扱われるため、1次元のインデックスで指定可能です
indices <- sample(n_total, n_nonzero)
# 5. 選択した場所にランダムな0以外の数値を代入
mat[indices] <- sample(x = 1:10, size = n_nonzero, replace = TRUE)
# 行列のサイズ確認
cat("--- 行列のサイズ確認 ---\n")
dim(mat)
# 0以外の要素の割合を確認(疎行列の確認)
cat("\n--- 0以外の要素の割合を確認(疎行列の確認) ---\n")
sum(mat != 0) / n_total
# 行列の一部を確認
cat("\n--- 行列の一部を確認 ---\n")
mat[1:10, 1:10]--- 行列のサイズ確認 ---
[1] 2000 2000
--- 0以外の要素の割合を確認(疎行列の確認) ---
[1] 0.01
--- 行列の一部を確認 ---
[,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
[1,] 0 0 0 0 0 0 0 0 0 0
[2,] 0 0 0 0 0 0 0 0 0 0
[3,] 0 0 0 0 0 0 0 0 0 0
[4,] 0 0 0 0 0 0 0 0 0 0
[5,] 0 0 0 0 0 0 0 0 0 0
[6,] 0 0 0 0 0 0 0 0 0 0
[7,] 0 0 0 0 0 0 0 0 0 0
[8,] 0 0 0 0 0 0 0 0 0 0
[9,] 0 0 0 0 0 0 0 0 0 0
[10,] 0 0 0 0 0 0 0 0 0 0疎行列形式オブジェクトとして扱うメリット
非ゼロの要素の位置と値のみを記録する形式のオブジェクト(以降「疎行列形式オブジェクト」)には、以下の2つのメリットがあります。
メモリの節約
例えば、\(10,000 \times 10,000\) の行列があるとします。要素数は1億個です。
もし要素の99%が0だった場合、その1億個のデータをすべて記憶するのはメモリの無駄です。
疎行列形式オブジェクトを利用すれば、「どこに、何の値があるか(非ゼロ要素)」だけを記録すればよいため、メモリ使用量を減らすことが出来ます。
計算速度の向上
行列計算において、0を足しても値は変わらず、0を掛けると答えは必ず0になります。
疎行列形式オブジェクトを利用すると、0の部分の計算をスキップすることができるため、計算処理が高速になります。
Rによるシミュレーション
疎行列形式オブジェクトへの変換
非ゼロの要素の位置と値のみを記録する形式(疎行列形式)のオブジェクトに変換します。
# パターンA: Matrix::Matrix関数を使う
sparse_m1 <- Matrix::Matrix(mat, sparse = TRUE)
cat("--- Matrix::Matrix 関数による疎行列形式オブジェクトの一部を確認 ---\n")
sparse_m1[1:10, 1:10]
# パターンB: methods::as関数で型変換する
sparse_m2 <- methods::as(mat, "sparseMatrix")
cat("\n--- methods::as 関数による疎行列形式オブジェクトの一部を確認 ---\n")
sparse_m2[1:10, 1:10]--- Matrix::Matrix 関数による疎行列形式オブジェクトの一部を確認 ---
10 x 10 sparse Matrix of class "dgCMatrix"
[1,] . . . . . . . . . .
[2,] . . . . . . . . . .
[3,] . . . . . . . . . .
[4,] . . . . . . . . . .
[5,] . . . . . . . . . .
[6,] . . . . . . . . . .
[7,] . . . . . . . . . .
[8,] . . . . . . . . . .
[9,] . . . . . . . . . .
[10,] . . . . . . . . . .
--- methods::as 関数による疎行列形式オブジェクトの一部を確認 ---
10 x 10 sparse Matrix of class "dgCMatrix"
[1,] . . . . . . . . . .
[2,] . . . . . . . . . .
[3,] . . . . . . . . . .
[4,] . . . . . . . . . .
[5,] . . . . . . . . . .
[6,] . . . . . . . . . .
[7,] . . . . . . . . . .
[8,] . . . . . . . . . .
[9,] . . . . . . . . . .
[10,] . . . . . . . . . .メリット1:メモリ使用量の比較
# 削減率の計算
reduction <- as.numeric(object.size(mat)) / as.numeric(object.size(sparse_m1))
cat("--- 1. メモリ使用量の比較 ---\n\n")
cat(sprintf("疎行列形式オブジェクトを利用することで、メモリ効率が 約 %.1f 倍 になりました。\n\n", reduction))--- 1. メモリ使用量の比較 ---
疎行列形式オブジェクトを利用することで、メモリ効率が 約 65.4 倍 になりました。メリット2:計算速度の比較
# 行列の掛け算 (t(A) %*% A)
cat("--- 2. 計算速度の比較 (行列の掛け算) ---\n\n")
time_normal_object <- system.time({
res_dense <- crossprod(mat)
})
time_sparse_object <- system.time({
res_sparse <- crossprod(sparse_m1)
})
# 速度倍率の計算
speedup <- time_normal_object["elapsed"] / time_sparse_object["elapsed"]
cat(sprintf("疎行列形式オブジェクトを利用することで、計算が 約 %.1f 倍 速くなりました。\n", speedup))--- 2. 計算速度の比較 (行列の掛け算) ---
疎行列形式オブジェクトを利用することで、計算が 約 92.2 倍 速くなりました。以上です。
