特にGoogleの超並列計算システムのコンテキストでは、map/reduceについてよく耳にします。正確には何ですか?
Googleの要約 MapReduce 研究発表ページから:
MapReduceは、大きなデータセットを処理および生成するためのプログラミングモデルおよび関連する実装です。ユーザーは、キーと値のペアを処理して一連の中間キーと値のペアを生成するマップ関数と、同じ中間キーに関連付けられているすべての中間値をマージするリデュース関数を指定します。
MapReduceの利点は、処理を複数の処理ノード(複数のサーバー)で並行して実行できるため、非常に適切にスケーリングできるシステムであることです。
関数型プログラミング モデルに基づいているため、map
およびreduce
ステップにはそれぞれ副作用がありません(map
プロセスの各サブセクションの状態と結果は別のものに依存しません)。マップおよび削減されるデータセットは、それぞれ複数の処理ノードに分離できます。
Joelの プログラミング言語でこれを実行できるか? は、Googleで関数型プログラミングを理解することが、検索エンジンを強化するMapReduceを生み出すためにどのように不可欠であったかを説明しています。関数型プログラミングに精通しておらず、スケーラブルなコードをどのように実現できるかについては、非常に読みやすくなっています。
関連質問: 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おいであり、ジョエル・スポルスキーはお粗末な不公平な嘘についてJavaが実際には嘘をついていない、それはくだらない、しかし私はあなたを勝ち取ろうとしている)、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万通りの方法で改善できると確信しています。しかし、それは基本的な考え方です。
Reduceは、リスト上のすべてのアイテムを単一の値に変換する関数です。これを行うには、2つのアイテムを1つの値に変換する別の関数func
を指定する必要があります。最初の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の両方を複数のコンピューターで共有できるためです。
ただし、重要な要件は、言語が関数を値として処理できることではありません。 Any OO言語はそれを行うことができます。並列化の実際の要件は、mapおよびreduceに渡す小さなfunc
関数が状態を使用または更新してはならないことです。それらは状態を返す必要があります。渡された引数のみに依存する値。それ以外の場合、全体を並列実行しようとすると、結果が完全に台無しになります。
それは私ができることよりもよく説明します。それは役に立ちますか?
非常に長いワッフルまたは非常に短い漠然としたブログ投稿のどちらかで最もイライラした後、私は最終的にこれを発見しました 非常に良い厳密な簡潔な論文 。
次に、先に進み、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
}
MapReduce:
大きなものを実行するには、オフィス内の別のコンピューターの計算能力を使用できます。難しい部分は、タスクを異なるコンピューター間で分割することです。これは、MapReduceライブラリによって行われます。
基本的な考え方は、ジョブをMapとReduceの2つの部分に分割することです。マップは基本的に問題を取り、それをサブパートに分割し、サブパートを異なるマシンに送信します。そのため、すべてのピースが同時に実行されます。 Reduceはサブパートから結果を取得し、それらを組み合わせて単一の回答を取得します。
入力はレコードのリストです。マップ計算の結果はキー/値ペアのリストです。 Reduceは、同じキーを持つ値の各セットを取り、それらを単一の値に結合します。ジョブが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
マップは、配列に適用できるネイティブの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