डेटासेट में कस्टम ऑब्जेक्ट्स को कैसे स्टोर करें?


149

परिचय स्पार्क डेटासेट्स के अनुसार :

जैसा कि हम स्पार्क 2.0 के लिए तत्पर हैं, हम डेटासेट्स के लिए कुछ रोमांचक सुधारों की योजना बनाते हैं, विशेष रूप से: ... कस्टम एन्कोडर - जब हम वर्तमान में विभिन्न प्रकार के प्रकारों के लिए ऑटोजेनरेट एनकोडर करते हैं, तो हम कस्टम ऑब्जेक्ट्स के लिए एक एपीआई खोलना चाहते हैं।

और Datasetनिम्नलिखित त्रुटि के लिए सीसा में कस्टम प्रकार को संग्रहीत करने का प्रयास करता है :

डेटासेट में संग्रहीत प्रकार के लिए एनकोडर खोजने में असमर्थ। आदिम प्रकार (इंट, स्ट्रिंग, आदि) और उत्पाद प्रकार (केस कक्षाएं) sqlContext.implicits._ आयात करके समर्थित हैं। भविष्य के रिलीज में अन्य प्रकारों को क्रमबद्ध करने के लिए समर्थन जोड़ा जाएगा।

या:

Java.lang.UnsupportedOperationException: कोई एनकोडर नहीं मिला ...

क्या कोई मौजूदा वर्कअराउंड हैं?


ध्यान दें कि यह प्रश्न केवल समुदाय विकी उत्तर के लिए एक प्रवेश बिंदु के रूप में मौजूद है। प्रश्न और उत्तर दोनों को अपडेट / सुधार करने के लिए स्वतंत्र महसूस करें।

जवाबों:


240

अपडेट करें

इस उत्तर हालांकि चीजें अब बेहतर हैं, वैध और सूचनात्मक अब भी है 2.2 / 2.3, कहते हैं, जिसके लिए निर्मित एनकोडर समर्थन के बाद से Set, Seq, Map, Date, Timestamp, और BigDecimal। यदि आप केवल केस क्लासेस और सामान्य स्कैला प्रकारों के साथ टाइप करने के लिए चिपके रहते हैं, तो आपको केवल निहितार्थ के साथ ठीक होना चाहिए SQLImplicits


दुर्भाग्य से, वस्तुतः इसमें मदद करने के लिए कुछ भी नहीं जोड़ा गया है। के लिए खोज @since 2.0.0में Encoders.scalaया SQLImplicits.scalaपाता बातें ज्यादातर आदिम प्रकार (और मामले वर्ग के कुछ फेरबदल) से कोई लेना देना। तो, पहली बात कहने के लिए: वर्तमान में कस्टम वर्ग एन्कोडर के लिए कोई वास्तविक अच्छा समर्थन नहीं है । उस रास्ते से, जो कुछ चालें है, जो कि एक अच्छा काम है जैसा कि हम कभी भी उम्मीद कर सकते हैं, यह देखते हुए कि वर्तमान में हमारे पास क्या है। एक अग्रिम अस्वीकरण के रूप में: यह पूरी तरह से काम नहीं करेगा और मैं सभी सीमाओं को स्पष्ट और अग्रिम करने के लिए अपनी पूरी कोशिश करूंगा।

वास्तव में समस्या क्या है

जब आप एक डेटासेट बनाना चाहते हैं, तो स्पार्क को "एनकोडर (आंतरिक स्पार्क एसक्यूएल प्रतिनिधित्व से टाइप टी के जेवीएम ऑब्जेक्ट को परिवर्तित करने के लिए) की आवश्यकता होती है जो आम तौर पर ए से प्राप्त होने के माध्यम से स्वचालित रूप से बनाई जाती है SparkSession, या स्थिर तरीकों से स्पष्ट रूप से बनाई जा सकती है। पर Encoders"( डॉक्सcreateDataset से लिया गया )। एक एनकोडर वह रूप लेगा Encoder[T]जहां Tआप टाइप कर रहे हैं। पहला सुझाव जोड़ने का है import spark.implicits._(जो आपको ये निहित एनकोडर देता है ) और दूसरा सुझाव एनकोडर संबंधित कार्यों के इस सेट का उपयोग करके स्पष्ट रूप से निहित एनकोडर में पारित करना है।

नियमित कक्षाओं के लिए कोई एनकोडर उपलब्ध नहीं है, इसलिए

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

आपको निम्न अंतर्निहित संबंधित संकलन समय त्रुटि देगा:

डेटासेट में संग्रहीत प्रकार के लिए एनकोडर खोजने में असमर्थ। आदिम प्रकार (इंट, स्ट्रिंग, आदि) और उत्पाद प्रकार (केस कक्षाएं) sqlContext.implicits._ आयात करके समर्थित हैं। भविष्य के रिलीज में अन्य प्रकारों को क्रमबद्ध करने के लिए समर्थन जोड़ा जाएगा।

हालाँकि, यदि आप किसी भी प्रकार को लपेटते हैं, जिसका उपयोग आप उपरोक्त त्रुटि को किसी वर्ग में प्राप्त करने के लिए करते हैं Product, तो त्रुटि, रनिंग में देरी होने में देरी हो जाती है, इसलिए

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))))

संकलन ठीक है, लेकिन साथ रनटाइम में विफल रहता है

java.lang.UnsupportedOperationException: MyObj के लिए कोई एनकोडर नहीं मिला

इसका कारण यह है कि एनकोडर स्पार्क का निर्माण इनसिक्योरिटी के साथ होता है, जो वास्तव में केवल रनटाइम (स्केला रिलेटेन के माध्यम से) में बनाया जाता है। इस मामले में, संकलन समय पर सभी स्पार्क की जांच होती है कि सबसे बाहरी वर्ग का विस्तार होता है Product(जो सभी मामले कक्षाएं करते हैं), और केवल रनटाइम पर पता चलता है कि यह अभी भी नहीं जानता कि क्या करना है MyObj(वही समस्या तब होती है अगर मैंने बनाने की कोशिश की। a Dataset[(Int,MyObj)]- स्पार्क प्रतीक्षा करता है जब तक कि रनऑफ़ पर बर्फ़ न हो जाए MyObj)। ये केंद्रीय समस्याएं हैं जिन्हें ठीक किए जाने की सख्त जरूरत है:

  • कुछ कक्षाएं जो Productहमेशा रनटाइम पर दुर्घटनाग्रस्त होने के बावजूद संकलन का विस्तार करती हैं और
  • वहाँ नेस्टेड प्रकार के लिए कस्टम एनकोडर में पास करने का कोई तरीका नहीं है (मैं स्पार्क बस के लिए एक एनकोडर खिला का कोई तरीका नहीं MyObjऐसी है कि वह तो कैसे एनकोड करने के लिए जानता है Wrap[MyObj]या (Int,MyObj))।

महज प्रयोग करें kryo

समाधान सभी को पता चलता है कि kryoएनकोडर का उपयोग करना है ।

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)))

यह हालांकि बहुत थकाऊ उपवास हो जाता है। खासकर अगर आपका कोड सभी तरह के डेटासेट्स, ज्वाइनिंग, ग्रुपिंग आदि में हेरफेर कर रहा है, तो आप अतिरिक्त एक्सिकिट्स का एक गुच्छा तैयार करते हैं। तो, क्यों न केवल एक निहितार्थ बनाएं जो यह सब स्वचालित रूप से करता है?

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

और अब, ऐसा लगता है कि मैं लगभग कुछ भी कर सकता हूं जो मैं चाहता हूं (नीचे दिया गया उदाहरण काम नहीं करेगा spark-shellजहां 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!

या लगभग। समस्या यह है कि kryoस्पार्क के उपयोग से डेटासेट में प्रत्येक पंक्ति को एक फ्लैट बाइनरी ऑब्जेक्ट के रूप में संग्रहीत किया जाता है। के लिए map, filter, foreachवह पर्याप्त है, लेकिन जैसे कार्यों के लिए join, स्पार्क वास्तव में इन स्तंभों में विभाजित किया जाना चाहिए। के लिए स्कीमा निरीक्षण d2या d3, जैसा कि आप देख सिर्फ एक द्विआधारी स्तंभ है:

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

ट्यूपल्स के लिए आंशिक समाधान

इसलिए, स्काला में ( 6.26.3 ओवरलोडिंग रिज़ॉल्यूशन में अधिक ), मैं अपने आप को एक ऐसे इम्प्लिकेशन्स की श्रृंखला का उपयोग कर सकता हूं, जो कम से कम ट्यूपल्स के लिए अच्छा काम करेगा, और मौजूदा इम्प्लिट्स के लिए अच्छा काम करेगा:

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

फिर, इन निहितार्थों से लैस, मैं काम से ऊपर अपना उदाहरण बना सकता हूं, यद्यपि कुछ कॉलम का नाम बदलने के साथ

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")

मैंने अभी तक यह पता नहीं लगाया है कि बिना नाम बदलने के अपेक्षित टपल नाम ( _1, _2...) कैसे प्राप्त किया जा सकता है - यदि कोई अन्य व्यक्ति इसके साथ खेलना चाहता है, तो यह वह जगह है जहाँ नाम "value"प्रस्तुत किया गया है और यह वह जगह है जहाँ टपल है आमतौर पर नाम जोड़े जाते हैं। हालाँकि, महत्वपूर्ण बात यह है कि मेरे पास अब एक अच्छा संरचित स्कीमा है:

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)

तो, संक्षेप में, यह समाधान:

  • हमें tuples के लिए अलग कॉलम प्राप्त करने की अनुमति देता है (इसलिए हम tuples पर फिर से जुड़ सकते हैं, yay!)
  • हम फिर से बस इंक्विटिस पर भरोसा कर सकते हैं (इसलिए kryoसभी जगह से गुजरने की कोई आवश्यकता नहीं है)
  • लगभग पूरी तरह से पीछे की ओर संगत है import spark.implicits._(कुछ नाम बदलने के साथ)
  • करता नहीं हम पर शामिल हो kyroधारावाहिक बाइनरी कॉलम, उन हो सकता है खेतों में अकेले
  • कुछ टुपल कॉलम को "मान" में बदलने का अप्रिय साइड-इफ़ेक्ट है (यदि आवश्यक हो, तो इसे परिवर्तित करके .toDF, नए कॉलम नामों को निर्दिष्ट करके, और डेटासेट में वापस परिवर्तित करके पूर्ववत किया जा सकता है - और स्कीमा नाम जुड़ने से संरक्षित होने लगते हैं , जहां उन्हें सबसे ज्यादा जरूरत है)।

सामान्य रूप से कक्षाओं के लिए आंशिक समाधान

यह कम सुखद है और इसका कोई अच्छा समाधान नहीं है। हालांकि, अब जब हमारे पास ऊपर टुपल समाधान है, तो मेरे पास एक और उत्तर से निहित रूपांतरण समाधान का एक कूबड़ है, यह थोड़ा कम दर्दनाक भी होगा क्योंकि आप अपनी अधिक जटिल कक्षाओं को ट्यूपल्स में बदल सकते हैं। फिर, डेटासेट बनाने के बाद, आप शायद डेटाफ़्रेम दृष्टिकोण का उपयोग करके कॉलम का नाम बदल देंगे। अगर सब ठीक हो जाता है, तो यह वास्तव में एक सुधार है क्योंकि मैं अब अपनी कक्षाओं के क्षेत्रों में शामिल हो सकता हूं। अगर मैंने सिर्फ एक फ्लैट बाइनरी kryoसीरियलाइज़र का उपयोग किया होता जो संभव नहीं होता।

यहाँ एक उदाहरण है कि सब कुछ का एक सा करता है: मैं एक वर्ग है MyObjजो प्रकार के खाने हैं Int, java.util.UUID, और Set[String]। पहला खुद का ख्याल रखता है। दूसरा, हालांकि मैं उपयोग करके अनुक्रमित कर सकता हूं kryoयदि एक के रूप में संग्रहीत किया जाता है तो अधिक उपयोगी होगा String(क्योंकि UUIDs आमतौर पर कुछ ऐसा है जिसके खिलाफ मैं शामिल होना चाहूंगा )। तीसरा वास्तव में सिर्फ एक बाइनरी कॉलम में है।

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)

अब, मैं इस मशीनरी का उपयोग करके एक अच्छा स्कीमा के साथ एक डेटासेट बना सकता हूं:

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]

और स्कीमा मुझे सही नामों के साथ कॉलम दिखाता है और पहले दो चीजों के साथ मैं उनके खिलाफ जुड़ सकता हूं।

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

क्या ExpressionEncoderJSON सीरियलाइज़ेशन का उपयोग करके एक कस्टम क्लास बनाना संभव है ? मेरे मामले में मैं टुपल्स के साथ दूर नहीं जा सकता, और kryo मुझे एक बाइनरी कॉलम देता है ..
एलेक्सी Svyatkovskiy

1
@AlexeyS मुझे ऐसा नहीं लगता। लेकिन आप ऐसा क्यों चाहेंगे? आप मेरे द्वारा प्रस्तावित प्रस्ताव को अंतिम समाधान के साथ क्यों नहीं निकाल सकते? यदि आप अपना डेटा JSON में डाल सकते हैं, तो आपको खेतों को निकालने और उन्हें केस क्लास में रखने में सक्षम होना चाहिए ...
एलेक

1
दुर्भाग्य से इस उत्तर की निचली रेखा यह है कि कोई समाधान नहीं है जो काम करता है।
बाल

@ बाओल छाँटे। लेकिन याद रखें कि स्पार्क जो कर रहा है वह कितना मुश्किल है। स्काला का प्रकार प्रणाली केवल "व्युत्पन्न" एन्कोडर के लिए पर्याप्त शक्तिशाली नहीं है जो पुन: खेतों से गुजरती है। सच कहूं, मुझे आश्चर्य है कि किसी ने भी इसके लिए एनोटेशन मैक्रो नहीं बनाया। प्राकृतिक (लेकिन मुश्किल) समाधान की तरह लगता है।
एलेक

1
@combinatorist मेरी समझ यह है कि डेटासेट और डेटाफ्रेम (लेकिन RDDs नहीं, क्योंकि उन्हें एन्कोडर की आवश्यकता नहीं है!) एक प्रदर्शन के नजरिए के बराबर है। डेटासेट की प्रकार-सुरक्षा का अनुमान न करें! सिर्फ इसलिए कि स्पार्क आंतरिक रूप से एक टन परावर्तन, कास्ट आदि का उपयोग करता है, इसका मतलब यह नहीं है कि आपको उस इंटरफ़ेस की टाइप-सेफ्टी के बारे में परवाह नहीं करनी चाहिए जो उजागर हुई है। लेकिन यह मुझे अपने खुद के डेटासेट-आधारित प्रकार-सुरक्षित कार्यों को बनाने के बारे में बेहतर महसूस करता है जो हुड के नीचे डेटाफ्रेम का उपयोग करते हैं।
एलेक

32
  1. जेनेरिक एनकोडर का उपयोग करना।

    अब दो सामान्य एनकोडर उपलब्ध हैं kryoऔर javaSerializationबाद वाले को स्पष्ट रूप से वर्णित किया गया है:

    बेहद अक्षम और केवल अंतिम उपाय के रूप में इस्तेमाल किया जाना चाहिए।

    निम्न वर्ग मान रहा है

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

    आप इनकोडिंग का उपयोग कर इनकोडिंग एनकोडर जोड़ सकते हैं:

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

    जिनका उपयोग निम्नानुसार किया जा सकता है:

    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()
      }
    }

    binaryजब DataFrameआप स्कीमा के अनुसार परिवर्तित होते हैं तो यह वस्तुओं को स्तंभ के रूप में संग्रहीत करता है :

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

    kryoविशिष्ट क्षेत्र के लिए एनकोडर का उपयोग करके टुपल्स को एनकोड करना भी संभव है :

    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]

    कृपया ध्यान दें कि हम यहां निहित एन्कोडर पर निर्भर नहीं हैं, लेकिन एनकोडर को स्पष्ट रूप से पास करते हैं, इसलिए यह toDSविधि के साथ काम नहीं करेगा ।

  2. निहितार्थ रूपांतरणों का उपयोग करना:

    उदाहरण के लिए एन्कोडेड और कस्टम वर्ग के बीच निहित रूपांतरण प्रदान करें:

    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()
      }
    }

संबंधित सवाल:


समाधान 1 टाइप किए गए संग्रहों के लिए काम करने के लिए नहीं लगता है (कम से कम Set) मुझे मिलता है Exception in thread "main" java.lang.UnsupportedOperationException: No Encoder found for Set[Bar]
विक्टर पी।

@VictorP। यह उम्मीद है कि मुझे डर है इस तरह के मामले में आपको विशिष्ट प्रकार के लिए एक एनकोडर की आवश्यकता होगी ( kryo[Set[Bar]]उसी तरह अगर कक्षा में एक फ़ील्ड है जिसमें Barआपको पूरी वस्तु के लिए एनकोडर की आवश्यकता होती है। ये बहुत ही क्रूड विधियां हैं।
शून्य 323

@ शून्य 323 मैं एक ही मुद्दे का सामना कर रहा हूं। क्या आप पूरी परियोजना को कैसे एनकोड कर सकते हैं इसका एक कोड उदाहरण रख सकते हैं? बहुत धन्यवाद!
रॉक

@ मुझे यकीन नहीं है कि आप "संपूर्ण परियोजना" से क्या मतलब है
शून्य 323

@ शून्य 323 आपकी टिप्पणी के अनुसार, "यदि वर्ग में कोई फ़ील्ड है, Barतो आपको संपूर्ण ऑब्जेक्ट के लिए एनकोडर की आवश्यकता होती है"। मेरा सवाल था कि इस "पूरे प्रोजेक्ट" को कैसे एनकोड किया जाए?
रॉक

9

आप UDTRegistration और फिर Case Classes, Tuples इत्यादि का उपयोग कर सकते हैं ... सभी आपके उपयोगकर्ता परिभाषित प्रकार के साथ सही ढंग से काम करते हैं!

कहो कि आप एक कस्टम Enum का उपयोग करना चाहते हैं:

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
}

इसे इस तरह पंजीकृत करें:

// 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)

फिर इसका उपयोग करें!

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())

कहो कि आप एक पॉलीमॉर्फिक रिकॉर्ड का उपयोग करना चाहते हैं:

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

... और इसे इस तरह उपयोग करें:

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()

आप एक कस्टम UDT लिख सकते हैं जो बाइट्स के लिए सब कुछ एनकोड करता है (मैं यहां जावा सीरियलाइजेशन का उपयोग कर रहा हूं लेकिन स्पार्क के क्रियो संदर्भ को ध्यान में रखना बेहतर है)।

सबसे पहले UDT क्लास को परिभाषित करें:

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]
}

फिर इसे पंजीकृत करें:

// 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)

तब आप इसका उपयोग कर सकते हैं!

// 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
मैं यह नहीं देखता कि आपकी क्रीयो का उपयोग कहां किया जाता है (CustomPolyUDT में)
mathieu

मैं अपने प्रोजेक्ट में एक UDT को परिभाषित करने की कोशिश कर रहा हूं और मुझे यह त्रुटि मिल रही है "प्रतीक UserDefinedType इस जगह से दुर्गम है"। कोई मदद ?
रिजो जोसेफ

हाय @RijoJoseph। आपको अपनी परियोजना में एक पैकेज org.apache.spark बनाने की आवश्यकता है और उसमें अपना UDT कोड डालें।
चॉपी.लम्बरजैक

6

एनकोडर कमोबेश उसी में काम करते हैं Spark2.0। और Kryoअभी भी अनुशंसित serializationविकल्प है।

आप स्पार्क-शेल के साथ निम्नलिखित उदाहरण देख सकते हैं

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.

अब तक] appropriate encodersवर्तमान दायरे में नहीं थे इसलिए हमारे व्यक्तियों को binaryमूल्यों के रूप में एन्कोड नहीं किया गया था । लेकिन जब हम धारावाहिकीकरण implicitका उपयोग करते हुए कुछ एनकोडर प्रदान करेंगे तो यह बदल जाएगा 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

जावा बीन क्लास के मामले में, यह उपयोगी हो सकता है

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

अब आप केवल डेटा को कस्टम डेटाफ़्रेम के रूप में पढ़ सकते हैं

dataFrame.as[MyClass]

यह एक कस्टम क्लास एनकोडर बनाएगा न कि बाइनरी एक।


1

मेरे उदाहरण जावा में होंगे, लेकिन मुझे कल्पना नहीं है कि यह स्काला के लिए कठिन आदत है।

मैं काफी सफल परिवर्तित किया गया है RDD<Fruit>करने के लिए Dataset<Fruit>का उपयोग कर spark.createDataset और Encoders.bean जब तक Fruitएक सरल है जावा बीन

चरण 1: सरल जावा बीन बनाएँ।

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 लोगों को उनके एनकोडर से पहले गोमांस के रूप में आदिम प्रकार और स्ट्रिंग के साथ कक्षाओं के लिए छड़ी। यदि आपके पास नेस्टेड ऑब्जेक्ट के साथ एक वर्ग है, तो अपने सभी क्षेत्रों को समतल करने के साथ एक और सरल जावा बीन बनाएं, ताकि आप जटिल प्रकार को सरल बनाने के लिए आरडीडी परिवर्तनों का उपयोग कर सकें। यकीन है कि यह थोड़ा अतिरिक्त काम है, लेकिन मुझे लगता है कि यह एक फ्लैट स्कीमा के साथ काम करने में बहुत मदद करेगा।

चरण 2: RDD से अपना डेटासेट प्राप्त करें

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);

और वोइला! बल्कि, कुल्ला, दोहराएं।


मेरा सुझाव है कि सरल संरचनाओं के लिए आप उन्हें मूल स्पार्क प्रकारों में संग्रहीत करके बेहतर सेवा करेंगे, बजाय उन्हें एक बूँद के क्रमबद्ध करने के। वे पाइथन गेटवे के पार बेहतर काम करते हैं, जो पैर्केट में अधिक पारदर्शी है, और यहां तक ​​कि एक ही आकार की संरचनाओं में भी डाला जा सकता है।
मेटासिम

1

उन लोगों के लिए जो मेरी स्थिति में हो सकते हैं, मैंने अपना जवाब यहां भी दिया।

विस्तार से,

  1. मैं SQLContext से 'सेट टाइप डेटा' पढ़ रहा था। तो मूल डेटा प्रारूप DataFrame है।

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

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

  2. फिर इसे mutable.WrappedArray प्रकार के साथ rdd.map () का उपयोग करके RDD में परिवर्तित करें।

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

    परिणाम:

    (1,Set(1))


0

पहले से दिए गए सुझावों के अलावा, मैंने हाल ही में खोजा एक और विकल्प यह है कि आप अपने कस्टम वर्ग को विशेषता सहित घोषित कर सकते हैं org.apache.spark.sql.catalyst.DefinedByConstructorParams

यह तब काम करता है जब वर्ग में एक कंस्ट्रक्टर होता है जो एक्सप्रेशनइन्कोडर के प्रकारों का उपयोग करता है, समझ सकता है, अर्थात आदिम मान और मानक संग्रह। यह तब काम आता है जब आप कक्षा को केस क्लास घोषित करने में सक्षम नहीं होते हैं, लेकिन डेटासैट में शामिल होने पर इसे हर बार एनकोड करने के लिए क्रियो का उपयोग नहीं करना चाहते हैं।

उदाहरण के लिए, मैं एक केस क्लास घोषित करना चाहता था जिसमें ब्रीज़ वेक्टर शामिल था। एक ही एनकोडर जो कि सामान्य रूप से क्रायो को संभालने में सक्षम होगा। लेकिन अगर मैंने एक उपवर्ग घोषित किया जो कि ब्रीज़ डेंसवेक्टर और डिफाइंडबाइकस्ट्रक्टरप्रेम को बढ़ाता है, तो एक्सप्रेशनइन्कोडर समझ गया कि यह डबल्स की एक सरणी के रूप में क्रमबद्ध हो सकता है।

यहाँ बताया गया है कि मैंने इसे कैसे घोषित किया:

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]

अब मैं SerializableDenseVectorएक सरल एक्सप्रेशनइन्कोडर और नो आरियो का उपयोग करके डेटासेट (सीधे, या उत्पाद के हिस्से के रूप में) में उपयोग कर सकता हूं। यह एक ब्रीज DenseVector की तरह ही काम करता है लेकिन एक ऐरे [डबल] के रूप में काम करता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.