Możesz użyć ML Kit do wnioskowania na urządzeniu za pomocą Model TensorFlow Lite.
ML Kit może używać modeli TensorFlow Lite tylko na urządzeniach z iOS 9 nowszą.
Zanim zaczniesz
- Jeśli nie masz jeszcze w aplikacji dodanej Firebase, wykonaj czynności podane w przewodniku dla początkujących.
- Umieść biblioteki ML Kit w pliku Podfile:
pod 'Firebase/MLModelInterpreter', '6.25.0'
Po zainstalowaniu lub zaktualizowaniu podów projektu otwórz Xcode projektu za pomocą jego.xcworkspace
. - W aplikacji zaimportuj Firebase:
Swift
import Firebase
Objective-C
@import Firebase;
- Przekonwertuj model TensorFlow, którego chcesz używać, na format TensorFlow Lite. Zobacz TOCO: TensorFlow Lite Optimizing Converter
Hostowanie lub pakietowanie modelu
Zanim użyjesz modelu TensorFlow Lite do wnioskowania w aplikacji, musi udostępnić model ML Kit. ML Kit może używać TensorFlow Lite modele hostowane zdalnie przez Firebase, w pakiecie z plikiem binarnym aplikacji lub oba.
Hostując model w Firebase, możesz go aktualizować bez konieczności publikowania nowej wersji aplikacji. Możesz używać Remote Config i A/B Testing do dynamicznie udostępniać różne modele różnym grupom użytkowników.
Jeśli zdecydujesz się udostępniać model tylko poprzez hosting w Firebase, a nie pakietu z aplikacją, możesz zmniejszyć początkowy rozmiar pobieranej aplikacji. Pamiętaj jednak, że jeśli do aplikacji nie dołączony jest model, funkcje związane z modelem będą dostępne dopiero po pobraniu przez aplikację z użyciem modelu po raz pierwszy.
Jeśli połączysz model z aplikacją, będziesz mieć pewność, że funkcje ML w aplikacji będą działać. działają też wtedy, gdy model hostowany przez Firebase jest niedostępny.
Hostowanie modeli w Firebase
Aby hostować model TensorFlow Lite w Firebase:
- W sekcji ML Kit w konsoli Firebase kliknij kartę Niestandardowe.
- Kliknij Dodaj model niestandardowy (lub Dodaj kolejny model).
- Podaj nazwę, która będzie używana do identyfikowania Twojego modelu w Firebase
projektu, a następnie prześlij plik modelu TensorFlow Lite (zwykle kończący się
.tflite
lub.lite
).
Po dodaniu do projektu Firebase modelu niestandardowego możesz się odwoływać do w swoich aplikacjach o podanej przez Ciebie nazwie. W każdej chwili możesz przesłać nowego modelu TensorFlow Lite, a aplikacja go pobierze będzie można go używać po ponownym uruchomieniu aplikacji. Możesz określić, warunki wymagane do aktualizacji modelu przez aplikację (patrz poniżej).
Połącz modele z aplikacją
Aby połączyć model TensorFlow Lite z aplikacją, dodaj plik modelu (zwykle
o numerze kończącym się cyframi .tflite
lub .lite
) do Twojego projektu Xcode, wybierając
Skopiuj zasoby pakietu. Plik modelu zostanie dołączony do pliku
pakietu aplikacji i dostępne dla ML Kit.
Wczytaj model
Aby użyć modelu TensorFlow Lite w aplikacji, najpierw skonfiguruj ML Kit za pomocą w lokalizacjach, w których Twój model jest dostępny: zdalnie za pomocą Firebase, pamięci lokalnej lub obu tych metod. Jeśli określisz model lokalny i zdalny, możesz użyj modelu zdalnego, jeśli jest dostępny, i użyj funkcji model przechowywany lokalnie, jeśli model zdalny jest niedostępny.
Konfigurowanie modelu hostowanego w Firebase
Jeśli model był hostowany w Firebase, utwórz obiekt CustomRemoteModel
,
określając nazwę przypisaną do modelu podczas jego publikowania:
Swift
let remoteModel = CustomRemoteModel(
name: "your_remote_model" // The name you assigned in the Firebase console.
)
Objective-C
// Initialize using the name you assigned in the Firebase console.
FIRCustomRemoteModel *remoteModel =
[[FIRCustomRemoteModel alloc] initWithName:@"your_remote_model"];
Następnie rozpocznij zadanie pobierania modelu, określając warunki, które którzy chcą zezwolić na pobieranie. Jeśli nie ma modelu na urządzeniu lub jest on nowszy gdy dostępna będzie wersja modelu, zadanie asynchronicznie pobierze model z Firebase:
Swift
let downloadConditions = ModelDownloadConditions(
allowsCellularAccess: true,
allowsBackgroundDownloading: true
)
let downloadProgress = ModelManager.modelManager().download(
remoteModel,
conditions: downloadConditions
)
Objective-C
FIRModelDownloadConditions *downloadConditions =
[[FIRModelDownloadConditions alloc] initWithAllowsCellularAccess:YES
allowsBackgroundDownloading:YES];
NSProgress *downloadProgress =
[[FIRModelManager modelManager] downloadRemoteModel:remoteModel
conditions:downloadConditions];
Wiele aplikacji rozpoczyna zadanie pobierania w kodzie inicjowania, ale możesz to zrobić. więc w dowolnym momencie przed użyciem modelu.
Konfigurowanie modelu lokalnego
Jeśli Twoja aplikacja jest powiązana z modelem, utwórz obiekt CustomLocalModel
,
podaj nazwę pliku modelu TensorFlow Lite:
Swift
guard let modelPath = Bundle.main.path(
forResource: "your_model",
ofType: "tflite",
inDirectory: "your_model_directory"
) else { /* Handle error. */ }
let localModel = CustomLocalModel(modelPath: modelPath)
Objective-C
NSString *modelPath = [NSBundle.mainBundle pathForResource:@"your_model"
ofType:@"tflite"
inDirectory:@"your_model_directory"];
FIRCustomLocalModel *localModel =
[[FIRCustomLocalModel alloc] initWithModelPath:modelPath];
Tworzenie interpretera na podstawie modelu
Po skonfigurowaniu źródeł modelu utwórz
ModelInterpreter
z jednego z nich.
Jeśli masz tylko model zbiorczy, wystarczy, że prześlesz CustomLocalModel
obiekt w modelInterpreter(localModel:)
:
Swift
let interpreter = ModelInterpreter.modelInterpreter(localModel: localModel)
Objective-C
FIRModelInterpreter *interpreter =
[FIRModelInterpreter modelInterpreterForLocalModel:localModel];
Jeśli masz model hostowany zdalnie, musisz sprawdzić, czy został
pobrane przed uruchomieniem. Stan pobierania modelu możesz sprawdzić
za pomocą metody isModelDownloaded(remoteModel:)
menedżera modeli.
Mimo że przed uruchomieniem tłumaczenia rozmowy trzeba to potwierdzić,
korzystają zarówno z modelu hostowanego zdalnie, jak i z pakietu lokalnego, może to sprawić,
warto przeprowadzić tę kontrolę przy tworzeniu wystąpienia ModelInterpreter
: utwórz
z tłumacza zdalnego z modelu zdalnego, jeśli został on pobrany, oraz z lokalnego
w inny sposób.
Swift
var interpreter: ModelInterpreter
if ModelManager.modelManager().isModelDownloaded(remoteModel) {
interpreter = ModelInterpreter.modelInterpreter(remoteModel: remoteModel)
} else {
interpreter = ModelInterpreter.modelInterpreter(localModel: localModel)
}
Objective-C
FIRModelInterpreter *interpreter;
if ([[FIRModelManager modelManager] isModelDownloaded:remoteModel]) {
interpreter = [FIRModelInterpreter modelInterpreterForRemoteModel:remoteModel];
} else {
interpreter = [FIRModelInterpreter modelInterpreterForLocalModel:localModel];
}
Jeśli masz tylko model hostowany zdalnie, wyłącz powiązany z nim model funkcji – na przykład wyszarzenia lub ukrycia części interfejsu – do potwierdzasz, że model został pobrany.
Stan pobierania modelu możesz sprawdzić, dołączając obserwatorów do wartości domyślnej.
Centrum powiadomień. Pamiętaj, aby w obserwatorium używać słabego odniesienia do self
bo pobieranie może trochę potrwać, a źródłowy obiekt
zwolniony do momentu zakończenia pobierania. Przykład:
Swift
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] // ... }
Objective-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]; }];
Określ dane wejściowe i wyjściowe modelu
Następnie skonfiguruj formaty wejściowe i wyjściowe interpretera modelu.
Model TensorFlow Lite przyjmuje jako dane wejściowe i generuje jako dane wyjściowe co najmniej
wielowymiarowych tablic. Te tablice zawierają: byte
,
Wartości int
, long
lub float
. Musisz
skonfiguruj w ML Kit liczbę i wymiary („kształt”) tablic
przez model.
Jeśli nie znasz kształtu i typu danych wejściowych i wyjściowych modelu, możesz użyć interpretera TensorFlow Lite Python do sprawdzenia modelu. Przykład:
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'>
Po określeniu formatu danych wejściowych i wyjściowych modelu skonfiguruj
tłumacza modelu aplikacji przez utworzenie
ModelInputOutputOptions
.
Na przykład model klasyfikacji obrazów zmiennoprzecinkowych może przyjąć jako dane wejściowe
Tablica Nx224 x 224 x 3 z wartościami Float
, reprezentująca grupę
N Obrazy 3-kanałowe (RGB), 224 x 224, jako wynik wyjściowy
1000 wartości Float
, każda reprezentująca prawdopodobieństwo, do którego należy obraz
jedną z 1000 kategorii prognozowanych przez model.
W przypadku takiego modelu trzeba skonfigurować dane wejściowe i wyjściowe interpretera jak poniżej:
Swift
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)") }
Objective-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; }
Przeprowadź wnioskowanie na danych wejściowych
Aby przeprowadzić wnioskowanie przy użyciu modelu, pobierz dane wejściowe, wykonaj dowolne
przekształcenia danych, które mogą być niezbędne dla Twojego modelu,
Data
obiekt zawierający dane.
Na przykład jeśli model przetwarza obrazy, a model ma wymiary wejściowe
z [BATCH_SIZE, 224, 224, 3]
wartości zmiennoprzecinkowych, konieczne może być skalowanie
kolor obrazu do zakresu zmiennoprzecinkowego, jak w tym przykładzie:
Swift
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)") }
Objective-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; }
Gdy przygotujesz dane wejściowe modelu (i potwierdzisz, że model jest
dostępne), należy przekazać opcje wejścia i wyjścia oraz
run(inputs:options:completion:)
Twojego tłumacza modelu
.
Swift
interpreter.run(inputs: inputs, options: ioOptions) { outputs, error in guard error == nil, let outputs = outputs else { return } // Process outputs // ... }
Objective-C
[interpreter runWithInputs:inputs options:ioOptions completion:^(FIRModelOutputs * _Nullable outputs, NSError * _Nullable error) { if (error != nil || outputs == nil) { return; } // Process outputs // ... }];
Aby uzyskać dane wyjściowe, wywołaj metodę output(index:)
obiektu, który
. Przykład:
Swift
// 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]
Objective-C
// Get first and only output of inference with a batch size of 1 NSError *outputError; NSArray *probabilites = [outputs outputAtIndex:0 error:&outputError][0];
Sposób wykorzystania danych wyjściowych zależy od używanego modelu.
Jeśli na przykład przeprowadzasz klasyfikację, kolejnym krokiem może być zmapować indeksy wyników na etykiety, które reprezentują. Załóżmy, że masz plik tekstowy z ciągami etykiet dla każdej kategorii modelu; możesz umieścić na mapie ciągi etykiet do prawdopodobieństwa wynikowego, wykonując w ten sposób :
Swift
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)") } }
Objective-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); }
Dodatek: zabezpieczenia modelu
Niezależnie od tego, jak udostępnisz swoje modele TensorFlow Lite ML Kit przechowuje je w standardowym zserializowanym formacie protokołu w formacie pamięci lokalnej.
Teoretycznie oznacza to, że każdy może skopiować Twój model. Pamiętaj jednak: W praktyce większość modeli jest specyficzna dla danej aplikacji i pod kątem podobnych optymalizacji, jakie stwarzają konkurencji, demontaż ponownego wykorzystania kodu. Musisz jednak wiedzieć o tym ryzyku, zanim zaczniesz niestandardowy model w swojej aplikacji.