Dado un archivo de texto grande, quiero extraer los n-gramas de caracteres usando Apache Spark (hacer la tarea en paralelo).

Entrada de ejemplo (texto de 2 líneas): línea 1: (Hello World, it) línea 2: (es un buen día)

N-gramas de salida: Hel - ell -llo -lo_ - o_W - _Wo - Wor - orl - rld - ld, - d, _ -, _i - _it - it_ - t_i - _is - ... y así sucesivamente. Así que quiero que el valor de retorno sea un RDD [String], cada cadena contiene el n-grama.

Observe que la nueva línea se considera un espacio en blanco en los n-gramos de salida. Pongo cada línea entre paréntesis para que quede claro. Además, para que quede claro, la cadena o el texto no es una sola entrada en un RDD. Leí el archivo usando el método sc.textFile ().

5
Al Jenssen 25 ene. 2016 a las 20:49

3 respuestas

La mejor respuesta

La idea principal es tomar todas las líneas dentro de cada partición y combinarlas en una cadena larga. A continuación, reemplazamos "" con "_" y llamamos al deslizamiento en esta cadena para crear los trigramas para cada partición en paralelo.

Nota: Es posible que los trigramas resultantes no sean 100% precisos, ya que se perderán algunos trigramas desde el principio y el final de cada partición. Dado que cada partición puede tener varios millones de caracteres, la pérdida de seguridad debería ser insignificante. El principal beneficio aquí es que cada partición se puede ejecutar en paralelo.

Aquí hay algunos datos de juguetes. Todo lo que sigue se puede ejecutar en cualquier Spark REPL:

scala> val data = sc.parallelize(Seq("Hello World, it","is a nice day"))
data: org.apache.spark.rdd.RDD[String] = ParallelCollectionRDD[12] 

val trigrams = data.mapPartitions(_.toList.mkString(" ").replace(" ","_").sliding(3))
trigrams: org.apache.spark.rdd.RDD[String] = MapPartitionsRDD[14]

Aquí recopilaré los trigramas para mostrar cómo se ven (es posible que no desee hacer esto si su conjunto de datos es masivo)

scala> val asCollected = trigrams.collect
asCollected: Array[String] = Array(Hel, ell, llo, lo_, o_W, _Wo, Wor, orl, rld, ld,, d,_, ,_i, _it, is_, s_a, _a_, a_n, _ni, nic, ice, ce_, e_d, _da, day)
1
marios 26 ene. 2016 a las 17:06

Puede haber formas más breves de hacer esto,

Suponiendo que toda la cadena (incluida la nueva línea) es una sola entrada en un RDD, devolver lo siguiente de flatMap debería darle el resultado que desea.

val strings = text.foldLeft(("", List[String]())) {
  case ((s, l), c) =>
    if (s.length < 2) {
      val ns = s + c
      (ns, l)
    } else if (s.length == 2) {
      val ns = s + c
      (ns, ns :: l)
    } else {
      val ns = s.tail + c
      (ns, ns :: l)
    }
}._2
0
Angelo Genovese 25 ene. 2016 a las 19:23

Podría utilizar una función como la siguiente:

def n_gram(str:String, n:Int) = (str + " ").sliding(n)

Supongo que la nueva línea se ha eliminado al leer la línea, por lo que agregué un espacio para compensar eso. Si, por otro lado, se conserva la nueva línea, podría definirla como:

def n_gram(str:String, n:Int) = str.replace('\n', ' ').sliding(n)

Usando tu ejemplo:

println(n_gram("Hello World, it", 3).map(_.replace(' ', '_')).mkString(" - "))

Volvería:

Hel - ell - llo - lo_ - o_W - _Wo - Wor - orl - rld - ld, - d,_ - ,_i - _it - it_
1
Eduardo 25 ene. 2016 a las 19:43