از یک مدل TensorFlow Lite برای استنباط با ML Kit در iOS استفاده کنید

می‌توانید از ML Kit برای استنتاج روی دستگاه با مدل TensorFlow Lite استفاده کنید.

ML Kit می‌تواند از مدل‌های TensorFlow Lite فقط در دستگاه‌های دارای iOS 9 و جدیدتر استفاده کند.

قبل از اینکه شروع کنی

  1. اگر قبلاً Firebase را به برنامه خود اضافه نکرده اید، این کار را با دنبال کردن مراحل راهنمای شروع کار انجام دهید.
  2. کتابخانه‌های ML Kit را در فایل پادفایل خود قرار دهید:
    pod 'Firebase/MLModelInterpreter', '6.25.0'
    
    پس از نصب یا به‌روزرسانی Pods پروژه، حتماً پروژه Xcode خود را با استفاده از .xcworkspace آن باز کنید.
  3. در برنامه خود، Firebase را وارد کنید:

    سریع

    import Firebase

    هدف-C

    @import Firebase;
  4. مدل TensorFlow را که می خواهید استفاده کنید به قالب TensorFlow Lite تبدیل کنید. به TOCO: TensorFlow Lite Optimizing Converter مراجعه کنید.

مدل خود را میزبان یا بسته بندی کنید

قبل از اینکه بتوانید از یک مدل TensorFlow Lite برای استنباط در برنامه خود استفاده کنید، باید مدل را برای ML Kit در دسترس قرار دهید. ML Kit می‌تواند از مدل‌های TensorFlow Lite که از راه دور با استفاده از Firebase میزبانی می‌شوند، همراه با برنامه باینری یا هر دو استفاده کند.

با میزبانی یک مدل در Firebase، می‌توانید مدل را بدون انتشار نسخه جدید برنامه به‌روزرسانی کنید و می‌توانید از Remote Config و A/B Testing برای ارائه پویا مدل‌های مختلف به مجموعه‌های مختلف کاربران استفاده کنید.

اگر ترجیح می‌دهید مدل را فقط با میزبانی آن در Firebase ارائه دهید، و آن را با برنامه خود همراه نکنید، می‌توانید حجم دانلود اولیه برنامه خود را کاهش دهید. البته به خاطر داشته باشید که اگر مدل با برنامه شما همراه نباشد، تا زمانی که برنامه شما برای اولین بار مدل را دانلود نکند، هیچ عملکرد مرتبط با مدل در دسترس نخواهد بود.

با بسته‌بندی مدل خود با برنامه‌تان، می‌توانید مطمئن شوید که ویژگی‌های ML برنامه‌تان همچنان در زمانی که مدل میزبانی شده توسط Firebase در دسترس نیست، کار می‌کنند.

مدل های میزبان در Firebase

برای میزبانی مدل TensorFlow Lite خود در Firebase:

  1. در بخش ML Kit کنسول Firebase ، روی برگه Custom کلیک کنید.
  2. روی افزودن مدل سفارشی (یا افزودن مدل دیگر ) کلیک کنید.
  3. نامی را مشخص کنید که برای شناسایی مدل شما در پروژه Firebase شما استفاده شود، سپس فایل مدل TensorFlow Lite را آپلود کنید (معمولاً به .tflite یا .lite ختم می شود).

پس از اینکه یک مدل سفارشی را به پروژه Firebase خود اضافه کردید، می‌توانید با استفاده از نامی که مشخص کرده‌اید به مدل در برنامه‌های خود ارجاع دهید. هر زمان که بخواهید، می‌توانید یک مدل جدید TensorFlow Lite آپلود کنید و برنامه شما مدل جدید را دانلود می‌کند و پس از راه‌اندازی مجدد برنامه، شروع به استفاده از آن می‌کند. می‌توانید شرایط دستگاه مورد نیاز برای برنامه‌تان برای به‌روزرسانی مدل را تعریف کنید (به زیر مراجعه کنید).

مدل ها را با یک برنامه بسته بندی کنید

برای بسته‌بندی مدل TensorFlow Lite با برنامه‌تان، فایل مدل (معمولاً به .tflite یا .lite ختم می‌شود) را به پروژه Xcode خود اضافه کنید، مراقب باشید که منابع بسته Copy را انتخاب کنید. فایل مدل در بسته برنامه گنجانده شده و در ML Kit در دسترس خواهد بود.

مدل را بارگذاری کنید

برای استفاده از مدل TensorFlow Lite در برنامه‌تان، ابتدا ML Kit را با مکان‌هایی که مدل شما در آن در دسترس است پیکربندی کنید: از راه دور با استفاده از Firebase، در حافظه محلی یا هر دو. اگر هم مدل محلی و هم مدل راه دور را مشخص می‌کنید، می‌توانید از مدل راه دور در صورت موجود بودن استفاده کنید و اگر مدل راه دور در دسترس نباشد، به مدل ذخیره‌شده محلی برگردید.

یک مدل میزبانی شده توسط Firebase را پیکربندی کنید

اگر مدل خود را با Firebase میزبانی کرده اید، یک شی CustomRemoteModel ایجاد کنید و نامی را که به مدل اختصاص داده اید در هنگام انتشار آن مشخص کنید:

سریع

let remoteModel = CustomRemoteModel(
  name: "your_remote_model"  // The name you assigned in the Firebase console.
)

هدف-C

// Initialize using the name you assigned in the Firebase console.
FIRCustomRemoteModel *remoteModel =
    [[FIRCustomRemoteModel alloc] initWithName:@"your_remote_model"];

سپس، با مشخص کردن شرایطی که می‌خواهید اجازه دانلود را بدهید، کار دانلود مدل را شروع کنید. اگر مدل در دستگاه نباشد، یا اگر نسخه جدیدتری از مدل موجود باشد، این کار به صورت ناهمزمان مدل را از Firebase دانلود می‌کند:

سریع

let downloadConditions = ModelDownloadConditions(
  allowsCellularAccess: true,
  allowsBackgroundDownloading: true
)

let downloadProgress = ModelManager.modelManager().download(
  remoteModel,
  conditions: downloadConditions
)

هدف-C

FIRModelDownloadConditions *downloadConditions =
    [[FIRModelDownloadConditions alloc] initWithAllowsCellularAccess:YES
                                         allowsBackgroundDownloading:YES];

NSProgress *downloadProgress =
    [[FIRModelManager modelManager] downloadRemoteModel:remoteModel
                                             conditions:downloadConditions];

بسیاری از برنامه‌ها وظیفه دانلود را در کد اولیه خود شروع می‌کنند، اما شما می‌توانید این کار را در هر زمانی قبل از نیاز به استفاده از مدل انجام دهید.

یک مدل محلی را پیکربندی کنید

اگر مدل را با برنامه خود همراه کرده اید، یک شی CustomLocalModel ایجاد کنید و نام فایل مدل TensorFlow Lite را مشخص کنید:

سریع

guard let modelPath = Bundle.main.path(
  forResource: "your_model",
  ofType: "tflite",
  inDirectory: "your_model_directory"
) else { /* Handle error. */ }
let localModel = CustomLocalModel(modelPath: modelPath)

هدف-C

NSString *modelPath = [NSBundle.mainBundle pathForResource:@"your_model"
                                                    ofType:@"tflite"
                                               inDirectory:@"your_model_directory"];
FIRCustomLocalModel *localModel =
    [[FIRCustomLocalModel alloc] initWithModelPath:modelPath];

یک مترجم از مدل خود ایجاد کنید

پس از پیکربندی منابع مدل خود، یک شی ModelInterpreter از یکی از آنها ایجاد کنید.

اگر فقط یک مدل باندل محلی دارید، فقط شی CustomLocalModel را به modelInterpreter(localModel:) ارسال کنید:

سریع

let interpreter = ModelInterpreter.modelInterpreter(localModel: localModel)

هدف-C

FIRModelInterpreter *interpreter =
    [FIRModelInterpreter modelInterpreterForLocalModel:localModel];

اگر یک مدل با میزبانی از راه دور دارید، قبل از اجرای آن باید بررسی کنید که دانلود شده است. با استفاده از روش isModelDownloaded(remoteModel:) مدیر مدل می توانید وضعیت وظیفه دانلود مدل را بررسی کنید.

اگر چه شما فقط باید قبل از اجرای مفسر این موضوع را تأیید کنید، اگر هم یک مدل میزبان از راه دور و هم یک مدل با بسته‌بندی محلی دارید، ممکن است انجام این بررسی هنگام نمونه‌برداری از ModelInterpreter منطقی باشد: اگر یک مفسر از مدل راه دور ایجاد کنید. دانلود شده است، و در غیر این صورت از مدل محلی.

سریع

var interpreter: ModelInterpreter
if ModelManager.modelManager().isModelDownloaded(remoteModel) {
  interpreter = ModelInterpreter.modelInterpreter(remoteModel: remoteModel)
} else {
  interpreter = ModelInterpreter.modelInterpreter(localModel: localModel)
}

هدف-C

FIRModelInterpreter *interpreter;
if ([[FIRModelManager modelManager] isModelDownloaded:remoteModel]) {
  interpreter = [FIRModelInterpreter modelInterpreterForRemoteModel:remoteModel];
} else {
  interpreter = [FIRModelInterpreter modelInterpreterForLocalModel:localModel];
}

اگر فقط یک مدل با میزبانی از راه دور دارید، باید عملکردهای مربوط به مدل را غیرفعال کنید - به عنوان مثال، خاکستری کردن یا پنهان کردن بخشی از رابط کاربری خود - تا زمانی که تأیید کنید مدل دانلود شده است.

می‌توانید با پیوست کردن ناظران به مرکز اطلاع رسانی پیش‌فرض، وضعیت دانلود مدل را دریافت کنید. مطمئن شوید که از یک مرجع ضعیف به self در بلوک ناظر استفاده کنید، زیرا دانلودها ممکن است مدتی طول بکشد، و شی مبدا می‌تواند تا پایان دانلود آزاد شود. مثلا:

سریع

NotificationCenter.default.addObserver(
    forName: .firebaseMLModelDownloadDidSucceed,
    object: nil,
    queue: nil
) { [weak self] notification in
    guard let strongSelf = self,
        let userInfo = notification.userInfo,
        let model = userInfo[ModelDownloadUserInfoKey.remoteModel.rawValue]
            as? RemoteModel,
        model.name == "your_remote_model"
        else { return }
    // The model was downloaded and is available on the device
}

NotificationCenter.default.addObserver(
    forName: .firebaseMLModelDownloadDidFail,
    object: nil,
    queue: nil
) { [weak self] notification in
    guard let strongSelf = self,
        let userInfo = notification.userInfo,
        let model = userInfo[ModelDownloadUserInfoKey.remoteModel.rawValue]
            as? RemoteModel
        else { return }
    let error = userInfo[ModelDownloadUserInfoKey.error.rawValue]
    // ...
}

هدف-C

__weak typeof(self) weakSelf = self;

[NSNotificationCenter.defaultCenter
    addObserverForName:FIRModelDownloadDidSucceedNotification
                object:nil
                 queue:nil
            usingBlock:^(NSNotification *_Nonnull note) {
              if (weakSelf == nil | note.userInfo == nil) {
                return;
              }
              __strong typeof(self) strongSelf = weakSelf;

              FIRRemoteModel *model = note.userInfo[FIRModelDownloadUserInfoKeyRemoteModel];
              if ([model.name isEqualToString:@"your_remote_model"]) {
                // The model was downloaded and is available on the device
              }
            }];

[NSNotificationCenter.defaultCenter
    addObserverForName:FIRModelDownloadDidFailNotification
                object:nil
                 queue:nil
            usingBlock:^(NSNotification *_Nonnull note) {
              if (weakSelf == nil | note.userInfo == nil) {
                return;
              }
              __strong typeof(self) strongSelf = weakSelf;

              NSError *error = note.userInfo[FIRModelDownloadUserInfoKeyError];
            }];

ورودی و خروجی مدل را مشخص کنید

سپس، فرمت های ورودی و خروجی مفسر مدل را پیکربندی کنید.

یک مدل TensorFlow Lite به عنوان ورودی می گیرد و یک یا چند آرایه چند بعدی را به عنوان خروجی تولید می کند. این آرایه ها حاوی مقادیر byte ، int ، long یا float هستند. شما باید ML Kit را با تعداد و ابعاد ("شکل") آرایه هایی که مدل شما استفاده می کند پیکربندی کنید.

اگر شکل و نوع داده ورودی و خروجی مدل خود را نمی دانید، می توانید از مفسر TensorFlow Lite Python برای بررسی مدل خود استفاده کنید. مثلا:

import tensorflow as tf

interpreter = tf.lite.Interpreter(model_path="my_model.tflite")
interpreter.allocate_tensors()

# Print input shape and type
print(interpreter.get_input_details()[0]['shape'])  # Example: [1 224 224 3]
print(interpreter.get_input_details()[0]['dtype'])  # Example: <class 'numpy.float32'>

# Print output shape and type
print(interpreter.get_output_details()[0]['shape'])  # Example: [1 1000]
print(interpreter.get_output_details()[0]['dtype'])  # Example: <class 'numpy.float32'>

پس از تعیین فرمت ورودی و خروجی مدل خود، با ایجاد یک شی ModelInputOutputOptions ، مفسر مدل برنامه خود را پیکربندی کنید.

به عنوان مثال، یک مدل طبقه‌بندی تصویر ممیز شناور ممکن است یک آرایه N x224x224x3 از مقادیر Float را به عنوان ورودی دریافت کند، که مجموعه‌ای از تصاویر سه کاناله N 224x224 (RGB) را نشان می‌دهد و به عنوان خروجی فهرستی از 1000 مقدار Float را تولید کند که هر کدام نشان‌دهنده به احتمال زیاد تصویر عضو یکی از 1000 دسته ای است که مدل پیش بینی می کند.

برای چنین مدلی، ورودی و خروجی مفسر مدل را مطابق شکل زیر پیکربندی کنید:

سریع

let ioOptions = ModelInputOutputOptions()
do {
    try ioOptions.setInputFormat(index: 0, type: .float32, dimensions: [1, 224, 224, 3])
    try ioOptions.setOutputFormat(index: 0, type: .float32, dimensions: [1, 1000])
} catch let error as NSError {
    print("Failed to set input or output format with error: \(error.localizedDescription)")
}

هدف-C

FIRModelInputOutputOptions *ioOptions = [[FIRModelInputOutputOptions alloc] init];
NSError *error;
[ioOptions setInputFormatForIndex:0
                             type:FIRModelElementTypeFloat32
                       dimensions:@[@1, @224, @224, @3]
                            error:&error];
if (error != nil) { return; }
[ioOptions setOutputFormatForIndex:0
                              type:FIRModelElementTypeFloat32
                        dimensions:@[@1, @1000]
                             error:&error];
if (error != nil) { return; }

انجام استنتاج بر روی داده های ورودی

در نهایت، برای انجام استنتاج با استفاده از مدل، داده‌های ورودی خود را دریافت کنید، هر گونه تغییری را روی داده‌هایی که ممکن است برای مدل شما ضروری باشد، انجام دهید و یک آبجکت Data که حاوی داده‌ها است بسازید.

به عنوان مثال، اگر مدل شما تصاویر را پردازش می کند و مدل شما دارای ابعاد ورودی [BATCH_SIZE, 224, 224, 3] مقادیر ممیز شناور است، ممکن است لازم باشد مقادیر رنگ تصویر را به محدوده ممیز شناور تغییر دهید مانند مثال زیر. :

سریع

let image: CGImage = // Your input image
guard let context = CGContext(
  data: nil,
  width: image.width, height: image.height,
  bitsPerComponent: 8, bytesPerRow: image.width * 4,
  space: CGColorSpaceCreateDeviceRGB(),
  bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue
) else {
  return false
}

context.draw(image, in: CGRect(x: 0, y: 0, width: image.width, height: image.height))
guard let imageData = context.data else { return false }

let inputs = ModelInputs()
var inputData = Data()
do {
  for row in 0 ..< 224 {
    for col in 0 ..< 224 {
      let offset = 4 * (col * context.width + row)
      // (Ignore offset 0, the unused alpha channel)
      let red = imageData.load(fromByteOffset: offset+1, as: UInt8.self)
      let green = imageData.load(fromByteOffset: offset+2, as: UInt8.self)
      let blue = imageData.load(fromByteOffset: offset+3, as: UInt8.self)

      // Normalize channel values to [0.0, 1.0]. This requirement varies
      // by model. For example, some models might require values to be
      // normalized to the range [-1.0, 1.0] instead, and others might
      // require fixed-point values or the original bytes.
      var normalizedRed = Float32(red) / 255.0
      var normalizedGreen = Float32(green) / 255.0
      var normalizedBlue = Float32(blue) / 255.0

      // Append normalized values to Data object in RGB order.
      let elementSize = MemoryLayout.size(ofValue: normalizedRed)
      var bytes = [UInt8](repeating: 0, count: elementSize)
      memcpy(&bytes, &normalizedRed, elementSize)
      inputData.append(&bytes, count: elementSize)
      memcpy(&bytes, &normalizedGreen, elementSize)
      inputData.append(&bytes, count: elementSize)
      memcpy(&ammp;bytes, &normalizedBlue, elementSize)
      inputData.append(&bytes, count: elementSize)
    }
  }
  try inputs.addInput(inputData)
} catch let error {
  print("Failed to add input: \(error)")
}

هدف-C

CGImageRef image = // Your input image
long imageWidth = CGImageGetWidth(image);
long imageHeight = CGImageGetHeight(image);
CGContextRef context = CGBitmapContextCreate(nil,
                                             imageWidth, imageHeight,
                                             8,
                                             imageWidth * 4,
                                             CGColorSpaceCreateDeviceRGB(),
                                             kCGImageAlphaNoneSkipFirst);
CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), image);
UInt8 *imageData = CGBitmapContextGetData(context);

FIRModelInputs *inputs = [[FIRModelInputs alloc] init];
NSMutableData *inputData = [[NSMutableData alloc] initWithCapacity:0];

for (int row = 0; row < 224; row++) {
  for (int col = 0; col < 224; col++) {
    long offset = 4 * (col * imageWidth + row);
    // Normalize channel values to [0.0, 1.0]. This requirement varies
    // by model. For example, some models might require values to be
    // normalized to the range [-1.0, 1.0] instead, and others might
    // require fixed-point values or the original bytes.
    // (Ignore offset 0, the unused alpha channel)
    Float32 red = imageData[offset+1] / 255.0f;
    Float32 green = imageData[offset+2] / 255.0f;
    Float32 blue = imageData[offset+3] / 255.0f;

    [inputData appendBytes:&red length:sizeof(red)];
    [inputData appendBytes:&green length:sizeof(green)];
    [inputData appendBytes:&blue length:sizeof(blue)];
  }
}

[inputs addInput:inputData error:&error];
if (error != nil) { return nil; }

پس از اینکه ورودی مدل خود را آماده کردید (و پس از تأیید موجود بودن مدل)، گزینه های ورودی و ورودی/خروجی را به روش run(inputs:options:completion:) مترجم مدل خود منتقل کنید.

سریع

interpreter.run(inputs: inputs, options: ioOptions) { outputs, error in
    guard error == nil, let outputs = outputs else { return }
    // Process outputs
    // ...
}

هدف-C

[interpreter runWithInputs:inputs
                   options:ioOptions
                completion:^(FIRModelOutputs * _Nullable outputs,
                             NSError * _Nullable error) {
  if (error != nil || outputs == nil) {
    return;
  }
  // Process outputs
  // ...
}];

شما می توانید خروجی را با فراخوانی متد output(index:) شی ای که برگردانده شده است دریافت کنید. مثلا:

سریع

// Get first and only output of inference with a batch size of 1
let output = try? outputs.output(index: 0) as? [[NSNumber]]
let probabilities = output??[0]

هدف-C

// Get first and only output of inference with a batch size of 1
NSError *outputError;
NSArray *probabilites = [outputs outputAtIndex:0 error:&outputError][0];

نحوه استفاده از خروجی به مدلی که استفاده می کنید بستگی دارد.

برای مثال، اگر در حال انجام طبقه‌بندی هستید، در مرحله بعدی، ممکن است نمایه‌های نتیجه را به برچسب‌هایی که نشان می‌دهند نگاشت کنید. فرض کنید یک فایل متنی با رشته های برچسب برای هر یک از دسته بندی های مدل خود دارید. می توانید رشته های برچسب را به احتمالات خروجی با انجام کاری شبیه به زیر نگاشت کنید:

سریع

guard let labelPath = Bundle.main.path(forResource: "retrained_labels", ofType: "txt") else { return }
let fileContents = try? String(contentsOfFile: labelPath)
guard let labels = fileContents?.components(separatedBy: "\n") else { return }

for i in 0 ..< labels.count {
  if let probability = probabilities?[i] {
    print("\(labels[i]): \(probability)")
  }
}

هدف-C

NSError *labelReadError = nil;
NSString *labelPath = [NSBundle.mainBundle pathForResource:@"retrained_labels"
                                                    ofType:@"txt"];
NSString *fileContents = [NSString stringWithContentsOfFile:labelPath
                                                   encoding:NSUTF8StringEncoding
                                                      error:&labelReadError];
if (labelReadError != nil || fileContents == NULL) { return; }
NSArray<NSString *> *labels = [fileContents componentsSeparatedByString:@"\n"];
for (int i = 0; i < labels.count; i++) {
    NSString *label = labels[i];
    NSNumber *probability = probabilites[i];
    NSLog(@"%@: %f", label, probability.floatValue);
}

ضمیمه: امنیت مدل

صرف نظر از اینکه چگونه مدل‌های TensorFlow Lite خود را برای ML Kit در دسترس قرار می‌دهید، ML Kit آنها را در قالب استاندارد پروتوباف سریالی در حافظه محلی ذخیره می‌کند.

در تئوری، این بدان معنی است که هر کسی می تواند مدل شما را کپی کند. با این حال، در عمل، بیشتر مدل‌ها به قدری برنامه‌های کاربردی خاص هستند و به‌وسیله بهینه‌سازی‌ها مبهم هستند که خطر آن مشابه خطر جداسازی و استفاده مجدد کد شما توسط رقبا است. با این وجود، قبل از استفاده از یک مدل سفارشی در برنامه خود، باید از این خطر آگاه باشید.