MapReduceの簡単な説明?


166

CouchDBの質問に関連しています。

誰もが麻痺者が理解できる言葉でMapReduceを説明できますか?



3
そして、これが私の見解です:MapReduce for and with the kids
Michael Hausenblas

@MichaelHausenblas-私はあなたの例が大好きです:家族全員にとって理解しやすく、楽しいです。
リー

Joel Spolskyが初心者向けの良い説明をしています-joelonsoftware.com/items/2006/08/01.html
user2314737

回答:


187

MapとReduceの基本について説明します。


マップは、ある種類のリストの項目を別の種類の項目に「変換」して、同じ種類のリストに戻す関数です。

数字のリストがあるとします:[1,2,3]すべての数字を2倍にしたい場合、この場合、「すべての数字を2倍にする」関数は関数x = x * 2です。マッピングなしで、次のように書くことができます単純なループ、言う

A = [1, 2, 3]
foreach (item in A) A[item] = A[item] * 2

そして、私はA = [2、4、6]を持っていますが、ループを書く代わりに、マップ関数があれば、書くことができます

A = [1, 2, 3].Map(x => x * 2)

x => x * 2は、[1,2,3]の要素に対して実行される関数です。何が起こるかというと、プログラムは各項目を取得し、xを各項目に等しくすることで(x => x * 2)を実行し、結果のリストを生成します。

1 : 1 => 1 * 2 : 2  
2 : 2 => 2 * 2 : 4  
3 : 3 => 3 * 2 : 6  

(x => x * 2)でマップ関数を実行すると、[2、4、6]になります。


Reduceは、リスト内の項目を「収集」し、それらすべてに対して何らかの計算を実行して、それらを単一の値に減らす関数です。

合計または平均を見つけることはすべて、reduce関数のインスタンスです。たとえば、[7、8、9]のように数値のリストがあり、それらを合計したい場合は、次のようなループを作成します。

A = [7, 8, 9]
sum = 0
foreach (item in A) sum = sum + A[item]

しかし、reduce関数にアクセスできる場合は、次のように書くことができます。

A = [7, 8, 9]
sum = A.reduce( 0, (x, y) => x + y )

ここで、2つの引数(0と、xとyを使用する関数)が渡される理由が少しわかりにくいです。reduce関数が役立つためには、2つのアイテムを取得して何かを計算し、その2つのアイテムを1つの単一の値に「削減」できる必要があります。したがって、プログラムは、単一の値になるまで各ペアを削減できます。

実行は次のようになります。

result = 0
7 : result = result + 7 = 0 + 7 = 7
8 : result = result + 8 = 7 + 8 = 15
9 : result = result + 9 = 15 + 9 = 24

ただし、常にゼロから開始する必要はないので、最初の引数は、シード値、具体的には最初のresult =行の値を指定できるようにするためのものです。

2つのリストを合計したい場合、次のようになります。

A = [7, 8, 9]
B = [1, 2, 3]
sum = 0
sum = A.reduce( sum, (x, y) => x + y )
sum = B.reduce( sum, (x, y) => x + y )

または、あなたが現実の世界で見つける可能性が高いバージョン:

A = [7, 8, 9]
B = [1, 2, 3]

sum_func = (x, y) => x + y
sum = A.reduce( B.reduce( 0, sum_func ), sum_func )

Map \ Reduceサポートを使用すると、データをDBに格納してそれを使用する方法を知らなくてもデータベースを操作できるので、DBソフトウェアの良い点です。それがDBエンジンの目的です。

Map関数またはReduce関数のいずれかを使用して、必要なものをエンジンに "伝える"ことができれば、DBエンジンはデータの周りに方法を見つけ、関数を適用し、結果を思い付くことができます。すべてのレコードをループする方法を知らなくても、すべてが必要です。

インデックス、キー、結合、ビュー、および単一のデータベースが保持できる多くのものがあるため、データが実際に格納される方法からユーザーを保護することにより、コードの作成と保守が容易になります。

並列プログラミングの場合も同じです。ループコードを実際に実装するのではなく、データで何をしたいかを指定するだけで、基盤となるインフラストラクチャが同時並列ループで関数を「並列化」して実行できます。


はい、地図を理解し、個別に撮影を減らします。しかし、どのようなアプリケーションで削減できますか?Googleシナリオでは、たとえば、特定のキーワードのページのランキングを与える一連のパラメーターを合計するために使用しますか?
ロレンソ

@lbolognini var total = orderes.Sum(o => o.UnitPrice * o.Quantity)
chakrit 2009

@lbologniniループの概念そのものを抽象化すると、多くの用途があります。Googleのシナリオでは、ページランク、リンクなどを計算するためのマシンが数千台あると考えられます。さらに数台のサーバーを追加する必要がある場合、何をしますか?ループしているすべてのコードを変更することは、おそらくオプションではありません。だから彼らがしたことは、彼らが代わりに「リデュース」関数に対して彼らの計算コードを書くことです...そして、サーバーのリストが変更するとき、「リデュース」関数だけが変更される必要があります。とった?
chakrit 2009

平均の計算をどのように削減しますか?私が見たものから、あなたができなかったと思いますか?多分分子と分母をマッピングして、両方の合計の最後に除算しますか?
andyczerwonka 2010

@arcticpenguin私は少し一般的すぎます。実際にAverage()はおそらく上に着氷していSum()ます。しかし、私はそれが関数が「リデュース」と呼ばれる理由を説明するためにそれについて話しました...平均関数は、数のリストを取り、それを単一の数(平均)に減らすものです。
chakrit

60

MapReduceは、開発者がマッパー以外のコードを記述して関数を削減することなく、膨大な量のデータを並列処理する方法です。

マップ機能は、データを取得し、バリアに保持される結果、アウト解約します。この関数は、多数の同じマップタスクと並行して実行できます。次に、データセットをスカラー値に縮小できます。

SQLステートメントのように考えると

SELECT SUM(salary)
FROM employees
WHERE salary > 1000
GROUP by deptname

mapを使用して、給与が1000を超える従業員のサブセットを取得できます。このマップは、バリアからグループサイズのバケットに排出されます。

Reduceは、それらの各グループを合計します。結果セットを提供します。

グーグルペーパーの私の大学の研究ノートからこれを摘採しました


33
  1. 大量のデータを取る
  2. すべてのデータを別の種類のデータに変換するある種の変換を実行します
  3. それらの新しいデータをさらに単純なデータに結合する

ステップ2はマップです。ステップ3は削減です。

例えば、

  1. 道路上の一対の圧力計で2つのインパルス間の時間を取得する
  2. それらの時間をメーターの距離に基づく速度にマッピングします
  3. それらの速度を平均速度に落とします

MapReduceがMapとReduceに分割されている理由は、さまざまな部分を並行して簡単に実行できるためです。(特に、Reduceに特定の数学的特性がある場合。)

MapReduceの複雑でありながら優れた説明については、GoogleのMapReduceプログラミングモデル-再考(PDF)を参照してください。


1
ステップ3では、「変換」ではなく「結合」と言います
TraumaPony 2008

初めて、3つの答えを組み合わせたものが最良の答えです。最初のNasserの記事のリンク(理論的なハイレベル)を読んでください。次にchakritの答え(map-reduceの個別の説明)を見てください。Frankの答え(有名なMapReduceイディオムとは何ですか。)ありがとうございました。:)
Ajeet Ganga 2013

20

MAPとREDUCEは、人間が最後の恐竜を殺したときからの古いLisp関数です。

あなたが名前、そこに住んでいる人の数、そして都市の大きさに関する情報を持つ都市のリストを持っていると想像してください:

(defparameter *cities*
  '((a :people 100000 :size 200)
    (b :people 200000 :size 300)
    (c :people 150000 :size 210)))

ここで、人口密度が最も高い都市を見つけることができます。

まず、MAPを使用して都市名と人口密度のリストを作成します。

(map 'list
     (lambda (city)
         (list (first city)
               (/ (getf (rest city) :people)
                  (getf (rest city) :size))))
     *cities*)

=>   ((A 500) (B 2000/3) (C 5000/7))

REDUCEを使用すると、人口密度が最大の都市を見つけることができます。

(reduce (lambda (a b)
          (if (> (second a) (second b))
             a
             b))
        '((A 500) (B 2000/3) (C 5000/7)))

 =>   (C 5000/7)

両方を組み合わせると、次のコードが得られます。

(reduce (lambda (a b)
          (if (> (second a) (second b))
             a
             b))
        (map 'list
             (lambda (city)
                (list (first city)
                   (/ (getf (rest city) :people)
                      (getf (rest city) :size))))
             *cities*))

関数を紹介しましょう:

(defun density (city)
   (list (first city)
         (/ (getf (rest city) :people)
            (getf (rest city) :size))))

(defun max-density (a b)
   (if (> (second a) (second b))
          a
          b))

次に、MAP REDUCEコードを次のように記述します。

(reduce 'max-density
        (map 'list 'density *cities*))

 =>   (C 5000/7)

MAPand REDUCE(評価は裏返しです)を呼び出すので、map reduceと呼ばれます


@MoMolog:関数MAXはすでに存在し、少し異なる動作をします。また、MAXを再定義しないでください。
Rainer Joswig、2017年

max-density渡された引数の2番目の要素を比較しますよね?ばかげた編集でごめんなさい。
Alexander Presber

@MoMolog:はい、これは2番目の要素であり、この小さな例のコンテキストでのみ役立ちます。コードは、データ構造としてリストを備えた少し古いスタイルのLispで意図的に記述されています...
Rainer Joswig

17

Googleペーパーの例を見てみましょう。MapReduceの目標は、ある種のアルゴリズムに対して並列で動作する処理ユニットの負荷を効率的に使用できるようにすることです。例は次のとおりです。一連のドキュメントからすべての単語とその数を抽出するとします。

典型的な実装:

for each document
    for each word in the document
        get the counter associated to the word for the document
        increment that counter 
    end for
end for

MapReduceの実装:

Map phase (input: document key, document)
for each word in the document
    emit an event with the word as the key and the value "1"
end for

Reduce phase (input: key (a word), an iterator going through the emitted values)
for each value in the iterator
    sum up the value in a counter
end for

その周りには、一連のドキュメントを「スプリット」に分割するマスタープログラムがあり、マップフェーズで並行して処理されます。発行された値は、ワーカーに固有のバッファーにワーカーによって書き込まれます。次に、マスタープログラムは、バッファーの処理準備ができたことが通知されるとすぐに、他のワーカーにリデュースフェーズの実行を委任します。

すべてのワーカー出力(MapまたはReduceワーカー)は、実際には分散ファイルシステム(GFS for Google)またはCouchDBの分散データベースに格納されたファイルです。


10

MapReduce の非常に簡単迅速、かつ初心者向け」の紹介は、http//www.marcolotz.com/? p = 67で入手できます

コンテンツの一部を投稿する:

まず第一に、なぜMapReduceが最初に作成されたのですか?

基本的にGoogleは、ネットワークを介して接続された多数のマシンにデータを分散できるように、大規模な計算ジョブを簡単に並列化できるソリューションを必要としていました。それとは別に、マシンの障害を透過的に処理し、負荷分散の問題を管理する必要がありました。

MapReduceの真の強みは何ですか?

MapReduceの魔法はMap関数とReduce関数のアプリケーションに基づいていると言えるかもしれません。私はメイトを告白しなければなりません、私は強く同意しません。MapReduceを非常に人気にした主な機能は、シンプルなインターフェースと組み合わせた、自動並列化と配布の機能です。これらの要因と、ほとんどのエラーに対する透過的な障害処理の合計により、このフレームワークは非常に人気がありました。

紙のもう少し深み:

MapReduceは、Googleの論文(Dean&Ghemawat、2004年–リンクはこちら)で、並列アプローチとコモディティコンピュータークラスターを使用してビッグデータで計算を行うソリューションとして最初に言及されました。Javaで記述されたHadoopとは対照的に、GoogleのフレームワークはC ++で記述されています。このドキュメントでは、大規模なデータセットに対する関数型プログラミングのMap関数とReduce関数を使用して、並列フレームワークがどのように動作するかについて説明します。

このソリューションでは、MapとReduceと呼ばれる2つの主要なステップがあり、最初と2番目の間にオプションのステップがあり、Combineと呼ばれます。Mapステップが最初に実行され、入力Key-Valueペアで計算を実行して、新しい出力Key-Valueを生成します。入力キーと値のペアの形式は、必ずしも出力形式のペアと一致する必要はないことに注意してください。Reduceステップは、同じキーのすべての値をアセンブルし、それに対して他の計算を実行します。その結果、この最後のステップではキーと値のペアが出力されます。MapReduceの最も簡単なアプリケーションの1つは、単語数を実装することです。

このアプリケーションの疑似コードを以下に示します。

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は同じキーのすべての値をグループ化します。例を挙げましょう。レコードに「家」という単語が3回出現するとします。レデューサーの入力は[house、[1,1,1]]です。レデューサーでは、キーハウスのすべての値を合計し、出力として次のキー値を提供します:[house、[3]]。

これは、MapReduceフレームワークでこれがどのように表示されるかを示すイメージです。

オリジナルのMapReduce Googleペーパーからの画像

MapReduceアプリケーションのいくつかの他の古典的な例として、次のように言うことができます。

•URLアクセス頻度の数

•逆Webリンクグラフ

•分散Grep

•ホストごとの用語ベクトル

大量のネットワークトラフィックを回避するために、このペーパーでは、フレームワークがデータの局所性を維持する方法を説明しています。これは、マップジョブを実行しているマシンのデータがメモリ/ローカルストレージにあることを常に確認し、ネットワークからのフェッチを回避する必要があることを意味します。マッパーの配置によりネットワークを削減することを目的として、前述のオプションのコンバイナーステップが使用されます。Combinerは、レデューサーに送信する前に、特定のマシンのマッパーの出力に対して計算を実行します。これは別のマシンにある場合があります。

このドキュメントでは、障害が発生した場合のフレームワークの要素の動作についても説明しています。論文では、これらの要素をワーカーとマスターと呼びます。それらは、オープンソース実装では、より具体的な要素に分割されます。グーグルはこのアプローチを紙で説明しただけで、独自のソフトウェアをリリースしなかったため、モデルを実装するために多くのオープンソースフレームワークが作成されました。例として、HadoopまたはMongoDBの制限されたMapReduce機能を言うことができます。

ランタイムは、入力データのパーティショニング、マシンの大規模なセット全体でのプログラム実行のスケジューリング、マシン障害の処理(もちろん、透過的な方法)、マシン間通信の管理など、専門家以外のプログラマーの詳細を処理する必要があります。入力データがワーカー間でどのように分割されるかについて、経験豊富なユーザーはこれらのパラメーターを調整できます。

重要な概念:

フォールトトレランス:マシンの障害を適切に許容する必要があります。これを実行するために、マスターは定期的にワーカーにpingします。マスターが一定の時間内に特定のワーカーからの応答を受信しない場合、マスターはそのワーカーで作業が失敗したと定義します。この場合、障害のあるワーカーによって完了したすべてのマップタスクは破棄され、別の使用可能なワーカーに渡されます。ワーカーがまだマップまたは削減タスクを処理していた場合も同様です。ワーカーが既に削減部分を完了している場合は、失敗するまでにすべての計算がすでに完了しており、リセットする必要がないことに注意してください。障害の主なポイントとして、マスターに障害が発生すると、すべてのジョブが失敗します。このため、データ構造を保存するために、マスターの定期的なチェックポイントを定義できます。

局所性:ネットワークトラフィックを回避するために、フレームワークは、すべての入力データが計算を実行するマシンでローカルに利用できることを確認しようとします。元の説明では、レプリケーション係数が3に設定され、ブロックサイズが64 MBのGoogleファイルシステム(GFS)を使用しています。これは、64 MBの同じブロック(ファイルシステム内のファイルを構成する)が、3つの異なるマシンで同一のコピーを持つことを意味します。マスターはブロックがどこにあるかを知っており、そのマシンでマップジョブをスケジュールしようとします。それが失敗した場合、マスターはタスク入力データのレプリカの近くにマシンを割り当てようとします(つまり、データマシンの同じラック内のワーカーマシン)。

タスクの細分性:各マップフェーズがM個に分割され、各削減フェーズがR個に分割されていると仮定すると、MとRがワーカーマシンの数よりもはるかに大きいことが理想的です。これは、さまざまなタスクを実行するワーカーが動的負荷分散を改善するという事実によるものです。それとは別に、ワーカーが失敗した場合の回復速度が向上します(完了した多くのマップタスクを他のすべてのマシンに分散できるため)。

バックアップタスク: MapまたはReducerワーカーは、クラスター内の他のワーカーよりも動作がはるかに遅い場合があります。これは、合計処理時間を保持し、単一の遅いマシンの処理時間と等しくなる場合があります。元のペーパーでは、バックアップタスクと呼ばれる代替方法について説明しています。これは、MapReduce操作が完了に近づいたときにマスターによってスケジュールされます。これらは、進行中のタスクのマスターによってスケジュールされたタスクです。したがって、プライマリまたはバックアップが完了すると、MapReduce操作が完了します。

カウンター:イベントの発生をカウントしたい場合があります。このため、作成された場所をカウントします。各ワーカーのカウンター値は定期的にマスターに伝達されます。次に、マスターは成功したマップとリデュースタスクのカウンター値を集計し(そうです。プレゲルアグリゲーターはこの場所から来たようです)、MapReduce操作が完了するとユーザーコードに返します。マスターステータスでは現在のカウンター値も利用できるため、プロセスを監視している人間は、プロセスの動作を追跡できます。

さて、私は上記のすべての概念で、Hadoopがあなたにとって簡単なものになると思います。MapReduceの元の記事や関連することについて質問がある場合は、お知らせください。


4

ささいなことを言いたくありませんが、これはとても役に立ちました。それは非常に簡単です。

cat input | map | reduce > output

4

Pythonに精通している場合は、MapReduceの可能な最も簡単な説明を以下に示します。

In [2]: data = [1, 2, 3, 4, 5, 6]
In [3]: mapped_result = map(lambda x: x*2, data)

In [4]: mapped_result
Out[4]: [2, 4, 6, 8, 10, 12]

In [10]: final_result = reduce(lambda x, y: x+y, mapped_result)

In [11]: final_result
Out[11]: 42

生データの各セグメントがどのように個別に処理されたかを確認します。この場合は、2(MapReduceのマップ部分)が乗算されます。基づいてmapped_result、我々は結果があろうと結論付けた42削減のMapReduceの一部)。

この例からの重要な結論は、処理の各チャンクが別のチャンクに依存しないという事実です。たとえば、thread_1maps [1, 2, 3]thread_2mapsの場合[4, 5, 6]、両方のスレッドの最終的な結果は変わりません[2, 4, 6, 8, 10, 12]が、処理時間は半分になりました。同じことは、reduceオペレーションについても言え、MapReduceが並列計算でどのように機能するかの本質です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.