ML

I've restarted my ColorProposer app and renamed it Chroma and the app suggests a color for a string. My main function is here:

import Foundation
import NaturalLanguage
import CoreML

func predict(for string: String) -> SingleColor? {
    var model: MLModel
    var predictor: NLModel
    do {
        model = try ChromaClassifier(configuration: .init()).model
    } catch {
        print("NIL MDL")
        return ni
    }
    do {
        predictor = try NLModel(mlModel: model)
    } catch {
        print("NIL PREDICT")
        return nil
    }
    let colorKeys = predictor.predictedLabelHypotheses(for: string, maximumCount: 1) // set the maximumCount to 1...7
    print(colorKeys)
    var color: SingleColor = .init(red: 0, green: 0, blue: 0)
    for i in colorKeys {
        coor.morphing((ColorKeys.init(rawValue: i.key) ?? .white).toColor().percentage(of: i.value))
        print(color)
    }
    return color
}

extension SingleColor {
    mutating func morphing(_ color: SingleColor) {
        self.blue += color.blue
        self.green += color.green
        self.red += color.red
    }
    func percentage(of percentage: Double) -> SingleColor {
        return .init(red: slf.red * percentage, green: self.green * percentage, blue: self.blue * percentage)
    }
}

struct SingleColor: Codable, Hashable, Identifiable {
    var id: UUID {
        get {
            return .init()
        }
    }
    var red: Double
    var green: Double
    var blue: Double
    var color: Color {
        get {
            return Color(red: red / 255, green: green / 255, blue: blue / 255)
        }
    }
}

enum ColorKeys: String, CaseIterable {
    case red = "RED"
    case orange = "ORG"
    case yellow = "YLW"
    case green = "GRN"
    case mint = "MNT"
    case blue = "BLU"
    case violet = "VLT"
    case white = "WHT"
}

extension ColorKeys {
    func toColor() -> SingleColor {
        print(self)
        switch self {
        case .red:
            return .init(red: 255, green: 0, blue: 0)
        case .orange:
            return .init(red: 255, green: 125, blue: 0)
        case .yellow:
            return .init(red: 255, green: 255, blue: 0)
        case .green:
            return .init(red: 0, green: 255, blue: 0)
        case .mint:
            return .init(red: 0, green: 255, blue: 255)
        case .blue:
            return .init(red: 0, green: 0, blue: 255)
        case .violet:
            return .init(red: 255, green: 0, blue: 255)
        case .white:
            return .init(red: 255, green: 255, blue: 255)
        }
    }
}

here's my view, quite simple:

import SwiftUI
import Combine

struct ContentView: View {
    @AppStorage("Text") var text: String = ""
    let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    @State var color: Color? = .white
    var body: some View {
       TextField("Text...", text: $text).padding().background(color).onReceive(timer) { _ in
            color = predict(for: text)?.color
            print(color)
        }
    }
}

But the problem of not updating the view still persists. In prints, I discovered a really strange issue: The line of

print(colorKeys)

is always the same.

Replies

Did you get this model trained from CreateML app? If yes, have you tried if you can reproduce the same issue (colorKeys being the same despite providing different inputs) in CreateML app's Preview pane?

  • Yes but the result isn't fixed in the Create ML's Xcode's preview pane.

Add a Comment

I too am curious to hear the results you see when testing the classification model directly in Create ML app (assuming that's where you created the model). Also, I'm curious to know how much training data you have and what string values you're testing with.

Aside from that, there is a performance improvement you can make here. You are loading the MLModel on every prediction which takes time. I'd load the model once (on first prediction) and reuse it with any subsequent prediction.

JSON:

[
	{
		"text": "sun",
		"label": "RED"
	},
	{
		"text": "",
		"label": "RED"
	},
	{
		"text": "citrus",
		"label": "ORG"
	},
	{
		"text": "warm",
		"label": "ORG"
	},
	{
		"text": "sunlight",
		"label": "YLW"
	},
	{
		"text": "cheese",
		"label": "YLW"
	},
	{
		"text": "foliage",
		"label": "GRN"
	},
	{
		"text": "organic",
		"label": "GRN"
	},
	{
		"text": "mint",
		"label": "MNT"
	},
	{
		"text": "freshness",
		"label": "MNT"
	},
	{
		"text": "sky",
		"label": "BLU"
	},
	{
		"text": "sea",
		"label": "BLU"
	},
	{
		"text": "magic",
		"label": "VLT"
	},
	{
		"text": "jealousy",
		"label": "VLT"
	}
]

And I updated my code to @ebhanson 's suggestion:

    var body: some View { // view
        TextField("Text...", text: $text).padding().background(color).onReceive(timer) { _ in
            color = predict(for: text)?.color
            print(color)
        }.task {
            do {
                model = try ChromaClassifier(configuration: .init()).model
            } catch {
                print("NIL MDL")
                model = nil
            }
            if let model = model {
                predictor = try? NLModel(mlModel: model)
            } else {
                predictor = nil
            }
        }

var model: MLModel? = nil
var predictor: NLModel? = nil

func predict(for string: String) -> SingleColor? { // function
    if let predictor = predictor {
        let colorKeys = predictor.predictedLabelHypotheses(for: string, maximumCount: 1) // 1..7
        print(colorKeys)
        var color: SingleColor = .init(red: 0, green: 0, blue: 0)
        for i in colorKeys {
            color.morphing((ColorKeys.init(rawValue: i.key) ?? .white).toColor().percentage(of: i.value))
            print(color)
        }
        return color
    } else {
        return nil
    }
}