基本的に行列方程式を解くプロジェクトに取り組んでいます
A.dot(x) = d
ここで、A
は 2000 年までにおよそ 10 000 000 の次元を持つ行列です (最終的にはこれを両方向に増やしたいと考えています)。
A
明らかにメモリに収まらないため、これを並列化する必要があります。A.T.dot(A).dot(x) = A.T.dot(d)
代わりに解決することでそれを行います。A.T
の次元は 2000 x 2000 になります。A
とd
をチャンクA_i
に分割しd_i
、 行に沿って と を計算A_i.T.dot(A_i)
しA_i.T.dot(d_i)
、これらを合計することで計算できます。並列化に最適です。私はこれを multiprocessing モジュールで実装できましたが、1) メモリの使用により、それ以上のスケーリングが難しく (A
両方の次元で増加)、2) きれいではありません (したがって、保守が容易ではありません)。
Dask は、これらの問題の両方を解決するための非常に有望なライブラリのように思われ、いくつかの試みを行ってきました。私のA
行列は計算が複雑です: 約 15 の異なる配列 (サイズは の行数に等しい) に基づいており、A
関連するルジャンドル関数を評価するために反復アルゴリズムで使用されるものもあります。チャンクが小さい場合 (10000 行)、タスク グラフの作成に非常に長い時間がかかり、多くのメモリが必要になります (メモリの増加は反復アルゴリズムの呼び出しと一致します)。チャンクが大きい場合 (50000 行)、計算前のメモリ消費量ははるかに少なくなりますが、計算時に急速に使い果たされますA.T.dot(A)
。で試してみましcache.Chest
たが、計算が大幅に遅くなります。
タスク グラフは非常に大きく複雑でなければなりません - 呼び出しがA._visualize()
クラッシュします。より単純なA
行列では、これを直接行うことができます (@MRocklin による応答を参照)。単純化する方法はありますか?
これを回避する方法についてのアドバイスは大歓迎です。
おもちゃの例として、私は試しました
A = da.ones((2e3, 1e7), chunks = (2e3, 1e3))
(A.T.dot(A)).compute()
これも失敗し、アクティブなコアが 1 つだけですべてのメモリが使い果たされました。を使用chunks = (2e3, 1e5)
すると、すべてのコアがほぼすぐに開始されますが、MemoryError
1 秒以内に表示されます (現在のコンピューターには 15 GB あります)。chunks = (2e3, 1e4)
より有望でしたが、すべてのメモリも消費してしまいました。
編集:寸法が間違っていたため、おもちゃの例のテストを打ち消し、残りの寸法を修正しました。@MRocklin が言うように、正しい寸法で動作します。私の問題にもっと関連していると思う質問を追加しました。
Edit2:これは、私がやろうとしていたことの非常に単純化された例です。問題は、 の列の定義に含まれる再帰にあると思いますA
。
import dask.array as da
N = 1e6
M = 500
x = da.random.random((N, 1), chunks = 5*M)
# my actual
A_dict = {0:x}
for i in range(1, M):
A_dict[i] = 2*A_dict[i-1]
A = da.hstack(tuple(A_dict.values()))
A = A.rechunk((M*5, M))
ATA = A.T.dot(A)
これは非常に複雑なタスク グラフにつながるようで、計算が始まる前に大量のメモリを占有します。
再帰をnumpy
配列を使用して関数に配置することでこれを解決しましたが、多かれ少なかれ do A = x.map_blocks(...)
.
2 つ目の注意として、A
マトリックス タスク グラフを取得すると、A.T.dot(A)
直接計算するとメモリの問題が発生するようです (メモリの使用量はあまり安定していません)。したがって、チャンクで明示的に計算し、結果を合計します。これらの回避策を使用しても、dask は速度と可読性に大きな違いをもたらします。