একটি অপশন তৈরি করুন

আপনি যদি এমন একটি অপ তৈরি করতে চান যা বিদ্যমান TensorFlow লাইব্রেরি দ্বারা আচ্ছাদিত নয়, তাহলে আমরা আপনাকে প্রথমে পাইথনে বিদ্যমান পাইথন অপ্স বা ফাংশনগুলির একটি রচনা হিসাবে অপটি লেখার চেষ্টা করার পরামর্শ দিই৷ যদি এটি সম্ভব না হয়, আপনি একটি কাস্টম C++ অপশন তৈরি করতে পারেন। আপনি একটি কাস্টম C++ op তৈরি করতে চাইতে পারেন এমন বিভিন্ন কারণ রয়েছে:

  • বিদ্যমান অপ্সের সংমিশ্রণ হিসাবে আপনার ক্রিয়াকলাপ প্রকাশ করা সহজ বা সম্ভব নয়।
  • বিদ্যমান আদিম উপাদানগুলির একটি রচনা হিসাবে আপনার ক্রিয়াকলাপ প্রকাশ করা কার্যকর নয়।
  • আপনি প্রাইমিটিভের একটি কম্পোজিশন হ্যান্ড-ফিউজ করতে চান যা ভবিষ্যতের কম্পাইলারের জন্য কঠিন ফিউজিং হবে।

উদাহরণস্বরূপ, কল্পনা করুন যে আপনি "ম্যাক্সপুল" অপারেটরের মতো "মিডিয়ান পুলিং" এর মতো কিছু বাস্তবায়ন করতে চান, তবে সর্বাধিক মানের পরিবর্তে স্লাইডিং উইন্ডোতে মিডিয়ান গণনা করতে চান৷ ক্রিয়াকলাপগুলির একটি সংমিশ্রণ ব্যবহার করে এটি করা সম্ভব হতে পারে (যেমন, ExtractImagePatches এবং TopK ব্যবহার করে), কিন্তু একটি নেটিভ অপারেশনের মতো পারফরম্যান্স- বা মেমরি-দক্ষ নাও হতে পারে যেখানে আপনি একটি একক, ফিউজড অপারেশনে আরও চতুর কিছু করতে পারেন। সর্বদা হিসাবে, অপারেটর কম্পোজিশন ব্যবহার করে আপনি যা চান তা প্রকাশ করার চেষ্টা করা সাধারণত প্রথমে মূল্যবান, শুধুমাত্র যদি এটি কঠিন বা অদক্ষ বলে প্রমাণিত হয় তবে একটি নতুন অপারেশন যোগ করা বেছে নেওয়া।

আপনার কাস্টম অপটি অন্তর্ভুক্ত করতে আপনার প্রয়োজন হবে:

  1. একটি C++ ফাইলে নতুন অপটি নিবন্ধন করুন। অপ রেজিস্ট্রেশন অপের কার্যকারিতার জন্য একটি ইন্টারফেস (স্পেসিফিকেশন) সংজ্ঞায়িত করে, যা অপের বাস্তবায়ন থেকে স্বাধীন। উদাহরণস্বরূপ, op রেজিস্ট্রেশন op এর নাম এবং op এর ইনপুট এবং আউটপুট নির্ধারণ করে। এটি টেনসর আকৃতির অনুমানের জন্য ব্যবহৃত আকৃতি ফাংশনকেও সংজ্ঞায়িত করে।
  2. C++ এ অপটি প্রয়োগ করুন। একটি অপের বাস্তবায়ন একটি কার্নেল হিসাবে পরিচিত, এবং এটি ধাপ 1-এ আপনি নিবন্ধিত স্পেসিফিকেশনের সুনির্দিষ্ট বাস্তবায়ন। বিভিন্ন ইনপুট/আউটপুট প্রকার বা আর্কিটেকচারের জন্য একাধিক কার্নেল থাকতে পারে (উদাহরণস্বরূপ, CPU, GPU)।
  3. একটি পাইথন মোড়ক তৈরি করুন (ঐচ্ছিক)। এই মোড়ক হল পাবলিক API যা পাইথনে অপ তৈরি করতে ব্যবহৃত হয়। অপ রেজিস্ট্রেশন থেকে একটি ডিফল্ট মোড়ক তৈরি করা হয়, যা সরাসরি ব্যবহার করা যেতে পারে বা যোগ করা যেতে পারে।
  4. অপের জন্য গ্রেডিয়েন্ট গণনা করার জন্য একটি ফাংশন লিখুন (ঐচ্ছিক)।
  5. অপ পরীক্ষা করুন। আমরা সাধারণত সুবিধার জন্য পাইথনে এটি করি, তবে আপনি C++ এ অপটিও পরীক্ষা করতে পারেন। আপনি যদি গ্রেডিয়েন্টগুলি সংজ্ঞায়িত করেন, তাহলে আপনি Python tf.test.compute_gradient_error দিয়ে তাদের যাচাই করতে পারেন। একটি উদাহরণ হিসাবে relu_op_test.py দেখুন যা Relu-এর মতো অপারেটর এবং তাদের গ্রেডিয়েন্টগুলির ফরোয়ার্ড ফাংশন পরীক্ষা করে।

পূর্বশর্ত

অপ ইন্টারফেস সংজ্ঞায়িত করুন

আপনি টেনসরফ্লো সিস্টেমের সাথে নিবন্ধন করে একটি অপের ইন্টারফেস সংজ্ঞায়িত করুন। রেজিস্ট্রেশনে, আপনি আপনার অপের নাম, এর ইনপুট (প্রকার এবং নাম) এবং আউটপুট (প্রকার এবং নাম), সেইসাথে ডকস্ট্রিং এবং অপারেটিং এর প্রয়োজন হতে পারে এমন যেকোনো অ্যাটিআরস উল্লেখ করেন।

এটি কীভাবে কাজ করে তা দেখতে, ধরুন আপনি একটি অপ তৈরি করতে চান যা int32 s এর টেনসর নেয় এবং টেনসরের একটি অনুলিপি আউটপুট করে, প্রথম উপাদানটি শূন্যে সেট করা ছাড়া। এটি করার জন্য, zero_out.cc নামে একটি ফাইল তৈরি করুন। তারপর REGISTER_OP ম্যাক্রোতে একটি কল যোগ করুন যা আপনার অপশনের জন্য ইন্টারফেস সংজ্ঞায়িত করে:

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

using namespace tensorflow;

REGISTER_OP("ZeroOut")
    .Input("to_zero: int32")
    .Output("zeroed: int32")
    .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) {
      c->set_output(0, c->input(0));
      return Status::OK();
    });

এই ZeroOut অপটি ইনপুট হিসাবে 32-বিট পূর্ণসংখ্যার একটি টেনসরকে to_zero করে নেয় এবং 32-বিট পূর্ণসংখ্যার একটি টেনসরকে zeroed করে। আউটপুট টেনসরটি ইনপুট টেনসরের মতো একই আকৃতির তা নিশ্চিত করতে অপটি একটি আকৃতি ফাংশনও ব্যবহার করে। উদাহরণস্বরূপ, যদি ইনপুটটি আকৃতির একটি টেনসর হয় [10, 20], তবে এই শেপ ফাংশনটি নির্দিষ্ট করে যে আউটপুট আকৃতিটিও [10, 20]।

অপের জন্য কার্নেল প্রয়োগ করুন

আপনি ইন্টারফেস সংজ্ঞায়িত করার পরে, অপের এক বা একাধিক বাস্তবায়ন প্রদান করুন। এই কার্নেলগুলির মধ্যে একটি তৈরি করতে, একটি ক্লাস তৈরি করুন যা OpKernel প্রসারিত করে এবং Compute পদ্ধতিকে ওভাররাইড করে। Compute পদ্ধতি OpKernelContext* টাইপের একটি context যুক্তি প্রদান করে, যেখান থেকে আপনি ইনপুট এবং আউটপুট টেনসরের মতো দরকারী জিনিসগুলি অ্যাক্সেস করতে পারেন।

আপনি উপরে তৈরি করা ফাইলে আপনার কার্নেল যোগ করুন। কার্নেল এই মত কিছু দেখতে পারে:

#include "tensorflow/core/framework/op_kernel.h"

using namespace tensorflow;

class ZeroOutOp : public OpKernel {
 public:
  explicit ZeroOutOp(OpKernelConstruction* context) : OpKernel(context) {}

  void Compute(OpKernelContext* context) override {
    // Grab the input tensor
    const Tensor& input_tensor = context->input(0);
    auto input = input_tensor.flat<int32>();

    // Create an output tensor
    Tensor* output_tensor = NULL;
    OP_REQUIRES_OK(context, context->allocate_output(0, input_tensor.shape(),
                                                     &output_tensor));
    auto output_flat = output_tensor->flat<int32>();

    // Set all but the first element of the output tensor to 0.
    const int N = input.size();
    for (int i = 1; i < N; i++) {
      output_flat(i) = 0;
    }

    // Preserve the first input value if possible.
    if (N > 0) output_flat(0) = input(0);
  }
};

আপনার কার্নেল প্রয়োগ করার পরে, আপনি এটি টেনসরফ্লো সিস্টেমের সাথে নিবন্ধন করুন। রেজিস্ট্রেশনে, আপনি বিভিন্ন সীমাবদ্ধতা উল্লেখ করেন যার অধীনে এই কার্নেলটি চলবে। উদাহরণস্বরূপ, আপনার কাছে CPU-এর জন্য একটি কার্নেল এবং GPU-এর জন্য একটি পৃথক কার্নেল থাকতে পারে।

ZeroOut অপের জন্য এটি করতে, zero_out.cc এ নিম্নলিখিত যোগ করুন:

REGISTER_KERNEL_BUILDER(Name("ZeroOut").Device(DEVICE_CPU), ZeroOutOp);

মাল্টি-থ্রেডেড CPU কার্নেল

একটি মাল্টি-থ্রেডেড CPU কার্নেল লিখতে, work_sharder.h এ Shard ফাংশন ব্যবহার করা যেতে পারে। এই ফাংশনটি ইন্ট্রা-অপ থ্রেডিংয়ের জন্য কনফিগার করা থ্রেড জুড়ে একটি গণনা ফাংশনকে শার্ড করে ( config.proto এ intra_op_parallelism_threads দেখুন)।

GPU কার্নেল

একটি GPU কার্নেল দুটি অংশে প্রয়োগ করা হয়: OpKernel এবং CUDA কার্নেল এবং এর লঞ্চ কোড।

কখনও কখনও OpKernel বাস্তবায়ন একটি CPU এবং GPU কার্নেলের মধ্যে সাধারণ, যেমন ইনপুট পরিদর্শন এবং আউটপুট বরাদ্দ করা। সেই ক্ষেত্রে, একটি প্রস্তাবিত বাস্তবায়ন হল:

  1. ডিভাইসে টেমপ্লেট করা OpKernel এবং টেনসরের আদিম ধরন সংজ্ঞায়িত করুন।
  2. আউটপুটের প্রকৃত গণনা করতে, Compute ফাংশনটি একটি টেমপ্লেটেড ফাংশন স্ট্রাকটকে কল করে।
  3. CPUDevice-এর জন্য সেই ফাংশনের স্পেশালাইজেশন একই ফাইলে সংজ্ঞায়িত করা হয়েছে, কিন্তু GPUDevice-এর স্পেশালাইজেশন একটি .cu.cc ফাইলে সংজ্ঞায়িত করা হয়েছে, যেহেতু এটি CUDA কম্পাইলারের সাথে কম্পাইল করা হবে।

এখানে একটি উদাহরণ বাস্তবায়ন.

// kernel_example.h
#ifndef KERNEL_EXAMPLE_H_
#define KERNEL_EXAMPLE_H_

#include <unsupported/Eigen/CXX11/Tensor>

template <typename Device, typename T>
struct ExampleFunctor {
  void operator()(const Device& d, int size, const T* in, T* out);
};

#if GOOGLE_CUDA
// Partially specialize functor for GpuDevice.
template <typename T>
struct ExampleFunctor<Eigen::GpuDevice, T> {
  void operator()(const Eigen::GpuDevice& d, int size, const T* in, T* out);
};
#endif

#endif KERNEL_EXAMPLE_H_
// kernel_example.cc
#include "kernel_example.h"

#include "tensorflow/core/framework/op.h"
#include "tensorflow/core/framework/shape_inference.h"
#include "tensorflow/core/framework/op_kernel.h"

using namespace tensorflow;

using CPUDevice = Eigen::ThreadPoolDevice;
using GPUDevice = Eigen::GpuDevice;

REGISTER_OP("Example")
    .Attr("T: numbertype")
    .Input("input: T")
    .Output("input_times_two: T")
    .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) {
      c->set_output(0, c->input(0));
      return Status::OK();
    });

// CPU specialization of actual computation.
template <typename T>
struct ExampleFunctor<CPUDevice, T> {
  void operator()(const CPUDevice& d, int size, const T* in, T* out) {
    for (int i = 0; i < size; ++i) {
      out[i] = 2 * in[i];
    }
  }
};

// OpKernel definition.
// template parameter <T> is the datatype of the tensors.
template <typename Device, typename T>
class ExampleOp : public OpKernel {
 public:
  explicit ExampleOp(OpKernelConstruction* context) : OpKernel(context) {}

  void Compute(OpKernelContext* context) override {
    // Grab the input tensor
    const Tensor& input_tensor = context->input(0);

    // Create an output tensor
    Tensor* output_tensor = NULL;
    OP_REQUIRES_OK(context, context->allocate_output(0, input_tensor.shape(),
                                                     &output_tensor));

    // Do the computation.
    OP_REQUIRES(context, input_tensor.NumElements() <= tensorflow::kint32max,
                errors::InvalidArgument("Too many elements in tensor"));
    ExampleFunctor<Device, T>()(
        context->eigen_device<Device>(),
        static_cast<int>(input_tensor.NumElements()),
        input_tensor.flat<T>().data(),
        output_tensor->flat<T>().data());
  }
};

// Register the CPU kernels.
#define REGISTER_CPU(T)                                          \
  REGISTER_KERNEL_BUILDER(                                       \
      Name("Example").Device(DEVICE_CPU).TypeConstraint<T>("T"), \
      ExampleOp<CPUDevice, T>);
REGISTER_CPU(float);
REGISTER_CPU(int32);

// Register the GPU kernels.
#ifdef GOOGLE_CUDA
#define REGISTER_GPU(T)                                          \
  /* Declare explicit instantiations in kernel_example.cu.cc. */ \
  extern template class ExampleFunctor<GPUDevice, T>;            \
  REGISTER_KERNEL_BUILDER(                                       \
      Name("Example").Device(DEVICE_GPU).TypeConstraint<T>("T"), \
      ExampleOp<GPUDevice, T>);
REGISTER_GPU(float);
REGISTER_GPU(int32);
#endif  // GOOGLE_CUDA
// kernel_example.cu.cc
#ifdef GOOGLE_CUDA
#define EIGEN_USE_GPU
#include "kernel_example.h"
#include "tensorflow/core/util/gpu_kernel_helper.h"

using namespace tensorflow;

using GPUDevice = Eigen::GpuDevice;

// Define the CUDA kernel.
template <typename T>
__global__ void ExampleCudaKernel(const int size, const T* in, T* out) {
  for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < size;
       i += blockDim.x * gridDim.x) {
    out[i] = 2 * __ldg(in + i);
  }
}

// Define the GPU implementation that launches the CUDA kernel.
template <typename T>
void ExampleFunctor<GPUDevice, T>::operator()(
    const GPUDevice& d, int size, const T* in, T* out) {
  // Launch the cuda kernel.
  //
  // See core/util/gpu_kernel_helper.h for example of computing
  // block count and thread_per_block count.
  int block_count = 1024;
  int thread_per_block = 20;
  ExampleCudaKernel<T>
      <<<block_count, thread_per_block, 0, d.stream()>>>(size, in, out);
}

// Explicitly instantiate functors for the types of OpKernels registered.
template struct ExampleFunctor<GPUDevice, float>;
template struct ExampleFunctor<GPUDevice, int32>;

#endif  // GOOGLE_CUDA

অপ লাইব্রেরি তৈরি করুন

আপনার সিস্টেম কম্পাইলার ব্যবহার করে অপটি কম্পাইল করুন (টেনসরফ্লো বাইনারি ইনস্টলেশন)

আপনি একটি C++ কম্পাইলার যেমন g++ বা আপনার সিস্টেমে উপলব্ধ clang দিয়ে zero_out.cc কম্পাইল করতে সক্ষম হবেন। বাইনারি পিআইপি প্যাকেজ শিরোনাম ফাইল এবং লাইব্রেরি ইনস্টল করে যা আপনাকে সিস্টেম নির্দিষ্ট স্থানে আপনার অপটি কম্পাইল করতে হবে। যাইহোক, TensorFlow পাইথন লাইব্রেরি হেডার ডিরেক্টরি পেতে get_include ফাংশন প্রদান করে, এবং get_lib ডিরেক্টরিতে লিঙ্ক করার জন্য একটি শেয়ার করা বস্তু রয়েছে। এখানে একটি উবুন্টু মেশিনে এই ফাংশনগুলির আউটপুট রয়েছে।

$ python
>>> import tensorflow as tf
>>> tf.sysconfig.get_include()
'/usr/local/lib/python3.6/site-packages/tensorflow/include'
>>> tf.sysconfig.get_lib()
'/usr/local/lib/python3.6/site-packages/tensorflow'

ধরে নিচ্ছি যে আপনি g++ ইনস্টল করেছেন, এখানে কমান্ডের ক্রম রয়েছে যা আপনি একটি ডায়নামিক লাইব্রেরিতে আপনার অপটি কম্পাইল করতে ব্যবহার করতে পারেন।

TF_CFLAGS=( $(python -c 'import tensorflow as tf; print(" ".join(tf.sysconfig.get_compile_flags()))') )
TF_LFLAGS=( $(python -c 'import tensorflow as tf; print(" ".join(tf.sysconfig.get_link_flags()))') )
g++ -std=c++14 -shared zero_out.cc -o zero_out.so -fPIC ${TF_CFLAGS[@]} ${TF_LFLAGS[@]} -O2

macOS-এ, .so ফাইল তৈরি করার সময় অতিরিক্ত পতাকা "-অনির্ধারিত ডায়নামিক_লুকআপ" প্রয়োজন।

gcc সংস্করণে নোট করুন >=5 : gcc সংস্করণ 5 থেকে নতুন C++ ABI ব্যবহার করে। TensorFlow 2.8 এবং তার আগের gcc4 দিয়ে তৈরি করা হয়েছিল যা পুরানো ABI ব্যবহার করে। আপনি যদি TensorFlow-এর এই সংস্করণগুলি ব্যবহার করেন এবং gcc>=5 দিয়ে আপনার op লাইব্রেরি কম্পাইল করার চেষ্টা করেন, তাহলে লাইব্রেরিটিকে পুরানো ABI-এর সাথে সামঞ্জস্যপূর্ণ করতে কমান্ড লাইনে -D_GLIBCXX_USE_CXX11_ABI=0 যোগ করুন। TensorFlow 2.9+ প্যাকেজগুলি ডিফল্টরূপে নতুন ABI-এর সাথে সামঞ্জস্যপূর্ণ।

বেজেল ব্যবহার করে অপটি কম্পাইল করুন (টেনসরফ্লো সোর্স ইনস্টলেশন)

আপনার যদি TensorFlow উত্স ইনস্টল করা থাকে, তাহলে আপনি আপনার অপ কম্পাইল করতে TensorFlow এর বিল্ড সিস্টেম ব্যবহার করতে পারেন। tensorflow/core/user_ops ডিরেক্টরিতে নিম্নলিখিত Bazel বিল্ড নিয়ম সহ একটি BUILD ফাইল রাখুন।

load("//tensorflow:tensorflow.bzl", "tf_custom_op_library")

tf_custom_op_library(
    name = "zero_out.so",
    srcs = ["zero_out.cc"],
)

zero_out.so তৈরি করতে নিম্নলিখিত কমান্ডটি চালান।

$ bazel build --config opt //tensorflow/core/user_ops:zero_out.so

Example অপারেশন কম্পাইল করার জন্য, CUDA কার্নেলের সাথে, আপনাকে tf_custom_op_library এর gpu_srcs প্যারামিটার ব্যবহার করতে হবে। tensorflow/core/user_ops ডিরেক্টরির (যেমন "example_gpu") ভিতরে একটি নতুন ফোল্ডারে নিম্নলিখিত Bazel বিল্ড নিয়ম সহ একটি BUILD ফাইল রাখুন।

load("//tensorflow:tensorflow.bzl", "tf_custom_op_library")

tf_custom_op_library(
    # kernel_example.cc  kernel_example.cu.cc  kernel_example.h
    name = "kernel_example.so",
    srcs = ["kernel_example.h", "kernel_example.cc"],
    gpu_srcs = ["kernel_example.cu.cc", "kernel_example.h"],
)

kernel_example.so তৈরি করতে নিম্নলিখিত কমান্ডটি চালান।

$ bazel build --config opt //tensorflow/core/user_ops/example_gpu:kernel_example.so

পাইথনে অপ ব্যবহার করুন

TensorFlow Python API ডাইনামিক লাইব্রেরি লোড করার জন্য tf.load_op_library ফাংশন প্রদান করে এবং TensorFlow ফ্রেমওয়ার্কের সাথে op নিবন্ধন করে। load_op_library একটি পাইথন মডিউল প্রদান করে যেটিতে op এবং কার্নেলের জন্য Python র‍্যাপার রয়েছে। সুতরাং, একবার আপনি অপটি তৈরি করলে, আপনি পাইথন থেকে এটি চালানোর জন্য নিম্নলিখিতগুলি করতে পারেন:

import tensorflow as tf
zero_out_module = tf.load_op_library('./zero_out.so')
print(zero_out_module.zero_out([[1, 2], [3, 4]]).numpy())

# Prints
array([[1, 0], [0, 0]], dtype=int32)

মনে রাখবেন, জেনারেট করা ফাংশনটিকে একটি snake_case নাম দেওয়া হবে ( PEP8 মেনে চলতে)। সুতরাং, যদি C++ ফাইলগুলিতে আপনার অপের নাম ZeroOut হয়, পাইথন ফাংশনটিকে zero_out বলা হবে।

একটি পাইথন মডিউল থেকে একটি নিয়মিত ফাংশন import -যোগ্য হিসাবে op উপলব্ধ করার জন্য, পাইথন সোর্স ফাইলে নিম্নরূপ load_op_library কল করা দরকারী হতে পারে:

import tensorflow as tf

zero_out_module = tf.load_op_library('./zero_out.so')
zero_out = zero_out_module.zero_out

অপটি কাজ করে তা যাচাই করুন

আপনি সফলভাবে আপনার অপ প্রয়োগ করেছেন তা যাচাই করার একটি ভাল উপায় হল এটির জন্য একটি পরীক্ষা লেখা। বিষয়বস্তু সহ zero_out_op_test.py ফাইলটি তৈরি করুন:

import tensorflow as tf

class ZeroOutTest(tf.test.TestCase):
  def testZeroOut(self):
    zero_out_module = tf.load_op_library('./zero_out.so')
    with self.test_session():
      result = zero_out_module.zero_out([5, 4, 3, 2, 1])
      self.assertAllEqual(result.eval(), [5, 0, 0, 0, 0])

if __name__ == "__main__":
  tf.test.main()

তারপরে আপনার পরীক্ষা চালান (ধরে নিচ্ছেন যে আপনি টেনসরফ্লো ইনস্টল করেছেন):

$ python zero_out_op_test.py

আপনার অপশনে উন্নত বৈশিষ্ট্য তৈরি করুন

এখন যেহেতু আপনি জানেন কিভাবে একটি মৌলিক (এবং কিছুটা সীমাবদ্ধ) অপশন তৈরি করতে হয় এবং বাস্তবায়ন করতে হয়, আমরা আরও কিছু জটিল জিনিস দেখব যা সাধারণত আপনার অপশনে তৈরি করতে হবে। এর মধ্যে রয়েছে:

শর্তাধীন চেক এবং বৈধতা

উপরের উদাহরণটি ধরে নেওয়া হয়েছে যে অপটি যেকোন আকৃতির একটি টেনসরে প্রয়োগ করা হয়েছে। যদি এটি শুধুমাত্র ভেক্টর প্রয়োগ করা হয়? এর মানে উপরের OpKernel বাস্তবায়নে একটি চেক যোগ করা।

  void Compute(OpKernelContext* context) override {
    // Grab the input tensor
    const Tensor& input_tensor = context->input(0);

    OP_REQUIRES(context, TensorShapeUtils::IsVector(input_tensor.shape()),
                errors::InvalidArgument("ZeroOut expects a 1-D vector."));
    // ...
  }

এটি দাবী করে যে ইনপুটটি একটি ভেক্টর, এবং যদি এটি না থাকে তবে InvalidArgument স্থিতি সেট করে ফেরত দেয়। OP_REQUIRES ম্যাক্রো তিনটি আর্গুমেন্ট নেয়:

  • context , যা হয় একটি OpKernelContext বা OpKernelConstruction পয়েন্টার হতে পারে ( tensorflow/core/framework/op_kernel.h দেখুন ), এর SetStatus() পদ্ধতির জন্য।
  • শর্ত. উদাহরণস্বরূপ, tensorflow/core/framework/tensor_shape.h এ টেনসরের আকৃতি যাচাই করার জন্য ফাংশন রয়েছে
  • ত্রুটি নিজেই, যা একটি Status বস্তু দ্বারা প্রতিনিধিত্ব করা হয়, tensorflow/core/platform/status.h দেখুন। একটি Status একটি প্রকার (প্রায়ই InvalidArgument , কিন্তু প্রকারের তালিকা দেখুন) এবং একটি বার্তা উভয়ই থাকে৷ একটি ত্রুটি নির্মাণের ফাংশন tensorflow/core/platform/errors.h এ পাওয়া যেতে পারে।

বিকল্পভাবে, যদি আপনি কিছু ফাংশন থেকে ফিরে আসা একটি Status অবজেক্ট একটি ত্রুটি কিনা তা পরীক্ষা করতে চান এবং যদি তাই এটি ফেরত দেন, তাহলে OP_REQUIRES_OK ব্যবহার করুন। এই দুটি ম্যাক্রো ত্রুটির উপর ফাংশন থেকে ফিরে আসে।

অপ নিবন্ধন

Attrs

Ops-এ attrs থাকতে পারে, যার মান সেট করা হয় যখন op একটি গ্রাফে যোগ করা হয়। এগুলি অপ কনফিগার করার জন্য ব্যবহার করা হয়, এবং তাদের মানগুলি কার্নেল বাস্তবায়নের মধ্যে এবং অপ রেজিস্ট্রেশনে ইনপুট এবং আউটপুট উভয় প্রকারে অ্যাক্সেস করা যেতে পারে। সম্ভব হলে attr এর পরিবর্তে একটি ইনপুট ব্যবহার করতে পছন্দ করুন, যেহেতু ইনপুটগুলি আরও নমনীয়। এটি কারণ attrs ধ্রুবক এবং গ্রাফ নির্মাণের সময় সংজ্ঞায়িত করা আবশ্যক। বিপরীতে, ইনপুট হল Tensors যার মানগুলি গতিশীল হতে পারে; অর্থাৎ, ইনপুটগুলি প্রতিটি ধাপে পরিবর্তন করতে পারে, একটি ফিড ব্যবহার করে সেট করা যেতে পারে ইত্যাদি। Attrs ব্যবহার করা হয় এমন জিনিসগুলির জন্য যা ইনপুট দিয়ে করা যায় না: যে কোনও কনফিগারেশন যা স্বাক্ষরকে প্রভাবিত করে (সংখ্যা বা ইনপুট বা আউটপুটগুলির ধরন) বা এটি করতে পারে' ধাপে ধাপে পরিবর্তন না।

আপনি একটি attr সংজ্ঞায়িত করেন যখন আপনি op নিবন্ধন করেন, Attr পদ্ধতি ব্যবহার করে এর নাম এবং টাইপ উল্লেখ করে, যা ফর্মের একটি বিশেষত্ব আশা করে:

<name>: <attr-type-expr>

যেখানে <name> একটি অক্ষর দিয়ে শুরু হয় এবং এটি আলফানিউমেরিক অক্ষর এবং আন্ডারস্কোর দিয়ে গঠিত হতে পারে, এবং <attr-type-expr> নীচে বর্ণিত ফর্মের একটি প্রকার অভিব্যক্তি।

উদাহরণস্বরূপ, যদি আপনি ZeroOut অপটি শুধুমাত্র 0 তম উপাদানের পরিবর্তে একটি ব্যবহারকারী-নির্দিষ্ট সূচী সংরক্ষণ করতে চান, তাহলে আপনি এইভাবে অপটি নিবন্ধন করতে পারেন:

REGISTER_OP("ZeroOut")
    .Attr("preserve_index: int")
    .Input("to_zero: int32")
    .Output("zeroed: int32");

(উল্লেখ্য যে অ্যাট্রিবিউট প্রকারের সেটটি ইনপুট এবং আউটপুটগুলির জন্য ব্যবহৃত tf.DType থেকে আলাদা।)

আপনার কার্নেল context প্যারামিটারের মাধ্যমে তার কনস্ট্রাক্টরে এই attr অ্যাক্সেস করতে পারে:

class ZeroOutOp : public OpKernel {
 public:
  explicit ZeroOutOp(OpKernelConstruction* context) : OpKernel(context) {
    // Get the index of the value to preserve
    OP_REQUIRES_OK(context,
                   context->GetAttr("preserve_index", &preserve_index_));
    // Check that preserve_index is positive
    OP_REQUIRES(context, preserve_index_ >= 0,
                errors::InvalidArgument("Need preserve_index >= 0, got ",
                                        preserve_index_));
  }
  void Compute(OpKernelContext* context) override {
    // ...
  }
 private:
  int preserve_index_;
};

যা তারপর Compute পদ্ধতিতে ব্যবহার করা যেতে পারে:

  void Compute(OpKernelContext* context) override {
    // ...

    // We're using saved attr to validate potentially dynamic input
    // So we check that preserve_index is in range
    OP_REQUIRES(context, preserve_index_ < input.dimension(0),
                errors::InvalidArgument("preserve_index out of range"));

    // Set all the elements of the output tensor to 0
    const int N = input.size();
    for (int i = 0; i < N; i++) {
      output_flat(i) = 0;
    }

    // Preserve the requested input value
    output_flat(preserve_index_) = input(preserve_index_);
  }

Attr প্রকার

নিম্নলিখিত ধরনের একটি attr সমর্থিত হয়:

  • string : বাইটের যেকোনো ক্রম (UTF8 হওয়ার প্রয়োজন নেই)।
  • int : একটি স্বাক্ষরিত পূর্ণসংখ্যা।
  • float : একটি ফ্লোটিং পয়েন্ট সংখ্যা।
  • bool : সত্য বা মিথ্যা।
  • type : DataType এর (নন-রেফ) মানগুলির মধ্যে একটি।
  • shape : একটি TensorShapeProto
  • list(<type>) : <type> এর একটি তালিকা, যেখানে <type> উপরের প্রকারগুলির মধ্যে একটি। মনে রাখবেন যে list(list(<type>)) অবৈধ।

আরও দেখুন: একটি নির্দিষ্ট তালিকার জন্য op_def_builder.cc:FinalizeAttr

ডিফল্ট মান এবং সীমাবদ্ধতা

Attrs এর ডিফল্ট মান থাকতে পারে এবং কিছু ধরণের attrs এর সীমাবদ্ধতা থাকতে পারে। সীমাবদ্ধতার সাথে একটি attr সংজ্ঞায়িত করতে, আপনি নিম্নলিখিত <attr-type-expr> s ব্যবহার করতে পারেন:

{'<string1>', '<string2>'} : মানটি অবশ্যই একটি স্ট্রিং হতে হবে যার মান <string1> বা <string2> থাকে। আপনি যখন এই সিনট্যাক্স ব্যবহার করেন তখন টাইপের নাম, string , বোঝানো হয়। এটি একটি enum অনুকরণ করে:

REGISTER_OP("EnumExample")
    .Attr("e: {'apple', 'orange'}");

{<type1>, <type2>} : মানটি টাইপ type , এবং অবশ্যই <type1> বা <type2> এর মধ্যে একটি হতে হবে, যেখানে <type1> এবং <type2> tf.DType সমর্থিত। আপনি উল্লেখ করবেন না যে attr-এর ধরনটি type । যখন আপনার {...} এ প্রকারের তালিকা থাকে তখন এটি বোঝানো হয়। উদাহরণস্বরূপ, এই ক্ষেত্রে attr t একটি প্রকার যা অবশ্যই একটি int32 , একটি float বা একটি bool হতে হবে:

REGISTER_OP("RestrictedTypeExample")
    .Attr("t: {int32, float, bool}");

সাধারণ ধরনের সীমাবদ্ধতার জন্য শর্টকাট আছে:

  • numbertype : টাইপ type সংখ্যাসূচক (নন-স্ট্রিং এবং নন-বুল) প্রকারে সীমাবদ্ধ।
  • realnumbertype : জটিল প্রকার ছাড়া numbertype মত।
  • quantizedtype : numbertype মত কিন্তু শুধু quantized number type.

এগুলির দ্বারা অনুমোদিত ধরণের নির্দিষ্ট তালিকাগুলি tensorflow/core/framework/types.h এ ফাংশন (যেমন NumberTypes() ) দ্বারা সংজ্ঞায়িত করা হয়। এই উদাহরণে attr t অবশ্যই সাংখ্যিক প্রকারের একটি হতে হবে:

REGISTER_OP("NumberType")
    .Attr("t: numbertype");

এই অপশনের জন্য:

tf.number_type(t=tf.int32)  # Valid
tf.number_type(t=tf.bool)   # Invalid

তালিকাগুলি অন্যান্য তালিকা এবং একক প্রকারের সাথে একত্রিত করা যেতে পারে। নিম্নোক্ত অপটি attr t যেকোনও সংখ্যাসূচক প্রকার বা বুল প্রকার হতে দেয়:

REGISTER_OP("NumberOrBooleanType")
    .Attr("t: {numbertype, bool}");

এই অপশনের জন্য:

tf.number_or_boolean_type(t=tf.int32)  # Valid
tf.number_or_boolean_type(t=tf.bool)   # Valid
tf.number_or_boolean_type(t=tf.string) # Invalid

int >= <n> : মানটি অবশ্যই একটি int হতে হবে যার মান <n> এর থেকে বেশি বা সমান, যেখানে <n> একটি স্বাভাবিক সংখ্যা। উদাহরণস্বরূপ, নিম্নলিখিত অপ রেজিস্ট্রেশনটি নির্দিষ্ট করে যে attr a একটি মান থাকতে হবে যা কমপক্ষে 2 :

REGISTER_OP("MinIntExample")
    .Attr("a: int >= 2");

list(<type>) >= <n> : <type> প্রকারের একটি তালিকা যার দৈর্ঘ্য <n> এর থেকে বেশি বা সমান। উদাহরণস্বরূপ, নিম্নলিখিত অপ রেজিস্ট্রেশনটি নির্দিষ্ট করে যে attr a হল প্রকারের একটি তালিকা (হয় int32 বা float ), এবং তাদের মধ্যে কমপক্ষে 3টি থাকতে হবে:

REGISTER_OP("TypeListExample")
    .Attr("a: list({int32, float}) >= 3");

একটি attr এর জন্য একটি ডিফল্ট মান সেট করতে (উত্পন্ন কোডে এটি ঐচ্ছিক করে), শেষে = <default> যোগ করুন, যেমন:

REGISTER_OP("AttrDefaultExample")
    .Attr("i: int = 0");

অতিরিক্তভাবে, একটি সীমাবদ্ধতা এবং একটি ডিফল্ট মান উভয়ই নির্দিষ্ট করা যেতে পারে:

REGISTER_OP("AttrConstraintAndDefaultExample")
    .Attr("i: int >= 1 = 1");

ডিফল্ট মানের সমর্থিত সিনট্যাক্স হল যা গ্রাফডিফ সংজ্ঞার প্রোটো উপস্থাপনায় ব্যবহার করা হবে।

এখানে কিভাবে সব ধরনের জন্য একটি ডিফল্ট নির্দিষ্ট করতে উদাহরণ আছে:

REGISTER_OP("AttrDefaultExampleForAllTypes")
   .Attr("s: string = 'foo'")
   .Attr("i: int = 0")
   .Attr("f: float = 1.0")
   .Attr("b: bool = true")
   .Attr("ty: type = DT_INT32")
   .Attr("sh: shape = { dim { size: 1 } dim { size: 2 } }")
   .Attr("te: tensor = { dtype: DT_INT32 int_val: 5 }")
   .Attr("l_empty: list(int) = []")
   .Attr("l_int: list(int) = [2, 3, 5, 7]");

বিশেষভাবে লক্ষ্য করুন যে টাইপ type মান tf.DType ব্যবহার করে।

পলিমরফিজম

পলিমরফিজম টাইপ করুন

অপ্সের জন্য যেগুলি ইনপুট হিসাবে বিভিন্ন প্রকার নিতে পারে বা বিভিন্ন আউটপুট প্রকার তৈরি করতে পারে, আপনি অপ রেজিস্ট্রেশনে একটি ইনপুট বা আউটপুট প্রকারে একটি attr নির্দিষ্ট করতে পারেন। সাধারণত আপনি প্রতিটি সমর্থিত প্রকারের জন্য একটি OpKernel নিবন্ধন করবেন।

উদাহরণস্বরূপ, আপনি যদি ZeroOut অপটি int32 s ছাড়াও float s-এ কাজ করতে চান তবে আপনার op নিবন্ধনটি দেখতে এরকম হতে পারে:

REGISTER_OP("ZeroOut")
    .Attr("T: {float, int32}")
    .Input("to_zero: T")
    .Output("zeroed: T");

আপনার অপ রেজিস্ট্রেশন এখন নির্দিষ্ট করে যে ইনপুটের ধরনটি float বা int32 হতে হবে এবং এর আউটপুট একই ধরনের হবে, যেহেতু উভয়েরই T টাইপ আছে।

নামকরণ

ইনপুট, আউটপুট এবং attrs সাধারণত snake_case নাম দেওয়া উচিত। একটি ব্যতিক্রম হল attrs যা একটি ইনপুটের ধরন হিসাবে বা একটি আউটপুটের প্রকারে ব্যবহৃত হয়। সেই attrs অনুমান করা যেতে পারে যখন op গ্রাফে যোগ করা হয় এবং তাই op এর ফাংশনে প্রদর্শিত হয় না। উদাহরণস্বরূপ, ZeroOut এর এই শেষ সংজ্ঞাটি একটি পাইথন ফাংশন তৈরি করবে যা দেখতে এইরকম:

def zero_out(to_zero, name=None):
  """...
  Args:
    to_zero: A `Tensor`. Must be one of the following types:
        `float32`, `int32`.
    name: A name for the operation (optional).

  Returns:
    A `Tensor`. Has the same type as `to_zero`.
  """

যদি to_zero একটি int32 টেনসর পাস করা হয়, তাহলে T স্বয়ংক্রিয়ভাবে int32 এ সেট হয়ে যায় (ভাল, আসলে DT_INT32 )। এই অনুমানকৃত attrs কে Capitalized বা CamelCase নাম দেওয়া হয়।

এটিকে একটি অপের সাথে তুলনা করুন যার একটি টাইপ attr আছে যা আউটপুট প্রকার নির্ধারণ করে:

REGISTER_OP("StringToNumber")
    .Input("string_tensor: string")
    .Output("output: out_type")
    .Attr("out_type: {float, int32} = DT_FLOAT");
    .Doc(R"doc(
Converts each string in the input Tensor to the specified numeric type.
)doc");

এই ক্ষেত্রে, ব্যবহারকারীকে উৎপন্ন পাইথনের মতো আউটপুট প্রকার নির্দিষ্ট করতে হবে:

def string_to_number(string_tensor, out_type=None, name=None):
  """Converts each string in the input Tensor to the specified numeric type.

  Args:
    string_tensor: A `Tensor` of type `string`.
    out_type: An optional `tf.DType` from: `tf.float32, tf.int32`.
      Defaults to `tf.float32`.
    name: A name for the operation (optional).

  Returns:
    A `Tensor` of type `out_type`.
  """
পলিমরফিজম উদাহরণ টাইপ করুন
#include "tensorflow/core/framework/op_kernel.h"

class ZeroOutInt32Op : public OpKernel {
  // as before
};

class ZeroOutFloatOp : public OpKernel {
 public:
  explicit ZeroOutFloatOp(OpKernelConstruction* context)
      : OpKernel(context) {}

  void Compute(OpKernelContext* context) override {
    // Grab the input tensor
    const Tensor& input_tensor = context->input(0);
    auto input = input_tensor.flat<float>();

    // Create an output tensor
    Tensor* output = NULL;
    OP_REQUIRES_OK(context,
                   context->allocate_output(0, input_tensor.shape(), &output));
    auto output_flat = output->template flat<float>();

    // Set all the elements of the output tensor to 0
    const int N = input.size();
    for (int i = 0; i < N; i++) {
      output_flat(i) = 0;
    }

    // Preserve the first input value
    if (N > 0) output_flat(0) = input(0);
  }
};

// Note that TypeConstraint<int32>("T") means that attr "T" (defined
// in the op registration above) must be "int32" to use this template
// instantiation.
REGISTER_KERNEL_BUILDER(
    Name("ZeroOut")
    .Device(DEVICE_CPU)
    .TypeConstraint<int32>("T"),
    ZeroOutInt32Op);
REGISTER_KERNEL_BUILDER(
    Name("ZeroOut")
    .Device(DEVICE_CPU)
    .TypeConstraint<float>("T"),
    ZeroOutFloatOp);

পিছনের সামঞ্জস্য রক্ষা করতে, একটি বিদ্যমান অপে একটি attr যোগ করার সময় আপনার একটি ডিফল্ট মান নির্দিষ্ট করা উচিত:

REGISTER_OP("ZeroOut")
  .Attr("T: {float, int32} = DT_INT32")
  .Input("to_zero: T")
  .Output("zeroed: T")

ধরা যাক আপনি আরও প্রকার যোগ করতে চেয়েছিলেন, double বলুন:

REGISTER_OP("ZeroOut")
    .Attr("T: {float, double, int32}")
    .Input("to_zero: T")
    .Output("zeroed: T");

উপরের মত অপ্রয়োজনীয় কোড সহ অন্য OpKernel লেখার পরিবর্তে, প্রায়শই আপনি পরিবর্তে একটি C++ টেমপ্লেট ব্যবহার করতে সক্ষম হবেন। ওভারলোড প্রতি আপনার কাছে এখনও একটি কার্নেল নিবন্ধন ( REGISTER_KERNEL_BUILDER কল) থাকবে৷

template <typename T>
class ZeroOutOp : public OpKernel {
 public:
  explicit ZeroOutOp(OpKernelConstruction* context) : OpKernel(context) {}

  void Compute(OpKernelContext* context) override {
    // Grab the input tensor
    const Tensor& input_tensor = context->input(0);
    auto input = input_tensor.flat<T>();

    // Create an output tensor
    Tensor* output = NULL;
    OP_REQUIRES_OK(context,
                   context->allocate_output(0, input_tensor.shape(), &output));
    auto output_flat = output->template flat<T>();

    // Set all the elements of the output tensor to 0
    const int N = input.size();
    for (int i = 0; i < N; i++) {
      output_flat(i) = 0;
    }

    // Preserve the first input value
    if (N > 0) output_flat(0) = input(0);
  }
};

// Note that TypeConstraint<int32>("T") means that attr "T" (defined
// in the op registration above) must be "int32" to use this template
// instantiation.
REGISTER_KERNEL_BUILDER(
    Name("ZeroOut")
    .Device(DEVICE_CPU)
    .TypeConstraint<int32>("T"),
    ZeroOutOp<int32>);
REGISTER_KERNEL_BUILDER(
    Name("ZeroOut")
    .Device(DEVICE_CPU)
    .TypeConstraint<float>("T"),
    ZeroOutOp<float>);
REGISTER_KERNEL_BUILDER(
    Name("ZeroOut")
    .Device(DEVICE_CPU)
    .TypeConstraint<double>("T"),
    ZeroOutOp<double>);

আপনার যদি একাধিক ওভারলোড থাকে তবে আপনি একটি ম্যাক্রোতে নিবন্ধন করতে পারেন।

#include "tensorflow/core/framework/op_kernel.h"

#define REGISTER_KERNEL(type)                                       \
  REGISTER_KERNEL_BUILDER(                                          \
      Name("ZeroOut").Device(DEVICE_CPU).TypeConstraint<type>("T"), \
      ZeroOutOp<type>)

REGISTER_KERNEL(int32);
REGISTER_KERNEL(float);
REGISTER_KERNEL(double);

#undef REGISTER_KERNEL

আপনি যে ধরনের কার্নেল নিবন্ধন করছেন তার তালিকার উপর নির্ভর করে, আপনি tensorflow/core/framework/register_types.h দ্বারা প্রদত্ত একটি ম্যাক্রো ব্যবহার করতে সক্ষম হতে পারেন :

#include "tensorflow/core/framework/op_kernel.h"
#include "tensorflow/core/framework/register_types.h"

REGISTER_OP("ZeroOut")
    .Attr("T: realnumbertype")
    .Input("to_zero: T")
    .Output("zeroed: T");

template <typename T>
class ZeroOutOp : public OpKernel { ... };

#define REGISTER_KERNEL(type)                                       \
  REGISTER_KERNEL_BUILDER(                                          \
      Name("ZeroOut").Device(DEVICE_CPU).TypeConstraint<type>("T"), \
      ZeroOutOp<type>)

TF_CALL_REAL_NUMBER_TYPES(REGISTER_KERNEL);

#undef REGISTER_KERNEL
ইনপুট এবং আউটপুট তালিকা করুন

বিভিন্ন ধরণের গ্রহণ বা উত্পাদন করতে সক্ষম হওয়ার পাশাপাশি, অপসগুলি পরিবর্তনশীল সংখ্যক টেনসর গ্রহণ বা উত্পাদন করতে পারে।

পরবর্তী উদাহরণে, attr T প্রকারের একটি তালিকা ধারণ করে এবং ইনপুট in এবং আউটপুট out উভয়ের ধরন হিসাবে ব্যবহৃত হয়। ইনপুট এবং আউটপুট হল সেই ধরনের টেনসরের তালিকা (এবং আউটপুটে টেনসরের সংখ্যা এবং প্রকারগুলি ইনপুটের মতোই, যেহেতু উভয়েরই T টাইপ আছে)।

REGISTER_OP("PolymorphicListExample")
    .Attr("T: list(type)")
    .Input("in: T")
    .Output("out: T");

এছাড়াও আপনি তালিকায় কি ধরনের নির্দিষ্ট করা যেতে পারে তার উপর সীমাবদ্ধতা রাখতে পারেন। এই পরবর্তী ক্ষেত্রে, ইনপুট হল float এবং double টেনসরের একটি তালিকা। অপটি গ্রহণ করে, উদাহরণস্বরূপ, ইনপুট প্রকারগুলি (float, double, float) এবং সেই ক্ষেত্রে আউটপুট প্রকারটিও হবে (float, double, float)

REGISTER_OP("ListTypeRestrictionExample")
    .Attr("T: list({float, double})")
    .Input("in: T")
    .Output("out: T");

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

REGISTER_OP("IntListInputExample")
    .Attr("N: int")
    .Input("in: N * int32")
    .Output("out: int32");

এটি int32 টেনসরগুলির একটি তালিকা গ্রহণ করে এবং তালিকার দৈর্ঘ্য নির্দিষ্ট করতে একটি int attr N ব্যবহার করে।

এটিও টাইপ পলিমরফিক করা যেতে পারে। পরবর্তী উদাহরণে, ইনপুট হল একই (কিন্তু অনির্দিষ্ট) টাইপের ( "T" ) টেনসরের একটি তালিকা (দৈর্ঘ্য "N" সহ) এবং আউটপুট হল মিল টাইপের একটি একক টেনসর:

REGISTER_OP("SameListInputExample")
    .Attr("N: int")
    .Attr("T: type")
    .Input("in: N * T")
    .Output("out: T");

ডিফল্টরূপে, টেনসর তালিকার ন্যূনতম দৈর্ঘ্য থাকে 1। আপনি সংশ্লিষ্ট attr-এ ">=" সীমাবদ্ধতা ব্যবহার করে সেই ডিফল্ট পরিবর্তন করতে পারেন। এই পরবর্তী উদাহরণে, ইনপুটটি কমপক্ষে 2টি int32 টেনসরের একটি তালিকা:

REGISTER_OP("MinLengthIntListExample")
    .Attr("N: int >= 2")
    .Input("in: N * int32")
    .Output("out: int32");

একই সিনট্যাক্স "list(type)" attrs এর সাথে কাজ করে:

REGISTER_OP("MinimumLengthPolymorphicListExample")
    .Attr("T: list(type) >= 3")
    .Input("in: T")
    .Output("out: T");

ইনপুট এবং আউটপুট

উপরের সংক্ষিপ্তসারে, একটি অপ রেজিস্ট্রেশনে একাধিক ইনপুট এবং আউটপুট থাকতে পারে:

REGISTER_OP("MultipleInsAndOuts")
    .Input("y: int32")
    .Input("z: float")
    .Output("a: string")
    .Output("b: int32");

প্রতিটি ইনপুট বা আউটপুট স্পেক ফর্মের:

<name>: <io-type-expr>

যেখানে <name> একটি অক্ষর দিয়ে শুরু হয় এবং এটি আলফানিউমেরিক অক্ষর এবং আন্ডারস্কোর দিয়ে গঠিত হতে পারে। <io-type-expr> নিম্নলিখিত ধরনের এক্সপ্রেশনগুলির মধ্যে একটি:

  • <type> , যেখানে <type> একটি সমর্থিত ইনপুট প্রকার (যেমন float , int32 , string )। এটি প্রদত্ত ধরণের একটি একক টেনসর নির্দিষ্ট করে।

    tf.DType দেখুন।

    REGISTER_OP("BuiltInTypesExample")
        .Input("integers: int32")
        .Input("complex_numbers: complex64");
    
  • <attr-type> , যেখানে <attr-type> টাইপ type বা list(type) সহ একটি Attr- এর নাম (সম্ভাব্য ধরনের সীমাবদ্ধতা সহ)। এই সিনট্যাক্স পলিমরফিক অপ্সের জন্য অনুমতি দেয়।

    REGISTER_OP("PolymorphicSingleInput")
        .Attr("T: type")
        .Input("in: T");
    
    REGISTER_OP("RestrictedPolymorphicSingleInput")
        .Attr("T: {int32, int64}")
        .Input("in: T");
    

    টাইপ list(type) এর একটি attr উল্লেখ করা আপনাকে টেনসরের একটি ক্রম গ্রহণ করতে দেয়।

    REGISTER_OP("ArbitraryTensorSequenceExample")
        .Attr("T: list(type)")
        .Input("in: T")
        .Output("out: T");
    
    REGISTER_OP("RestrictedTensorSequenceExample")
        .Attr("T: list({int32, int64})")
        .Input("in: T")
        .Output("out: T");
    

    মনে রাখবেন যে আউটপুট out টেনসরের সংখ্যা এবং প্রকারগুলি ইনপুট in মতোই, যেহেতু উভয়ই T টাইপ।

  • একই ধরনের টেনসরের একটি ক্রম-এর জন্য: <number> * <type> , যেখানে <number> int টাইপ সহ একটি Attr এর নাম। <type> হয় একটি tf.DType হতে পারে, অথবা টাইপ type সহ একটি attr-এর নাম। প্রথমটির উদাহরণ হিসাবে, এই অপটি int32 টেনসরগুলির একটি তালিকা গ্রহণ করে:

    REGISTER_OP("Int32SequenceExample")
        .Attr("NumTensors: int")
        .Input("in: NumTensors * int32")
    

    যেখানে এই অপটি যেকোন ধরণের টেনসরের একটি তালিকা গ্রহণ করে, যতক্ষণ না তারা সব একই থাকে:

    REGISTER_OP("SameTypeSequenceExample")
        .Attr("NumTensors: int")
        .Attr("T: type")
        .Input("in: NumTensors * T")
    
  • একটি টেনসরের রেফারেন্সের জন্য: Ref(<type>) , যেখানে <type> পূর্ববর্তী প্রকারগুলির মধ্যে একটি।

ইনপুটের প্রকারে ব্যবহৃত যে কোনো attr অনুমান করা হবে। নিয়ম অনুসারে এই অনুমানকৃত atts মূল নাম ব্যবহার করে (যেমন T বা N )। অন্যথায় ইনপুট, আউটপুট এবং attrs-এর ফাংশন প্যারামিটারের মতো নাম থাকে (যেমন num_outputs )। আরো বিস্তারিত জানার জন্য, নামকরণের পূর্ববর্তী বিভাগটি দেখুন।

আরো বিস্তারিত জানার জন্য, tensorflow/core/framework/op_def_builder.h দেখুন।

পিছনের সামঞ্জস্য

ধরুন আপনি একটি সুন্দর, কাস্টম অপশন লিখেছেন এবং অন্যদের সাথে শেয়ার করেছেন, যাতে আপনার অপারেশন ব্যবহার করে আপনার খুশি গ্রাহকরা থাকে। যাইহোক, আপনি কিছু উপায়ে অপশনে পরিবর্তন করতে চান।

সাধারণভাবে, বিদ্যমান, চেক-ইন স্পেসিফিকেশনের পরিবর্তনগুলি অবশ্যই পিছনের-সামঞ্জস্যপূর্ণ হতে হবে: একটি অপের স্পেসিফিকেশন পরিবর্তন করলে পুরানো স্পেসিফিকেশনগুলি থেকে নির্মিত পূর্বের সিরিয়ালাইজড GraphDef প্রোটোকল বাফারগুলিকে ভাঙতে হবে না। GraphDef সামঞ্জস্যের বিশদ বিবরণ এখানে বর্ণনা করা হয়েছে

পিছনের-সামঞ্জস্যতা রক্ষা করার বিভিন্ন উপায় আছে।

  1. একটি অপারেশনে যোগ করা যেকোন নতুন attrs-এর অবশ্যই ডিফল্ট মান সংজ্ঞায়িত থাকতে হবে এবং সেই ডিফল্ট মান সহ op এর মূল আচরণ থাকতে হবে। একটি অপারেশনকে পলিমরফিক নয় থেকে পলিমরফিক তে পরিবর্তন করতে, আপনাকে অবশ্যই একটি ডিফল্ট মান দিতে হবে নতুন ধরনের attr কে ডিফল্টরূপে আসল স্বাক্ষর সংরক্ষণ করতে। উদাহরণস্বরূপ, যদি আপনার অপারেশন ছিল:

    REGISTER_OP("MyGeneralUnaryOp")
        .Input("in: float")
        .Output("out: float");
    

    আপনি এটি ব্যবহার করে একটি পিছনের-সামঞ্জস্যপূর্ণ উপায়ে বহুরূপী করতে পারেন:

    REGISTER_OP("MyGeneralUnaryOp")
        .Input("in: T")
        .Output("out: T")
        .Attr("T: numerictype = DT_FLOAT");
    
  2. আপনি নিরাপদে একটি attr কম সীমাবদ্ধ একটি সীমাবদ্ধতা করতে পারেন. উদাহরণস্বরূপ, আপনি {int32, int64} থেকে {int32, int64, float} বা type করতে পারেন। অথবা আপনি {"apple", "orange"} থেকে {"apple", "banana", "orange"} বা string পরিবর্তন করতে পারেন।

  3. আপনি একক ইনপুট/আউটপুটগুলিকে তালিকা ইনপুট/আউটপুটে পরিবর্তন করতে পারেন, যতক্ষণ না তালিকার প্রকারের ডিফল্টটি পুরানো স্বাক্ষরের সাথে মেলে।

  4. আপনি একটি নতুন তালিকা ইনপুট/আউটপুট যোগ করতে পারেন, যদি এটি ডিফল্ট খালি থাকে।

  5. আপনার প্রজেক্টের অনন্য কিছু দিয়ে অপের নামের উপসর্গ দিয়ে আপনার তৈরি করা যেকোনো নতুন অপ্সকে নেমস্পেস করুন। এটি TensorFlow-এর ভবিষ্যত সংস্করণে অন্তর্ভুক্ত হতে পারে এমন কোনো অপ্সের সাথে আপনার অপের সংঘর্ষ এড়ায়।

  6. এগিয়ে পরিকল্পনা! অপের জন্য ভবিষ্যতের ব্যবহার অনুমান করার চেষ্টা করুন। কিছু স্বাক্ষর পরিবর্তন একটি সামঞ্জস্যপূর্ণ উপায়ে করা যাবে না (উদাহরণস্বরূপ, একই ধরনের একটি তালিকাকে বিভিন্ন ধরনের তালিকায় পরিণত করা)।

নিরাপদ এবং অনিরাপদ পরিবর্তনের সম্পূর্ণ তালিকা tensorflow/core/framework/op_compatibility_test.cc এ পাওয়া যাবে। আপনি যদি একটি অপারেশনে আপনার পরিবর্তনকে পিছনের দিকে সামঞ্জস্যপূর্ণ করতে না পারেন, তাহলে নতুন শব্দার্থবিদ্যার সাথে একটি নতুন নামে একটি নতুন অপারেশন তৈরি করুন।

এছাড়াও মনে রাখবেন যে এই পরিবর্তনগুলি GraphDef সামঞ্জস্য বজায় রাখতে পারে, উত্পন্ন পাইথন কোডটি এমনভাবে পরিবর্তিত হতে পারে যা পুরানো কলারের সাথে সামঞ্জস্যপূর্ণ নয়। Python API-কে একটি হাতে লেখা পাইথন র‍্যাপারে সতর্ক পরিবর্তনের মাধ্যমে সামঞ্জস্যপূর্ণ রাখা যেতে পারে, সম্ভবত শেষে নতুন ঐচ্ছিক আর্গুমেন্ট যোগ করা ছাড়া পুরানো স্বাক্ষর রেখে। সাধারণত বেমানান পরিবর্তনগুলি তখনই করা যেতে পারে যখন TensorFlow প্রধান সংস্করণগুলি পরিবর্তন করে, এবং অবশ্যই GraphDef সংস্করণের শব্দার্থবিদ্যার সাথে সঙ্গতিপূর্ণ হবে।

GPU সমর্থন

আপনি বিভিন্ন OpKernels বাস্তবায়ন করতে পারেন এবং CPU এর জন্য একটি এবং GPU এর জন্য আরেকটি নিবন্ধন করতে পারেন, ঠিক যেমন আপনি বিভিন্ন ধরনের কার্নেল নিবন্ধন করতে পারেন। tensorflow/core/kernels/ এ GPU সমর্থন সহ কার্নেলের বেশ কয়েকটি উদাহরণ রয়েছে। লক্ষ্য করুন কিছু কার্নেলের একটি .cc ফাইলে একটি CPU সংস্করণ, _gpu.cu.cc এ শেষ হওয়া একটি ফাইলের একটি GPU সংস্করণ এবং একটি .h ফাইলে কিছু কোড কমন শেয়ার করা আছে।

উদাহরণস্বরূপ, tf.padtensorflow/core/kernels/pad_op.cc GPU কার্নেল ছাড়া সবকিছুই আছে। GPU কার্নেলটি tensorflow/core/kernels/pad_op_gpu.cu.cc এ রয়েছে এবং শেয়ার করা কোডটি tensorflow/core/kernels/pad_op.h এ সংজ্ঞায়িত একটি টেমপ্লেটেড ক্লাস। আমরা দুটি কারণে এইভাবে কোডটি সংগঠিত করি: এটি আপনাকে CPU এবং GPU বাস্তবায়নের মধ্যে সাধারণ কোড ভাগ করার অনুমতি দেয় এবং এটি GPU বাস্তবায়নকে একটি পৃথক ফাইলে রাখে যাতে এটি শুধুমাত্র GPU কম্পাইলার দ্বারা কম্পাইল করা যায়।

একটি বিষয় লক্ষণীয়, এমনকি যখন pad GPU কার্নেল সংস্করণ ব্যবহার করা হয়, তখনও CPU মেমরিতে এটির "paddings" ইনপুট প্রয়োজন। সিপিইউতে ইনপুট বা আউটপুট রাখা হয়েছে তা চিহ্নিত করতে, কার্নেল রেজিস্ট্রেশনে একটি HostMemory() কল যোগ করুন, যেমন:

#define REGISTER_GPU_KERNEL(T)                         \
  REGISTER_KERNEL_BUILDER(Name("Pad")                  \
                              .Device(DEVICE_GPU)      \
                              .TypeConstraint<T>("T")  \
                              .HostMemory("paddings"), \
                          PadOp<GPUDevice, T>)

GPU ডিভাইসের জন্য কার্নেল কম্পাইল করা হচ্ছে

একটি উদাহরণের জন্য cuda_op_kernel.cu.cc দেখুন যা একটি অপ প্রয়োগ করতে CUDA কার্নেল ব্যবহার করে। tf_custom_op_library একটি gpu_srcs আর্গুমেন্ট গ্রহণ করে যেখানে CUDA কার্নেল ( *.cu.cc ফাইল) ধারণকারী উৎস ফাইলের তালিকা নির্দিষ্ট করা যেতে পারে। TensorFlow এর বাইনারি ইনস্টলেশনের সাথে ব্যবহারের জন্য, CUDA কার্নেলগুলিকে NVIDIA-এর nvcc কম্পাইলারের সাথে কম্পাইল করতে হবে। এখানে cuda_op_kernel.cu.cc এবং cuda_op_kernel.cc একটি একক গতিশীলভাবে লোডযোগ্য লাইব্রেরিতে কম্পাইল করতে আপনি ব্যবহার করতে পারেন এমন কমান্ডের ক্রম:

nvcc -std=c++14 -c -o cuda_op_kernel.cu.o cuda_op_kernel.cu.cc \
  ${TF_CFLAGS[@]} -D GOOGLE_CUDA=1 -x cu -Xcompiler -fPIC

g++ -std=c++14 -shared -o cuda_op_kernel.so cuda_op_kernel.cc \
  cuda_op_kernel.cu.o ${TF_CFLAGS[@]} -fPIC -lcudart ${TF_LFLAGS[@]}

cuda_op_kernel.so উপরে উত্পাদিত tf.load_op_library ফাংশন ব্যবহার করে পাইথনে যথারীতি লোড করা যেতে পারে।

মনে রাখবেন যে যদি আপনার CUDA লাইব্রেরিগুলি /usr/local/lib64 এ ইনস্টল করা না থাকে, তাহলে আপনাকে উপরের দ্বিতীয় (g++) কমান্ডে স্পষ্টভাবে পথটি নির্দিষ্ট করতে হবে। উদাহরণস্বরূপ, -L /usr/local/cuda-8.0/lib64/ যোগ করুন যদি আপনার CUDA /usr/local/cuda-8.0 এ ইনস্টল করা থাকে।

পাইথনে গ্রেডিয়েন্ট প্রয়োগ করুন

Ops-এর একটি গ্রাফ দেওয়া, TensorFlow স্বয়ংক্রিয় পার্থক্য (ব্যাকপ্রোপাগেশন) ব্যবহার করে বিদ্যমান অপসের ক্ষেত্রে গ্রেডিয়েন্টের প্রতিনিধিত্বকারী নতুন অপস যোগ করতে। নতুন অপের জন্য স্বয়ংক্রিয় পার্থক্য কাজ করার জন্য, আপনাকে অবশ্যই একটি গ্রেডিয়েন্ট ফাংশন নিবন্ধন করতে হবে যা অপ্সের আউটপুটগুলির ক্ষেত্রে প্রদত্ত গ্রেডিয়েন্টগুলিকে অপ্সের ইনপুটগুলির সাপেক্ষে গ্রেডিয়েন্ট গণনা করে৷

গাণিতিকভাবে, যদি একটি অপ গণনা করে \(y = f(x)\) নিবন্ধিত গ্রেডিয়েন্ট অপ গ্রেডিয়েন্ট রূপান্তর করে \(\partial L/ \partial y\) ক্ষতি \(L\) সম্মানের সাথে\(y\) গ্রেডিয়েন্টে \(\partial L/ \partial x\) সম্মানের সাথে \(x\) চেইন নিয়মের মাধ্যমে:

\[\frac{\partial L}{\partial x} = \frac{\partial L}{\partial y} \frac{\partial y}{\partial x} = \frac{\partial L}{\partial y} \frac{\partial f}{\partial x}.\]

ZeroOut এর ক্ষেত্রে, ইনপুটে শুধুমাত্র একটি এন্ট্রি আউটপুটকে প্রভাবিত করে, তাই ইনপুটের ক্ষেত্রে গ্রেডিয়েন্ট হল একটি স্পার্স "একটি গরম" টেনসর। এটি নিম্নরূপ প্রকাশ করা হয়:

from tensorflow.python.framework import ops
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import sparse_ops

@ops.RegisterGradient("ZeroOut")
def _zero_out_grad(op, grad):
  """The gradients for `zero_out`.

  Args:
    op: The `zero_out` `Operation` that we are differentiating, which we can use
      to find the inputs and outputs of the original op.
    grad: Gradient with respect to the output of the `zero_out` op.

  Returns:
    Gradients with respect to the input of `zero_out`.
  """
  to_zero = op.inputs[0]
  shape = array_ops.shape(to_zero)
  index = array_ops.zeros_like(shape)
  first_grad = array_ops.reshape(grad, [-1])[0]
  to_zero_grad = sparse_ops.sparse_to_dense([index], shape, first_grad, 0)
  return [to_zero_grad]  # List of one Tensor, since we have one input

tf.RegisterGradient এর সাথে গ্রেডিয়েন্ট ফাংশন নিবন্ধন সম্পর্কে বিশদ:

  • একটি আউটপুট সহ একটি অপের জন্য, গ্রেডিয়েন্ট ফাংশনটি একটি tf.Operation , op , এবং একটি tf.Tensor grad নেবে এবং টেনসরগুলি op.inputs[i] , op.outputs[i] , এবং grad থেকে নতুন অপস তৈরি করবে। যেকোনো attrs সম্পর্কে তথ্য tf.Operation.get_attr এর মাধ্যমে পাওয়া যাবে।

  • op-এর একাধিক আউটপুট থাকলে, গ্রেডিয়েন্ট ফাংশন op এবং grads গ্রহণ করবে, যেখানে grads হল প্রতিটি আউটপুটের ক্ষেত্রে গ্রেডিয়েন্টের একটি তালিকা। গ্রেডিয়েন্ট ফাংশনের ফলাফলটি অবশ্যই প্রতিটি ইনপুটের সাথে গ্রেডিয়েন্টের প্রতিনিধিত্বকারী Tensor বস্তুর একটি তালিকা হতে হবে।

  • যদি কিছু ইনপুটের জন্য কোন সু-সংজ্ঞায়িত গ্রেডিয়েন্ট না থাকে, যেমন সূচক হিসাবে ব্যবহৃত পূর্ণসংখ্যা ইনপুটগুলির জন্য, সংশ্লিষ্ট রিটার্ন গ্রেডিয়েন্টটি None হওয়া উচিত। উদাহরণস্বরূপ, একটি ফ্লোটিং পয়েন্ট টেনসর x এবং একটি পূর্ণসংখ্যা সূচক i নেওয়ার জন্য, গ্রেডিয়েন্ট ফাংশনটি return [x_grad, None] করবে।

  • যদি op-এর জন্য কোনো অর্থপূর্ণ গ্রেডিয়েন্ট না থাকে, তাহলে আপনাকে প্রায়শই কোনো গ্রেডিয়েন্ট নিবন্ধন করতে হবে না, এবং যতক্ষণ পর্যন্ত op এর গ্রেডিয়েন্টের প্রয়োজন হয় না, আপনি ঠিক থাকবেন। কিছু ক্ষেত্রে, একটি অপের কোন সু-সংজ্ঞায়িত গ্রেডিয়েন্ট নেই তবে গ্রেডিয়েন্টের গণনার সাথে জড়িত হতে পারে। এখানে আপনি শূন্যকে স্বয়ংক্রিয়ভাবে পিছনের দিকে প্রচার করতে ops.NotDifferentiable ব্যবহার করতে পারেন।

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

কোডটিকে আরও পঠনযোগ্য, ডিবাগযোগ্য, রক্ষণাবেক্ষণ করা সহজ এবং ডেটা যাচাইকরণের মাধ্যমে আরও শক্তিশালী করতে একটি অপ টাইপের জন্য কাস্টম গ্রেডিয়েন্ট নিবন্ধন করার সময় টাইপ ইঙ্গিত যোগ করুন। উদাহরণস্বরূপ, একটি ফাংশনে একটি প্যারামিটার হিসাবে একটি op নেওয়ার সময়, গ্রেডিয়েন্ট ফাংশনটি প্যারামিটারের ধরণ হিসাবে একটি tf.Operation গ্রহণ করবে তা নির্দিষ্ট করুন।

C++ এ শেপ ফাংশন

টেনসরফ্লো এপিআই-এর "শেপ ইনফারেন্স" নামে একটি বৈশিষ্ট্য রয়েছে যা গ্রাফটি চালানো ছাড়াই টেনসরের আকার সম্পর্কে তথ্য প্রদান করে। আকৃতির অনুমান "শেপ ফাংশন" দ্বারা সমর্থিত যা C++ REGISTER_OP ঘোষণায় প্রতিটি অপ টাইপের জন্য নিবন্ধিত, এবং দুটি ভূমিকা সম্পাদন করে: গ্রাফ নির্মাণের সময় ইনপুটগুলির আকারগুলি সামঞ্জস্যপূর্ণ বলে দাবি করা এবং আউটপুটগুলির জন্য আকারগুলি নির্দিষ্ট করা৷

শেপ ফাংশনগুলি shape_inference::InferenceContext ক্লাসে অপারেশন হিসাবে সংজ্ঞায়িত করা হয়। উদাহরণস্বরূপ, জিরোআউটের জন্য আকৃতি ফাংশনে:

    .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) {
      c->set_output(0, c->input(0));
      return Status::OK();
    });

c->set_output(0, c->input(0)); ঘোষণা করে যে প্রথম আউটপুটের আকৃতিটি প্রথম ইনপুটের আকারে সেট করা উচিত। যদি উপরের উদাহরণের মতো আউটপুটটি তার সূচক দ্বারা নির্বাচিত হয়, তাহলে set_output এর দ্বিতীয় প্যারামিটারটি একটি ShapeHandle অবজেক্ট হওয়া উচিত। আপনি এর ডিফল্ট কনস্ট্রাক্টর দ্বারা একটি খালি ShapeHandle অবজেক্ট তৈরি করতে পারেন। ইনডেক্স idx সহ একটি ইনপুটের জন্য ShapeHandle অবজেক্ট c->input(idx) দ্বারা প্রাপ্ত করা যেতে পারে।

অনেকগুলি সাধারণ আকৃতি ফাংশন রয়েছে যা অনেকগুলি অপশনে প্রযোজ্য, যেমন shape_inference::UnchangedShape যা common_shape_fns.h- এ পাওয়া যায় এবং নিম্নরূপ ব্যবহার করা যেতে পারে:

REGISTER_OP("ZeroOut")
    .Input("to_zero: int32")
    .Output("zeroed: int32")
    .SetShapeFn(::tensorflow::shape_inference::UnchangedShape);

একটি আকৃতি ফাংশন একটি ইনপুট আকৃতি সীমাবদ্ধ করতে পারে. ভেক্টর আকৃতির সীমাবদ্ধতার সাথে ZeroOut এর সংস্করণের জন্য, আকৃতি ফাংশনটি নিম্নরূপ হবে:

    .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) {
      ::tensorflow::shape_inference::ShapeHandle input;
      TF_RETURN_IF_ERROR(c->WithRank(c->input(0), 1, &input));
      c->set_output(0, input);
      return Status::OK();
    });

WithRank কলটি যাচাই করে যে ইনপুট আকৃতি c->input(0) একটি আকৃতি রয়েছে যার ঠিক একটি মাত্রা রয়েছে (অথবা যদি ইনপুট আকারটি অজানা থাকে, আউটপুট আকারটি একটি অজানা মাত্রা সহ একটি ভেক্টর হবে)।

যদি আপনার অপটি একাধিক ইনপুট সহ বহুরূপী হয়, তাহলে আপনি InferenceContext এর সদস্যদের ব্যবহার করে আকৃতির সংখ্যা নির্ধারণ করতে পারেন, এবং আকারগুলি সব সামঞ্জস্যপূর্ণ কিনা তা যাচাই করতে Merge পারেন (বিকল্পভাবে, অ্যাক্সেস বৈশিষ্ট্যগুলি যা দৈর্ঘ্য নির্দেশ করে, InferenceContext::GetAttr , সহ যা op এর বৈশিষ্ট্যগুলিতে অ্যাক্সেস সরবরাহ করে)।

    .SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) {
      ::tensorflow::shape_inference::ShapeHandle input;
      ::tensorflow::shape_inference::ShapeHandle output;
      for (size_t i = 0; i < c->num_inputs(); ++i) {
        TF_RETURN_IF_ERROR(c->WithRank(c->input(i), 2, &input));
        TF_RETURN_IF_ERROR(c->Merge(output, input, &output));
      }
      c->set_output(0, output);
      return Status::OK();
    });

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

InferenceContext ক্লাসে অনেকগুলি ফাংশন রয়েছে যা আকার ফাংশন ম্যানিপুলেশনগুলিকে সংজ্ঞায়িত করতে ব্যবহার করা যেতে পারে। উদাহরণস্বরূপ, আপনি InferenceContext::Dim এবং InferenceContext::WithValue ব্যবহার করে একটি নির্দিষ্ট মাত্রার একটি খুব নির্দিষ্ট মান আছে তা যাচাই করতে পারেন; আপনি নির্দিষ্ট করতে পারেন যে একটি আউটপুট মাত্রা হল InferenceContext::Add এবং InferenceContext::Multiply ব্যবহার করে দুটি ইনপুট মাত্রার যোগফল / গুণফল। আপনি নির্দিষ্ট করতে পারেন এমন বিভিন্ন আকারের ম্যানিপুলেশনগুলির জন্য InferenceContext ক্লাসটি দেখুন। নিম্নলিখিত উদাহরণটি প্রথম আউটপুটের আকৃতি সেট করে (n, 3), যেখানে প্রথম ইনপুটটির আকৃতি রয়েছে (n, ...)

.SetShapeFn([](::tensorflow::shape_inference::InferenceContext* c) {
    c->set_output(0, c->Matrix(c->Dim(c->input(0), 0), 3));
    return Status::OK();
});

আপনার যদি একটি জটিল আকৃতি ফাংশন থাকে, তাহলে আপনাকে যাচাই করার জন্য একটি পরীক্ষা যোগ করার কথা বিবেচনা করা উচিত যে বিভিন্ন ইনপুট আকৃতির সমন্বয় প্রত্যাশিত আউটপুট আকৃতি সমন্বয় তৈরি করে। আপনি আমাদের কিছু মূল অপস পরীক্ষায় এই পরীক্ষাগুলি কীভাবে লিখবেন তার উদাহরণ দেখতে পারেন। ( INFER_OK এবং INFER_ERROR এর সিনট্যাক্স একটু গুপ্ত, কিন্তু পরীক্ষায় ইনপুট এবং আউটপুট আকৃতির স্পেসিফিকেশনগুলি উপস্থাপন করার জন্য কম্প্যাক্ট হওয়ার চেষ্টা করুন। আপাতত, আকৃতির স্ট্রিং স্পেসিফিকেশন বোঝার জন্য সেই পরীক্ষাগুলিতে আশেপাশের মন্তব্যগুলি দেখুন)।

আপনার কাস্টম অপের জন্য একটি পিপ প্যাকেজ তৈরি করুন

আপনার অপের জন্য একটি pip প্যাকেজ তৈরি করতে, টেনসরফ্লো/কাস্টম-অপ উদাহরণ দেখুন। এই গাইডটি দেখায় কিভাবে উৎস থেকে TensorFlow নির্মাণের পরিবর্তে TensorFlow পিপ প্যাকেজ থেকে কাস্টম অপস তৈরি করা যায়।