特にGoogleの超並列計算システムのコンテキストでは、map / reduceについて多くのことを聞きます。正確には何ですか?
回答:
GoogleのMapReduce研究出版ページの要約から:
MapReduceは、大規模なデータセットを処理および生成するためのプログラミングモデルおよび関連する実装です。ユーザーは、キーと値のペアを処理して中間のキーと値のペアのセットを生成するマップ関数と、同じ中間キーに関連付けられているすべての中間値をマージするreduce関数を指定します。
MapReduceの利点は、処理を複数の処理ノード(複数のサーバー)で並行して実行できるため、非常に適切に拡張できるシステムであるということです。
関数型プログラミングモデルに基づいているためmap
、reduce
ステップとステップにはそれぞれ副作用がありません(map
プロセスの各サブセクションの状態と結果は別のサブセクションに依存しません)。したがって、マッピングおよび縮小されるデータセットはそれぞれ分離できます。複数の処理ノードにわたって。
Joelのプログラミング言語はこれを行うことができますか?この記事では、検索エンジンを強化するMapReduceを考案するために、関数型プログラミングを理解することがGoogleでどのように不可欠であったかについて説明しています。関数型プログラミングと、それがスケーラブルなコードをどのように可能にするかについてよく知らない場合は、非常に良い読み物です。
関連する質問:mapreduceについて簡単に説明してください
それは私ができるよりもよく説明します。それは役に立ちますか?
Mapは、リスト上のすべてのアイテムに別の関数を適用して、すべての戻り値を含む別のリストを作成する関数です。(「fをxに適用する」という別の言い方は「fを呼び出してxを渡す」です。したがって、「呼び出す」の代わりに「適用する」と言う方が良い場合があります。)
これはおそらくマップがC#で書かれている方法です(それSelect
は呼び出され、標準ライブラリにあります):
public static IEnumerable<R> Select<T, R>(this IEnumerable<T> list, Func<T, R> func)
{
foreach (T item in list)
yield return func(item);
}
あなたはJavaの男であり、Joel Spolskyは、JavaがいかにくだらないかについてGROSSLY UNFAIR LIESに話すのが好きです(実際、彼は嘘をついていません、それはくだらないですが、私はあなたを倒そうとしています)、これが私の非常に大まかな試みですJavaバージョン(私はJavaコンパイラを持っておらず、Javaバージョン1.1を漠然と覚えています!):
// represents a function that takes one arg and returns a result
public interface IFunctor
{
object invoke(object arg);
}
public static object[] map(object[] list, IFunctor func)
{
object[] returnValues = new object[list.length];
for (int n = 0; n < list.length; n++)
returnValues[n] = func.invoke(list[n]);
return returnValues;
}
これは100万通りの方法で改善できると確信しています。しかし、それは基本的な考え方です。
削減は、リスト上のすべての項目を単一の値に変換する関数です。これを行うには、func
2つの項目を1つの値に変換する別の関数を指定する必要があります。最初の2つの項目をに与えることで機能しfunc
ます。次に、その結果と3番目の項目。次に、その結果を4番目の項目で、以下同様に、すべての項目がなくなり、1つの値が残るまで続けます。
C#ではreduceが呼び出されAggregate
、再び標準ライブラリにあります。Javaバージョンに直接スキップします。
// represents a function that takes two args and returns a result
public interface IBinaryFunctor
{
object invoke(object arg1, object arg2);
}
public static object reduce(object[] list, IBinaryFunctor func)
{
if (list.length == 0)
return null; // or throw something?
if (list.length == 1)
return list[0]; // just return the only item
object returnValue = func.invoke(list[0], list[1]);
for (int n = 1; n < list.length; n++)
returnValue = func.invoke(returnValue, list[n]);
return returnValue;
}
これらのJavaバージョンにはジェネリックスを追加する必要がありますが、Javaでそれを行う方法がわかりません。ただし、ファンクターを提供するために、匿名の内部クラスを渡すことができるはずです。
string[] names = getLotsOfNames();
string commaSeparatedNames = (string)reduce(names,
new IBinaryFunctor {
public object invoke(object arg1, object arg2)
{ return ((string)arg1) + ", " + ((string)arg2); }
}
うまくいけば、ジェネリックはギプスを取り除くでしょう。C#で同等のタイプセーフは次のとおりです。
string commaSeparatedNames = names.Aggregate((a, b) => a + ", " + b);
なぜこれが「かっこいい」のですか?大きな計算を小さな断片に分割して、さまざまな方法で元に戻すことができるようにする簡単な方法は、常にクールです。Googleがこのアイデアを適用する方法は、並列化です。これは、mapとreduceの両方を複数のコンピューターで共有できるためです。
ただし、重要な要件は、言語が関数を値として扱うことができるということではありません。どのオブジェクト指向言語でもそれを行うことができます。並列化の実際の要件は、func
mapおよびreduceに渡す小さな関数が、状態を使用または更新してはならないことです。渡された引数のみに依存する値を返す必要があります。そうしないと、すべてを並行して実行しようとすると、結果が完全に台無しになります。
非常に長いワフリーまたは非常に短い漠然としたブログ投稿のいずれかに最も不満を感じた後、私は最終的にこの非常に優れた厳密で簡潔な紙を発見しました。
次に、先に進み、Scalaに翻訳することで、より簡潔にしました。ここでは、ユーザーがアプリケーションのmap
とreduce
部分を指定するだけの最も単純なケースを提供しました。Hadoop / Sparkでは、厳密に言えば、プログラミングのより複雑なモデルが採用されており、ユーザーはここで概説されているさらに4つの関数を明示的に指定する必要があります:http://en.wikipedia.org/wiki/MapReduce#Dataflow
import scalaz.syntax.id._
trait MapReduceModel {
type MultiSet[T] = Iterable[T]
// `map` must be a pure function
def mapPhase[K1, K2, V1, V2](map: ((K1, V1)) => MultiSet[(K2, V2)])
(data: MultiSet[(K1, V1)]): MultiSet[(K2, V2)] =
data.flatMap(map)
def shufflePhase[K2, V2](mappedData: MultiSet[(K2, V2)]): Map[K2, MultiSet[V2]] =
mappedData.groupBy(_._1).mapValues(_.map(_._2))
// `reduce` must be a monoid
def reducePhase[K2, V2, V3](reduce: ((K2, MultiSet[V2])) => MultiSet[(K2, V3)])
(shuffledData: Map[K2, MultiSet[V2]]): MultiSet[V3] =
shuffledData.flatMap(reduce).map(_._2)
def mapReduce[K1, K2, V1, V2, V3](data: MultiSet[(K1, V1)])
(map: ((K1, V1)) => MultiSet[(K2, V2)])
(reduce: ((K2, MultiSet[V2])) => MultiSet[(K2, V3)]): MultiSet[V3] =
mapPhase(map)(data) |> shufflePhase |> reducePhase(reduce)
}
// Kinda how MapReduce works in Hadoop and Spark except `.par` would ensure 1 element gets a process/thread on a cluster
// Furthermore, the splitting here won't enforce any kind of balance and is quite unnecessary anyway as one would expect
// it to already be splitted on HDFS - i.e. the filename would constitute K1
// The shuffle phase will also be parallelized, and use the same partition as the map phase.
abstract class ParMapReduce(mapParNum: Int, reduceParNum: Int) extends MapReduceModel {
def split[T](splitNum: Int)(data: MultiSet[T]): Set[MultiSet[T]]
override def mapPhase[K1, K2, V1, V2](map: ((K1, V1)) => MultiSet[(K2, V2)])
(data: MultiSet[(K1, V1)]): MultiSet[(K2, V2)] = {
val groupedByKey = data.groupBy(_._1).map(_._2)
groupedByKey.flatMap(split(mapParNum / groupedByKey.size + 1))
.par.flatMap(_.map(map)).flatten.toList
}
override def reducePhase[K2, V2, V3](reduce: ((K2, MultiSet[V2])) => MultiSet[(K2, V3)])
(shuffledData: Map[K2, MultiSet[V2]]): MultiSet[V3] =
shuffledData.map(g => split(reduceParNum / shuffledData.size + 1)(g._2).map((g._1, _)))
.par.flatMap(_.map(reduce))
.flatten.map(_._2).toList
}
Mapは、配列に適用できるネイティブJSメソッドです。元の配列のすべての要素にマップされた関数の結果として、新しい配列が作成されます。したがって、function(element){return element * 2;}をマップすると、すべての要素が2倍になった新しい配列が返されます。元の配列は変更されません。
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
Reduceは、配列にも適用できるネイティブJSメソッドです。配列に関数を適用し、アキュムレータと呼ばれる初期出力値を持ちます。配列内の各要素をループし、関数を適用して、それらを単一の値(アキュムレータとして開始)に減らします。必要な出力を得ることができるので便利です。そのタイプのアキュムレータから始める必要があります。したがって、何かをオブジェクトに縮小したい場合は、アキュムレータ{}から始めます。
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce?v=a
MapReduce:
大きなものを実行するために、オフィス内のさまざまなコンピューターの計算能力を使用できます。難しい部分は、異なるコンピューター間でタスクを分割することです。これは、MapReduceライブラリによって実行されます。
基本的な考え方は、ジョブをマップとリデュースの2つの部分に分割することです。Mapは基本的に問題を取り上げ、それをサブパーツに分割し、サブパーツを異なるマシンに送信します。そのため、すべてのピースが同時に実行されます。Reduceは、サブパーツから結果を取得し、それらを組み合わせて1つの回答を取得します。
入力はレコードのリストです。マップ計算の結果は、キーと値のペアのリストです。Reduceは、同じキーを持つ値の各セットを取得し、それらを1つの値に結合します。ジョブが100個に分割されたのか2個に分割されたのかはわかりません。最終結果は、単一のマップの結果とほとんど同じように見えます。
簡単なマップを見て、プログラムを減らしてください:
マップ関数は、元のリストにいくつかの関数を適用するために使用されるため、新しいリストが生成されます。Pythonのmap()関数は、関数とリストを引数として受け取ります。リストの各項目に関数を適用すると、新しいリストが返されます。
li = [5, 7, 4, 9]
final_list = list(map(lambda x: x*x , li))
print(final_list) #[25, 49, 16, 81]
Pythonのreduce()関数は、関数とリストを引数として受け取ります。この関数はラムダ関数とリストを使用して呼び出され、新しい縮小結果が返されます。これにより、リストのペアに対して繰り返し操作が実行されます。
#reduce func to find product/sum of list
x=(1,2,3,4)
from functools import reduce
reduce(lambda a,b:a*b ,x) #24