MapReduceとは


もともと投稿my blog
ビッグデータのこの時代とデータ科学者と呼ばれる新種の上昇では、米国の“単なる”ソフトウェア開発者はMapReduceと呼ばれるものを聞いたかもしれませんが、それは本当に何ですか?そして、なぜそれは過去10年間でゲームを変更するツールだった?これは単純な用語でその概念を説明しようとする試みです.
概念を説明するために、私はPythonを使います、特に、それがMapReduceのために使われる概念を導入した言語を尊重するために、データサイエンスに関する最も人気のある言語の1つとLisp(特にClojure).
MapReduceとは何ですか?それは非常に簡単な方法で大量のデータを処理することができるいくつかの問題を解決するためのアプローチです.それはGoogle whitepaper 2004年に、それはApache Hadoopのようなツールのための基礎でした.

古い信頼できる方法
しかし、抽象的な記述で自分自身を失わないようにしましょう.
def powers(numbers):
  results = []
  for number in numbers:
    results.append(number * number)
  return results
それは非常に慣用的ではないok?より良いバージョンは以下の通りです.
def powers(numbers):
  return [number * number for number in numbers]
私は、上記のコードを書くことができるすべての人がここで起こっていることを理解していると確信しています.私たちはある種の配列を持っています.これらの要素のそれぞれについて、2と2のパワーを計算し、その結果が新しい配列になります.
これがどのようにより低いレベルで動作するかを見るのは難しく、マシン自体では、配列はメモリの領域であり、このメモリ領域の先頭を指し、最初の値をCPUレジスタに読み込み、それ自身で乗算し、その値を新しい配列に対応する別のメモリ領域に格納する.次に、配列の最後まで次の要素に進みます.
私たちは、コンピュータが何をすべきかを正確に話しているので、私たちはこのようにプログラム命令プログラミングを書く方法を呼びます.

他の古い信頼できる方法
Lispでは同じことが書かれますか?それは複雑ではない.
(defn powers [numbers] (map (fn [number] (* number number)) numbers))
おっ!すべてのかっこはどうですか.わかりました、簡単にダイジェストの部分でそれを壊しましょう.
(defn powers [numbers] ...)
ここで関数名を定義しますpowers それは議論をする.numbers .
(fn [number] ...)
引数をとる匿名関数を作成しますnumber . Pythonでは、同じことを達成するlambda number: ... .
(* number number)
これは最初は少し奇妙に見えますが、それは単に我々が掛け算することを意味しますnumber 単独で!
ジューシーな部分を今すぐに:
(map (fn [number] (* number number)) numbers)
フレンドになるmap ! この関数は単純です.2番目の引数として渡されたリストのすべての項目に適用された最初の引数として渡された関数の結果である値のリストを返します.
または別の方法で置く.map すべての値をnumbers , 各値に関数を適用し、この関数の結果を新しいリストに返します.それは我々のループの等価です!
しかし、より低いレベルで何が起こるかについて、第1に反映しましょう:我々が実際に見るコードから、これがどのように遊ぶかについて、知ることができません!forループと同じ順序でメモリを歩いていきますか?または逆の順序で?またはいくつかのランダムな順序で?結果の生成方法の詳細は私たちの手ではありませんが、プログラミング言語の実装によって考慮されます.
これは、宣言型プログラミングの形式である関数型プログラミングと呼ばれています.我々は、我々が望むものを述べます.しかし、なぜこれは重要ですか?それは単にスタイルと個人的な好みの問題ですか?

大きなデータを考える
私たちの例は、データを通してループする方法が重要ではないほど小さいです.しかし、代わりに我々はメモリに保持することができるものよりも大きな数の非常に大きなリストを持っていた場合はどうですか?乗算の代わりに、我々はより多くのCPU集約関数を実行しなければならなかったか?どのように、これは遊びますか?
これは、すべてのCPUコアを使用するためにいくつかの形式の並列性を導入したいかもしれないシナリオです.どのように、それは我々のPythonの例で見えますか?1
# To use Threads, we would use ThreadPoolExecutor
with concurrent.futures.ProcessPoolExecutor(max_workers=NUM_WORKERS) as executor:
  futures = [executor.submit(expensive_function, number) for number in numbers]
  concurrent.futures.wait(futures)
  results = [future.result() for future in futures]
複雑さは確実に上がった!そしてこれはconcurrent.futures モジュールは、すでに多くの詳細を簡素化します.2
Lispについて
はい、
(pmap expensive-function numbers)
唯一の関連する変更は、我々が使用することですpmap の代わりにmap , そして、私たちが全く機能を変えなければならない唯一の理由は、別々の糸で機能を実行するのが必ずしも望ましいというわけではないかもしれないからです.それはプログラミングスタイルや構文の砂糖だけの問題ではない、これは実際には非常に大きな取引です!
それでは、さらに推論を拡張しましょう.私たちが処理するのに多くのデータを持っているのであれば、おそらく数ギガバイトあるいはテラバイトのファイルからではないかと思います.我々は、サーバーの数百または数千以上の負荷を配布する必要があります!どうしたらそんなことできるの?

配水工
どのように、我々は別々の糸またはプロセスでそれをする代わりに、いろいろなサーバーの向こう側に我々の仕事を分割することができましたか?これは、毛むくじゃらを取得し始めるところです.我々はどうにか、我々のサーバーのクラスタを管理しなければなりません、彼らが届くのを確実にして、仕事を分配して、失敗と再試行を処理して、理想的に彼らを自動的にスケールしてください.複雑に思える?まあ、我々は分散システムについて話しているので、多くの方法が失敗することができますし、無数の落とし穴を避けるためにあります.分散システムは正しく実装するのが難しい.
それで、どのように我々のコードは、Pythonでどのように見えますか?正直に言うと、私はそれから始めたくない.それはこれを達成するための巨大な努力であり、それは確かにこのブログのポストに収まることはありません.可能なアイデアは、Celleryを使用して、計算のスライスをenqueueして、異なるホスト間で負荷を分散するために使用することです.
どのようにLISPで見ますか?理論的には、変更する必要はありませんdmap 複数のノード間でワークを分配する関数.実際には、クラスタを設定し、その動作を制御する必要があるからである.
私は、例なしであなたを残したくありません、しかし、ここでは、1と呼ばれています
Scoop :
from scoop import futures

list(futures.map(expensive_function, numbers))
これが実際に複数のマシンで動くかもしれないということを伝えることができますか?

しかし、減少についてはどうですか?
我々はそのことについて詳しく話したmap しかし、我々は言及していないreduce まだ.それは何ですか.
何度かこの操作に遭遇したことを確信しています.
def sum_nums(numbers):
    count = 0
    for number in numbers:
        count += number
    return count
これや100倍の変化をしたんだ、確かだ.配列をいくつかの並べ替えのハッシュマップに変えるのに便利です.
def group_by_id(customers):
    customers_map = {}
    for customer in customers:
        customers_map[customer.id] = customer
    return customers_map
どのように、これは機能的な世界で見えますか?はい、
(defn sum_nums [numbers]
  (reduce + 0 numbers))
再び、詳細は隠されます、そして、我々は結合している操作を提供します+ この場合、初期値(0 この特定の場合には省略することができます.
何が起こるかは、コレクションから要素を受け取り、それをアキュムレータと組み合わせる0 ), 新しいアキュムレータとしてこの組み合わせを使用し、コレクション内の次の要素を続行します.
我々がそれと結合するならばmap 操作は、いくつかの非常に強力なものを行うことができます
(defn sum_squares [numbers]
  (reduce + (map * numbers)))
命令的な等価は以下の通りです:
def sum_squares(numbers):
    final_sum = 0
    for number in numbers:
        final_sum += number * number
    return final_sum

ついにMapReduce
私たちは、建物のブロックとその背後にある概念を明らかにしたので、MapReduceがすべてについて理解するのは簡単です.GoogleのWhitePaperから直接例を取り上げましょう.これはテキスト内の単語の出現を数えます.
    map(String key, String value):
      // key: document name
      // value: document contents
      for each word w in value:
        EmitIntermediate(w, "1");

    reduce(String key, Iterator values):
      // key: a word
      // values: a list of counts
      int result = 0;
      for each v in values:
        result += ParseInt(v);
      Emit(AsString(result));
私たちは最初にマップ操作を定義します.そして、それが受け取る各ドキュメントのために、それは語にそれを分割します、そして、各々の語がキー/値組を発します1 .
reduce関数は、キーとそのキーに対応する値のリストを受け取ります.それぞれの単語の場合は、単語の繰り返しごとに1つのリストが表示されます.
その後、それらのすべてを取ると我々は一緒にそれらを合計します.これは、私たちが発する値です.
これは実際には分散的な方法で起こりますが、コードは本当に知る必要はありません.どのようにクールですか?

これで終わりですか.
それはすべてですか?ほとんどははい.もちろん、多くの知っているが、基本的な考え方は簡単です.この考えから、他のクールなものが建てられたのだ!
これがMapReduceの背後にある概念を理解し、それができることについて一般的な感触を持つことを可能にすることを願っています.これと実際にそれを使用しての間に多くの手順を作るが、おそらく今それはより威圧的に見えます.
これは役に立ちましたか.コメントで知らせてください!
グローバルインタプリタロックのため、実際には複数のプロセスをここで生成します.PythonスレッドはCPUの集中的な操作には役に立たない.畝
Executorを使用すると、Executorを使用することができます.その問題へのアプローチです.これは本当にこの強力な方法を強調表示します.畝