Veri Kümesinde özel nesneler nasıl saklanır?


151

Spark Veri Kümelerini Tanıtmaya Göre :

Spark 2.0'ı dört gözle beklerken, Veri Kümelerinde bazı heyecan verici iyileştirmeler planlıyoruz, özellikle: ... Özel kodlayıcılar - şu anda çok çeşitli türler için kodlayıcıları otomatik olarak oluştururken, özel nesneler için bir API açmak istiyoruz.

ve Datasetaşağıdaki gibi bir hataya neden olacak şekilde özel türü depolamaya çalışır :

Veri Kümesinde depolanan tür için kodlayıcı bulunamıyor. İlkel türler (Int, String, vb.) Ve Ürün türleri (vaka sınıfları) sqlContext.implicits'in içe aktarılmasıyla desteklenir._ Diğer türleri serileştirme desteği gelecekteki sürümlerde eklenecektir

veya:

Java.lang.UnsupportedOperationException: ... için Kodlayıcı bulunamadı.

Mevcut herhangi bir geçici çözüm var mı?


Bu sorunun yalnızca Topluluk Wiki yanıtı için bir giriş noktası olarak var olduğunu unutmayın. Hem soruyu hem de cevabı güncellemekten / iyileştirmekten çekinmeyin.

Yanıtlar:


243

Güncelleme

Bu cevap şeyler şimdi daha iyi olmasına rağmen, hala geçerli ve bilgilendirici yerleşik ekler kodlayıcı desteği için 2.2 / 2.3, çünkü Set, Seq, Map, Date, Timestamp, ve BigDecimal. Yalnızca vaka sınıfları ve olağan Scala türleriyle türler yapmaya devam ederseniz, yalnızca içinde örtük olan SQLImplicits.


Ne yazık ki, buna yardımcı olmak için neredeyse hiçbir şey eklenmedi. Aranıyor @since 2.0.0içinde Encoders.scalaveya SQLImplicits.scalabulur şeyler çoğunlukla ilkel türleri (ve vaka sınıflarının bazı verdiği) ile ilgisi yok. Öyleyse, söylenecek ilk şey: şu anda özel sınıf kodlayıcılar için gerçek anlamda iyi bir destek yok . Bunun dışında, şu anda elimizde olanı göz önünde bulundurarak, umduğumuz kadar iyi bir iş çıkaran bazı püf noktaları takip ediyor. Önceden bir sorumluluk reddi beyanı olarak: Bu mükemmel bir şekilde çalışmayacak ve tüm sınırlamaları net ve açık hale getirmek için elimden geleni yapacağım.

Sorun tam olarak nedir

Eğer bir veri kümesi yapmak istediğinizde, Kıvılcım "genellikle gelen implicits aracılığıyla otomatik olarak oluşturulduğunu (ve iç Spark SQL gösteriminden tipi T bir JVM nesnesi dönüştürmek için) bir kodlayıcı gerektirir SparkSession, ya da statik yöntemler arayarak açıkça oluşturulabilir on Encoders"( üzerindeki belgelerdencreateDataset alınmıştır ). Kodlayıcı, kodladığınız türün Encoder[T]bulunduğu biçimi alacaktır T. İlk öneri eklemektir import spark.implicits._(bu size bu örtük kodlayıcıları verir ) ve ikinci öneri, bu kodlayıcıyla ilgili işlevler kümesini kullanarak örtük kodlayıcıyı açıkça geçirmektir .

Normal sınıflar için kodlayıcı yoktur, bu nedenle

import spark.implicits._
class MyObj(val i: Int)
// ...
val d = spark.createDataset(Seq(new MyObj(1),new MyObj(2),new MyObj(3)))

size aşağıdaki örtük ilgili derleme zamanı hatasını verecektir:

Veri Kümesinde depolanan tür için kodlayıcı bulunamıyor. İlkel türler (Int, String, vb.) Ve Ürün türleri (vaka sınıfları) sqlContext.implicits'in içe aktarılmasıyla desteklenir._ Diğer türleri serileştirme desteği gelecekteki sürümlerde eklenecektir

Ancak, genişleyen bazı sınıflarda yukarıdaki hatayı almak için kullandığınız türü sararsanız Product, hata kafa karıştırıcı bir şekilde çalışma zamanına ertelenir, bu nedenle

import spark.implicits._
case class Wrap[T](unwrap: T)
class MyObj(val i: Int)
// ...
val d = spark.createDataset(Seq(Wrap(new MyObj(1)),Wrap(new MyObj(2)),Wrap(new MyObj(3))))

İyi derler, ancak çalışma zamanında başarısız olur.

java.lang.UnsupportedOperationException: MyObj için Kodlayıcı bulunamadı

Bunun nedeni Spark'ın yarattığı kodlayıcıların gerçekte sadece çalışma zamanında (scala relfection yoluyla) yapılmasıdır. Bu durumda, derleme zamanında tüm Spark kontrolleri, en dıştaki sınıfın genişlediğidir Product(tüm durum sınıfları bunu yapar) ve yalnızca çalışma zamanında ne yapacağını hala bilmediğini anlar MyObj(aynı sorun, a Dataset[(Int,MyObj)]- Spark, çalışma zamanının kapanmasını bekler MyObj). Bunlar, düzeltilmesi gereken önemli sorunlardır:

  • Producther zaman çalışma zamanında çökmesine rağmen derlemeyi genişleten bazı sınıflar ve
  • yuvalanmış türler için özel kodlayıcılarda geçiş yapmanın bir yolu yoktur (Spark'ı kodlayıcıyı MyObjdaha sonra nasıl kodlanacağını Wrap[MyObj]veya kodlayacağını bilecek şekilde beslememin bir yolu yok (Int,MyObj)).

Sadece kullan kryo

Herkesin önerdiği çözüm, kryokodlayıcıyı kullanmaktır .

import spark.implicits._
class MyObj(val i: Int)
implicit val myObjEncoder = org.apache.spark.sql.Encoders.kryo[MyObj]
// ...
val d = spark.createDataset(Seq(new MyObj(1),new MyObj(2),new MyObj(3)))

Yine de bu oldukça sıkıcı bir hal alıyor. Özellikle kodunuz her türden veri kümesini manipüle ediyorsa, birleştiriyorsa, gruplandırıyorsa, vb. Sonunda bir sürü ekstra sonuç elde edersiniz. Öyleyse, neden tüm bunları otomatik olarak yapan bir örtük yapmıyorsunuz?

import scala.reflect.ClassTag
implicit def kryoEncoder[A](implicit ct: ClassTag[A]) = 
  org.apache.spark.sql.Encoders.kryo[A](ct)

Ve şimdi, neredeyse istediğim her şeyi yapabilirmişim gibi görünüyor (aşağıdaki örnek , otomatik olarak içe aktarıldığı spark-shellyerde çalışmayacaktır spark.implicits._)

class MyObj(val i: Int)

val d1 = spark.createDataset(Seq(new MyObj(1),new MyObj(2),new MyObj(3)))
val d2 = d1.map(d => (d.i+1,d)).alias("d2") // mapping works fine and ..
val d3 = d1.map(d => (d.i,  d)).alias("d3") // .. deals with the new type
val d4 = d2.joinWith(d3, $"d2._1" === $"d3._1") // Boom!

Ya da neredeyse. Sorun şu ki kryo, Spark'ı kullanmak sadece veri kümesindeki her satırı düz bir ikili nesne olarak depoluyor. İçin map, filter, foreachyeterli olduğunu ancak böyle işlemler için join, Spark gerçekten bu sütunlara ayrılmış olması gerekir. Şemayı d2veya için inceleyerek d3, yalnızca bir ikili sütun olduğunu görürsünüz:

d2.printSchema
// root
//  |-- value: binary (nullable = true)

Demetler için kısmi çözüm

Bu yüzden, Scala'daki ima sihrini kullanarak (daha fazlası 6.26.3 Aşırı Yükleme Çözünürlüğü ), kendime mümkün olduğu kadar iyi bir iş çıkaracak, en azından gruplar için bir dizi çıkarım yapabilirim ve mevcut etkilerle iyi çalışacak:

import org.apache.spark.sql.{Encoder,Encoders}
import scala.reflect.ClassTag
import spark.implicits._  // we can still take advantage of all the old implicits

implicit def single[A](implicit c: ClassTag[A]): Encoder[A] = Encoders.kryo[A](c)

implicit def tuple2[A1, A2](
  implicit e1: Encoder[A1],
           e2: Encoder[A2]
): Encoder[(A1,A2)] = Encoders.tuple[A1,A2](e1, e2)

implicit def tuple3[A1, A2, A3](
  implicit e1: Encoder[A1],
           e2: Encoder[A2],
           e3: Encoder[A3]
): Encoder[(A1,A2,A3)] = Encoders.tuple[A1,A2,A3](e1, e2, e3)

// ... you can keep making these

Daha sonra, bu türden etkilerle donanmış olarak, yukarıdaki örneğimi, bazı sütun yeniden adlandırmalarıyla da olsa, çalıştırabilirim.

class MyObj(val i: Int)

val d1 = spark.createDataset(Seq(new MyObj(1),new MyObj(2),new MyObj(3)))
val d2 = d1.map(d => (d.i+1,d)).toDF("_1","_2").as[(Int,MyObj)].alias("d2")
val d3 = d1.map(d => (d.i  ,d)).toDF("_1","_2").as[(Int,MyObj)].alias("d3")
val d4 = d2.joinWith(d3, $"d2._1" === $"d3._1")

Henüz (beklenen tanımlama grubu isimleri nasıl çözemedim _1, _2onları yeniden adlandırma olmadan varsayılan olarak, ...) - başkası Bununla oynamak istiyorsa, bu isim nerede "value"tanıtıldı alır ve bu nereye başlık olur isimler genellikle eklenir. Bununla birlikte, kilit nokta, şimdi güzel bir yapılandırılmış şemaya sahip olmam:

d4.printSchema
// root
//  |-- _1: struct (nullable = false)
//  |    |-- _1: integer (nullable = true)
//  |    |-- _2: binary (nullable = true)
//  |-- _2: struct (nullable = false)
//  |    |-- _1: integer (nullable = true)
//  |    |-- _2: binary (nullable = true)

Özetle, bu geçici çözüm:

  • tuple'lar için ayrı sütunlar almamızı sağlar (böylece tuple'lara tekrar katılabiliriz, yaşasın!)
  • yine sadece sonuçlara güvenebiliriz (bu yüzden kryoher yerden geçmeye gerek yok )
  • ile neredeyse tamamen geriye doğru uyumludur import spark.implicits._(bazı yeniden adlandırma dahil)
  • yok değil bize üzerinde katılmasına izin kyrotefrika ikili sütunlar, o olabilir tarlalarda dursun
  • bazı tuple sütunlarını "değer" olarak yeniden adlandırmanın hoş olmayan yan etkisine sahiptir (gerekirse, bu, dönüştürerek .toDF, yeni sütun adlarını belirleyerek ve bir veri kümesine geri dönüştürerek geri alınabilir - ve şema adları birleştirme yoluyla korunmuş gibi görünüyor , en çok ihtiyaç duyulan yer).

Genel olarak sınıflar için kısmi çözüm

Bu daha az hoş ve iyi bir çözümü yok. Bununla birlikte, şimdi yukarıdaki tuple çözümüne sahip olduğumuza göre, daha karmaşık sınıflarınızı demetlere dönüştürebileceğiniz için başka bir cevabın örtük dönüştürme çözümünün de biraz daha az acı verici olacağına dair bir önsezim var. Ardından, veri kümesini oluşturduktan sonra, muhtemelen veri çerçevesi yaklaşımını kullanarak sütunları yeniden adlandırırsınız. Her şey yolunda giderse, bu gerçekten bir gelişme çünkü artık sınıflarımın alanlarında birleştirme yapabiliyorum. Eğer bir tane düz ikili kryoserileştirici kullansaydım bu mümkün olmazdı.

İşte her şeyin biraz yapar örneğidir: Bir sınıf olması MyObjtiplerinin alanları vardır Int, java.util.UUIDve Set[String]. İlki kendi başının çaresine bakar. İkincisi, kullanarak serileştirebilsem kryode, bir olarak saklanırsa daha kullanışlı olurdu String(çünkü UUIDs genellikle karşı çıkmak isteyeceğim bir şeydir). Üçüncüsü gerçekten bir ikili sütuna aittir.

class MyObj(val i: Int, val u: java.util.UUID, val s: Set[String])

// alias for the type to convert to and from
type MyObjEncoded = (Int, String, Set[String])

// implicit conversions
implicit def toEncoded(o: MyObj): MyObjEncoded = (o.i, o.u.toString, o.s)
implicit def fromEncoded(e: MyObjEncoded): MyObj =
  new MyObj(e._1, java.util.UUID.fromString(e._2), e._3)

Şimdi, bu makineyi kullanarak güzel bir şema ile bir veri kümesi oluşturabilirim:

val d = spark.createDataset(Seq[MyObjEncoded](
  new MyObj(1, java.util.UUID.randomUUID, Set("foo")),
  new MyObj(2, java.util.UUID.randomUUID, Set("bar"))
)).toDF("i","u","s").as[MyObjEncoded]

Ve şema bana doğru adlara sahip sütunlar ve katılabileceğim ilk iki şeyle birlikte gösteriyor.

d.printSchema
// root
//  |-- i: integer (nullable = false)
//  |-- u: string (nullable = true)
//  |-- s: binary (nullable = true)

1
@AlexeyS Sanmıyorum. Ama neden bunu istiyorsun? Neden önerdiğim son çözümden kurtulamıyorsunuz? Verilerinizi JSON'a koyabiliyorsanız, alanları çıkarabilmeli ve bir vaka sınıfına koyabilmelisiniz ...
Alec

1
Maalesef bu cevabın özü, işe yarayan bir çözüm olmadığıdır.
baol

1
@combinatorist Benim anlayışıma göre, Veri Kümeleri ve Veri Çerçeveleri (kodlayıcılara ihtiyaç duymadıkları için RDD'ler değil!) performans açısından eşdeğerdir. Veri Kümelerinin tür güvenliğini küçümsemeyin! Spark'ın dahili olarak bir ton yansıma, yayın vb. Kullanması, açığa çıkan arayüzün tip güvenliğini önemsememeniz gerektiği anlamına gelmez. Ancak, başlık altında Dataframes kullanan kendi Veri Kümesi tabanlı tür güvenli işlevlerimi oluşturma konusunda kendimi daha iyi hissettiriyor.
Alec

1
Cazibe olarak çalışan bir çözümüm var. Şunlardan oluşur: 1. Özel sınıf üzerinde bir sparkSql UDT tanımlayın 2. onu kaydedin 3. Sınıfınızı bir Ürüne dahil edin (örneğin
Tuple1

1
UDTRegistration'ı kullanmayı incelediniz mi?
ChoppyTheLumberjack

32
  1. Genel kodlayıcıları kullanma.

    Şimdilik iki genel kodlayıcı vardır kryove javaSerializationikincisi açıkça şu şekilde tanımlanır:

    son derece verimsizdir ve yalnızca son çare olarak kullanılmalıdır.

    Aşağıdaki sınıfı varsayarsak

    class Bar(i: Int) {
      override def toString = s"bar $i"
      def bar = i
    }
    

    Bu kodlayıcıları örtük kodlayıcı ekleyerek kullanabilirsiniz:

    object BarEncoders {
      implicit def barEncoder: org.apache.spark.sql.Encoder[Bar] = 
      org.apache.spark.sql.Encoders.kryo[Bar]
    }
    

    aşağıdaki gibi birlikte kullanılabilir:

    object Main {
      def main(args: Array[String]) {
        val sc = new SparkContext("local",  "test", new SparkConf())
        val sqlContext = new SQLContext(sc)
        import sqlContext.implicits._
        import BarEncoders._
    
        val ds = Seq(new Bar(1)).toDS
        ds.show
    
        sc.stop()
      }
    }
    

    Nesneleri binarysütun olarak depolar, böylece dönüştürüldüğünde DataFrameaşağıdaki şemayı alırsınız:

    root
     |-- value: binary (nullable = true)
    

    kryoBelirli alan için kodlayıcı kullanarak demetleri kodlamak da mümkündür :

    val longBarEncoder = Encoders.tuple(Encoders.scalaLong, Encoders.kryo[Bar])
    
    spark.createDataset(Seq((1L, new Bar(1))))(longBarEncoder)
    // org.apache.spark.sql.Dataset[(Long, Bar)] = [_1: bigint, _2: binary]
    

    Lütfen burada örtük kodlayıcılara bağlı olmadığımızı, ancak kodlayıcıyı açıkça geçirdiğimizi, bu nedenle bu büyük olasılıkla toDSyöntemle çalışmayacağına dikkat edin .

  2. Örtülü dönüştürmeleri kullanma:

    Kodlanabilen temsil ile özel sınıf arasında örtük dönüşümler sağlayın, örneğin:

    object BarConversions {
      implicit def toInt(bar: Bar): Int = bar.bar
      implicit def toBar(i: Int): Bar = new Bar(i)
    }
    
    object Main {
      def main(args: Array[String]) {
        val sc = new SparkContext("local",  "test", new SparkConf())
        val sqlContext = new SQLContext(sc)
        import sqlContext.implicits._
        import BarConversions._
    
        type EncodedBar = Int
    
        val bars: RDD[EncodedBar]  = sc.parallelize(Seq(new Bar(1)))
        val barsDS = bars.toDS
    
        barsDS.show
        barsDS.map(_.bar).show
    
        sc.stop()
      }
    }
    

İlgili sorular:


Çözüm 1, Setaldığım yazılan koleksiyonlar için (en azından ) işe yaramıyor gibi görünüyor Exception in thread "main" java.lang.UnsupportedOperationException: No Encoder found for Set[Bar].
Victor P.

@VictorP. Korkarım ki böyle bir durumda, belirli tür için bir kodlayıcıya ihtiyacınız olacak ( kryo[Set[Bar]]. Aynı şekilde, sınıf bir alan içeriyorsa Bar, bütün bir nesne için kodlayıcıya ihtiyacınız vardır. Bunlar çok kaba yöntemlerdir.
zero323

@ zero323 Aynı sorunla karşı karşıyayım. Tüm projenin nasıl kodlanacağına dair bir kod örneği koyabilir misiniz? Çok teşekkürler!
Kaya

@Rock "Bütün proje" ile ne demek istediğinizden emin değilim
zero323

Yorumunuz başına @ zero323, "sınıf bir alan içeriyorsa Bar, tüm nesne için kodlayıcıya ihtiyacınız vardır" sorum bu "tüm proje" nin nasıl kodlanacağıydı?
Kaya

11

UDTRegistration'ı ve ardından Case Classes, Tuple'ı vb. Kullanabilirsiniz ... Hepsi Kullanıcı Tanımlı Türünüzle doğru şekilde çalışır!

Özel bir Enum kullanmak istediğinizi varsayalım:

trait CustomEnum { def value:String }
case object Foo extends CustomEnum  { val value = "F" }
case object Bar extends CustomEnum  { val value = "B" }
object CustomEnum {
  def fromString(str:String) = Seq(Foo, Bar).find(_.value == str).get
}

Bunu şu şekilde kaydedin:

// First define a UDT class for it:
class CustomEnumUDT extends UserDefinedType[CustomEnum] {
  override def sqlType: DataType = org.apache.spark.sql.types.StringType
  override def serialize(obj: CustomEnum): Any = org.apache.spark.unsafe.types.UTF8String.fromString(obj.value)
  // Note that this will be a UTF8String type
  override def deserialize(datum: Any): CustomEnum = CustomEnum.fromString(datum.toString)
  override def userClass: Class[CustomEnum] = classOf[CustomEnum]
}

// Then Register the UDT Class!
// NOTE: you have to put this file into the org.apache.spark package!
UDTRegistration.register(classOf[CustomEnum].getName, classOf[CustomEnumUDT].getName)

O zaman KULLANIN!

case class UsingCustomEnum(id:Int, en:CustomEnum)

val seq = Seq(
  UsingCustomEnum(1, Foo),
  UsingCustomEnum(2, Bar),
  UsingCustomEnum(3, Foo)
).toDS()
seq.filter(_.en == Foo).show()
println(seq.collect())

Polimorfik Kayıt kullanmak istediğinizi varsayalım:

trait CustomPoly
case class FooPoly(id:Int) extends CustomPoly
case class BarPoly(value:String, secondValue:Long) extends CustomPoly

... ve bunu şu şekilde kullanın:

case class UsingPoly(id:Int, poly:CustomPoly)

Seq(
  UsingPoly(1, new FooPoly(1)),
  UsingPoly(2, new BarPoly("Blah", 123)),
  UsingPoly(3, new FooPoly(1))
).toDS

polySeq.filter(_.poly match {
  case FooPoly(value) => value == 1
  case _ => false
}).show()

Her şeyi bayt olarak kodlayan özel bir UDT yazabilirsiniz (burada java serileştirmeyi kullanıyorum ama muhtemelen Spark'ın Kryo bağlamını kullanmak daha iyidir).

Önce UDT sınıfını tanımlayın:

class CustomPolyUDT extends UserDefinedType[CustomPoly] {
  val kryo = new Kryo()

  override def sqlType: DataType = org.apache.spark.sql.types.BinaryType
  override def serialize(obj: CustomPoly): Any = {
    val bos = new ByteArrayOutputStream()
    val oos = new ObjectOutputStream(bos)
    oos.writeObject(obj)

    bos.toByteArray
  }
  override def deserialize(datum: Any): CustomPoly = {
    val bis = new ByteArrayInputStream(datum.asInstanceOf[Array[Byte]])
    val ois = new ObjectInputStream(bis)
    val obj = ois.readObject()
    obj.asInstanceOf[CustomPoly]
  }

  override def userClass: Class[CustomPoly] = classOf[CustomPoly]
}

Ardından kaydedin:

// NOTE: The file you do this in has to be inside of the org.apache.spark package!
UDTRegistration.register(classOf[CustomPoly].getName, classOf[CustomPolyUDT].getName)

O zaman kullanabilirsin!

// As shown above:
case class UsingPoly(id:Int, poly:CustomPoly)

Seq(
  UsingPoly(1, new FooPoly(1)),
  UsingPoly(2, new BarPoly("Blah", 123)),
  UsingPoly(3, new FooPoly(1))
).toDS

polySeq.filter(_.poly match {
  case FooPoly(value) => value == 1
  case _ => false
}).show()

1
Kryo'nuzun nerede kullanıldığını göremiyorum (CustomPolyUDT'da)
mathieu

Projemde bir UDT tanımlamaya çalışıyorum ve "Symbol UserDefinedType bu yerden erişilemez" hatasını alıyorum. Herhangi bir yardım ?
Rijo Joseph

Merhaba @RijoJoseph. Projenizde org.apache.spark paketini yapmanız ve içine UDT kodunuzu koymanız gerekiyor.
ChoppyTheLumberjack

Kodumu org.apache.spark içindeki bir pakete koyarak bu yöntemi denedim. Ve kaydı aradım. Ama yine de numaram için kodlayıcı olmamasıyla ilgili hatalar alıyorum ...?
DCameronMauch

6

Kodlayıcılar aşağı yukarı aynı şekilde çalışır Spark2.0. Ve Kryohala önerilen serializationseçimdir.

Spark-shell ile aşağıdaki örneğe bakabilirsiniz

scala> import spark.implicits._
import spark.implicits._

scala> import org.apache.spark.sql.Encoders
import org.apache.spark.sql.Encoders

scala> case class NormalPerson(name: String, age: Int) {
 |   def aboutMe = s"I am ${name}. I am ${age} years old."
 | }
defined class NormalPerson

scala> case class ReversePerson(name: Int, age: String) {
 |   def aboutMe = s"I am ${name}. I am ${age} years old."
 | }
defined class ReversePerson

scala> val normalPersons = Seq(
 |   NormalPerson("Superman", 25),
 |   NormalPerson("Spiderman", 17),
 |   NormalPerson("Ironman", 29)
 | )
normalPersons: Seq[NormalPerson] = List(NormalPerson(Superman,25), NormalPerson(Spiderman,17), NormalPerson(Ironman,29))

scala> val ds1 = sc.parallelize(normalPersons).toDS
ds1: org.apache.spark.sql.Dataset[NormalPerson] = [name: string, age: int]

scala> val ds2 = ds1.map(np => ReversePerson(np.age, np.name))
ds2: org.apache.spark.sql.Dataset[ReversePerson] = [name: int, age: string]

scala> ds1.show()
+---------+---+
|     name|age|
+---------+---+
| Superman| 25|
|Spiderman| 17|
|  Ironman| 29|
+---------+---+

scala> ds2.show()
+----+---------+
|name|      age|
+----+---------+
|  25| Superman|
|  17|Spiderman|
|  29|  Ironman|
+----+---------+

scala> ds1.foreach(p => println(p.aboutMe))
I am Ironman. I am 29 years old.
I am Superman. I am 25 years old.
I am Spiderman. I am 17 years old.

scala> val ds2 = ds1.map(np => ReversePerson(np.age, np.name))
ds2: org.apache.spark.sql.Dataset[ReversePerson] = [name: int, age: string]

scala> ds2.foreach(p => println(p.aboutMe))
I am 17. I am Spiderman years old.
I am 25. I am Superman years old.
I am 29. I am Ironman years old.

Şimdiye kadar] appropriate encodersmevcut kapsamda hiçbir şey yoktu , bu yüzden insanlarımız binarydeğerler olarak kodlanmadı . Ancak serileştirmeyi implicitkullanarak bazı kodlayıcılar sağladığımızda bu değişecek Kryo.

// Provide Encoders

scala> implicit val normalPersonKryoEncoder = Encoders.kryo[NormalPerson]
normalPersonKryoEncoder: org.apache.spark.sql.Encoder[NormalPerson] = class[value[0]: binary]

scala> implicit val reversePersonKryoEncoder = Encoders.kryo[ReversePerson]
reversePersonKryoEncoder: org.apache.spark.sql.Encoder[ReversePerson] = class[value[0]: binary]

// Ecoders will be used since they are now present in Scope

scala> val ds3 = sc.parallelize(normalPersons).toDS
ds3: org.apache.spark.sql.Dataset[NormalPerson] = [value: binary]

scala> val ds4 = ds3.map(np => ReversePerson(np.age, np.name))
ds4: org.apache.spark.sql.Dataset[ReversePerson] = [value: binary]

// now all our persons show up as binary values
scala> ds3.show()
+--------------------+
|               value|
+--------------------+
|[01 00 24 6C 69 6...|
|[01 00 24 6C 69 6...|
|[01 00 24 6C 69 6...|
+--------------------+

scala> ds4.show()
+--------------------+
|               value|
+--------------------+
|[01 00 24 6C 69 6...|
|[01 00 24 6C 69 6...|
|[01 00 24 6C 69 6...|
+--------------------+

// Our instances still work as expected    

scala> ds3.foreach(p => println(p.aboutMe))
I am Ironman. I am 29 years old.
I am Spiderman. I am 17 years old.
I am Superman. I am 25 years old.

scala> ds4.foreach(p => println(p.aboutMe))
I am 25. I am Superman years old.
I am 29. I am Ironman years old.
I am 17. I am Spiderman years old.

3

Java Bean sınıfı durumunda bu yararlı olabilir

import spark.sqlContext.implicits._
import org.apache.spark.sql.Encoders
implicit val encoder = Encoders.bean[MyClasss](classOf[MyClass])

Artık dataFrame'i özel DataFrame olarak okuyabilirsiniz.

dataFrame.as[MyClass]

Bu, ikili kodlayıcı değil, özel bir sınıf kodlayıcı oluşturacaktır.


2

Örneklerim Java'da olacak, ancak Scala'ya uyum sağlamanın zor olacağını sanmıyorum.

Ben dönüştürerek oldukça başarılı olmuştur RDD<Fruit>etmek Dataset<Fruit>kullanarak spark.createDataset ve Encoders.bean uzun olduğunca Fruitbasit bir Java Bean .

Adım 1: Basit Java Bean'i oluşturun.

public class Fruit implements Serializable {
    private String name  = "default-fruit";
    private String color = "default-color";

    // AllArgsConstructor
    public Fruit(String name, String color) {
        this.name  = name;
        this.color = color;
    }

    // NoArgsConstructor
    public Fruit() {
        this("default-fruit", "default-color");
    }

    // ...create getters and setters for above fields
    // you figure it out
}

DataBricks milleti Kodlayıcılarını güçlendirmeden önce, alanlar olarak ilkel türlere ve String'e sahip sınıflara bağlı kalırdım. İç içe nesneye sahip bir sınıfınız varsa, tüm alanları düzleştirilmiş başka bir basit Java Bean oluşturun, böylece karmaşık türü daha basit olanla eşlemek için RDD dönüşümlerini kullanabilirsiniz. Elbette biraz fazladan bir iş, ancak düz bir şema ile çalışmanın performans konusunda çok yardımcı olacağını düşünüyorum.

Adım 2: Veri Kümenizi RDD'den alın

SparkSession spark = SparkSession.builder().getOrCreate();
JavaSparkContext jsc = new JavaSparkContext();

List<Fruit> fruitList = ImmutableList.of(
    new Fruit("apple", "red"),
    new Fruit("orange", "orange"),
    new Fruit("grape", "purple"));
JavaRDD<Fruit> fruitJavaRDD = jsc.parallelize(fruitList);


RDD<Fruit> fruitRDD = fruitJavaRDD.rdd();
Encoder<Fruit> fruitBean = Encoders.bean(Fruit.class);
Dataset<Fruit> fruitDataset = spark.createDataset(rdd, bean);

Ve voila! Köpürtün, durulayın, tekrarlayın.


Basit yapılar için, onları bir blob'a serileştirmek yerine yerel Spark türlerinde depolayarak daha iyi hizmet edeceğinize dikkat çekmeyi öneririm. Python ağ geçidinde daha iyi çalışırlar, Parquet'te daha şeffaftırlar ve hatta aynı şekle sahip yapılara dönüştürülebilirler.
metasim

1

Benim durumumda olabilecekler için de cevabımı buraya koyuyorum.

Spesifik olmak,

  1. SQLContext'ten 'Yazılan verileri ayarla' okuyordum. Yani orijinal veri formatı DataFrame'dir.

    val sample = spark.sqlContext.sql("select 1 as a, collect_set(1) as b limit 1") sample.show()

    +---+---+ | a| b| +---+---+ | 1|[1]| +---+---+

  2. Daha sonra mutable.WrappedArray türü ile rdd.map () kullanarak RDD'ye dönüştürün.

    sample .rdd.map(r => (r.getInt(0), r.getAs[mutable.WrappedArray[Int]](1).toSet)) .collect() .foreach(println)

    Sonuç:

    (1,Set(1))


0

Önceden verilen önerilere ek olarak, yakın zamanda keşfettiğim bir başka seçenek de, özellik dahil olmak üzere özel sınıfınızı açıklayabilmenizdir org.apache.spark.sql.catalyst.DefinedByConstructorParams.

Bu, sınıfın ExpressionEncoder'ın anlayabileceği türler, yani ilkel değerler ve standart koleksiyonlar kullanan bir yapıcıya sahip olması durumunda çalışır. Sınıfı bir vaka sınıfı olarak açıklayamadığınızda kullanışlı olabilir, ancak bir Veri Kümesine her eklendiğinde onu kodlamak için Kryo'yu kullanmak istemiyorsanız.

Örneğin, Breeze vektörü içeren bir vaka sınıfı bildirmek istedim. Bunu kaldırabilecek tek kodlayıcı normalde Kryo olacaktır. Ancak Breeze DenseVector ve DefinedByConstructorParams'ı genişleten bir alt sınıf bildirmişsem, ExpressionEncoder bunun bir Çiftler dizisi olarak serileştirilebileceğini anladı.

İşte bunu nasıl ilan ettim:

class SerializableDenseVector(values: Array[Double]) extends breeze.linalg.DenseVector[Double](values) with DefinedByConstructorParams
implicit def BreezeVectorToSerializable(bv: breeze.linalg.DenseVector[Double]): SerializableDenseVector = bv.asInstanceOf[SerializableDenseVector]

Şimdi SerializableDenseVectorbir Veri Kümesinde (doğrudan veya bir Ürünün parçası olarak) basit bir ExpressionEncoder kullanarak ve Kryo kullanmadan kullanabilirim. Breeze DenseVector gibi çalışır ancak Array [Double] olarak serileştirilir.


0

@ Alec'in cevabı harika! Sadece cevabının bu kısmına bir yorum eklemek için:

import spark.implicits._
case class Wrap[T](unwrap: T)
class MyObj(val i: Int)
// ...
val d = spark.createDataset(Seq(Wrap(new MyObj(1)),Wrap(new MyObj(2)),Wrap(new MyObj(3))))

@Alec şunları söylüyor:

yuvalanmış türler için özel kodlayıcılarda geçiş yapmanın bir yolu yoktur (Spark'ı yalnızca MyObj için bir kodlayıcı beslemenin bir yolu yok, böylece Wrap [MyObj] veya (Int, MyObj) nasıl kodlanacağını bilir).

Öyle görünüyor, çünkü için bir kodlayıcı eklersem MyObj:

implicit val myEncoder = org.apache.spark.sql.Encoders.kryo[MyObj]

hala başarısız oluyor:

java.lang.UnsupportedOperationException: No Encoder found for MyObj
- field (class: "MyObj", name: "unwrap")
- root class: "Wrap"
  at org.apache.spark.sql.catalyst.ScalaReflection$$anonfun$org$apache$spark$sql$catalyst$ScalaReflection$$serializerFor$1.apply(ScalaReflection.scala:643)

Ancak önemli hata mesajına dikkat edin:

kök sınıf: "Wrap"

Aslında kodlamanın MyObjyeterli olmadığına dair bir ipucu veriyor ve dahil olmak üzere tüm zinciri kodlamanız gerekiyor Wrap[T].

Yani bunu yaparsam sorunu çözer :

implicit val myWrapperEncoder = org.apache.spark.sql.Encoders.kryo[Wrap[MyObj]]

Dolayısıyla, @Alec'in yorumu o kadar doğru DEĞİLDİR :

Spark'ı yalnızca MyObj için bir kodlayıcı beslemenin hiçbir yolu yok, böylece Wrap [MyObj] veya (Int, MyObj) nasıl kodlanacağını bilir

Spark'ı, Wrap [MyObj] veya (Int, MyObj) 'yi nasıl kodlayacağını bilecek şekilde beslemek içinMyObj hala bir yolumuz var .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.