web-dev-qa-db-ja.com

計算を行列の乗算として表現すると、計算が速くなるのはなぜですか?

Googleの TensorFlowを使用したMNistチュートリアル では、1つのステップが行列にベクトルを乗算することと同じである計算が示されています。 Googleは最初に、計算の実行に入る各数値の乗算と加算が完全に書き出されている画像を表示します。次に、代わりに行列乗算として表現された図を示し、このバージョンの計算は高速である、または少なくとも高速であると主張します。

これを方程式として書き出すと、次のようになります。

scalar equation

この手順を「ベクトル化」して、行列の乗算とベクトルの加算に変換できます。これは計算効率を上げるのに役立ちます。 (これはまた、考えるのに便利な方法です。)

vector equation

このような方程式は通常、機械学習の専門家によって行列乗算形式で記述され、コードの簡潔さや数学の理解の観点からそうすることの利点を理解できることは知っています。私が理解していないのは、ロングハンド形式からマトリックス形式への変換 "は計算効率の向上に役立つ"のGoogleの主張です。

いつ、なぜ、どのように計算を行列の乗算として表現することにより、ソフトウェアのパフォーマンスを向上させることができるでしょうか?私が人間として2番目の(行列ベースの)画像の行列乗算を計算する場合、最初の(スカラー)画像に示されている個別の計算をそれぞれ順次実行することで計算します。私にとって、それらは同じ計算シーケンスの2つの表記にすぎません。私のコンピューターではなぜ違うのですか?なぜコンピュータはスカラよりも速く行列計算を実行できるのでしょうか?

18
Mark Amery

これは当たり前のように聞こえるかもしれませんが、コンピュータは formulas を実行せず、 code を実行します。実行にかかる時間はコードに直接依存しますそれらは、コードが実装するあらゆる概念に対してのみ間接的に実行されます。論理的に同一の2つのコードは、パフォーマンス特性が大きく異なる場合があります。特に行列の乗算で発生する可能性が高いいくつかの理由:

  • 複数のスレッドを使用する。マルチコアを持たない最新のCPUはほとんどなく、多くは最大8つまであり、ハイパフォーマンスコンピューティング専用のマシンは、いくつかのソケットで64を簡単に使用できます。通常のプログラミング言語では、明白な方法でコードを記述するには、そのうちのoneのみを使用します。言い換えると、実行しているマシンの利用可能なコンピューティングリソースの2%未満を使用する可能性があります。
  • SIMD命令を使用する(紛らわしいことに、これは「ベクトル化」とも呼ばれますが、質問のテキスト引用符とは異なる意味で)。基本的に、4または8程度のスカラー算術命令の代わりに、CPUに4〜8程度のレジスタで並列に演算を実行するCPU one命令を指定します。これにより、文字通りいくつかの計算を行うことができます(完全に独立していて、命令セットに適合している場合)、4または8倍速くなります。
  • よりスマートにする キャッシュの使用時間的および空間的に一貫性があるの場合、メモリアクセスはより高速です。つまり、連続したアクセスは近くのアドレスへのアクセスであり、アドレスに2回アクセスすると、長い一時停止ではなく2回連続してアクセスします。
  • GPUなどのアクセラレータを使用する。これらのデバイスはCPUとは非常に異なる動物であり、それらを効率的にプログラミングすることは、それ自体が芸術の全体的な形です。たとえば、数百のコアが数十のコアのグループにグループ化され、これらのグループはリソースを共有します。通常のメモリよりもはるかに高速な数KiBのメモリを共有し、グループのいずれかのコアがifステートメントそのグループ内の他のすべてのユーザーはそれを待つ必要があります。
  • 作業を分散する いくつかのマシンに分散(スーパーコンピューターでは非常に重要です)。これにより、新しい頭痛の種が膨大になりますが、もちろん、はるかに大きなコンピューティングリソースにアクセスできます。
  • よりスマートなアルゴリズム。行列の乗算の場合、上記のトリックで適切に最適化された単純なO(n ^ 3)アルゴリズムは、妥当な行列サイズの サブキュービック よりも高速ですが、勝つ場合もあります。スパース行列などの特殊なケースでは、特殊なアルゴリズムを作成できます。

多くの賢明な人々が、非常に 一般的な線形代数演算の効率的なコード を記述しています。上記のトリックやその他のトリックを使用し、通常はプラットフォーム固有の愚かなトリックも使用します。したがって、数式を行列乗算に変換そして、成熟した線形代数ライブラリを呼び出してその計算を実装するは、その最適化の取り組みから利益を得ます。対照的に、単純に高水準言語で明白な方法で式を記述する場合、最終的に生成されるマシンコードはこれらのトリックのすべてを使用するわけではなく、それほど高速ではありません。これは、行列の定式化を取り、自分で記述した単純な行列乗算ルーチンを呼び出して(これもまた明白な方法で)実装する場合にも当てはまります。

コードを高速にする作業が必要。最後の1オンスのパフォーマンスが必要な場合は、多くの場合非常に多くの作業が必要です。非常に多くの重要な計算は、いくつかの線形代数演算の組み合わせとして表現できるため、これらの演算用に高度に最適化されたコードを作成すると経済的です。ただし、1回限りの特殊なユースケースですか?あなた以外は誰も気にしないので、その中から最適化するのは経済的ではありません。

19
user7043

(スパース)行列-ベクトル乗算は高度に並列化可能です。これは、データが大きく、サーバーファームを自由に利用できる場合に非常に便利です。

これは、行列とベクトルをチャンクに分割し、別のマシンに処理の一部を実行させることができることを意味します。次に、それらの結果の一部を互いに共有して、最終結果を取得します。

あなたの例では、操作は次のようになります

  1. グリッド内の座標に従って、それぞれがWx、yを保持するプロセッサのグリッドをセットアップする

  2. 各列に沿ってソースベクトルをブロードキャストします(コストO(log height)

  3. ローカルで乗算する各プロセッサを持っている(コストO(width of submatrix * heightof submatrix)

  4. 合計を使用して各行に沿って結果を折りたたみます(コストO(log width)

合計が結合的であるため、この最後の操作は有効です。

これにより、冗長性を組み込むことができ、すべての情報を1台のマシンに入れる必要がなくなります。

グラフィックに見られるような小さな4x4行列の場合、それは、CPUがそれらの操作を処理するための特別な命令とレジスターを持っているためです。

4
ratchet freak