توفّر TensorFlow Lite Task Library الذي تم إنشاؤه مسبقًا واجهات برمجة تطبيقات C++ وAndroid وiOS أعلى البنية الأساسية نفسها التي تستخلص منها TensorFlow. يمكنك توسيع البنية الأساسية لواجهة برمجة التطبيقات Task API لإنشاء واجهات برمجة تطبيقات مخصَّصة. إذا لم يكن نموذجك متوافقًا مع مكتبات المهام الحالية.
نظرة عامة
تتمتع البنية الأساسية لواجهة برمجة تطبيقات المهام بهيكل من طبقتين: طبقة C++ السفلية تغليف بيئة تشغيل TFLite وطبقة أعلى من Java/ObjC يتواصل مع طبقة C++ من خلال JNI أو برنامج تضمين.
يؤدي تنفيذ كل منطق TensorFlow باستخدام لغة C++ فقط إلى تقليل التكلفة وزيادة واستنتاج الأداء وتبسيط سير العمل العام عبر المنصات.
لإنشاء فئة مهمة، قم بتوسيع BaseTaskApi لتوفير منطق الإحالة الناجحة بين واجهة نموذج TFLite وTask API ثم استخدم أدوات Java/ObjC لإنشاء واجهات برمجة التطبيقات المقابلة. مع تم إخفاء كل تفاصيل TensorFlow، ويمكنك تفعيل نموذج TFLite في تطبيقاتك. دون أي معرفة بالتعلم الآلي.
يوفّر تطبيق TensorFlow Lite بعض واجهات برمجة التطبيقات المنشأة مسبقًا للتطبيقات الأكثر رواجًا. مهام الرؤية وNLP: يمكنك إنشاء واجهات برمجة التطبيقات الخاصة بك لمهام أخرى باستخدام البنية الأساسية لواجهة برمجة تطبيقات المهام.
إنشاء واجهة برمجة تطبيقات باستخدام Task API أدناه
واجهة برمجة تطبيقات C++
يتم تنفيذ جميع تفاصيل TFLite في واجهة برمجة التطبيقات C++ API. إنشاء عنصر واجهة برمجة التطبيقات من خلال باستخدام إحدى وظائف المصنع والحصول على نتائج النموذج من خلال استدعاء الدوال المحددة في الواجهة.
مثال
فيما يلي مثال باستخدام لغة C++
BertQuestionAnswerer
حيث
MobileBert:
char kBertModelPath[] = "path/to/model.tflite";
// Create the API from a model file
std::unique_ptr<BertQuestionAnswerer> question_answerer =
BertQuestionAnswerer::CreateFromFile(kBertModelPath);
char kContext[] = ...; // context of a question to be answered
char kQuestion[] = ...; // question to be answered
// ask a question
std::vector<QaAnswer> answers = question_answerer.Answer(kContext, kQuestion);
// answers[0].text is the best answer
إنشاء واجهة برمجة التطبيقات
لإنشاء عنصر واجهة برمجة التطبيقات، يجب تقديم المعلومات التالية من خلال توسيع
BaseTaskApi
تحديد واجهة برمجة التطبيقات I/O: من المفترض أن تعرض واجهة برمجة التطبيقات بيانات إدخال وإخراج مماثلة. عبر منصات مختلفة. مثلاً: تأخذ
BertQuestionAnswerer
سلسلتين(std::string& context, std::string& question)
كإدخال وإخراج خط متجه للإجابة المحتملة والاحتمالات على النحو التاليstd::vector<QaAnswer>
. هذا النمط يتم من خلال تحديد الأنواع المطابقة في دالةBaseTaskApi
مَعلمة النموذج. مع تحديد معلمات النموذج،BaseTaskApi::Infer
أنواع الإدخال/الإخراج الصحيحة في الدالة. يمكن أن تكون هذه الدالة يتم استدعاؤها مباشرة عن طريق برامج واجهة برمجة التطبيقات، ولكن من الجيد إحاطتها داخل دالة محددة لنموذج، وهيBertQuestionAnswerer::Answer
في هذه الحالة.class BertQuestionAnswerer : public BaseTaskApi< std::vector<QaAnswer>, // OutputType const std::string&, const std::string& // InputTypes > { // Model specific function delegating calls to BaseTaskApi::Infer std::vector<QaAnswer> Answer(const std::string& context, const std::string& question) { return Infer(context, question).value(); } }
توفير منطق الإحالة الناجحة بين إدخالات/إخراج واجهة برمجة التطبيقات وموتر الإدخال/الإخراج الخاص model - مع تحديد أنواع المدخلات والمخرجات، تحتاج الفئات الفرعية أيضًا إلى تنفيذ الدوال المكتوبة
BaseTaskApi::Preprocess
أوBaseTaskApi::Postprocess
توفر الدالتان الإدخالات أو المخرجات من TFLiteFlatBuffer
. الفئة الفرعية مسؤولة عن تعيين من واجهة برمجة التطبيقات I/O إلى موتّرات وحدات الإدخال والإخراج. الاطّلاع على عملية التنفيذ الكاملة مثال فيBertQuestionAnswerer
class BertQuestionAnswerer : public BaseTaskApi< std::vector<QaAnswer>, // OutputType const std::string&, const std::string& // InputTypes > { // Convert API input into tensors absl::Status BertQuestionAnswerer::Preprocess( const std::vector<TfLiteTensor*>& input_tensors, // input tensors of the model const std::string& context, const std::string& query // InputType of the API ) { // Perform tokenization on input strings ... // Populate IDs, Masks and SegmentIDs to corresponding input tensors PopulateTensor(input_ids, input_tensors[0]); PopulateTensor(input_mask, input_tensors[1]); PopulateTensor(segment_ids, input_tensors[2]); return absl::OkStatus(); } // Convert output tensors into API output StatusOr<std::vector<QaAnswer>> // OutputType BertQuestionAnswerer::Postprocess( const std::vector<const TfLiteTensor*>& output_tensors, // output tensors of the model ) { // Get start/end logits of prediction result from output tensors std::vector<float> end_logits; std::vector<float> start_logits; // output_tensors[0]: end_logits FLOAT[1, 384] PopulateVector(output_tensors[0], &end_logits); // output_tensors[1]: start_logits FLOAT[1, 384] PopulateVector(output_tensors[1], &start_logits); ... std::vector<QaAnswer::Pos> orig_results; // Look up the indices from vocabulary file and build results ... return orig_results; } }
إنشاء وظائف المصنع لواجهة برمجة التطبيقات: ملف نموذج
OpResolver
مطلوبة لإعدادtflite::Interpreter
TaskAPIFactory
توفر وظائف مساعدة لإنشاء مثيلات BaseTaskApi.ويجب أيضًا تقديم أي ملفات مرتبطة بالنموذج. مثلاً: يمكن أيضًا أن يكون لدى "
BertQuestionAnswerer
" ملف إضافي خاص بأداة إنشاء الرموز المميّزة المفردات.class BertQuestionAnswerer : public BaseTaskApi< std::vector<QaAnswer>, // OutputType const std::string&, const std::string& // InputTypes > { // Factory function to create the API instance StatusOr<std::unique_ptr<QuestionAnswerer>> BertQuestionAnswerer::CreateBertQuestionAnswerer( const std::string& path_to_model, // model to passed to TaskApiFactory const std::string& path_to_vocab // additional model specific files ) { // Creates an API object by calling one of the utils from TaskAPIFactory std::unique_ptr<BertQuestionAnswerer> api_to_init; ASSIGN_OR_RETURN( api_to_init, core::TaskAPIFactory::CreateFromFile<BertQuestionAnswerer>( path_to_model, absl::make_unique<tflite::ops::builtin::BuiltinOpResolver>(), kNumLiteThreads)); // Perform additional model specific initializations // In this case building a vocabulary vector from the vocab file. api_to_init->InitializeVocab(path_to_vocab); return api_to_init; } }
Android API
إنشاء واجهات برمجة تطبيقات Android من خلال تحديد واجهة Java/Kotlin وتفويض المنطق إلى طبقة C++ من خلال JNI. تتطلب واجهة برمجة تطبيقات Android إنشاء واجهة برمجة تطبيقات أصلية أولاً.
مثال
فيما يلي مثال على استخدام Java
BertQuestionAnswerer
حيث
MobileBert:
String BERT_MODEL_FILE = "path/to/model.tflite";
String VOCAB_FILE = "path/to/vocab.txt";
// Create the API from a model file and vocabulary file
BertQuestionAnswerer bertQuestionAnswerer =
BertQuestionAnswerer.createBertQuestionAnswerer(
ApplicationProvider.getApplicationContext(), BERT_MODEL_FILE, VOCAB_FILE);
String CONTEXT = ...; // context of a question to be answered
String QUESTION = ...; // question to be answered
// ask a question
List<QaAnswer> answers = bertQuestionAnswerer.answer(CONTEXT, QUESTION);
// answers.get(0).text is the best answer
إنشاء واجهة برمجة التطبيقات
على غرار واجهات برمجة التطبيقات الأصلية، يجب توفير واجهة برمجة التطبيقات للعميل
المعلومات التالية من خلال تمديد
BaseTaskApi
,
توفِّر عمليات معالجة JNI لجميع واجهات برمجة تطبيقات مهام Java.
تحديد إدخال/إخراج واجهة برمجة التطبيقات: يؤدي هذا عادةً إلى النسخ المطابق للواجهات الأصلية. مثلاً: يتم استخدام
(String context, String question)
كإدخال فيBertQuestionAnswerer
والنتائجList<QaAnswer>
. يتطلّب التنفيذ منشئ محتوى خاصًا. ذات توقيع مشابه، باستثناء أنها تتضمن معلمة إضافيةlong nativeHandle
، وهي المؤشر الذي يتم عرضه من C++.class BertQuestionAnswerer extends BaseTaskApi { public List<QaAnswer> answer(String context, String question) { return answerNative(getNativeHandle(), context, question); } private static native List<QaAnswer> answerNative( long nativeHandle, // C++ pointer String context, String question // API I/O ); }
إنشاء وظائف المصنع لواجهة برمجة التطبيقات: كما أنه يتماشى مع المصنع الأصلي بخلاف ذلك، باستثناء أن وظائف Android المصنع تحتاج أيضًا إلى اتخاذ
Context
للوصول إلى الملفات. يستدعي التنفيذ إحدى الأدوات المساعدة فيTaskJniUtils
لإنشاء كائن واجهة برمجة تطبيقات C++ المقابل وتمرير مؤشر الماوس إلى الدالة الإنشائيةBaseTaskApi
.class BertQuestionAnswerer extends BaseTaskApi { private static final String BERT_QUESTION_ANSWERER_NATIVE_LIBNAME = "bert_question_answerer_jni"; // Extending super constructor by providing the // native handle(pointer of corresponding C++ API object) private BertQuestionAnswerer(long nativeHandle) { super(nativeHandle); } public static BertQuestionAnswerer createBertQuestionAnswerer( Context context, // Accessing Android files String pathToModel, String pathToVocab) { return new BertQuestionAnswerer( // The util first try loads the JNI module with name // BERT_QUESTION_ANSWERER_NATIVE_LIBNAME, then opens two files, // converts them into ByteBuffer, finally ::initJniWithBertByteBuffers // is called with the buffer for a C++ API object pointer TaskJniUtils.createHandleWithMultipleAssetFilesFromLibrary( context, BertQuestionAnswerer::initJniWithBertByteBuffers, BERT_QUESTION_ANSWERER_NATIVE_LIBNAME, pathToModel, pathToVocab)); } // modelBuffers[0] is tflite model file buffer, and modelBuffers[1] is vocab file buffer. // returns C++ API object pointer casted to long private static native long initJniWithBertByteBuffers(ByteBuffer... modelBuffers); }
تنفيذ وحدة JNI للدوال الأصلية - جميع طرق Java الأصلية يتم تنفيذها من خلال استدعاء دالة أصلية مقابلة من JNI واحدة. ستعمل دوال المصنع على إنشاء كائن واجهة برمجة تطبيقات أصلي ثم إرجاع المؤشر الخاص بها كنوع طويل لـ Java. في الاتصالات اللاحقة بواجهة برمجة تطبيقات Java، يتم تمرير مؤشر type مرة أخرى إلى JNI وإعادته إلى كائن واجهة برمجة التطبيقات الأصلي. يتم بعد ذلك تحويل نتائج واجهة برمجة التطبيقات الأصلية إلى نتائج Java.
على سبيل المثال، هذه هي الطريقة bert_question_answerer_jni تنفيذها.
// Implements BertQuestionAnswerer::initJniWithBertByteBuffers extern "C" JNIEXPORT jlong JNICALL Java_org_tensorflow_lite_task_text_qa_BertQuestionAnswerer_initJniWithBertByteBuffers( JNIEnv* env, jclass thiz, jobjectArray model_buffers) { // Convert Java ByteBuffer object into a buffer that can be read by native factory functions absl::string_view model = GetMappedFileBuffer(env, env->GetObjectArrayElement(model_buffers, 0)); // Creates the native API object absl::StatusOr<std::unique_ptr<QuestionAnswerer>> status = BertQuestionAnswerer::CreateFromBuffer( model.data(), model.size()); if (status.ok()) { // converts the object pointer to jlong and return to Java. return reinterpret_cast<jlong>(status->release()); } else { return kInvalidPointer; } } // Implements BertQuestionAnswerer::answerNative extern "C" JNIEXPORT jobject JNICALL Java_org_tensorflow_lite_task_text_qa_BertQuestionAnswerer_answerNative( JNIEnv* env, jclass thiz, jlong native_handle, jstring context, jstring question) { // Convert long to native API object pointer QuestionAnswerer* question_answerer = reinterpret_cast<QuestionAnswerer*>(native_handle); // Calls the native API std::vector<QaAnswer> results = question_answerer->Answer(JStringToString(env, context), JStringToString(env, question)); // Converts native result(std::vector<QaAnswer>) to Java result(List<QaAnswerer>) jclass qa_answer_class = env->FindClass("org/tensorflow/lite/task/text/qa/QaAnswer"); jmethodID qa_answer_ctor = env->GetMethodID(qa_answer_class, "<init>", "(Ljava/lang/String;IIF)V"); return ConvertVectorToArrayList<QaAnswer>( env, results, [env, qa_answer_class, qa_answer_ctor](const QaAnswer& ans) { jstring text = env->NewStringUTF(ans.text.data()); jobject qa_answer = env->NewObject(qa_answer_class, qa_answer_ctor, text, ans.pos.start, ans.pos.end, ans.pos.logit); env->DeleteLocalRef(text); return qa_answer; }); } // Implements BaseTaskApi::deinitJni by delete the native object extern "C" JNIEXPORT void JNICALL Java_task_core_BaseTaskApi_deinitJni( JNIEnv* env, jobject thiz, jlong native_handle) { delete reinterpret_cast<QuestionAnswerer*>(native_handle); }
واجهة برمجة تطبيقات iOS
يمكنك إنشاء واجهات برمجة تطبيقات لنظام التشغيل iOS من خلال دمج عنصر واجهة برمجة تطبيقات أصلي في كائن واجهة برمجة تطبيقات ObjC. تشير رسالة الأشكال البيانية يمكن استخدام كائن واجهة برمجة التطبيقات الذي تم إنشاؤه إما في ObjC أو Swift. تتطلب واجهة برمجة تطبيقات iOS الأصلية التي سيتم إنشاؤها أولاً.
مثال
فيما يلي مثال باستخدام ObjC
TFLBertQuestionAnswerer
لشركة MobileBert
في Swift.
static let mobileBertModelPath = "path/to/model.tflite";
// Create the API from a model file and vocabulary file
let mobileBertAnswerer = TFLBertQuestionAnswerer.mobilebertQuestionAnswerer(
modelPath: mobileBertModelPath)
static let context = ...; // context of a question to be answered
static let question = ...; // question to be answered
// ask a question
let answers = mobileBertAnswerer.answer(
context: TFLBertQuestionAnswererTest.context, question: TFLBertQuestionAnswererTest.question)
// answers.[0].text is the best answer
إنشاء واجهة برمجة التطبيقات
واجهة برمجة تطبيقات iOS هي برنامج تضمين بسيط من نوع ObjC أعلى واجهة برمجة التطبيقات الأصلية. إنشاء واجهة برمجة التطبيقات من خلال باتباع الخطوات أدناه:
تحديد برنامج تضمين ObjC - تحديد فئة ObjC وتفويض عمليات التنفيذ لكائن واجهة برمجة التطبيقات الأصلي المقابل. ملاحظة المحتوى الأصلي يمكن أن تظهر التبعيات فقط في ملف .mm بسبب عدم قدرة Swift على إمكانية التشغيل التفاعلي مع C++.
- ملف .h
@interface TFLBertQuestionAnswerer : NSObject // Delegate calls to the native BertQuestionAnswerer::CreateBertQuestionAnswerer + (instancetype)mobilebertQuestionAnswererWithModelPath:(NSString*)modelPath vocabPath:(NSString*)vocabPath NS_SWIFT_NAME(mobilebertQuestionAnswerer(modelPath:vocabPath:)); // Delegate calls to the native BertQuestionAnswerer::Answer - (NSArray<TFLQAAnswer*>*)answerWithContext:(NSString*)context question:(NSString*)question NS_SWIFT_NAME(answer(context:question:)); }
- ملف .mm
using BertQuestionAnswererCPP = ::tflite::task::text::BertQuestionAnswerer; @implementation TFLBertQuestionAnswerer { // define an iVar for the native API object std::unique_ptr<QuestionAnswererCPP> _bertQuestionAnswerwer; } // Initialize the native API object + (instancetype)mobilebertQuestionAnswererWithModelPath:(NSString *)modelPath vocabPath:(NSString *)vocabPath { absl::StatusOr<std::unique_ptr<QuestionAnswererCPP>> cQuestionAnswerer = BertQuestionAnswererCPP::CreateBertQuestionAnswerer(MakeString(modelPath), MakeString(vocabPath)); _GTMDevAssert(cQuestionAnswerer.ok(), @"Failed to create BertQuestionAnswerer"); return [[TFLBertQuestionAnswerer alloc] initWithQuestionAnswerer:std::move(cQuestionAnswerer.value())]; } // Calls the native API and converts C++ results into ObjC results - (NSArray<TFLQAAnswer *> *)answerWithContext:(NSString *)context question:(NSString *)question { std::vector<QaAnswerCPP> results = _bertQuestionAnswerwer->Answer(MakeString(context), MakeString(question)); return [self arrayFromVector:results]; } }