কীভাবে ডেটাসেটে কাস্টম অবজেক্টস সংরক্ষণ করবেন?


149

স্পার্ক ডেটাসেটের পরিচয় অনুসারে :

আমরা স্পার্ক ২.০-এর প্রত্যাশায় আমরা বিশেষ করে ডেটাসেটগুলিতে কিছু আকর্ষণীয় উন্নতির পরিকল্পনা করেছি: বিশেষত: ... কাস্টম এনকোডারগুলি যখন আমরা বর্তমানে বিভিন্ন ধরণের এনকোডারগুলিকে অটোজারেট করি, আমরা কাস্টম অবজেক্টগুলির জন্য একটি API খুলতে চাই।

এবং Datasetনিম্নলিখিত ত্রুটি যেমন কাস্টম টাইপ সংরক্ষণ করতে চেষ্টা :

কোনও ডেটাসেটে সঞ্চিত প্রকারের জন্য এনকোডার খুঁজে পাওয়া যায়নি। প্রিমিটিভ টাইপ (ইনট, স্ট্রিং, ইত্যাদি) এবং প্রোডাক্ট টাইপ (কেস ক্লাস) sqlContext.implicits আমদানি করে সমর্থিত __ অন্যান্য ধরণের সিরিয়ালাইজ করার জন্য সমর্থন ভবিষ্যতে প্রকাশিত হবে

বা:

জাভা.লাং.অনসম্পর্কিত অপারেশন এক্সসেপশন: এর জন্য কোনও এনকোডার পাওয়া যায় নি ....

কোন বিদ্যমান workaround আছে?


নোট করুন এই প্রশ্নটি শুধুমাত্র একটি সম্প্রদায়ের উইকি উত্তরের প্রবেশের পয়েন্ট হিসাবে উপস্থিত রয়েছে। প্রশ্ন এবং উত্তর উভয় আপডেট / উন্নত নির্দ্বিধায়।

উত্তর:


240

হালনাগাদ

এই উত্তরটি যদিও সবকিছু এখন আরও ভাল হয়, বৈধ এবং তথ্যপূর্ণ এখনও 2.2 / 2.3, যোগ, যার জন্য বিল্ট ইন এনকোডার সমর্থন যেহেতু Set, Seq, Map, Date, Timestamp, এবং BigDecimal। যদি আপনি কেবল কেস ক্লাস এবং সাধারণ স্কালাল প্রকারের সাথে ধরণের তৈরি করতে থাকেন তবে কেবল অন্তর্নিহিত দ্বারা আপনার ভাল হওয়া উচিত SQLImplicits


দুর্ভাগ্যক্রমে, এটিতে কার্যত কিছুই যোগ করা হয়নি। খুঁজছেন @since 2.0.0যে Encoders.scalaবা SQLImplicits.scalaখুঁজে বের করে কিছু বেশিরভাগই আদিম প্রকার (এবং কেস ক্লাস কিছু টোয়েকিং) সঙ্গে না। সুতরাং, প্রথমটি বলার জন্য: কাস্টম শ্রেণীর এনকোডারদের জন্য বর্তমানে কোনও সত্যিকারের ভাল সমর্থন নেই । উপায়টির বাইরে যাওয়ার সাথে সাথে কিছু কৌশল অনুসরণ করা হয় যা একটি ভাল কাজ করে যা আমরা কখনই আশা করতে পারি, আমাদের বর্তমানে আমাদের কাছে যা আছে তা প্রদত্ত। একটি সুস্পষ্ট দাবি অস্বীকার হিসাবে: এটি পুরোপুরি কার্যকর হবে না এবং আমি সমস্ত সীমাবদ্ধতা পরিষ্কার এবং সামনে করার জন্য যথাসাধ্য চেষ্টা করব।

ঠিক সমস্যা কি

আপনি যখন কোনও ডেটাসেট বানাতে চান, তখন স্পার্কের জন্য একটি এনকোডার প্রয়োজন হয় (অভ্যন্তরীণ স্পার্ক এসকিউএল উপস্থাপনায় এবং টাইপ টির একটি জেভিএম বস্তু রূপান্তর করতে) যা সাধারণত কোনও থেকে ইমপ্লিটের মাধ্যমে স্বয়ংক্রিয়ভাবে তৈরি হয় SparkSession, বা স্ট্যাটিক পদ্ধতিতে কল করে স্পষ্টভাবে তৈরি করা যেতে পারে on 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(যদি আমি তৈরি করার চেষ্টা করি তবে একই সমস্যা দেখা দেয়) এ 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)))

এটি যদিও খুব ক্লান্তিকর হয়ে ওঠে। বিশেষত যদি আপনার কোডটি সব ধরণের ডেটাসেটগুলিতে চালাচ্ছে, যোগদান করছে, গোষ্ঠীকরণ করছে You সুতরাং, কেন কেবল এমন একটি অন্তর্নিহিত না করে যা স্বয়ংক্রিয়ভাবে এগুলি করে?

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

আমি এখনো মূর্ত আউট নি প্রত্যাশিত tuple নাম পেতে কিভাবে ( _1, _2তাদের পুনঃনামকরনের ছাড়া, ...) ডিফল্টরূপে - অন্য কেউ এই সঙ্গে চারপাশে খেলা করতে চায় কিনা, এই যেখানে নাম "value"চালু পরার এবং এই যেখানে tuple হয় নামগুলি সাধারণত যুক্ত করা হয়। তবে মূল কথাটি হ'ল আমার কাছে এখন একটি সুন্দর কাঠামোগত স্কিমা রয়েছে:

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)

সুতরাং, সংক্ষেপে, এই কর্মসূচী:

  • আমাদের টিপলসের জন্য আলাদা কলাম পেতে দেয় (যাতে আমরা আবার টিপলগুলিতে যোগ দিতে পারি, হ্যাঁ!)
  • আমরা আবার কেবল প্রভাবগুলির উপর নির্ভর করতে পারি (সুতরাং kryoপুরো জায়গা জুড়ে যাওয়ার দরকার নেই )
  • প্রায় পুরোপুরি পিছনের দিকে সামঞ্জস্যপূর্ণ import spark.implicits._(কিছু নাম পরিবর্তনের সাথে জড়িত)
  • সিরিয়ালযুক্ত বাইনারি কলামগুলিতে আমাদের যোগদান করতে দেয় নাkyro , ক্ষেত্রগুলি ছেড়ে দিন alone
  • কিছু টিপল কলামকে "মান" হিসাবে নামকরণের অপ্রীতিকর পার্শ্ব-প্রতিক্রিয়া রয়েছে (যদি প্রয়োজন হয় তবে এটি রূপান্তর করে .toDF, নতুন কলামের নাম উল্লেখ করে এবং কোনও ডেটাশেটে রূপান্তরিত করে পূর্বাবস্থায় ফেলা যায় - এবং স্কিমা নামগুলি যোগদানের মাধ্যমে সংরক্ষণ করা হবে বলে মনে হয়) , যেখানে তাদের সবচেয়ে বেশি প্রয়োজন))

সাধারণভাবে ক্লাসের জন্য আংশিক সমাধান

এই এক কম আনন্দদায়ক এবং কোন ভাল সমাধান আছে। তবে, এখন যেহেতু আমাদের উপরে উপরের টিউপল সলিউশন রয়েছে, আমার কাছে অন্য একটি উত্তর থেকে অন্তর্নিহিত রূপান্তর সমাধানটি কিছুটা কম বেদনাদায়ক হবে কারণ আপনি আপনার আরও জটিল ক্লাসগুলিকে টুপলে রূপান্তর করতে পারেন। তারপরে, ডেটাसेट তৈরি করার পরে, আপনি সম্ভবত ডেটা ফ্রেম পদ্ধতির ব্যবহার করে কলামগুলির নাম পরিবর্তন করবেন। যদি সবকিছু ঠিকঠাক হয় তবে এটি সত্যিই একটি উন্নতি, কারণ আমি এখন আমার ক্লাসের ক্ষেত্রগুলিতে যোগদান করতে পারি। আমি যদি কেবল একটি ফ্ল্যাট বাইনারি kryoসিরিয়ালাইজার ব্যবহার করি তবে এটি সম্ভব হত না।

এখানে একটি উদাহরণ যে সবকিছু একটি বিট নেই: আমি একটি বর্গ আছে MyObjকোন ধরনের ক্ষেত্র রয়েছে Int, java.util.UUIDএবং Set[String]। প্রথম নিজের যত্ন নেয়। দ্বিতীয়টি, যদিও আমি সিরিয়ালটি ব্যবহার করে kryoআরও কার্যকর হতে পারি যদি এটি হিসাবে সংরক্ষণ করা হয় String(যেহেতু UUIDসাধারণত এমন কিছু যা আমি এর সাথে যোগ দিতে চাই)। তৃতীয়টি সত্যিই কেবল বাইনারি কলামে অন্তর্ভুক্ত।

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)

ExpressionEncoderজেএসওএন সিরিয়ালাইজেশন ব্যবহার করে কি কাস্টম ক্লাস তৈরি করা সম্ভব ? আমার ক্ষেত্রে আমি টিপলস নিয়ে দূরে যেতে পারি না, এবং ক্রিও আমাকে একটি বাইনারি কলাম দেয় ..
আলেক্সি শ্যাভিটকভস্কিই

1
@ অ্যালেক্সিজ আমি এটি মনে করি না। তবে কেন আপনি এটি চান? আমার প্রস্তাবিত শেষ সমাধানটি কেন আপনি পালাতে পারবেন না? আপনি যদি JSON এ আপনার ডেটা রাখতে পারেন, আপনার ক্ষেত্রগুলি বের করতে এবং কেস শ্রেণিতে রাখতে সক্ষম হওয়া উচিত ...
এলেক

1
দুর্ভাগ্যক্রমে এই উত্তরটির মূল কথাটি হ'ল কোনও সমাধান নেই যা কাজ করে।
বাওল

@ বাউল সাজান তবে মনে রাখবেন স্পার্ক কী করছে তা কতটা কঠিন। স্কালার টাইপ সিস্টেমটি এনকোডারগুলিকে "প্রাপ্ত" করার পক্ষে যথেষ্ট শক্তিশালী নয় যা পুনরাবৃত্তভাবে ক্ষেত্রগুলির মধ্যে দিয়ে যায়। সত্যই, আমি আশ্চর্য হয়েছি যে কেউ এ জন্য কোনও টীকা ম্যাক্রো করেনি। প্রাকৃতিক (তবে কঠিন) সমাধানের মতো মনে হচ্ছে।
আলেক

1
@ কম্বিনেটরিস্ট আমার ধারণাটি হ'ল ডেটাসেটস এবং ডেটাফ্রেমগুলি (তবে আরডিডি নয়, যেহেতু তাদের এনকোডার দরকার নেই!) পারফরম্যান্সের দৃষ্টিভঙ্গি থেকে সমান। ডেটাসেটের টাইপ-সুরক্ষার স্বল্প-অনুমান করবেন না! কেবল স্পার্ক অভ্যন্তরীণভাবে এক টন প্রতিবিম্ব, কাসট ইত্যাদি ব্যবহার করে তার অর্থ এই নয় যে আপনার ইন্টারফেসের ধরণ-সুরক্ষার যত্ন নেওয়া উচিত নয় that তবে এটি আমার নিজের ডেটাসেট ভিত্তিক টাইপ-নিরাপদ ফাংশনগুলি তৈরি করতে আরও ভাল বোধ করে যা হুডের নীচে ডেটাফ্রেম ব্যবহার করে।
এলেক

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। আশা করা যায় আমি ভালো যদি ভীত আপনি (নির্দিষ্ট টাইপ জন্য একটি এনকোডার প্রয়োজন হবে am kryo[Set[Bar]]একই ভাবে যদি বর্গ একটি ক্ষেত্র রয়েছে। Barআপনি একটি সম্পূর্ণ বস্তুর জন্য এনকোডার দরকার এই খুব অশোধিত পদ্ধতি।।
zero323

@ শূন্য ৩৩৩ আমি একই সমস্যার মুখোমুখি হচ্ছি। আপনি কীভাবে পুরো প্রকল্পটি এনকোড করবেন তার একটি কোড উদাহরণ রাখতে পারেন? অনেক ধন্যবাদ!
রক

@ রক "পুরো প্রকল্প"
শূন্য 323

@ শূন্য ৩৩৩ আপনার মন্তব্যে, "শ্রেণিতে যদি একটি ক্ষেত্র থাকে তবে Barপুরো সামগ্রীর জন্য আপনার এনকোডার প্রয়োজন।" আমার প্রশ্ন ছিল কিভাবে এই "পুরো প্রকল্পটি" এনকোড করব?
রক

9

আপনি ইউডিটি নিবন্ধন এবং তারপরে কেস ক্লাস, টিপলস ইত্যাদি ব্যবহার করতে পারেন ... সমস্ত আপনার ব্যবহারকারী সংজ্ঞায়িত প্রকারের সাথে সঠিকভাবে কাজ করে!

বলুন আপনি একটি কাস্টম এনুম ব্যবহার করতে চান:

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

আপনি একটি কাস্টম ইউডিটি লিখতে পারেন যা সবকিছুতে বাইটগুলিতে এনকোড থাকে (আমি এখানে জাভা সিরিয়ালাইজেশন ব্যবহার করছি তবে স্পার্কের ক্রিয়ো প্রসঙ্গটি ইনস্ট্রুমেন্টের চেয়ে সম্ভবত ভাল)।

প্রথমে ইউডিটি শ্রেণীর সংজ্ঞা দাও:

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
আমি কোথায় আপনার kryo ব্যবহার করা হয় (CustomPolyUDT মধ্যে) দেখতে না
Mathieu

আমি আমার প্রকল্পে একটি ইউডিটি সংজ্ঞায়িত করার চেষ্টা করছি এবং আমি এই ত্রুটিটি পাচ্ছি "চিহ্নটি ব্যবহারকারীর সংজ্ঞায়িত টাইপটি এই জায়গা থেকে অ্যাক্সেসযোগ্য"। কোন সাহায্য ?
রিজো জোসেফ

হাই @ রিজো জোসেফ আপনার প্রকল্পে আপনার একটি প্যাকেজ org.apache.spark তৈরি করতে হবে এবং এতে আপনার ইউডিটি কোডটি রাখা উচিত।
চপ্পি দ্য লম্বারজ্যাক

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

আমার উদাহরণগুলি জাভাতে থাকবে, তবে আমি এটি কল্পনা করি না যে এটি স্কালার সাথে মানিয়ে নেওয়া কঠিন be

আমি বেশ সফল রূপান্তর হয়েছে 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
}

ডেটাব্রিক্স লোকেরা তাদের এনকোডারগুলি বিফ আপ করার আগে আমি ক্ষেত্র হিসাবে আদিম ধরণের এবং স্ট্রিং সহ ক্লাসে আটকে থাকতাম। আপনার যদি নেস্টেড অবজেক্টের সাথে ক্লাস থাকে তবে এর সমস্ত ক্ষেত্র সমতল করে আরও একটি সাধারণ জাভা বিন তৈরি করুন, যাতে আপনি জটিলটির ম্যাপকে সরলরূপে মানচিত করতে আরডিডি রূপান্তর ব্যবহার করতে পারেন।অবশ্যই এটি কিছুটা অতিরিক্ত কাজ, তবে আমি ধারণা করি এটি ফ্ল্যাট স্কিমার সাথে কাজ করে পারফরম্যান্সে অনেক সহায়তা করবে।

পদক্ষেপ 2: আরডিডি থেকে আপনার ডেটাসেটটি পান

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. আমি এসকিউএল কনটেক্সট থেকে 'সেট টাইপড ডেটা' পড়ছিলাম। সুতরাং আসল ডেটা ফর্ম্যাটটি হ'ল ডেটা ফ্রেম।

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

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

  2. তারপরে এটিকে আরডিডিতে রূপান্তর করুন rdd.map () ব্যবহার করে mutable.WrapedArray টাইপ দিয়ে।

    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

ক্লাসে এমন কোনও নির্মাণকারী রয়েছে যা এক্সপ্রেশন-এনকোডার বুঝতে পারে অর্থাত্ আদিম মান এবং মানক সংগ্রহগুলি ব্যবহার করে works আপনি যখন ক্লাস কে কেস ক্লাস হিসাবে ঘোষণা করতে না পারছেন তখন এটি কাজে আসবে, তবে যখনই এটি কোনও ডেটাসেটের অন্তর্ভুক্ত থাকে তখন ক্রিও এটিকে এনকোড করতে ব্যবহার করতে চান না।

উদাহরণস্বরূপ, আমি একটি কেস ক্লাস ঘোষণা করতে চেয়েছিলাম যাতে একটি ব্রীজ ভেক্টর অন্তর্ভুক্ত ছিল। একমাত্র এনকোডার যা হ্যান্ডেল করতে সক্ষম হবে এটি সাধারণত ক্রাইও হবে। তবে আমি যদি এমন একটি সাবক্লাস ঘোষণা করি যা ব্রীজ ডেনসেক্টর এবং সংজ্ঞায়িত বাইকন্সট্রাক্টরপ্যারামগুলিকে প্রসারিত করে, এক্সপ্রেসইনকোডার বুঝতে পেরেছিলেন যে এটি ডাবলসের অ্যারে হিসাবে সিরিয়ালায়িত হতে পারে।

এখানে আমি এটি কীভাবে ঘোষণা করেছি:

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একটি সাধারণ এক্সপ্রেশনএকোডার এবং কোনও ক্রিয়ো ব্যবহার করে কোনও ডেটাসেট (সরাসরি বা কোনও পণ্য হিসাবে) ব্যবহার করতে পারি। এটি ঠিক একটি বাতাসের ঘনক্ষেত্রের মতো কাজ করে তবে অ্যারে [ডাবল] হিসাবে সিরিয়ালাইজ করে।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.