Section 15/161 menit
15. Real Use Cases
15. Real Use Cases
Use Case 1: Klasifikasi Foto Produk di E-Commerce
Fitur: User foto produk → app deteksi kategori otomatis dan isi form.
swift
import CoreML
import Vision
import UIKit
// Model: MobileNetV2 fine-tuned untuk 100 kategori produk
// Training: Create ML dengan 10.000 foto produk per kategori
// Ukuran model: ~8MB (Float16 quantized)
final class ProductClassificationService {
static let shared = ProductClassificationService()
private lazy var request: VNCoreMLRequest = {
let config = MLModelConfiguration()
config.computeUnits = .all
let model = try! ProductClassifier(configuration: config)
let visionModel = try! VNCoreMLModel(for: model.model)
let req = VNCoreMLRequest(model: visionModel)
req.imageCropAndScaleOption = .centerCrop
return req
}()
struct ProductCategory {
let id: String
let name: String
let confidence: Float
let suggestedPrice: ClosedRange<Double>?
}
private let categoryMetadata: [String: (name: String, priceRange: ClosedRange<Double>?)] = [
"electronics_phone": ("Smartphone", 1_000_000...15_000_000),
"electronics_laptop": ("Laptop", 3_000_000...30_000_000),
"fashion_tshirt": ("Kaos", 50_000...500_000),
// ... 97 kategori lainnya
]
func classify(photo: UIImage) async throws -> [ProductCategory] {
guard let cgImage = photo.cgImage else { throw ClassificationError.invalidImage }
let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
return try await withCheckedThrowingContinuation { continuation in
DispatchQueue.global(qos: .userInitiated).async {
do {
try handler.perform([self.request])
guard let results = self.request.results as? [VNClassificationObservation] else {
continuation.resume(returning: [])
return
}
let categories = results.prefix(3).compactMap { obs -> ProductCategory? in
guard obs.confidence > 0.1 else { return nil }
let meta = self.categoryMetadata[obs.identifier]
return ProductCategory(
id: obs.identifier,
name: meta?.name ?? obs.identifier,
confidence: obs.confidence,
suggestedPrice: meta?.priceRange
)
}
continuation.resume(returning: Array(categories))
} catch {
continuation.resume(throwing: error)
}
}
}
}
}
// SwiftUI Integration
struct ProductPhotoView: View {
@State private var classificationResults: [ProductClassificationService.ProductCategory] = []
@State private var isClassifying = false
var body: some View {
VStack {
// Camera/Gallery picker...
if isClassifying {
ProgressView("Mendeteksi produk...")
} else {
ForEach(classificationResults, id: \.id) { category in
HStack {
Text(category.name)
Spacer()
Text("\(Int(category.confidence * 100))%")
.foregroundColor(.secondary)
}
}
}
}
.onChange(of: /* selectedImage */) { _, image in
Task {
isClassifying = true
if let image {
classificationResults = (try? await ProductClassificationService.shared.classify(photo: image)) ?? []
}
isClassifying = false
}
}
}
}
Use Case 2: Real-Time OCR + Document Intelligence
Fitur: Kamera arahkan ke struk/dokumen → ekstrak teks + klasifikasi jenis dokumen.
swift
import Vision
import NaturalLanguage
final class DocumentIntelligenceService {
// Vision Text Recognition (tidak butuh Core ML model custom — built-in Apple)
private lazy var textRequest: VNRecognizeTextRequest = {
let req = VNRecognizeTextRequest()
req.recognitionLevel = .accurate
req.recognitionLanguages = ["id-ID", "en-US"]
req.usesLanguageCorrection = true
return req
}()
// Custom Core ML model untuk klasifikasi dokumen
private lazy var docClassifier: NLModel = {
let url = Bundle.main.url(forResource: "DocumentClassifier", withExtension: "mlmodelc")!
return try! NLModel(contentsOf: url)
}()
enum DocumentType: String {
case receipt = "receipt"
case invoice = "invoice"
case idCard = "id_card"
case contract = "contract"
case unknown = "unknown"
}
struct DocumentAnalysis {
let rawText: String
let documentType: DocumentType
let confidence: Double
let extractedFields: [String: String]
}
func analyze(image: UIImage) async throws -> DocumentAnalysis {
guard let cgImage = image.cgImage else { throw DocumentError.invalidImage }
// Step 1: OCR
let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
try handler.perform([textRequest])
let recognizedText = textRequest.results?
.compactMap { $0.topCandidates(1).first?.string }
.joined(separator: "\n") ?? ""
// Step 2: Klasifikasi jenis dokumen
let label = docClassifier.predictedLabel(for: recognizedText) ?? DocumentType.unknown.rawValue
let documentType = DocumentType(rawValue: label) ?? .unknown
let hypotheses = docClassifier.predictedLabelHypotheses(for: recognizedText, maximumCount: 1)
let confidence = hypotheses[label] ?? 0
// Step 3: Extract fields berdasarkan tipe dokumen
let fields = extractFields(from: recognizedText, documentType: documentType)
return DocumentAnalysis(
rawText: recognizedText,
documentType: documentType,
confidence: confidence,
extractedFields: fields
)
}
private func extractFields(from text: String, documentType: DocumentType) -> [String: String] {
var fields: [String: String] = [:]
// NLTagger untuk Named Entity Recognition
let tagger = NLTagger(tagSchemes: [.nameType, .lexicalClass])
tagger.string = text
tagger.enumerateTags(in: text.startIndex..<text.endIndex,
unit: .word,
scheme: .nameType,
options: [.omitWhitespace, .omitPunctuation]) { tag, range in
guard let tag else { return true }
let word = String(text[range])
switch tag {
case .organizationName:
fields["merchant"] = word
case .personalName:
fields["customer"] = word
default: break
}
return true
}
// Regex untuk tanggal dan nominal
if let dateMatch = text.range(of: #"\d{2}[/-]\d{2}[/-]\d{4}"#, options: .regularExpression) {
fields["date"] = String(text[dateMatch])
}
if let totalMatch = text.range(of: #"(?:TOTAL|Total):?\s*Rp\.?\s*[\d.,]+"#, options: .regularExpression) {
fields["total"] = String(text[totalMatch])
}
return fields
}
}
Use Case 3: On-Device Recommendation Engine
Fitur: Rekomendasi konten berdasarkan histori pengguna, tanpa data ke server.
swift
import CoreML
// Model: Collaborative Filtering yang di-convert dari PyTorch
// Input: user_embedding (64-dim) + item_features (128-dim)
// Output: relevance_score (Float)
// Training: Dilakukan di server, model di-distribute via App Store update
actor RecommendationEngine {
private let model: ContentRecommender
private var userEmbedding: [Float]
init(userEmbedding: [Float]) throws {
let config = MLModelConfiguration()
config.computeUnits = .cpuAndNeuralEngine
self.model = try ContentRecommender(configuration: config)
self.userEmbedding = userEmbedding
}
struct ContentItem {
let id: String
let title: String
let features: [Float] // 128-dim feature vector
}
func rank(items: [ContentItem]) async throws -> [ContentItem] {
// Score setiap item
var scores: [(item: ContentItem, score: Float)] = []
for item in items {
let userArray = try MLMultiArray(shape: [64], dataType: .float32)
let itemArray = try MLMultiArray(shape: [128], dataType: .float32)
for (i, val) in userEmbedding.enumerated() { userArray[i] = NSNumber(value: val) }
for (i, val) in item.features.enumerated() { itemArray[i] = NSNumber(value: val) }
let input = ContentRecommenderInput(
user_embedding: userArray,
item_features: itemArray
)
let output = try await model.prediction(input: input)
scores.append((item, output.relevance_score))
}
return scores
.sorted { $0.score > $1.score }
.map { $0.item }
}
// Update user embedding berdasarkan interaksi baru (on-device)
func updateUserEmbedding(basedOn interaction: ContentItem, liked: Bool) {
let learningRate: Float = liked ? 0.01 : -0.005
// Gradient descent sederhana: geser embedding ke arah item yang disukai
let itemFeatures = interaction.features.prefix(64).map { $0 }
userEmbedding = zip(userEmbedding, itemFeatures).map { user, item in
min(max(user + learningRate * item, -1), 1)
}
}
}