توفر مكتبة المهام TensorFlow Lite واجهات برمجة التطبيقات الأصلية/Android/iOS المعدة مسبقًا بالإضافة إلى البنية التحتية نفسها التي تلخص TensorFlow. يمكنك توسيع البنية الأساسية لـ Task API لإنشاء واجهات برمجة تطبيقات مخصصة إذا كان النموذج الخاص بك غير مدعوم من مكتبات المهام الموجودة.
ملخص
تحتوي البنية الأساسية لـ Task API على بنية مكونة من طبقتين: طبقة C++ السفلية التي تغلف وقت تشغيل TFLite الأصلي وطبقة Java/ObjC العليا التي تتواصل مع طبقة C++ من خلال JNI أو الغلاف الأصلي.
يؤدي تنفيذ كل منطق TensorFlow في C++ فقط إلى تقليل التكلفة وزيادة أداء الاستدلال إلى الحد الأقصى وتبسيط سير العمل الإجمالي عبر الأنظمة الأساسية.
لإنشاء فئة مهمة، قم بتوسيع BaseTaskApi لتوفير منطق التحويل بين واجهة نموذج TFLite وواجهة Task API، ثم استخدم أدوات Java/ObjC المساعدة لإنشاء واجهات برمجة التطبيقات المقابلة. مع إخفاء جميع تفاصيل TensorFlow، يمكنك نشر نموذج TFLite في تطبيقاتك دون أي معرفة بالتعلم الآلي.
يوفر TensorFlow Lite بعض واجهات برمجة التطبيقات المعدة مسبقًا لمهام Vision وNLP الأكثر شيوعًا. يمكنك إنشاء واجهات برمجة التطبيقات (API) الخاصة بك لمهام أخرى باستخدام البنية الأساسية لـ Task API.
أنشئ واجهة برمجة التطبيقات (API) الخاصة بك باستخدام Task API infra
واجهة برمجة تطبيقات C++
يتم تنفيذ جميع تفاصيل TFLite في واجهة برمجة التطبيقات الأصلية. قم بإنشاء كائن 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
بناء واجهة برمجة التطبيقات
لإنشاء كائن API، يجب عليك توفير المعلومات التالية عن طريق توسيع BaseTaskApi
تحديد الإدخال/الإخراج لواجهة برمجة التطبيقات (API) - يجب أن تعرض واجهة برمجة التطبيقات (API) لديك مدخلات/مخرجات مماثلة عبر منصات مختلفة. على سبيل المثال، يأخذ
BertQuestionAnswerer
سلسلتين(std::string& context, std::string& question)
كمدخلات ويخرج متجهًا للإجابة والاحتمالات المحتملة مثلstd::vector<QaAnswer>
. ويتم ذلك عن طريق تحديد الأنواع المقابلة في معلمة قالبBaseTaskApi
. مع تحديد معلمات القالب، ستحتوي الدالةBaseTaskApi::Infer
على أنواع الإدخال/الإخراج الصحيحة. يمكن لعملاء واجهة برمجة التطبيقات (API) استدعاء هذه الوظيفة مباشرةً، لكن من الممارسات الجيدة تغليفها داخل دالة خاصة بنموذج معين، في هذه الحالة،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(); } }
توفير منطق التحويل بين الإدخال/الإخراج لواجهة برمجة التطبيقات (API) وموتر الإدخال/الإخراج للنموذج - مع تحديد أنواع الإدخال والإخراج، تحتاج الفئات الفرعية أيضًا إلى تنفيذ الوظائف المكتوبة
BaseTaskApi::Preprocess
وBaseTaskApi::Postprocess
. توفر الوظيفتان المدخلات والمخرجات من TFLiteFlatBuffer
. الفئة الفرعية مسؤولة عن تعيين القيم من موترات الإدخال/الإخراج لواجهة برمجة التطبيقات (API) إلى موترات الإدخال/الإخراج. راجع مثال التنفيذ الكامل في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; } }
إنشاء وظائف المصنع لواجهة برمجة التطبيقات (API) - يلزم وجود ملف نموذجي و
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 عن طريق تحديد واجهة Java/Kotlin وتفويض المنطق إلى طبقة C++ من خلال JNI. يتطلب Android API إنشاء واجهة برمجة التطبيقات الأصلية أولاً.
استخدام العينة
فيما يلي مثال لاستخدام 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
بناء واجهة برمجة التطبيقات
كما هو الحال مع واجهات برمجة التطبيقات الأصلية، لإنشاء كائن API، يحتاج العميل إلى توفير المعلومات التالية عن طريق توسيع BaseTaskApi
، الذي يوفر معالجات JNI لجميع واجهات برمجة تطبيقات مهام Java.
تحديد واجهة الإدخال/الإخراج لواجهة برمجة التطبيقات (API) - وهذا عادةً ما يعكس الواجهات الأصلية. على سبيل المثال،
BertQuestionAnswerer
يأخذ(String context, String question)
كمدخلات ومخرجات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 ); }
إنشاء وظائف المصنع لواجهة برمجة التطبيقات (API) - يعكس هذا أيضًا وظائف المصنع الأصلية، باستثناء أن وظائف مصنع Android تحتاج أيضًا إلى اتخاذ
Context
للوصول إلى الملفات. يستدعي التنفيذ إحدى الأدوات المساعدة فيTaskJniUtils
لإنشاء كائن C++ API المقابل وتمرير المؤشر الخاص به إلى مُنشئ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. ستقوم وظائف المصنع بإنشاء كائن API أصلي وإرجاع المؤشر الخاص به كنوع طويل إلى Java. في الاستدعاءات اللاحقة لـ Java API، يتم تمرير مؤشر النوع الطويل مرة أخرى إلى JNI وإعادته إلى كائن API الأصلي. يتم بعد ذلك تحويل نتائج API الأصلية مرة أخرى إلى نتائج 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 عن طريق التفاف كائن API الأصلي في كائن ObjC API. يمكن استخدام كائن API الذي تم إنشاؤه في ObjC أو Swift. يتطلب iOS API إنشاء واجهة برمجة التطبيقات الأصلية أولاً.
استخدام العينة
فيما يلي مثال لاستخدام 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 API عبارة عن غلاف ObjC بسيط أعلى واجهة برمجة التطبيقات الأصلية. أنشئ واجهة برمجة التطبيقات (API) باتباع الخطوات التالية:
تحديد غلاف ObjC - تحديد فئة ObjC وتفويض التطبيقات إلى كائن API الأصلي المقابل. لاحظ أن التبعيات الأصلية يمكن أن تظهر فقط في ملف .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]; } }
إنّ محتوى هذه الصفحة مرخّص بموجب ترخيص Creative Commons Attribution 4.0 ما لم يُنصّ على خلاف ذلك، ونماذج الرموز مرخّصة بموجب ترخيص Apache 2.0. للاطّلاع على التفاصيل، يُرجى مراجعة سياسات موقع Google Developers. إنّ Java هي علامة تجارية مسجَّلة لشركة Oracle و/أو شركائها التابعين.
تاريخ التعديل الأخير: 2023-12-02 (حسب التوقيت العالمي المتفَّق عليه)