แผนที่ / ลดคืออะไร?


84

ฉันได้ยินเกี่ยวกับแผนที่ / ลดจำนวนมากโดยเฉพาะอย่างยิ่งในบริบทของระบบประมวลผลคู่ขนานของ Google มันคืออะไรกันแน่?


3
@Rinat: อย่างไรก็ตามก็ยังเป็นคำถามที่ดี
Bill Karwin

3
แน่นอนว่าฉันทำได้และทำได้กับ Google แต่ (ก) SO ตั้งใจที่จะมีคำตอบสำหรับคำถามที่สำคัญทั้งหมด (เราได้รับการสนับสนุนแม้กระทั่งโพสต์คำถามที่เรามีคำตอบอยู่แล้ว) และ (b) ฉันต้องการให้ชุมชนนี้ดำเนินการต่อไป
Lawrence Dol

คำตอบ:


69

จากบทคัดย่อของหน้าสิ่งพิมพ์งานวิจัยMapReduceของ Google :

MapReduce เป็นรูปแบบการเขียนโปรแกรมและการใช้งานที่เกี่ยวข้องสำหรับการประมวลผลและสร้างชุดข้อมูลขนาดใหญ่ ผู้ใช้ระบุฟังก์ชันแผนที่ที่ประมวลผลคู่คีย์ / ค่าเพื่อสร้างชุดของคู่คีย์ / ค่ากลางและฟังก์ชันลดที่รวมค่ากลางทั้งหมดที่เชื่อมโยงกับคีย์กลางเดียวกัน

ข้อดีของ MapReduce คือการประมวลผลสามารถดำเนินการควบคู่กันบนโหนดการประมวลผลหลายโหนด (เซิร์ฟเวอร์หลายเครื่อง) จึงเป็นระบบที่ปรับขนาดได้ดีมาก

เนื่องจากมันขึ้นอยู่กับรูปแบบการเขียนโปรแกรมเชิงฟังก์ชันขั้นตอนmapและreduceแต่ละขั้นตอนจึงไม่มีผลข้างเคียงใด ๆ (สถานะและผลลัพธ์จากแต่ละส่วนย่อยของmapกระบวนการไม่ขึ้นอยู่กับส่วนย่อยอื่น) ดังนั้นชุดข้อมูลที่ถูกแมปและลดลงจึงสามารถแยกออกจากกันได้ บนโหนดการประมวลผลหลายโหนด

ภาษาโปรแกรมของคุณ Joel สามารถทำสิ่งนี้ได้หรือไม่? ชิ้นกล่าวถึงความเข้าใจในการเขียนโปรแกรมเชิงฟังก์ชันเป็นสิ่งสำคัญใน Google เพื่อสร้าง MapReduce ซึ่งขับเคลื่อนเครื่องมือค้นหา เป็นการอ่านที่ดีมากหากคุณไม่คุ้นเคยกับการเขียนโปรแกรมเชิงฟังก์ชันและวิธีที่อนุญาตให้ใช้โค้ดที่ปรับขนาดได้

ดูเพิ่มเติมที่: Wikipedia: MapReduce

คำถามที่เกี่ยวข้อง: กรุณาอธิบาย mapreduce ง่ายๆ


3
อธิบายได้ดีเยี่ยม และสำหรับ Software Monkey M / R นั้นง่ายอย่างเหลือเชื่อที่จะนำไปใช้ในทุกสิ่งเมื่อคุณเข้าใจและไม่ จำกัด เฉพาะตัวอย่างที่ให้ไว้ที่นี่ มีหลายวิธีที่จะทำให้คุณเข้าใจมันเราอาจคิดว่ามันเป็นนักสะสมและช่องทาง
Esko


16

แผนที่เป็นฟังก์ชันที่ใช้ฟังก์ชันอื่นกับรายการทั้งหมดในรายการเพื่อสร้างรายการอื่นที่มีค่าส่งคืนทั้งหมด (อีกวิธีหนึ่งในการพูดว่า "ใช้ f กับ x" คือ "เรียก f โดยส่ง x" ดังนั้นบางครั้งการพูดว่า "ใช้" แทนที่จะเป็น "call" จะดีกว่า)

นี่คือวิธีที่อาจเขียนแผนที่ด้วย 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 ชอบที่จะบอก GROSSLY UNFAIR LIES เกี่ยวกับ 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;
}

ฉันมั่นใจว่าสิ่งนี้สามารถปรับปรุงได้หลายล้านวิธี แต่เป็นความคิดพื้นฐาน

ลดเป็นฟังก์ชันที่เปลี่ยนรายการทั้งหมดในรายการให้เป็นค่าเดียว ในการดำเนินการนี้จำเป็นต้องกำหนดฟังก์ชันอื่นfuncที่เปลี่ยนสองรายการให้เป็นค่าเดียว funcมันจะทำงานโดยให้ทั้งสองรายการแรกที่จะ จากนั้นผลลัพธ์ของสิ่งนั้นพร้อมกับรายการที่สาม จากนั้นผลลัพธ์ของสิ่งนั้นกับรายการที่สี่ไปเรื่อย ๆ จนกว่ารายการทั้งหมดจะหายไปและเราจะเหลือเพียงค่าเดียว

ใน C # ลดจะถูกเรียก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 เหล่านี้ต้องการ generics เพิ่ม แต่ฉันไม่รู้วิธีทำใน Java แต่คุณควรจะสามารถผ่านชั้นเรียนภายในที่ไม่ระบุตัวตนได้เพื่อให้ functors:

string[] names = getLotsOfNames();

string commaSeparatedNames = (string)reduce(names, 
   new IBinaryFunctor {
       public object invoke(object arg1, object arg2)
           { return ((string)arg1) + ", " + ((string)arg2); }
   }

หวังว่ายาชื่อสามัญจะกำจัดการร่ายได้ เทียบเท่า typesafe ใน C # คือ:

string commaSeparatedNames = names.Aggregate((a, b) => a + ", " + b);

ทำไมถึง "เจ๋ง" ขนาดนี้? วิธีง่ายๆในการแยกการคำนวณขนาดใหญ่ออกเป็นชิ้นเล็ก ๆ เพื่อให้สามารถนำกลับมารวมกันได้หลายวิธีนั้นยอดเยี่ยมเสมอ วิธีที่ Google ใช้แนวคิดนี้คือการทำให้ขนานกันเนื่องจากทั้งแผนที่และลดสามารถใช้ร่วมกันบนคอมพิวเตอร์หลายเครื่องได้

แต่ข้อกำหนดหลักไม่ใช่ว่าภาษาของคุณสามารถใช้ฟังก์ชันเป็นค่าได้ ภาษา OO ใด ๆ ก็ทำได้ ข้อกำหนดที่แท้จริงสำหรับการขนานคือfuncฟังก์ชันเล็ก ๆ น้อย ๆ ที่คุณส่งผ่านไปยังแมปและลดจะต้องไม่ใช้หรืออัปเดตสถานะใด ๆ พวกเขาจะต้องส่งคืนค่าที่ขึ้นอยู่กับอาร์กิวเมนต์ที่ส่งไปให้เท่านั้น มิฉะนั้นผลลัพธ์จะถูกทำให้สับสนอย่างสมบูรณ์เมื่อคุณพยายามเรียกใช้ทั้งสิ่งแบบขนาน


2
โดยรวมแล้วคำตอบที่ดีคุ้มค่า +1; ไม่ชอบ jab ที่ Java - แต่ฉันพลาดค่าฟังก์ชันนับตั้งแต่ย้ายไปที่ Java จาก C และยอมรับว่าความพร้อมใช้งานของพวกเขาค้างชำระใน Java เป็นเวลานาน
Lawrence Dol

1
ไม่ใช่การกระทุ้งที่รุนแรงที่ Java - มีข้อบกพร่องสามข้อหรือมากกว่านั้นที่เพียงพอที่จะทำให้ฉันชอบ C # ในตอนนี้ แต่ C # ก็มีรายการข้อบกพร่องเช่นกันซึ่งอาจทำให้ฉันต้องการภาษาอื่นในสักวันหนึ่ง
Daniel Earwicker

อย่างไรก็ตามฉันจะชอบถ้ามีคนแก้ไขตัวอย่างเพื่อให้พวกเขาใช้ Java generics ถ้าเป็นไปได้จริง หรือถ้าแก้ไขไม่ได้ให้โพสต์ตัวอย่างที่นี่แล้วฉันจะแก้ไข
Daniel Earwicker

ฉันเริ่มแก้ไข แต่เมธอด map () สร้างอาร์เรย์ของประเภทการส่งคืน Java ไม่อนุญาตให้สร้างอาร์เรย์ประเภททั่วไป ฉันสามารถเปลี่ยนมันเพื่อใช้รายการ (และอาจแปลงเป็นอาร์เรย์) แต่ตอนนั้นฉันหมดความทะเยอทะยาน
Michael Myers

1
ไวยากรณ์การปิดที่คล้ายกับ (a, b) => a + "," + b เป็นสิ่งที่ฉันรอคอยอย่างมากใน Java 7 โดยเฉพาะอย่างยิ่งกับสิ่งใหม่ ๆ ของ API ที่ดูเหมือนว่าจะเข้าสู่ไวยากรณ์นั้น ได้ทำสิ่งต่างๆเช่นนี้ให้สะอาดขึ้นมาก แย่จังดูเหมือนมันจะไม่เกิดขึ้น
Adam Jaskiewicz

2

หลังจากผิดหวังมากที่สุดกับวาฟเฟิลที่ยาวมากหรือบล็อกโพสต์สั้น ๆ ที่คลุมเครือในที่สุดฉันก็ค้นพบบทความที่กระชับอย่างดีนี้

จากนั้นฉันก็ดำเนินการต่อและทำให้กระชับมากขึ้นโดยการแปลเป็น 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
}


0

แผนที่เป็นวิธี JS ดั้งเดิมที่สามารถใช้กับอาร์เรย์ได้ สร้างอาร์เรย์ใหม่อันเป็นผลมาจากฟังก์ชันบางอย่างที่แมปกับทุกองค์ประกอบในอาร์เรย์เดิม ดังนั้นหากคุณแมปฟังก์ชัน (องค์ประกอบ) {return element * 2;} มันจะส่งคืนอาร์เรย์ใหม่โดยทุกองค์ประกอบจะเพิ่มเป็นสองเท่า อาร์เรย์เดิมจะไม่มีการแก้ไข

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map

ลดเป็นวิธี JS ดั้งเดิมที่สามารถนำไปใช้กับอาร์เรย์ได้ ใช้ฟังก์ชันกับอาร์เรย์และมีค่าเอาต์พุตเริ่มต้นที่เรียกว่าแอคคูมูเลเตอร์ มันวนผ่านแต่ละองค์ประกอบในอาร์เรย์ใช้ฟังก์ชันและลดให้เป็นค่าเดียว (ซึ่งเริ่มต้นจากตัวสะสม) มันมีประโยชน์เพราะคุณสามารถมีเอาท์พุทที่คุณต้องการได้คุณก็ต้องเริ่มด้วยตัวสะสมประเภทนั้น ดังนั้นถ้าฉันต้องการลดบางสิ่งลงในวัตถุฉันจะเริ่มต้นด้วยตัวสะสม {}

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce?v=a


0

MapReduce:

ในการเรียกใช้สิ่งที่มีขนาดใหญ่เราสามารถใช้พลังการคำนวณของคอมพิวเตอร์เครื่องอื่นในสำนักงานของเรา ส่วนที่ยากคือการแบ่งงานระหว่างคอมพิวเตอร์เครื่องอื่นทำโดยห้องสมุด MapReduce

แนวคิดพื้นฐานคือคุณแบ่งงานออกเป็นสองส่วนคือแผนที่และส่วนลด โดยทั่วไปแผนที่จะใช้ปัญหาแบ่งออกเป็นส่วนย่อยและส่งชิ้นส่วนย่อยไปยังเครื่องจักรต่างๆดังนั้นทุกชิ้นจึงทำงานพร้อมกัน ลดนำผลลัพธ์จากส่วนย่อยและรวมกลับเข้าด้วยกันเพื่อให้ได้คำตอบเดียว

อินพุตคือรายการของบันทึกผลลัพธ์ของการคำนวณแผนที่คือรายการคู่คีย์ / ค่า Reduce นำชุดของค่าแต่ละชุดที่มีคีย์เดียวกันมารวมกันเป็นค่าเดียวคุณไม่สามารถบอกได้ว่างานนั้นแบ่งออกเป็น 100 ชิ้นหรือ 2 ชิ้น ผลลัพธ์สุดท้ายดูเหมือนผลของแผนที่เดียว

โปรดดูแผนที่อย่างง่ายและลดโปรแกรม:

ฟังก์ชั่นแผนที่ใช้เพื่อใช้ฟังก์ชันบางอย่างในรายการเดิมของเราและรายการใหม่จึงถูกสร้างขึ้น ฟังก์ชัน map () ใน Python ใช้ฟังก์ชันและรายการเป็นอาร์กิวเมนต์ รายการใหม่จะถูกส่งคืนโดยใช้ฟังก์ชันกับแต่ละรายการของรายการ

li = [5, 7, 4, 9] 
final_list = list(map(lambda x: x*x , li)) 
print(final_list)  #[25, 49, 16, 81]

ฟังก์ชันลด () ใน Python ใช้ฟังก์ชันและรายการเป็นอาร์กิวเมนต์ ฟังก์ชันถูกเรียกด้วยฟังก์ชันแลมบ์ดาและรายการและผลลัพธ์ที่ลดลงใหม่จะถูกส่งกลับ การดำเนินการนี้จะดำเนินการซ้ำ ๆ กับคู่ของรายการ

#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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.