TensorFlow 1.8 - Reading custom file and record formats

कस्टम फ़ाइल और रिकॉर्ड प्रारूप पढ़ना




tensorflow

कस्टम फ़ाइल और रिकॉर्ड प्रारूप पढ़ना

आवश्यक:

हम फ़ाइल स्वरूप को दो टुकड़ों में समर्थन करने के कार्य को विभाजित करते हैं:

  • फ़ाइल प्रारूप: हम कच्चे रिकॉर्ड को पढ़ने के लिए एक रीडर tf.data.Dataset का उपयोग करते हैं (जो आमतौर पर स्केलर स्ट्रिंग tf.data.Dataset द्वारा दर्शाया जाता है, लेकिन एक फ़ाइल से अधिक संरचना हो सकती है)।
  • रिकॉर्ड प्रारूप: हम डिकोडर या पार्सिंग ऑप्स का उपयोग करके एक स्ट्रिंग रिकॉर्ड को टेन्सरफ्लो द्वारा प्रयोग करने योग्य टेंसरों में बदल देते हैं।

उदाहरण के लिए, CSV फ़ाइल को पढ़ने के लिए, हम लाइन-दर-पंक्ति पाठ फ़ाइलों को पढ़ने के लिए एक डेटासेट का उपयोग करते हैं और फिर एक op map करते हैं जो डेटासेट में पाठ की प्रत्येक पंक्ति से CSV डेटा को पार्स करता है।

फ़ाइल स्वरूप के लिए Dataset लिखना

एक tf.data.Dataset तत्वों के एक अनुक्रम का प्रतिनिधित्व करता है , जो किसी फ़ाइल में अलग-अलग रिकॉर्ड हो सकता है। "रीडर" डेटासेट के कई उदाहरण हैं जो पहले से ही TensorFlow में निर्मित हैं:

इनमें से प्रत्येक कार्यान्वयन में तीन संबंधित वर्ग शामिल हैं:

  • एक tensorflow::DatasetOpKernel उपवर्ग (जैसे TextLineDatasetOp ), जो TensorFlow को बताता है कि कैसे एक डेटासेट से MakeDataset() से अपने MakeDataset() विधि में निर्माण किया जाए।

  • एक tensorflow::GraphDatasetBase उपवर्ग (जैसे TextLineDatasetOp::Dataset ), जो स्वयं डेटासेट की अपरिवर्तनीय परिभाषा का प्रतिनिधित्व करता है, और TensorFlow को बताता है कि कैसे उस डेटासेट पर एक इटाइटर ऑब्जेक्ट का निर्माण किया जाए, अपने MakeIterator() विधि में।

  • एक tensorflow::DatasetIterator<Dataset> उपवर्ग (जैसे TextLineDatasetOp::Dataset::Iterator ), जो किसी विशेष डेटासेट पर एक पुनरावृत्त की उत्परिवर्तनीय स्थिति का प्रतिनिधित्व करता है, और TensorFlow को बताता है कि अगले तत्व को कैसे प्राप्त किया जाए, अपने GetNextInternal() विधि।

सबसे महत्वपूर्ण विधि GetNextInternal() विधि है, क्योंकि यह परिभाषित करती है कि वास्तव में फ़ाइल से रिकॉर्ड कैसे पढ़ें और उन्हें एक या एक से अधिक Tensor ऑब्जेक्ट के रूप में प्रतिनिधित्व करें।

MyReaderDataset नामक एक नया रीडर डेटासेट बनाने के लिए, आपको निम्न की आवश्यकता होगी:

  1. C ++ में, tensorflow::DatasetOpKernel उपवर्गों को परिभाषित करें tensorflow::DatasetOpKernel , tensorflow::GraphDatasetBase , और tensorflow::DatasetIterator<Dataset> जो रीडिंग लॉजिक को कार्यान्वित करता है।
  2. C ++ में, "MyReaderDataset" नाम के साथ एक नया रीडर ऑप और कर्नेल पंजीकृत करें।
  3. पायथन में, tf.data.Dataset एक उपवर्ग को tf.data.Dataset जिसे MyReaderDataset कहा जाता है।

आप सभी C ++ कोड एक ही फाइल में डाल सकते हैं, जैसे कि my_reader_dataset_op.cc । यदि आप एक सेशन जोड़ने के साथ परिचित हैं तो यह मदद करेगा। निम्नलिखित कंकाल को आपके कार्यान्वयन के लिए शुरुआती बिंदु के रूप में इस्तेमाल किया जा सकता है:

#include "tensorflow/core/framework/common_shape_fns.h"
#include "tensorflow/core/framework/dataset.h"
#include "tensorflow/core/framework/op.h"
#include "tensorflow/core/framework/shape_inference.h"

namespace tensorflow {
namespace {

class MyReaderDatasetOp : public DatasetOpKernel {
 public:

  MyReaderDatasetOp(OpKernelConstruction* ctx) : DatasetOpKernel(ctx) {
    // Parse and validate any attrs that define the dataset using
    // `ctx->GetAttr()`, and store them in member variables.
  }

  void MakeDataset(OpKernelContext* ctx, DatasetBase** output) override {
    // Parse and validate any input tensors 0that define the dataset using
    // `ctx->input()` or the utility function
    // `ParseScalarArgument<T>(ctx, &arg)`.

    // Create the dataset object, passing any (already-validated) arguments from
    // attrs or input tensors.
    *output = new Dataset(ctx);
  }

 private:
  class Dataset : public GraphDatasetBase {
   public:
    Dataset(OpKernelContext* ctx) : GraphDatasetBase(ctx) {}

    std::unique_ptr<IteratorBase> MakeIterator(
        const string& prefix) const override {
      return std::unique_ptr<IteratorBase>(
          new Iterator({this, strings::StrCat(prefix, "::MyReader")}));
    }

    // Record structure: Each record is represented by a scalar string tensor.
    //
    // Dataset elements can have a fixed number of components of different
    // types and shapes; replace the following two methods to customize this
    // aspect of the dataset.
    const DataTypeVector& output_dtypes() const override {
      static DataTypeVector* dtypes = new DataTypeVector({DT_STRING});
      return *dtypes;
    }
    const std::vector<PartialTensorShape>& output_shapes() const override {
      static std::vector<PartialTensorShape>* shapes =
          new std::vector<PartialTensorShape>({ {}});
      return *shapes;
    }

    string DebugString() override { return "MyReaderDatasetOp::Dataset"; }

   protected:
    // Optional: Implementation of `GraphDef` serialization for this dataset.
    //
    // Implement this method if you want to be able to save and restore
    // instances of this dataset (and any iterators over it).
    Status AsGraphDefInternal(DatasetGraphDefBuilder* b,
                              Node** output) const override {
      // Construct nodes to represent any of the input tensors from this
      // object's member variables using `b->AddScalar()` and `b->AddVector()`.
      std::vector<Node*> input_tensors;
      TF_RETURN_IF_ERROR(b->AddDataset(this, input_tensors, output));
      return Status::OK();
    }

   private:
    class Iterator : public DatasetIterator<Dataset> {
     public:
      explicit Iterator(const Params& params)
          : DatasetIterator<Dataset>(params), i_(0) {}

      // Implementation of the reading logic.
      //
      // The example implementation in this file yields the string "MyReader!"
      // ten times. In general there are three cases:
      //
      // 1. If an element is successfully read, store it as one or more tensors
      //    in `*out_tensors`, set `*end_of_sequence = false` and return
      //    `Status::OK()`.
      // 2. If the end of input is reached, set `*end_of_sequence = true` and
      //    return `Status::OK()`.
      // 3. If an error occurs, return an error status using one of the helper
      //    functions from "tensorflow/core/lib/core/errors.h".
      Status GetNextInternal(IteratorContext* ctx,
                             std::vector<Tensor>* out_tensors,
                             bool* end_of_sequence) override {
        // NOTE: `GetNextInternal()` may be called concurrently, so it is
        // recommended that you protect the iterator state with a mutex.
        mutex_lock l(mu_);
        if (i_ < 10) {
          // Create a scalar string tensor and add it to the output.
          Tensor record_tensor(ctx->allocator({}), DT_STRING, {});
          record_tensor.scalar<string>()() = "MyReader!";
          out_tensors->emplace_back(std::move(record_tensor));
          ++i_;
          *end_of_sequence = false;
        } else {
          *end_of_sequence = true;
        }
        return Status::OK();
      }

     protected:
      // Optional: Implementation of iterator state serialization for this
      // iterator.
      //
      // Implement these two methods if you want to be able to save and restore
      // instances of this iterator.
      Status SaveInternal(IteratorStateWriter* writer) override {
        mutex_lock l(mu_);
        TF_RETURN_IF_ERROR(writer->WriteScalar(full_name("i"), i_));
        return Status::OK();
      }
      Status RestoreInternal(IteratorContext* ctx,
                             IteratorStateReader* reader) override {
        mutex_lock l(mu_);
        TF_RETURN_IF_ERROR(reader->ReadScalar(full_name("i"), &i_));
        return Status::OK();
      }

     private:
      mutex mu_;
      int64 i_ GUARDED_BY(mu_);
    };
  };
};

// Register the op definition for MyReaderDataset.
//
// Dataset ops always have a single output, of type `variant`, which represents
// the constructed `Dataset` object.
//
// Add any attrs and input tensors that define the dataset here.
REGISTER_OP("MyReaderDataset")
    .Output("handle: variant")
    .SetIsStateful()
    .SetShapeFn(shape_inference::ScalarShape);

// Register the kernel implementation for MyReaderDataset.
REGISTER_KERNEL_BUILDER(Name("MyReaderDataset").Device(DEVICE_CPU),
                        MyReaderDatasetOp);

}  // namespace
}  // namespace tensorflow

अंतिम चरण सी ++ कोड का निर्माण करना और पायथन आवरण को जोड़ना है। ऐसा करने का सबसे आसान तरीका एक गतिशील पुस्तकालय (उदाहरण के लिए "my_reader_dataset_op.so" कहा जाता है), और पायथन वर्ग को जोड़ना है जो इसे लपेटने के लिए tf.data.Dataset को उप-वर्ग में tf.data.Dataset है। एक उदाहरण पायथन कार्यक्रम यहाँ दिया गया है:

import tensorflow as tf

# Assumes the file is in the current working directory.
my_reader_dataset_module = tf.load_op_library("./my_reader_dataset_op.so")

class MyReaderDataset(tf.data.Dataset):

  def __init__(self):
    super(MyReaderDataset, self).__init__()
    # Create any input attrs or tensors as members of this class.

  def _as_variant_tensor(self):
    # Actually construct the graph node for the dataset op.
    #
    # This method will be invoked when you create an iterator on this dataset
    # or a dataset derived from it.
    return my_reader_dataset_module.my_reader_dataset()

  # The following properties define the structure of each element: a scalar
  # <a href="../api_docs/python/tf/string"><code>tf.string</code></a> tensor. Change these properties to match the `output_dtypes()`
  # and `output_shapes()` methods of `MyReaderDataset::Dataset` if you modify
  # the structure of each element.
  @property
  def output_types(self):
    return tf.string

  @property
  def output_shapes(self):
    return tf.TensorShape([])

  @property
  def output_classes(self):
    return tf.Tensor

if __name__ == "__main__":
  # Create a MyReaderDataset and print its elements.
  with tf.Session() as sess:
    iterator = MyReaderDataset().make_one_shot_iterator()
    next_element = iterator.get_next()
    try:
      while True:
        print(sess.run(next_element))  # Prints "MyReader!" ten times.
    except tf.errors.OutOfRangeError:
      pass

आप tensorflow/python/data/ops/dataset_ops.py में Dataset रैपर कक्षाओं के कुछ उदाहरण देख सकते हैं।

रिकॉर्ड प्रारूप के लिए एक ऑप लिखना

आम तौर पर यह एक सामान्य ऑप है जो इनपुट के रूप में एक स्केलर स्ट्रिंग रिकॉर्ड लेता है, और इसलिए ओप जोड़ने के लिए निर्देशों का पालन करता है । आप वैकल्पिक रूप से इनपुट के रूप में एक स्केलर स्ट्रिंग कुंजी ले सकते हैं, और इसमें त्रुटिपूर्ण स्वरूपित डेटा की रिपोर्ट करने वाले त्रुटि संदेशों में शामिल हैं। इस तरह से उपयोगकर्ता अधिक आसानी से ट्रैक कर सकते हैं कि खराब डेटा कहां से आया है।

ऑप्स के उदाहरण डिकोडिंग रिकॉर्ड के लिए उपयोगी हैं:

ध्यान दें कि किसी विशेष रिकॉर्ड प्रारूप को डीकोड करने के लिए कई ऑप्स का उपयोग करना उपयोगी हो सकता है। उदाहरण के लिए, आपके पास tf.train.Example प्रोटोकॉल बफर में स्ट्रिंग के रूप में सहेजी गई छवि हो सकती है। उस छवि के प्रारूप के आधार पर, आप एक tf.parse_single_example op से संबंधित आउटपुट ले सकते हैं और tf.image.decode_jpeg , tf.image.decode_png , या tf.decode_raw कॉल कर tf.decode_raw tf.decode_raw का आउटपुट tf.decode_raw और टुकड़ों को निकालने के लिए tf.slice और tf.reshape का उपयोग करना tf.slice