Lección 8 - Algebra abstracta para procesamiento de datos

Objetivo

El objetivo de esta lección es presentar una introducción a los conceptos de algebra abstracta como herramienta para el procesamiento de datos.

Algebra para procesamiento de datos

Comencemos por explorar las propiedades algebráicas de una operación común como la suma.

La suma tiene algunas propiedades que son relevantes para el procesamiento de datos, una en particular es la asociatividad,

  • Asociatividad: dada una expresión con dos o más ocurrencias del mismo operador asociativo, el orden en que se realicen las operaciones no afecta el resultado.
$$(a + b) + c = a + (b + c)$$

La razón por la que esto es importante es por que si aplicamos la operación de suma en un sistema distribuido, no importa que las sumas se realicen de forma distribuida siempre y cuando todas las sumas se apliquen eventualmente.

Veamos algunos ejemplos


In [68]:
val a = List(1,2,3,4,5,6)
println(a.reduceLeft(_ + _))
println(a.reduceRight(_ * _))
println(a.reduce((a,b) => if (a > b) a else b))
println(a.reduce((a,b) => if (a < b) a else b))


21
720
6
1

Ahora supongamos que queremos operar sobre un elemento neutro.


In [8]:
println(a.fold(0)(_ + _))
println(a.fold(1)(_ * _))
println(a.fold(Int.MinValue)((a,b) => if (a > b) a else b))
println(a.fold(Int.MaxValue)((a,b) => if (a < b) a else b))


21
720
6
1

En este punto es más o menos claro que este principio puede aplicar a otras operaciones, por ejemplo obtener el máximo o el mínimo de un conjunto de datos, etc.

Ahora generalicemos este comportamiento a un objeto abstracto.


In [13]:
trait Monoid[T] {
 def zero: T
 def plus(a: T, b: T): T
}

object IntMax extends Monoid[Int] {
    def zero = Int.MinValue
    def plus(a: Int,b: Int) = if (a > b) a else b
}

object IntMin extends Monoid[Int] {
    def zero = Int.MaxValue
    def plus(a: Int,b: Int) = if (a < b) a else b
}

object IntSum extends Monoid[Int] {
    def zero = 0
    def plus(a: Int,b: Int) = (a + b)
}

object StringSum extends Monoid[String] {
    def zero = ""
    def plus(a: String,b: String) = (a + b)
}




Out[13]:
StringSum$@180b686e

In [15]:
def printSum(a:Seq[Int], op: Monoid[Int]) {
  println(a.fold(op.zero)(op.plus))
}

println(a.fold(IntMax.zero)(IntMax.plus))
println(a.fold(IntMin.zero)(IntMin.plus))
println(a.fold(IntSum.zero)(IntSum.plus))
println(a.map(_.toString).fold(StringSum.zero)(StringSum.plus))


6
1
21
123456

Semigrupos y Monoides

El objeto abstracto que definimos es una representación de una estructura algebráica conocida como monoide.

Un semigrupo es un conjunto $M$ que satisface la propiedad de asociatividad bajo la operación $\circ$:

  • $\forall \; x,y \in M$, $x \circ y \in M$ (cerradura)
  • $\forall \; x,y,z \in M$, $(x \circ y) \circ z = x \circ (y \circ z)$ (asociatividad)

Un monoide es un semigrupo que adicionalmente satisface la propiedad de identidad.

  • $\exists \; e \in M$ tal que $\forall \; x \in M$, $e \circ x = x \circ e$ (identidad)

Monoides y computo distribuido

Es importante recalcar la importancia de poder abstraer estas operaciones:

  • si el conjunto de datos es un semigrupo bajo cierta operación, podemos confiar que un sistema distribuido que aplique estas operaciones correctamente sin tener que preocuparse de que operación se está realizando
  • si el conjunto de datos es un monoide bajo cierta operación podemos scar ventajas de matrices ralas, ya que la propiedad de identidad nos permite operar sobre los elementos no especificados

Grupos y Anillos

Un grupo es es un monoide que adicionalmente satisface la propiedad de inversa

  • $\exists \; y \in G$ tal que $\forall \; x \in M$, $x \circ y = y \circ x= e$ (inverso)

Un grupo abeliano satisface además la propiedad de conmutatividad

Un anillo es un grupo abeliano sobre la suma que además:

  • Tiene cerradura en la multiplicación
  • Es asociativa en la multiplicación
  • Satisface la propiedad distributiva de la suma sobre la multiplicación

Típicamente se utilizan anillos en computo distribuido es para resolver problemas matriciales, con objetos que no son núméricos.

Un ejemplo de es el uso de un anillo para resolver el problema de caminos más cortos sobre un grafo.

Procesamiento de datos


In [ ]:
%libraryDependencies += "org.apache.spark" %% "spark-core" % "0.9.1"

In [16]:
%libraryDependencies += "com.twitter" %% "algebird-core" % "0.5.0"





In [ ]:
%resolvers += "Apache Spark" at "http://repo.maven.apache.org/maven2/"

In [ ]:
%update


[info] Resolving org.apache.spark#spark-core_2.10;0.9.1 ...
[info] Resolving org.apache.hadoop#hadoop-client;1.0.4 ...
[info] Resolving org.apache.hadoop#hadoop-core;1.0.4 ...
[info] Resolving xmlenc#xmlenc;0.52 ...
[info] Resolving commons-codec#commons-codec;1.4 ...
[info] Resolving org.apache.commons#commons-math;2.1 ...
[info] Resolving commons-configuration#commons-configuration;1.6 ...
[info] Resolving commons-collections#commons-collections;3.2.1 ...
[info] Resolving commons-lang#commons-lang;2.4 ...
[info] Resolving commons-logging#commons-logging;1.1.1 ...
[info] Resolving commons-digester#commons-digester;1.8 ...
[info] Resolving commons-beanutils#commons-beanutils;1.7.0 ...
[info] Resolving commons-beanutils#commons-beanutils-core;1.8.0 ...
[info] Resolving commons-net#commons-net;1.4.1 ...
[info] Resolving oro#oro;2.0.8 ...
[info] Resolving commons-el#commons-el;1.0 ...
[info] Resolving hsqldb#hsqldb;1.8.0.10 ...
[info] Resolving net.java.dev.jets3t#jets3t;0.7.1 ...
[info] Resolving commons-httpclient#commons-httpclient;3.1 ...
[info] Resolving org.apache.avro#avro;1.7.4 ...
[info] Resolving org.codehaus.jackson#jackson-core-asl;1.8.8 ...
[info] Resolving org.codehaus.jackson#jackson-mapper-asl;1.8.8 ...
[info] Resolving com.thoughtworks.paranamer#paranamer;2.3 ...
[info] Resolving org.xerial.snappy#snappy-java;1.0.5 ...
[info] Resolving org.apache.commons#commons-compress;1.4.1 ...
[info] Resolving org.tukaani#xz;1.0 ...
[info] Resolving org.slf4j#slf4j-api;1.7.2 ...
[info] Resolving org.apache.avro#avro-ipc;1.7.4 ...
[info] Resolving org.mortbay.jetty#jetty;6.1.26 ...
[info] Resolving org.mortbay.jetty#jetty-util;6.1.26 ...
[info] Resolving org.mortbay.jetty#servlet-api;2.5-20081211 ...
[info] Resolving org.apache.velocity#velocity;1.7 ...
[info] Resolving org.apache.zookeeper#zookeeper;3.4.5 ...
[info] Resolving org.slf4j#slf4j-log4j12;1.7.2 ...
[info] Resolving log4j#log4j;1.2.17 ...
[info] Resolving jline#jline;0.9.94 ...
[info] Resolving junit#junit;3.8.1 ...
[info] Resolving org.eclipse.jetty#jetty-server;7.6.8.v20121106 ...
[info] Resolving org.eclipse.jetty.orbit#javax.servlet;2.5.0.v201103041518 ...
[info] Resolving org.eclipse.jetty#jetty-continuation;7.6.8.v20121106 ...
[info] Resolving org.eclipse.jetty#jetty-http;7.6.8.v20121106 ...
[info] Resolving org.eclipse.jetty#jetty-io;7.6.8.v20121106 ...
[info] Resolving org.eclipse.jetty#jetty-util;7.6.8.v20121106 ...
[info] Resolving com.google.guava#guava;14.0.1 ...
[info] Resolving com.google.code.findbugs#jsr305;1.3.9 ...
[info] Resolving com.ning#compress-lzf;1.0.0 ...
[info] Resolving com.twitter#chill_2.10;0.3.1 ...
[info] Resolving com.twitter#chill-java;0.3.1 ...
[info] Resolving com.esotericsoftware.kryo#kryo;2.21 ...
[info] Resolving com.esotericsoftware.reflectasm#reflectasm;1.07 ...
[info] Resolving org.ow2.asm#asm;4.0 ...
[info] Resolving com.esotericsoftware.minlog#minlog;1.2 ...
[info] Resolving org.objenesis#objenesis;1.2 ...
[info] Resolving org.scala-lang#scala-library;2.10.3 ...
[info] Resolving org.ow2.asm#asm-commons;4.0 ...
[info] Resolving org.ow2.asm#asm-tree;4.0 ...
[info] Resolving org.spark-project.akka#akka-remote_2.10;2.2.3-shaded-protobuf ...
[info] Resolving org.spark-project.akka#akka-actor_2.10;2.2.3-shaded-protobuf ...
[info] Resolving com.typesafe#config;1.0.2 ...
[info] Resolving io.netty#netty;3.6.6.Final ...
[info] Resolving org.spark-project.protobuf#protobuf-java;2.4.1-shaded ...
[info] Resolving org.uncommons.maths#uncommons-maths;1.2.2a ...
[info] Resolving org.spark-project.akka#akka-slf4j_2.10;2.2.3-shaded-protobuf ...
[info] Resolving net.liftweb#lift-json_2.10;2.5.1 ...
[info] Resolving com.thoughtworks.paranamer#paranamer;2.4.1 ...
[info] Resolving it.unimi.dsi#fastutil;6.4.4 ...
[info] Resolving colt#colt;1.2.0 ...
[info] Resolving concurrent#concurrent;1.3.4 ...
[info] Resolving org.apache.mesos#mesos;0.13.0 ...
[info] Resolving com.google.protobuf#protobuf-java;2.4.1 ...
[info] Resolving io.netty#netty-all;4.0.13.Final ...
[info] Resolving com.clearspring.analytics#stream;2.4.0 ...
[info] Resolving com.codahale.metrics#metrics-core;3.0.0 ...
[info] Resolving com.codahale.metrics#metrics-jvm;3.0.0 ...
[info] Resolving com.codahale.metrics#metrics-json;3.0.0 ...
[info] Resolving com.fasterxml.jackson.core#jackson-databind;2.2.2 ...
[info] Resolving com.fasterxml.jackson.core#jackson-annotations;2.2.2 ...
[info] Resolving com.fasterxml.jackson.core#jackson-core;2.2.2 ...
[info] Resolving com.codahale.metrics#metrics-graphite;3.0.0 ...
[info] Resolving com.twitter#algebird-core_2.10;0.5.0 ...
[info] Resolving com.googlecode.javaewah#JavaEWAH;0.6.6 ...
[info] downloading https://oss.sonatype.org/content/repositories/releases/com/twitter/algebird-core_2.10/0.5.0/algebird-core_2.10-0.5.0.jar ...
[info] 	[SUCCESSFUL ] com.twitter#algebird-core_2.10;0.5.0!algebird-core_2.10.jar (7010ms)


Algebird

A continuación utilizaremos la librería para algebra abstracta Algebird, estos ejemplos provienen de https://github.com/twitter/algebird/wiki/Algebird-Examples-with-REPL


In [ ]:
import com.twitter.algebird._

Comencemos por definir un filtro de Bloom


In [23]:
val NUM_HASHES = 6
val WIDTH = 32
val SEED = 1
val bfMonoid = new BloomFilterMonoid(NUM_HASHES, WIDTH, SEED)
val bf = bfMonoid.create("1", "2", "3", "4", "100")
println(bf.contains("1"))
println(bf.contains("0"))


ApproximateBoolean(true,0.9290349745708529)
ApproximateBoolean(false,1.0)

Ahora sumemos operemos sobre varios filtros "provenientes de otras máquinas"


In [30]:
val bfList =  List(bf, 
                   bfMonoid.create("0", "2", "3", "4", "400"), 
                   bfMonoid.create("8.5"))
val bfSum =bfList.reduce(bfMonoid.plus(_,_))
println(bfSum.contains("1"))
println(bfSum.contains("43"))
println(bfSum.contains("0"))
println(bfSum.contains("8.5"))


ApproximateBoolean(true,0.7218319753855025)
ApproximateBoolean(false,1.0)
ApproximateBoolean(true,0.7218319753855025)
ApproximateBoolean(true,0.7218319753855025)

Apliquemos el conteo de elementos con HyperLogLog


In [2]:
import HyperLogLog._
val hll = new HyperLogLogMonoid(4)




Out[2]:
com.twitter.algebird.HyperLogLogMonoid@2d35ff1b

In [36]:
val data = List(1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 2, 2, 8, 9, 11, 30, 40)
val sumHll = data.map(hll(_)).reduce(hll.plus(_,_))
val approxSizeOf = hll.sizeOf(sumHll)
println(approxSizeOf.estimate)
println(data.toSet.size)


9
10

Ahora sumemos el resultado "proveniente de cada máquina"


In [42]:
val data2 = List(20, 35, 40)
val sumHll2 = data2.map(hll(_)).reduce(hll.plus(_,_))




Out[42]:
DenseHLL(4,Vector(0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 3))

In [43]:
val totalHll = hll.sum(List(sumHll, sumHll2))




Out[43]:
DenseHLL(4,Vector(0, 0, 4, 0, 1, 0, 1, 0, 0, 0, 2, 2, 1, 1, 0, 3))

In [46]:
println(hll.sizeOf(totalHll).estimate)
println((data ++ data2).toSet.size)


11
12

Un ejemplo más con Min-Hashing


In [47]:
val numHashes = 10
val numBands = MinHasher.pickBands(0.9, numHashes)
val minHasher = new MinHasher32(numHashes, numBands)




Out[47]:
com.twitter.algebird.MinHasher32@2042a235

In [51]:
val sig1 = List(1,2,3,4,5).map(minHasher.init(_)).reduce(minHasher.plus)
val sig2 = List(1,2,3,4,7).map(minHasher.init(_)).reduce(minHasher.plus)
val sig3 = List(8,9,2,3,1).map(minHasher.init(_)).reduce(minHasher.plus)

println(minHasher.similarity(sig1, sig2))
println(minHasher.similarity(sig1, sig3))
println(minHasher.similarity(sig2, sig3))


0.7
0.2
0.3

Count-Min-Sketch

Es un método que tiene similitud con los filtrros de bloom donde definimos con que probabilidad el conteo de un elemento es correcto es $p >= 1 - \text{delta}$.


In [66]:
val delta= 1E-5
val eps = 0.001
val seed = 1

val cmsMonoid = new CountMinSketchMonoid(eps, delta, seed)
val data = List(1L, 1L, 2L, 2L, 3L, 3L, 4L, 4L, 5L, 5L, 2L, 2L, 8L, 9L, 11L, 30L, 40L)
val cms = cmsMonoid.create(data)
println(cms.totalCount)
println(data.size)

println(cms.frequency(3L).estimate)
println(cms.frequency(2L).estimate)


17
17
2
4

Agreguemos los conteos provenientes de otra máquina


In [64]:
val data2 = List(1L, 1L, 2L, 2L, 3L, 3L, 4L, 4L, 5L, 5L, 2L, 2L, 8L, 9L, 11L, 30L, 40L)
val cms2 = cmsMonoid.create(data2)

val totalCms = List(cms, cms2).reduce(cmsMonoid.plus(_,_))

println(totalCms.totalCount)

println(totalCms.frequency(3L).estimate)
println(totalCms.frequency(2L).estimate)


34
4
8


In [ ]: