Usted está aquí: Inicio Ingeniería Informática Aprendizaje Automático para el Análisis de Datos tutorialSparklyr.nb.html

Acciones de Documento
  • Marcadores (bookmarks)
  • Exportación de LTI
Autor: aler

Información sobre sparklyr aquí: [https://spark.rstudio.com/]

# Con esto se instala:
# install.packages("sparklyr")
# Además, hay que instalar Spark. Con esto se instala una copia local. Es necesario Java 8 (con Java 9 no funciona)
# spark_install(version = "2.1.0")
library(dplyr)

Attaching package: <U+393C><U+3E31>dplyr<U+393C><U+3E32>

The following objects are masked from <U+393C><U+3E31>package:stats<U+393C><U+3E32>:

    filter, lag

The following objects are masked from <U+393C><U+3E31>package:base<U+393C><U+3E32>:

    intersect, setdiff, setequal, union

Warning message:
In class(object) <- "environment" :
  Setting class(x) to "environment" sets attribute to NULL; result will no longer be an S4 object

En primer lugar, establecemos una conexión con Spark (o sea, con el cluster manager). sc es una variable que permite usar el cluster.

sc <- spark_connect(master = "local")
* Using Spark: 2.1.0

Vamos a volver a usar el conjunto de datos de Titanic, para ver si podemos predecir si alguien se va a salvar.

La siguiente instrucción copia el data.frame local titanic_train al cluster. Eso significa que titanic_train está contenido en la memoria de nuestro ordenador (del ordenador en el que se ejecuta el driver), mientras que titanic_tbl es una variable que representa a un data.frame dividido en dos particiones. En teoría, cada partición debería estar almacenada en un ordenador distinto de la red. En nuestro caso, esas particiones están siendo simuladas (dado que no disponemos de un cluster de ordenadores). El nombre “titanicSpark” es el nombre de la tabla o matriz de datos para Spark. titanic_tbl es una referencia a esa tabla. Es decir, apunta a esa tabla, pero no es la tabla en sí.

titanic_tbl <- sdf_copy_to(sc, titanic_train, repartition = 2, overwrite = TRUE, name="titanicSpark")
titanic_tbl <- sdf_copy_to(sc, titanic_train, repartition = 2, overwrite = TRUE, name="titanicSpark")
class(titanic_train)
[1] "data.frame"
class(titanic_tbl)
[1] "tbl_spark" "tbl_sql"   "tbl_lazy"  "tbl"      

Podemos ver las tablas que hay en el cluster hasta el momento:

src_tbls(sc)
[1] "titanicspark"

Vamos a hacer cierto preprocesamiento sencillo. Primero, vamos a seleccionar sólo las columnas que nos interesan. Segundo, vamos a imputar con if_else los valores de Age, porque tiene muchos NA’s. Tercero, vamos a convertir los dolares en euros y vamos a crear una variable booleana que indique si el pasajero es niño o no. Nótese que ahora todas estas operaciones están ocurriendo en todos los ordenadores del cluster, en paralelo.

Un concepto básico de Spark es que es perezoso (lazy). Es decir, que algunas operaciones (select, filter, …) sólo se realizan cuando es necesario tener el resultado. En el caso anterior, aunque parece que se computó select y mutate, realmente no ocurrió nada. Spark simplemente anota que hay que realizar esas operaciones. Una manera de forzar a que se realice el cómputo es solicitar un resultado. Por ejemplo, con head pedimos visualizar las 10 primeras líneas, con lo cual Spark no tiene más remedio que forzar la realización del cálculo.

head(titanic_tbl)
Missing values are always removed in SQL.
Use `avg(x, na.rm = TRUE)` to silence this warningMissing values are always removed in SQL.
Use `avg(x, na.rm = TRUE)` to silence this warning

Otra manera de forzar que se compute el resultado es con collect. Pero hay que tener cuidado, porque collect computa el resultado y se lo trae al ordenador local (driver). Es decir, el resultado ya no es un data.frame distribuido Spark. Esto es importante, porque sólo se puede hacer collect cuando estemos seguros de que el resultado cabe en el ordenador local. Así que sólo usaremos collect cuando necesitemos un resultado para traernos al ordenador local.

class(titanic_LOCAL)
[1] "tbl_df"     "tbl"        "data.frame"

La manera de forzar a que se realice el cálculo y que el resultado siga siendo un data.frame Spark (o sea, distribuido), es con sdf_register y el nombre de la tabla donde queremos que se guarde el resultado.

titanic_tbl <- select(titanic_tbl, Survived, Pclass, Sex, Age, Fare) %>%
  mutate(Age = if_else(is.na(Age), mean(Age), Age)) %>%
  mutate(Fare = Fare * 0.86, menor = Age<=10) %>%
  sdf_register("titanicSpark")
Missing values are always removed in SQL.
Use `avg(x, na.rm = TRUE)` to silence this warningMissing values are always removed in SQL.
Use `avg(x, na.rm = TRUE)` to silence this warning

Pero en este caso, no necesitamos el resultado inmediatamente, así que podemos dejar que Spark lo calcule cuando lo necesite, así que volvemos a la secuencia inicial

Ahora partimos los datos en entrenamiento y test:

partitions <- titanic_tbl %>% 
  sdf_partition(training = 2/3, test = 1/3, seed = 0)
Missing values are always removed in SQL.
Use `avg(x, na.rm = TRUE)` to silence this warning

Podemos ver que ambas particiones son data.frames spark. Ambas están distribuidas en el cluster.

class(partitions$test)
[1] "tbl_spark" "tbl_sql"   "tbl_lazy"  "tbl"      

Ahora, entrenamos un árbol de decisión. Hay que tener en cuenta que los métodos están programados para Spark, con lo que se ejecutarán en paralelo, en la medida de lo posible. Primero, tenemos que saber los nombres de los atributos de entrada y de la variable de respuesta. En este caso la clase va en primer lugar.

response
[1] "Survived"

Ahora, entrenamos un árbol de decisión (se ejecutará en paralelo en todas las particiones de los datos):

Hacemos predicciones con el conjunto de test. Lo que hace Spark es añadir una columna más con la predicción (“prediction”). Nótese que predictions_tree_tbl es un dataframe Spark, o sea, está distribuido.

Podemos intentar verlas, pero vemos que nos sobran columnas …

Vamos a seleccionar las columnas que nos interesan, calculamos después una columna con el error para cada instancia (0 significa acierto, 1 significa error). Por último, calculamos la media de la columna Error, y esa es la tasa de error.

tasa_error
Missing values are always removed in SQL.
Use `AVG(x, na.rm = TRUE)` to silence this warningMissing values are always removed in SQL.
Use `AVG(x, na.rm = TRUE)` to silence this warning

Realmente Spark tiene funciones para calcular la tasa de error, con lo que hubieramos podido utilizar esto:

tasa_error
[1] 0.1696113

Por último, nos desconectamos del cluster.

spark_disconnect(sc)
NULL
LS0tDQp0aXRsZTogIlR1dG9yaWFsIFNwYXJrbHlyIg0KYXV0aG9yOiAiUmljYXJkbyBBbGVyIg0KZGF0ZTogIjEgQWdvc3RvIDIwMTkiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQogIHdvcmRfZG9jdW1lbnQ6IGRlZmF1bHQNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBkZl9wcmludDogcGFnZWQNCi0tLQ0KDQpJbmZvcm1hY2nzbiBzb2JyZSAqKnNwYXJrbHlyKiogYXF17TogW2h0dHBzOi8vc3BhcmsucnN0dWRpby5jb20vXQ0KDQoNCmBgYHtyfQ0KIyBDb24gZXN0byBzZSBpbnN0YWxhOg0KIyBpbnN0YWxsLnBhY2thZ2VzKCJzcGFya2x5ciIpDQojIEFkZW3hcywgaGF5IHF1ZSBpbnN0YWxhciBTcGFyay4gQ29uIGVzdG8gc2UgaW5zdGFsYSB1bmEgY29waWEgbG9jYWwuIEVzIG5lY2VzYXJpbyBKYXZhIDggKGNvbiBKYXZhIDkgbm8gZnVuY2lvbmEpDQojIHNwYXJrX2luc3RhbGwodmVyc2lvbiA9ICIyLjEuMCIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShzcGFya2x5cikNCmBgYA0KDQpFbiBwcmltZXIgbHVnYXIsIGVzdGFibGVjZW1vcyB1bmEgY29uZXhp824gY29uIFNwYXJrIChvIHNlYSwgY29uIGVsIGNsdXN0ZXIgbWFuYWdlcikuICoqc2MqKiBlcyB1bmEgdmFyaWFibGUgcXVlIHBlcm1pdGUgdXNhciBlbCBjbHVzdGVyLg0KDQpgYGB7cn0NCnNjIDwtIHNwYXJrX2Nvbm5lY3QobWFzdGVyID0gImxvY2FsIikNCmBgYA0KDQoNClZhbW9zIGEgdm9sdmVyIGEgdXNhciBlbCBjb25qdW50byBkZSBkYXRvcyBkZSBUaXRhbmljLCBwYXJhIHZlciBzaSBwb2RlbW9zIHByZWRlY2lyIHNpIGFsZ3VpZW4gc2UgdmEgYSBzYWx2YXIuDQoNCmBgYHtyfQ0KbGlicmFyeSh0aXRhbmljKQ0KZGF0YSgidGl0YW5pY190cmFpbiIpDQpoZWFkKHRpdGFuaWNfdHJhaW4pDQpgYGANCg0KTGEgc2lndWllbnRlIGluc3RydWNjafNuIGNvcGlhIGVsIGRhdGEuZnJhbWUgbG9jYWwgKip0aXRhbmljX3RyYWluKiogYWwgY2x1c3Rlci4gRXNvIHNpZ25pZmljYSBxdWUgKip0aXRhbmljX3RyYWluKiogZXN04SBjb250ZW5pZG8gZW4gbGEgbWVtb3JpYSBkZSBudWVzdHJvIG9yZGVuYWRvciAoZGVsIG9yZGVuYWRvciBlbiBlbCBxdWUgc2UgZWplY3V0YSBlbCAqKmRyaXZlcioqKSwgbWllbnRyYXMgcXVlICoqdGl0YW5pY190YmwqKiBlcyB1bmEgdmFyaWFibGUgcXVlIHJlcHJlc2VudGEgYSB1biBkYXRhLmZyYW1lIGRpdmlkaWRvIGVuIGRvcyBwYXJ0aWNpb25lcy4gRW4gdGVvcu1hLCBjYWRhIHBhcnRpY2nzbiBkZWJlcu1hIGVzdGFyIGFsbWFjZW5hZGEgZW4gdW4gb3JkZW5hZG9yIGRpc3RpbnRvIGRlIGxhIHJlZC4gRW4gbnVlc3RybyBjYXNvLCBlc2FzIHBhcnRpY2lvbmVzIGVzdOFuIHNpZW5kbyBzaW11bGFkYXMgKGRhZG8gcXVlIG5vIGRpc3BvbmVtb3MgZGUgdW4gY2x1c3RlciBkZSBvcmRlbmFkb3JlcykuIEVsIG5vbWJyZSAidGl0YW5pY1NwYXJrIiBlcyBlbCBub21icmUgZGUgbGEgdGFibGEgbyBtYXRyaXogZGUgZGF0b3MgcGFyYSBTcGFyay4gKip0aXRhbmljX3RibCoqIGVzIHVuYSAqKnJlZmVyZW5jaWEqKiBhIGVzYSB0YWJsYS4gRXMgZGVjaXIsICphcHVudGEqIGEgZXNhIHRhYmxhLCBwZXJvIG5vIGVzIGxhIHRhYmxhIGVuIHPtLiANCg0KYGBge3J9DQp0aXRhbmljX3RibCA8LSBzZGZfY29weV90byhzYywgdGl0YW5pY190cmFpbiwgcmVwYXJ0aXRpb24gPSAyLCBvdmVyd3JpdGUgPSBUUlVFLCBuYW1lPSJ0aXRhbmljU3BhcmsiKQ0KY2xhc3ModGl0YW5pY190cmFpbikNCmNsYXNzKHRpdGFuaWNfdGJsKQ0KYGBgDQpQb2RlbW9zIHZlciBsYXMgdGFibGFzIHF1ZSBoYXkgZW4gZWwgY2x1c3RlciBoYXN0YSBlbCBtb21lbnRvOg0KDQpgYGB7cn0NCnNyY190YmxzKHNjKQ0KYGBgDQoNClZhbW9zIGEgaGFjZXIgY2llcnRvIHByZXByb2Nlc2FtaWVudG8gc2VuY2lsbG8uIFByaW1lcm8sIHZhbW9zIGEgc2VsZWNjaW9uYXIgc/NsbyBsYXMgY29sdW1uYXMgcXVlIG5vcyBpbnRlcmVzYW4uIFNlZ3VuZG8sIHZhbW9zIGEgaW1wdXRhciBjb24gKippZl9lbHNlKiogbG9zIHZhbG9yZXMgZGUgKipBZ2UqKiwgcG9ycXVlIHRpZW5lIG11Y2hvcyBOQSdzLiBUZXJjZXJvLCB2YW1vcyBhIGNvbnZlcnRpciBsb3MgZG9sYXJlcyBlbiBldXJvcyB5IHZhbW9zIGEgY3JlYXIgdW5hIHZhcmlhYmxlIGJvb2xlYW5hIHF1ZSBpbmRpcXVlIHNpIGVsIHBhc2FqZXJvIGVzIG5p8W8gbyBuby4gTvN0ZXNlIHF1ZSBhaG9yYSB0b2RhcyBlc3RhcyBvcGVyYWNpb25lcyBlc3ThbiBvY3VycmllbmRvIGVuIHRvZG9zIGxvcyBvcmRlbmFkb3JlcyBkZWwgY2x1c3RlciwgZW4gcGFyYWxlbG8uIA0KDQpgYGB7cn0NCnRpdGFuaWNfdGJsIDwtIHNlbGVjdCh0aXRhbmljX3RibCwgU3Vydml2ZWQsIFBjbGFzcywgU2V4LCBBZ2UsIEZhcmUpICU+JQ0KICBtdXRhdGUoQWdlID0gaWZfZWxzZShpcy5uYShBZ2UpLCBtZWFuKEFnZSksIEFnZSkpICU+JQ0KICBtdXRhdGUoRmFyZSA9IEZhcmUgKiAwLjg2LCBtZW5vciA9IEFnZTw9MTApDQogIA0KYGBgDQoNClVuIGNvbmNlcHRvIGLhc2ljbyBkZSAqKlNwYXJrKiogZXMgcXVlIGVzICpwZXJlem9zbyogKCpsYXp5KikuIEVzIGRlY2lyLCBxdWUgYWxndW5hcyBvcGVyYWNpb25lcyAoc2VsZWN0LCBmaWx0ZXIsIC4uLikgc/NsbyBzZSByZWFsaXphbiBjdWFuZG8gZXMgbmVjZXNhcmlvIHRlbmVyIGVsIHJlc3VsdGFkby4gRW4gZWwgY2FzbyBhbnRlcmlvciwgYXVucXVlIHBhcmVjZSBxdWUgc2UgY29tcHV08yAqKnNlbGVjdCoqIHkgKiptdXRhdGUqKiwgcmVhbG1lbnRlIG5vIG9jdXJyafMgbmFkYS4gKipTcGFyayoqIHNpbXBsZW1lbnRlIGFub3RhIHF1ZSBoYXkgcXVlIHJlYWxpemFyIGVzYXMgb3BlcmFjaW9uZXMuIFVuYSBtYW5lcmEgZGUgZm9yemFyIGEgcXVlIHNlIHJlYWxpY2UgZWwgY/NtcHV0byBlcyBzb2xpY2l0YXIgdW4gcmVzdWx0YWRvLiBQb3IgZWplbXBsbywgY29uICoqaGVhZCoqIHBlZGltb3MgdmlzdWFsaXphciBsYXMgMTAgcHJpbWVyYXMgbO1uZWFzLCBjb24gbG8gY3VhbCAqKlNwYXJrKiogbm8gdGllbmUgbeFzIHJlbWVkaW8gcXVlIGZvcnphciBsYSByZWFsaXphY2nzbiBkZWwgY+FsY3Vsby4NCg0KYGBge3J9DQpoZWFkKHRpdGFuaWNfdGJsKQ0KYGBgDQoNCk90cmEgbWFuZXJhIGRlIGZvcnphciBxdWUgc2UgY29tcHV0ZSBlbCByZXN1bHRhZG8gZXMgY29uICoqY29sbGVjdCoqLiBQZXJvIGhheSBxdWUgdGVuZXIgY3VpZGFkbywgcG9ycXVlICoqY29sbGVjdCoqIGNvbXB1dGEgZWwgcmVzdWx0YWRvIHkgc2UgbG8gdHJhZSBhbCBvcmRlbmFkb3IgbG9jYWwgKCoqZHJpdmVyKiopLiBFcyBkZWNpciwgZWwgcmVzdWx0YWRvICoqeWEgbm8gZXMgdW4gZGF0YS5mcmFtZSBkaXN0cmlidWlkbyBTcGFyayoqLiBFc3RvIGVzIGltcG9ydGFudGUsIHBvcnF1ZSBz82xvIHNlIHB1ZWRlIGhhY2VyICoqY29sbGVjdCoqIGN1YW5kbyBlc3RlbW9zIHNlZ3Vyb3MgZGUgcXVlIGVsIHJlc3VsdGFkbyBjYWJlIGVuIGVsIG9yZGVuYWRvciBsb2NhbC4gQXPtIHF1ZSBz82xvIHVzYXJlbW9zICoqY29sbGVjdCoqIGN1YW5kbyBuZWNlc2l0ZW1vcyB1biByZXN1bHRhZG8gcGFyYSB0cmFlcm5vcyBhbCBvcmRlbmFkb3IgbG9jYWwuDQoNCmBgYHtyfQ0KdGl0YW5pY19MT0NBTCA8LSBzZWxlY3QodGl0YW5pY190YmwsIFN1cnZpdmVkLCBQY2xhc3MsIFNleCwgQWdlLCBGYXJlKSAlPiUNCiAgbXV0YXRlKEFnZSA9IGlmX2Vsc2UoaXMubmEoQWdlKSwgbWVhbihBZ2UsIG5hLnJtPVRSVUUpLCBBZ2UpKSAlPiUNCiAgbXV0YXRlKEZhcmUgPSBGYXJlICogMC44NiwgbWVub3IgPSBBZ2U8PTEwKSAlPiUNCiAgY29sbGVjdCgpDQoNCmNsYXNzKHRpdGFuaWNfTE9DQUwpDQpgYGANCkxhIG1hbmVyYSBkZSBmb3J6YXIgYSBxdWUgc2UgcmVhbGljZSBlbCBj4WxjdWxvIHkgcXVlIGVsIHJlc3VsdGFkbyBzaWdhIHNpZW5kbyB1biBkYXRhLmZyYW1lIFNwYXJrIChvIHNlYSwgZGlzdHJpYnVpZG8pLCBlcyBjb24gKipzZGZfcmVnaXN0ZXIqKiB5IGVsIG5vbWJyZSBkZSBsYSB0YWJsYSBkb25kZSBxdWVyZW1vcyBxdWUgc2UgZ3VhcmRlIGVsIHJlc3VsdGFkby4NCmBgYHtyfQ0KaGVhZCh0aXRhbmljX0xPQ0FMKQ0KYGBgDQoNCmBgYHtyfQ0KdGl0YW5pY190YmwgPC0gc2VsZWN0KHRpdGFuaWNfdGJsLCBTdXJ2aXZlZCwgUGNsYXNzLCBTZXgsIEFnZSwgRmFyZSkgJT4lDQogIG11dGF0ZShBZ2UgPSBpZl9lbHNlKGlzLm5hKEFnZSksIG1lYW4oQWdlKSwgQWdlKSkgJT4lDQogIG11dGF0ZShGYXJlID0gRmFyZSAqIDAuODYsIG1lbm9yID0gQWdlPD0xMCkgJT4lDQogIHNkZl9yZWdpc3RlcigidGl0YW5pY1NwYXJrIikNCmBgYA0KDQpQZXJvIGVuIGVzdGUgY2Fzbywgbm8gbmVjZXNpdGFtb3MgZWwgcmVzdWx0YWRvIGlubWVkaWF0YW1lbnRlLCBhc+0gcXVlIHBvZGVtb3MgZGVqYXIgcXVlIFNwYXJrIGxvIGNhbGN1bGUgY3VhbmRvIGxvIG5lY2VzaXRlLCBhc+0gcXVlIHZvbHZlbW9zIGEgbGEgc2VjdWVuY2lhIGluaWNpYWwNCg0KYGBge3J9DQp0aXRhbmljX3RibCA8LSBzZWxlY3QodGl0YW5pY190YmwsIFN1cnZpdmVkLCBQY2xhc3MsIFNleCwgQWdlLCBGYXJlKSAlPiUNCiAgbXV0YXRlKEFnZSA9IGlmX2Vsc2UoaXMubmEoQWdlKSwgbWVhbihBZ2UpLCBBZ2UpKSAlPiUNCiAgbXV0YXRlKEZhcmUgPSBGYXJlICogMC44NiwgbWVub3IgPSBBZ2U8PTEwKQ0KYGBgDQoNCkFob3JhIHBhcnRpbW9zIGxvcyBkYXRvcyBlbiBlbnRyZW5hbWllbnRvIHkgdGVzdDoNCg0KYGBge3J9DQpwYXJ0aXRpb25zIDwtIHRpdGFuaWNfdGJsICU+JSANCiAgc2RmX3BhcnRpdGlvbih0cmFpbmluZyA9IDIvMywgdGVzdCA9IDEvMywgc2VlZCA9IDApDQoNCmBgYA0KDQpQb2RlbW9zIHZlciBxdWUgYW1iYXMgcGFydGljaW9uZXMgc29uIGRhdGEuZnJhbWVzIHNwYXJrLiBBbWJhcyBlc3ThbiBkaXN0cmlidWlkYXMgZW4gZWwgY2x1c3Rlci4NCg0KYGBge3J9DQpjbGFzcyhwYXJ0aXRpb25zJHRyYWluaW5nKQ0KY2xhc3MocGFydGl0aW9ucyR0ZXN0KQ0KYGBgDQoNCkFob3JhLCBlbnRyZW5hbW9zIHVuIOFyYm9sIGRlIGRlY2lzafNuLiBIYXkgcXVlIHRlbmVyIGVuIGN1ZW50YSBxdWUgbG9zIG3pdG9kb3MgZXN04W4gcHJvZ3JhbWFkb3MgcGFyYSBTcGFyaywgY29uIGxvIHF1ZSBzZSBlamVjdXRhcuFuIGVuIHBhcmFsZWxvLCBlbiBsYSBtZWRpZGEgZGUgbG8gcG9zaWJsZS4gUHJpbWVybywgdGVuZW1vcyBxdWUgc2FiZXIgbG9zIG5vbWJyZXMgZGUgbG9zIGF0cmlidXRvcyBkZSBlbnRyYWRhIHkgZGUgbGEgdmFyaWFibGUgZGUgcmVzcHVlc3RhLiBFbiBlc3RlIGNhc28gbGEgY2xhc2UgdmEgZW4gcHJpbWVyIGx1Z2FyLg0KDQpgYGB7cn0NCm5vbWJyZXMgPC0gY29sbmFtZXMocGFydGl0aW9ucyR0cmFpbmluZykNCnByaW50KG5vbWJyZXMpDQpmZWF0dXJlcyA8LSBub21icmVzWy0xXQ0KcmVzcG9uc2UgPC0gbm9tYnJlc1sxXQ0KZmVhdHVyZXMNCnJlc3BvbnNlDQpgYGANCkFob3JhLCBlbnRyZW5hbW9zIHVuIOFyYm9sIGRlIGRlY2lzafNuIChzZSBlamVjdXRhcuEgZW4gcGFyYWxlbG8gZW4gdG9kYXMgbGFzIHBhcnRpY2lvbmVzIGRlIGxvcyBkYXRvcyk6DQoNCmBgYHtyfQ0KbW9kZWxfdHJlZSA8LSBtbF9kZWNpc2lvbl90cmVlKHBhcnRpdGlvbnMkdHJhaW5pbmcsIGZlYXR1cmVzPWZlYXR1cmVzLCByZXNwb25zZSA9IHJlc3BvbnNlLCB0eXBlPSJjbGFzc2lmaWNhdGlvbiIpDQoNCmBgYA0KSGFjZW1vcyBwcmVkaWNjaW9uZXMgY29uIGVsIGNvbmp1bnRvIGRlIHRlc3QuIExvIHF1ZSBoYWNlIFNwYXJrIGVzIGHxYWRpciB1bmEgY29sdW1uYSBt4XMgY29uIGxhIHByZWRpY2Np824gKCJwcmVkaWN0aW9uIikuIE7zdGVzZSBxdWUgKipwcmVkaWN0aW9uc190cmVlX3RibCoqIGVzIHVuIGRhdGFmcmFtZSBTcGFyaywgbyBzZWEsIGVzdOEgZGlzdHJpYnVpZG8uDQoNCmBgYHtyfQ0KcHJlZGljdGlvbnNfdHJlZV90YmwgPC0gbWxfcHJlZGljdChtb2RlbF90cmVlLCBwYXJ0aXRpb25zJHRlc3QpDQpgYGANCg0KUG9kZW1vcyBpbnRlbnRhciB2ZXJsYXMsIHBlcm8gdmVtb3MgcXVlIG5vcyBzb2JyYW4gY29sdW1uYXMgLi4uDQoNCmBgYHtyfQ0KaGVhZChwcmVkaWN0aW9uc190cmVlX3RibCkNCmBgYA0KVmFtb3MgYSBzZWxlY2Npb25hciBsYXMgY29sdW1uYXMgcXVlIG5vcyBpbnRlcmVzYW4sIGNhbGN1bGFtb3MgZGVzcHXpcyB1bmEgY29sdW1uYSBjb24gZWwgZXJyb3IgcGFyYSBjYWRhIGluc3RhbmNpYSAoMCBzaWduaWZpY2EgYWNpZXJ0bywgMSBzaWduaWZpY2EgZXJyb3IpLiBQb3Ig+mx0aW1vLCBjYWxjdWxhbW9zIGxhIG1lZGlhIGRlIGxhIGNvbHVtbmEgRXJyb3IsIHkgZXNhIGVzIGxhIHRhc2EgZGUgZXJyb3IuDQoNCmBgYHtyfQ0KdGFzYV9lcnJvciA8LSBwcmVkaWN0aW9uc190cmVlX3RibCAlPiUgDQogIHNlbGVjdChTdXJ2aXZlZCwgcHJlZGljdGlvbikgJT4lDQogIG11dGF0ZShFcnJvciA9IGFicyhTdXJ2aXZlZC1wcmVkaWN0aW9uKSkgJT4lDQogIHN1bW1hcmlzZSh0YXNhX2Vycm9yID0gbWVhbihFcnJvcikpIA0KYGBgDQoNCmBgYHtyfQ0KdGFzYV9lcnJvcg0KYGBgDQpSZWFsbWVudGUgU3BhcmsgdGllbmUgZnVuY2lvbmVzIHBhcmEgY2FsY3VsYXIgbGEgdGFzYSBkZSBlcnJvciwgY29uIGxvIHF1ZSBodWJpZXJhbW9zIHBvZGlkbyB1dGlsaXphciBlc3RvOg0KDQpgYGB7cn0NCiMgUG9yIGFsZ3VuYSByYXrzbiwgbGEgY2xhc2UgdGllbmUgcXVlIA0KdGFzYV9hY2llcnRvcyA8LSBwcmVkaWN0aW9uc190cmVlX3RibCAlPiUNCiAgbXV0YXRlKFN1cnZpdmVkID0gYXMuZG91YmxlKFN1cnZpdmVkKSkgJT4lDQogIG1sX211bHRpY2xhc3NfY2xhc3NpZmljYXRpb25fZXZhbHVhdG9yKCJwcmVkaWN0aW9uIiwgIlN1cnZpdmVkIiwgImFjY3VyYWN5IikNCg0KdGFzYV9lcnJvciA8LSAxIC0gdGFzYV9hY2llcnRvcw0KdGFzYV9lcnJvcg0KYGBgDQoNClBvciD6bHRpbW8sIG5vcyBkZXNjb25lY3RhbW9zIGRlbCBjbHVzdGVyLg0KDQpgYGB7cn0NCnNwYXJrX2Rpc2Nvbm5lY3Qoc2MpDQpgYGANCg0KDQoNCg==
Reutilizar Curso
Descargar este curso