Section 16/171 menit
16. Real Use Cases
16. Real Use Cases
Use Case 1: Real-time Style Transfer untuk Camera
Skenario: App filter kamera yang menerapkan artistic style ke setiap frame video secara real-time dengan target 30fps (33ms per frame).
swift
// MARK: - Real-time Style Transfer Pipeline
final class StyleTransferPipeline {
private let model: MLModel
private let outputWidth = 512
private let outputHeight = 512
// Pre-allocated output buffer untuk menghindari alokasi per frame
private let outputBuffer: CVPixelBuffer
init() throws {
let config = MLModelConfiguration()
config.computeUnits = .cpuAndNeuralEngine // ANE lebih hemat baterai untuk style transfer
model = try StyleTransferModel(configuration: config).model
// Pre-allocate output buffer
var buffer: CVPixelBuffer?
CVPixelBufferCreate(
kCFAllocatorDefault,
outputWidth,
outputHeight,
kCVPixelFormatType_32BGRA,
nil,
&buffer
)
outputBuffer = buffer!
}
// Input: CMSampleBuffer dari AVCaptureSession
// Output: UIImage yang sudah di-style transfer
func process(sampleBuffer: CMSampleBuffer) throws -> CVPixelBuffer? {
guard let inputPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
return nil
}
// Custom feature provider: zero-copy dari camera buffer
let inputProvider = StyleTransferInput(pixelBuffer: inputPixelBuffer)
let outputProvider = StyleTransferOutput(outputBuffer: outputBuffer)
// Predict dengan pre-allocated output
let options = MLPredictionOptions()
let result = try model.prediction(from: inputProvider, options: options)
return result.featureValue(for: "stylizedImage")?.imageBufferValue
}
}
// Zero-copy input provider
final class StyleTransferInput: MLFeatureProvider {
private let pixelBuffer: CVPixelBuffer
var featureNames: Set<String> { ["image"] }
init(pixelBuffer: CVPixelBuffer) {
self.pixelBuffer = pixelBuffer
}
func featureValue(for featureName: String) -> MLFeatureValue? {
guard featureName == "image" else { return nil }
return try? MLFeatureValue(pixelBuffer: pixelBuffer, pixelFormatType: kCVPixelFormatType_32BGRA)
}
}
final class StyleTransferOutput: MLFeatureProvider {
private let buffer: CVPixelBuffer
var featureNames: Set<String> { ["stylizedImage"] }
init(outputBuffer: CVPixelBuffer) { self.buffer = outputBuffer }
func featureValue(for featureName: String) -> MLFeatureValue? {
guard featureName == "stylizedImage" else { return nil }
return try? MLFeatureValue(pixelBuffer: buffer, pixelFormatType: kCVPixelFormatType_32BGRA)
}
}
// MARK: - Camera Integration
final class CameraStyleViewController: UIViewController {
private var captureSession: AVCaptureSession!
private var pipeline: StyleTransferPipeline!
private var displayLayer: AVSampleBufferDisplayLayer!
private var processingQueue = DispatchQueue(label: "style.transfer", qos: .userInteractive)
override func viewDidLoad() {
super.viewDidLoad()
try? setupPipeline()
setupCamera()
setupDisplayLayer()
}
private func setupPipeline() throws {
pipeline = try StyleTransferPipeline()
}
private func setupCamera() {
captureSession = AVCaptureSession()
captureSession.sessionPreset = .hd1280x720
guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back),
let input = try? AVCaptureDeviceInput(device: device) else { return }
captureSession.addInput(input)
let output = AVCaptureVideoDataOutput()
output.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]
output.setSampleBufferDelegate(self, queue: processingQueue)
captureSession.addOutput(output)
captureSession.startRunning()
}
private func setupDisplayLayer() {
displayLayer = AVSampleBufferDisplayLayer()
displayLayer.frame = view.bounds
view.layer.addSublayer(displayLayer)
}
}
extension CameraStyleViewController: AVCaptureVideoDataOutputSampleBufferDelegate {
func captureOutput(
_ output: AVCaptureOutput,
didOutput sampleBuffer: CMSampleBuffer,
from connection: AVCaptureConnection
) {
guard let styledBuffer = try? pipeline.process(sampleBuffer: sampleBuffer) else { return }
// Convert CVPixelBuffer back to CMSampleBuffer for display
// (simplified — production code perlu timing info)
displayLayer.enqueue(sampleBuffer)
}
}
Use Case 2: On-Device Recommendation Engine dengan Updatable Model
Skenario: E-commerce app yang belajar preferensi user dari interaksi dan mempersonalisasi rekomendasi produk on-device.
swift
// MARK: - Personalized Recommendation Engine
actor ProductRecommendationEngine {
private var model: MLModel
private var pendingInteractions: [UserInteraction] = []
private let minInteractionsForUpdate = 20
private let modelURL: URL
struct UserInteraction {
let productEmbedding: [Float] // 128-dim embedding dari server
let engaged: Bool // user klik/beli = true, skip = false
}
struct Recommendation {
let productID: String
let score: Float
}
init() throws {
let localModelURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
.first!.appending(component: "PersonalRecommender.mlmodelc")
// Gunakan model personal jika ada, fallback ke base model
if FileManager.default.fileExists(atPath: localModelURL.path) {
model = try MLModel(contentsOf: localModelURL)
modelURL = localModelURL
} else {
let baseURL = Bundle.main.url(forResource: "BaseRecommender", withExtension: "mlmodelc")!
model = try MLModel(contentsOf: baseURL)
modelURL = baseURL
}
}
func recordInteraction(_ interaction: UserInteraction) async throws {
pendingInteractions.append(interaction)
// Update model setelah cukup data terkumpul
if pendingInteractions.count >= minInteractionsForUpdate {
try await updateModelWithPendingInteractions()
}
}
func rankProducts(_ productEmbeddings: [(id: String, embedding: [Float])]) throws -> [Recommendation] {
return try productEmbeddings.compactMap { product in
let input = try makeRecommenderInput(embedding: product.embedding)
let output = try model.prediction(from: input)
let score = output.featureValue(for: "engagementScore")?.multiArrayValue?[0].floatValue ?? 0
return Recommendation(productID: product.id, score: score)
}
.sorted { $0.score > $1.score }
}
private func updateModelWithPendingInteractions() async throws {
let interactions = pendingInteractions
pendingInteractions.removeAll()
let providers: [MLFeatureProvider] = try interactions.map { interaction in
try makeTrainingInput(
embedding: interaction.productEmbedding,
label: interaction.engaged ? 1 : 0
)
}
let trainingData = MLArrayBatchProvider(array: providers)
// Update task
let updatedModelURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
.first!.appending(component: "PersonalRecommender_updated.mlmodelc")
let progressHandlers = MLUpdateTask.progressHandlers(
forStoreAt: modelURL,
trainingData: trainingData,
configuration: MLModelConfiguration()
) { _ in // progress
} completionHandler: { [weak self] context in
guard let self else { return }
Task {
if context.task.error == nil {
try? context.model.write(to: updatedModelURL)
if let newModel = try? MLModel(contentsOf: updatedModelURL) {
await self.updateModel(newModel)
}
}
}
}
let task = try MLUpdateTask(
forModelAt: modelURL,
trainingData: trainingData,
configuration: MLModelConfiguration(),
progressHandlers: progressHandlers
)
task.resume()
}
private func updateModel(_ newModel: MLModel) {
model = newModel
}
private func makeRecommenderInput(embedding: [Float]) throws -> MLFeatureProvider {
let array = try MLMultiArray(shape: [128], dataType: .float32)
embedding.withUnsafeBufferPointer { buffer in
array.dataPointer.assumingMemoryBound(to: Float.self)
.initialize(from: buffer.baseAddress!, count: buffer.count)
}
return SimpleFeatureProvider(features: [
"productEmbedding": MLFeatureValue(multiArray: array)
])
}
private func makeTrainingInput(embedding: [Float], label: Int) throws -> MLFeatureProvider {
let embeddingArray = try MLMultiArray(shape: [128], dataType: .float32)
embedding.withUnsafeBufferPointer { buffer in
embeddingArray.dataPointer.assumingMemoryBound(to: Float.self)
.initialize(from: buffer.baseAddress!, count: buffer.count)
}
let labelArray = try MLMultiArray(shape: [1], dataType: .int32)
labelArray.dataPointer.assumingMemoryBound(to: Int32.self)[0] = Int32(label)
return SimpleFeatureProvider(features: [
"productEmbedding": MLFeatureValue(multiArray: embeddingArray),
"engagementLabel": MLFeatureValue(multiArray: labelArray)
])
}
}