Rで疎行列の演算

Rで疎行列の演算を確認します。

本ポストはこちらの続きです。

Rの関数:sparseMatrix {Matrix}
Rの関数から sparseMatrix {Matrix} を確認します。関数 sparseMatrix とはsparseMatrix は、疎行列(成分の多くが 0 である行列)を効率的に生成するための関数です。疎行列を通常の行列として全ての...

疎行列とは

疎行列(そぎょうれつ、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 倍 速くなりました。

以上です。