यदि आप एक ऐसा ऑप बनाना चाहते हैं जो मौजूदा TensorFlow लाइब्रेरी द्वारा कवर नहीं किया गया है, तो हम अनुशंसा करते हैं कि आप पहले मौजूदा पायथन ऑप्स या फ़ंक्शंस की संरचना के रूप में ऑप को पायथन में लिखने का प्रयास करें। यदि यह संभव नहीं है, तो आप एक कस्टम C++ op बना सकते हैं। कस्टम C++ op बनाने के कई कारण हो सकते हैं:
- मौजूदा ऑप्स की संरचना के रूप में अपने ऑपरेशन को व्यक्त करना आसान या संभव नहीं है।
- मौजूदा प्राइमेटिव की संरचना के रूप में आपके ऑपरेशन को व्यक्त करना कुशल नहीं है।
- आप प्राइमेटिव्स की एक रचना को हाथ से फ्यूज करना चाहते हैं कि भविष्य के कंपाइलर को मुश्किल फ़्यूज़िंग मिल जाएगी।
उदाहरण के लिए, कल्पना करें कि आप "मैक्सपूल" ऑपरेटर के समान "मीडियन पूलिंग" जैसी किसी चीज़ को लागू करना चाहते हैं, लेकिन अधिकतम मानों के बजाय स्लाइडिंग विंडो पर माध्यिका की गणना करना चाहते हैं। संचालन की संरचना का उपयोग करके ऐसा करना संभव हो सकता है (उदाहरण के लिए, ExtractImagePatches और TopK का उपयोग करके), लेकिन मूल ऑपरेशन के रूप में प्रदर्शन-या स्मृति-कुशल नहीं हो सकता है जहां आप एकल, फ़्यूज्ड ऑपरेशन में कुछ और चालाक कर सकते हैं। हमेशा की तरह, यह आमतौर पर सबसे पहले यह व्यक्त करने के लायक है कि आप ऑपरेटर संरचना का उपयोग करके क्या चाहते हैं, केवल एक नया ऑपरेशन जोड़ने का चयन करना यदि यह मुश्किल या अक्षम साबित होता है।
अपने कस्टम ऑप को शामिल करने के लिए आपको यह करना होगा:
- नए ऑप को C++ फ़ाइल में पंजीकृत करें। Op पंजीकरण, op की कार्यक्षमता के लिए एक इंटरफ़ेस (विनिर्देश) को परिभाषित करता है, जो op के कार्यान्वयन से स्वतंत्र होता है। उदाहरण के लिए, op पंजीकरण op के नाम और op के इनपुट और आउटपुट को परिभाषित करता है। यह आकार फ़ंक्शन को भी परिभाषित करता है जिसका उपयोग टेंसर आकार अनुमान के लिए किया जाता है।
- सी ++ में सेशन लागू करें। एक सेशन के कार्यान्वयन को कर्नेल के रूप में जाना जाता है, और यह चरण 1 में आपके द्वारा पंजीकृत विनिर्देश का ठोस कार्यान्वयन है। विभिन्न इनपुट / आउटपुट प्रकार या आर्किटेक्चर (उदाहरण के लिए, सीपीयू, जीपीयू) के लिए कई कर्नेल हो सकते हैं।
- एक पायथन रैपर बनाएं (वैकल्पिक)। यह रैपर सार्वजनिक एपीआई है जिसका उपयोग पायथन में सेशन बनाने के लिए किया जाता है। op पंजीकरण से एक डिफ़ॉल्ट आवरण उत्पन्न होता है, जिसे सीधे उपयोग किया जा सकता है या इसमें जोड़ा जा सकता है।
- सेशन (वैकल्पिक) के लिए ग्रेडिएंट्स की गणना करने के लिए एक फ़ंक्शन लिखें।
- ऑप का परीक्षण करें। हम आमतौर पर सुविधा के लिए इसे पायथन में करते हैं, लेकिन आप सी ++ में सेशन का परीक्षण भी कर सकते हैं। यदि आप ग्रेडिएंट को परिभाषित करते हैं, तो आप उन्हें Python
tf.test.compute_gradient_error
से सत्यापित कर सकते हैं। एक उदाहरण के रूप मेंrelu_op_test.py
देखें जो Relu- जैसे ऑपरेटरों के आगे के कार्यों और उनके ग्रेडिएंट का परीक्षण करता है।
आवश्यक शर्तें
- सी ++ के साथ कुछ परिचित।
- TensorFlow बाइनरी स्थापित होना चाहिए, या TensorFlow स्रोत डाउनलोड करना चाहिए, और इसे बनाने में सक्षम होना चाहिए।
ऑप इंटरफ़ेस को परिभाषित करें
आप किसी op के इंटरफ़ेस को TensorFlow सिस्टम के साथ पंजीकृत करके परिभाषित करते हैं। पंजीकरण में, आप अपने ऑप का नाम, उसके इनपुट (प्रकार और नाम) और आउटपुट (प्रकार और नाम), साथ ही साथ डॉकस्ट्रिंग और ऑप की आवश्यकता वाले किसी भी एटीआर को निर्दिष्ट करते हैं।
यह देखने के लिए कि यह कैसे काम करता है, मान लीजिए कि आप एक ऑप बनाना चाहते हैं जो 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
op इनपुट के रूप में 32-बिट पूर्णांकों में से एक टेंसर to_zero
लेता है, और 32-बिट पूर्णांकों के zeroed
वाले टेंसर को आउटपुट करता है। op यह सुनिश्चित करने के लिए एक आकृति फ़ंक्शन का भी उपयोग करता है कि आउटपुट टेंसर इनपुट टेंसर के समान आकार का है। उदाहरण के लिए, यदि इनपुट आकार का टेंसर [10, 20] है, तो यह आकृति फ़ंक्शन निर्दिष्ट करता है कि आउटपुट आकार भी [10, 20] है।
सेशन के लिए कर्नेल लागू करें
इंटरफ़ेस को परिभाषित करने के बाद, op के एक या अधिक कार्यान्वयन प्रदान करें। इनमें से एक कर्नेल बनाने के लिए, एक वर्ग बनाएं जो 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);
}
};
अपने कर्नेल को लागू करने के बाद, आप इसे TensorFlow सिस्टम के साथ पंजीकृत करते हैं। पंजीकरण में, आप विभिन्न बाधाओं को निर्दिष्ट करते हैं जिसके तहत यह कर्नेल चलेगा। उदाहरण के लिए, आपके पास CPU के लिए एक कर्नेल और GPU के लिए एक अलग कर्नेल हो सकता है।
ZeroOut
op के लिए ऐसा करने के लिए, निम्नलिखित को zero_out.cc
में जोड़ें:
REGISTER_KERNEL_BUILDER(Name("ZeroOut").Device(DEVICE_CPU), ZeroOutOp);
मल्टी-थ्रेडेड सीपीयू कर्नेल
मल्टी-थ्रेडेड CPU कर्नेल लिखने के लिए, work_sharder.h
में Shard फ़ंक्शन का उपयोग किया जा सकता है। यह फ़ंक्शन इंट्रा-ऑप थ्रेडिंग के लिए उपयोग किए जाने के लिए कॉन्फ़िगर किए गए थ्रेड्स में एक गणना फ़ंक्शन को शार्प करता है (देखें config.proto
में intra_op_parallelism_threads)।
GPU गुठली
एक GPU कर्नेल दो भागों में कार्यान्वित किया जाता है: OpKernel और CUDA कर्नेल और इसका लॉन्च कोड।
कभी-कभी सीपीयू और जीपीयू कर्नेल के बीच ओपकर्नेल कार्यान्वयन सामान्य होता है, जैसे कि इनपुट का निरीक्षण करना और आउटपुट आवंटित करना। उस स्थिति में, एक सुझाया गया कार्यान्वयन है:
- डिवाइस पर OpKernel टेम्प्लेट और टेंसर के आदिम प्रकार को परिभाषित करें।
- आउटपुट की वास्तविक गणना करने के लिए, कंप्यूट फ़ंक्शन एक टेम्प्लेटेड फ़ैक्टर स्ट्रक्चर को कॉल करता है।
- 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
ऑप लाइब्रेरी बनाएं
अपने सिस्टम कंपाइलर (TensorFlow बाइनरी इंस्टॉलेशन) का उपयोग करके ऑप को संकलित करें
आपको अपने सिस्टम पर उपलब्ध 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
फ़ाइल बनाते समय अतिरिक्त फ़्लैग "-undefined dynamic_lookup" की आवश्यकता होती है।
gcc
संस्करण पर ध्यान दें>=5
: gcc संस्करण5
से नए C++ ABI का उपयोग करता है। TensorFlow वेबसाइट पर उपलब्ध बाइनरी पाइप पैकेजgcc4
के साथ बनाए गए हैं जो पुराने ABI का उपयोग करते हैं। यदि आप अपनी ऑप लाइब्रेरी कोgcc>=5
के साथ संकलित करते हैं,-D_GLIBCXX_USE_CXX11_ABI=0
को कमांड लाइन में जोड़ें ताकि लाइब्रेरी को पुराने abi के अनुकूल बनाया जा सके।
bazel (TensorFlow स्रोत स्थापना) का उपयोग करके op को संकलित करें
यदि आपके पास TensorFlow स्रोत स्थापित हैं, तो आप अपने op को संकलित करने के लिए 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 गतिशील लाइब्रेरी को लोड करने और TensorFlow ढांचे के साथ op को पंजीकृत करने के लिए tf.load_op_library
फ़ंक्शन प्रदान करता है। load_op_library
एक पायथन मॉड्यूल देता है जिसमें ऑप और कर्नेल के लिए पायथन रैपर होते हैं। इस प्रकार, एक बार जब आप ऑप बना लेते हैं, तो आप इसे पायथन से चलाने के लिए निम्न कार्य कर सकते हैं:
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)
ध्यान रखें, उत्पन्न फ़ंक्शन को एक सांप_केस नाम दिया जाएगा ( PEP8 का अनुपालन करने के लिए)। इसलिए, यदि आपके ऑप को सी ++ फाइलों में ZeroOut
नाम दिया गया है, तो पायथन फ़ंक्शन को zero_out
कहा जाएगा।
सेशन को एक नियमित फ़ंक्शन के रूप में उपलब्ध कराने के लिए - एक पायथन मॉड्यूल से import
-योग्य, यह संभवतः एक पायथन स्रोत फ़ाइल में 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
का उपयोग करें। ये दोनों मैक्रोज़ त्रुटि पर फ़ंक्शन से वापस आ जाते हैं।
ऑप पंजीकरण
अतट्र्स
Ops में attrs हो सकते हैं, जिनके मान तब सेट होते हैं जब op को ग्राफ़ में जोड़ा जाता है। इनका उपयोग ऑप को कॉन्फ़िगर करने के लिए किया जाता है, और उनके मूल्यों को कर्नेल कार्यान्वयन के भीतर और ऑप पंजीकरण में इनपुट और आउटपुट के प्रकार दोनों में पहुँचा जा सकता है। जब संभव हो तो एक attr के बजाय एक इनपुट का उपयोग करना पसंद करें, क्योंकि इनपुट अधिक लचीले होते हैं। ऐसा इसलिए है क्योंकि attrs स्थिरांक हैं और इन्हें ग्राफ़ निर्माण समय पर परिभाषित किया जाना चाहिए। इसके विपरीत, इनपुट टेंसर होते हैं जिनके मूल्य गतिशील हो सकते हैं; यानी, इनपुट हर चरण को बदल सकते हैं, फ़ीड का उपयोग करके सेट किया जा सकता है, आदि। Attrs का उपयोग उन चीजों के लिए किया जाता है जो इनपुट के साथ नहीं की जा सकती हैं: कोई भी कॉन्फ़िगरेशन जो हस्ताक्षर को प्रभावित करता है (संख्या या इनपुट या आउटपुट का प्रकार) या जो ' t चरण-दर-चरण परिवर्तन।
जब आप op को पंजीकृत करते हैं, तो आप एक attr को परिभाषित करते हैं, उसका नाम निर्दिष्ट करके और Attr
विधि का उपयोग करके टाइप करते हैं, जो फ़ॉर्म की एक विशिष्टता की अपेक्षा करता है:
<name>: <attr-type-expr>
जहां <name>
एक अक्षर से शुरू होता है और अल्फ़ान्यूमेरिक वर्णों और अंडरस्कोर से बना हो सकता है, और <attr-type-expr>
नीचे वर्णित फ़ॉर्म की एक प्रकार की अभिव्यक्ति है।
उदाहरण के लिए, यदि आप चाहते हैं कि ZeroOut
op केवल 0वें तत्व के बजाय उपयोगकर्ता द्वारा निर्दिष्ट अनुक्रमणिका को संरक्षित करे, तो आप op को इस प्रकार पंजीकृत कर सकते हैं:
REGISTER_OP("ZeroOut")
.Attr("preserve_index: int")
.Input("to_zero: int32")
.Output("zeroed: int32");
(ध्यान दें कि विशेषता प्रकारों का सेट इनपुट और आउटपुट के लिए उपयोग किए जाने वाले tf.DType
से अलग है।)
आपका कर्नेल तब इस attr को इसके कंस्ट्रक्टर में context
पैरामीटर के माध्यम से एक्सेस कर सकता है:
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 में समर्थित हैं:
-
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
का नाम निहित होता है। यह एक एनम का अनुकरण करता है:
REGISTER_OP("EnumExample")
.Attr("e: {'apple', 'orange'}");
{<type1>, <type2>}
: मान type 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
संख्या प्रकार।
इनके द्वारा अनुमत प्रकारों की विशिष्ट सूचियाँ 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>
एक प्राकृत संख्या है। उदाहरण के लिए, निम्न op पंजीकरण निर्दिष्ट करता है कि 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");
डिफ़ॉल्ट मान का समर्थित सिंटैक्स वह है जो परिणामी GraphDef परिभाषा के प्रोटो प्रतिनिधित्व में उपयोग किया जाएगा।
यहां सभी प्रकार के लिए डिफ़ॉल्ट निर्दिष्ट करने के उदाहरण दिए गए हैं:
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
का उपयोग करते हैं।
बहुरूपता
बहुरूपता टाइप करें
ऑप्स के लिए जो इनपुट के रूप में विभिन्न प्रकार ले सकते हैं या विभिन्न आउटपुट प्रकार उत्पन्न कर सकते हैं, आप op पंजीकरण में इनपुट या आउटपुट प्रकार में एक attr निर्दिष्ट कर सकते हैं। आम तौर पर आप प्रत्येक समर्थित प्रकार के लिए एक OpKernel
पंजीकृत करेंगे।
उदाहरण के लिए, यदि आप चाहते हैं कि ZeroOut
op int32
s के अलावा float
s पर भी काम करे, तो आपका op पंजीकरण ऐसा दिखाई दे सकता है:
REGISTER_OP("ZeroOut")
.Attr("T: {float, int32}")
.Input("to_zero: T")
.Output("zeroed: T");
आपका op पंजीकरण अब निर्दिष्ट करता है कि इनपुट का प्रकार होना चाहिए float
, या int32
, और इसका आउटपुट एक ही प्रकार का होगा, क्योंकि दोनों का प्रकार T
है।
नामकरण
आम तौर पर इनपुट, आउटपुट और attrs को सांप_केस नाम दिए जाने चाहिए। एक अपवाद attrs है जो इनपुट के प्रकार या आउटपुट के प्रकार के रूप में उपयोग किया जाता है। जब op को ग्राफ़ में जोड़ा जाता है तो उन attrs का अनुमान लगाया जा सकता है और इसलिए op के कार्य में प्रकट नहीं होते हैं। उदाहरण के लिए, ज़ीरोऑट की यह अंतिम परिभाषा एक पायथन फ़ंक्शन उत्पन्न करेगी जो इस तरह दिखता है:
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 को कैपिटलाइज़्ड या 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);
पश्च संगतता बनाए रखने के लिए, आपको किसी मौजूदा op में 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
टेंसर की एक सूची है। op स्वीकार करता है, उदाहरण के लिए, इनपुट प्रकार (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)
के एक एटीआर को संदर्भित करने से आप टेंसर के अनुक्रम को स्वीकार कर सकते हैं।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
हो सकता है, या एक attr का नाम typetype
के साथ हो सकता है। पहले के उदाहरण के रूप में, यह सेशन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 का अनुमान लगाया जाएगा। परंपरा के अनुसार वे अनुमानित attrs बड़े नामों (जैसे T
या N
) का उपयोग करते हैं। अन्यथा इनपुट, आउटपुट और attrs में फ़ंक्शन पैरामीटर (जैसे num_outputs
) जैसे नाम होते हैं। अधिक विवरण के लिए, नामकरण पर पिछला भाग देखें।
अधिक जानकारी के लिए, tensorflow/core/framework/op_def_builder.h
।
पिछेड़ी संगतता
आइए मान लें कि आपने एक अच्छा, कस्टम ऑप लिखा है और इसे दूसरों के साथ साझा किया है, ताकि आपके ऑपरेशन का उपयोग करके आपके पास खुश ग्राहक हों। हालांकि, आप किसी तरह से सेशन में बदलाव करना चाहेंगे।
सामान्य तौर पर, मौजूदा, चेक-इन विनिर्देशों में परिवर्तन पश्च-संगत होना चाहिए: एक सेशन के विनिर्देश को बदलने से पुराने विनिर्देशों से निर्मित पूर्व क्रमबद्ध GraphDef
प्रोटोकॉल बफ़र्स को नहीं तोड़ना चाहिए। GraphDef
संगतता का विवरण यहां वर्णित है ।
पश्च-संगतता को संरक्षित करने के कई तरीके हैं।
किसी ऑपरेशन में जोड़े गए किसी भी नए 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");
आप एक attr कम प्रतिबंधात्मक पर सुरक्षित रूप से एक बाधा बना सकते हैं। उदाहरण के लिए, आप
{int32, int64}
से{int32, int64, float}
बदल सकते हैं याtype
कर सकते हैं। या आप{"apple", "orange"}
से{"apple", "banana", "orange"}
याstring
में बदल सकते हैं।आप एकल इनपुट/आउटपुट को सूची इनपुट/आउटपुट में बदल सकते हैं, जब तक कि सूची प्रकार के लिए डिफ़ॉल्ट पुराने हस्ताक्षर से मेल खाता है।
आप एक नई सूची इनपुट/आउटपुट जोड़ सकते हैं, यदि यह डिफ़ॉल्ट रूप से खाली हो जाती है।
आपके द्वारा बनाए गए किसी भी नए ऑप्स को नेमस्पेस, अपने प्रोजेक्ट के लिए अद्वितीय कुछ के साथ ऑप नामों को प्रीफ़िक्स करके। यह आपके ऑप को किसी भी ऑप्स से टकराने से बचाता है जिसे TensorFlow के भविष्य के संस्करणों में शामिल किया जा सकता है।
आगे की योजना! सेशन के लिए भविष्य के उपयोगों का अनुमान लगाने का प्रयास करें। कुछ हस्ताक्षर परिवर्तन संगत तरीके से नहीं किए जा सकते हैं (उदाहरण के लिए, एक ही प्रकार की सूची को अलग-अलग प्रकारों की सूची में बनाना)।
सुरक्षित और असुरक्षित परिवर्तनों की पूरी सूची tensorflow/core/framework/op_compatibility_test.cc
में पाई जा सकती है। यदि आप किसी ऑपरेशन में अपने परिवर्तन को बैकवर्ड संगत नहीं बना सकते हैं, तो नए शब्दार्थ के साथ एक नए नाम के साथ एक नया ऑपरेशन बनाएं।
यह भी ध्यान दें कि हालांकि ये परिवर्तन GraphDef
संगतता बनाए रख सकते हैं, उत्पन्न पायथन कोड इस तरह से बदल सकता है जो पुराने कॉलर्स के साथ संगत नहीं है। अंत में नए वैकल्पिक तर्कों को जोड़ने के अलावा, पुराने हस्ताक्षर को रखकर, हाथ से लिखे गए पायथन रैपर में सावधानीपूर्वक परिवर्तन करके पायथन एपीआई को संगत रखा जा सकता है। आम तौर पर असंगत परिवर्तन केवल तभी किए जा सकते हैं जब TensorFlow प्रमुख संस्करणों को बदलता है, और GraphDef
संस्करण शब्दार्थ के अनुरूप होना चाहिए।
जीपीयू समर्थन
आप विभिन्न OpKernels को लागू कर सकते हैं और एक CPU के लिए और दूसरा GPU के लिए पंजीकृत कर सकते हैं, जैसे आप विभिन्न प्रकार के लिए कर्नेल पंजीकृत कर सकते हैं। tensorflow/core/kernels/
में GPU समर्थन वाले कर्नेल के कई उदाहरण हैं। ध्यान दें कि कुछ कर्नेल में .cc
फ़ाइल में CPU संस्करण, _gpu.cu.cc
में समाप्त होने वाली फ़ाइल में GPU संस्करण और .h
फ़ाइल में साझा किए गए कुछ कोड होते हैं।
उदाहरण के लिए, tf.pad
में tensorflow/core/kernels/pad_op.cc
में GPU कर्नेल के अलावा सब कुछ है। GPU कर्नेल tensorflow/core/kernels/pad_op_gpu.cu.cc
में है, और साझा कोड tensorflow/core/kernels/pad_op.h
में परिभाषित एक टेम्प्लेटेड क्लास है। हम कोड को दो कारणों से इस तरह व्यवस्थित करते हैं: यह आपको सीपीयू और जीपीयू कार्यान्वयन के बीच सामान्य कोड साझा करने की अनुमति देता है, और यह जीपीयू कार्यान्वयन को एक अलग फाइल में रखता है ताकि इसे केवल जीपीयू कंपाइलर द्वारा संकलित किया जा सके।
एक बात ध्यान देने योग्य है, जब pad
के GPU कर्नेल संस्करण का उपयोग किया जाता है, तब भी इसे CPU मेमोरी में इसके "paddings"
इनपुट की आवश्यकता होती है। यह चिह्नित करने के लिए कि इनपुट या आउटपुट CPU पर रखे गए हैं, कर्नेल पंजीकरण में एक 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
में स्थापित है।
पायथन में ग्रेडिएंट लागू करें
ऑप्स के एक ग्राफ को देखते हुए, TensorFlow मौजूदा ऑप्स के संबंध में ग्रेडिएंट का प्रतिनिधित्व करने वाले नए ऑप्स को जोड़ने के लिए स्वचालित भेदभाव (बैकप्रॉपैगैशन) का उपयोग करता है। नए ऑप्स के लिए स्वचालित विभेदन कार्य करने के लिए, आपको एक ग्रेडिएंट फ़ंक्शन पंजीकृत करना होगा जो ऑप्स के आउटपुट के संबंध में दिए गए ग्रेडिएंट ऑप्स के इनपुट के संबंध में ग्रेडिएंट की गणना करता है।
गणितीय रूप से, यदि कोई op \(y = f(x)\) की गणना करता है, तो पंजीकृत ग्रेडिएंट op, \(\partial L/ \partial y\) के नुकसान \(L\) 2 को\(y\) प्लेसहोल्डर4 के संबंध में ग्रेडिएंट \(\partial L/ \partial x\) के संबंध में \(x\) प्लेसहोल्डर6 के संबंध में चेन नियम के माध्यम से परिवर्तित करता है:
\[\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]
जाएगा।यदि ऑप के लिए कोई सार्थक ग्रेडिएंट नहीं है, तो आपको अक्सर किसी ग्रेडिएंट को पंजीकृत करने की आवश्यकता नहीं होगी, और जब तक ऑप के ग्रेडिएंट की कभी आवश्यकता नहीं होगी, तब तक आप ठीक रहेंगे। कुछ मामलों में, एक सेशन में कोई अच्छी तरह से परिभाषित ग्रेडिएंट नहीं होता है, लेकिन ग्रेडिएंट की गणना में शामिल हो सकता है। यहां आप
ops.NotDifferentiable
का उपयोग शून्य को पीछे की ओर स्वचालित रूप से प्रचारित करने के लिए कर सकते हैं।
ध्यान दें कि जिस समय ग्रेडिएंट फ़ंक्शन कहा जाता है, केवल ऑप्स का डेटा फ़्लो ग्राफ़ उपलब्ध होता है, न कि टेंसर डेटा। इस प्रकार, ग्राफ निष्पादन समय पर चलाने के लिए, सभी गणना अन्य टेंसरफ़्लो ऑप्स का उपयोग करके की जानी चाहिए।
सी ++ में आकार कार्य करता है
TensorFlow API में "आकृति अनुमान" नामक एक विशेषता है जो ग्राफ़ को निष्पादित किए बिना टेंसर के आकार के बारे में जानकारी प्रदान करती है। आकार अनुमान "आकृति कार्यों" द्वारा समर्थित है जो सी ++ 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();
});
चूंकि आकार का अनुमान एक वैकल्पिक विशेषता है, और टेंसर के आकार गतिशील रूप से भिन्न हो सकते हैं, किसी भी इनपुट के लिए आकृति फ़ंक्शन अपूर्ण आकार की जानकारी के लिए मजबूत होना चाहिए। InferenceContext
में Merge
विधि कॉल करने वाले को यह दावा करने की अनुमति देती है कि दो आकृतियाँ समान हैं, भले ही उनमें से किसी एक या दोनों के पास पूरी जानकारी न हो। आकार फ़ंक्शन सभी मुख्य TensorFlow ऑप्स के लिए परिभाषित किए गए हैं और कई अलग-अलग उपयोग उदाहरण प्रदान करते हैं।
InferenceContext
वर्ग में कई कार्य हैं जिनका उपयोग आकार फ़ंक्शन जोड़तोड़ को परिभाषित करने के लिए किया जा सकता है। उदाहरण के लिए, आप 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 pip पैकेज से कस्टम ऑप्स कैसे बनाया जाए।