Most examples, including within documentation, of using CoreML with iOS involve the creation of the Model under Xcode on a Mac and then inclusion of the Xcode generated MLFeatureProvider class into the iOS app and (re)compiling the app. However, it’s also possible to download an uncompiled model directly into an iOS app and then compile it (background tasks) - but there’s no MLFeatureProvider class. The same applies when using CreateML in an iOS app (iOS 15 beta) - there’s no automatically generated MLFeatureProvider. So how do you get one? I’ve seen a few queries on here and elsewhere related to this problem, but couldn’t find any clear examples of a solution. So after some experimentation, here’s my take on how to go about it:
Firstly, if you don’t know what features the Model uses, print the model description e.g. print("Model: ",mlModel!.modelDescription). Which gives Model:
inputs: (
"course : String",
"lapDistance : Double",
"cumTime : Double",
"distance : Double",
"lapNumber : Double",
"cumDistance : Double",
"lapTime : Double"
)
outputs: (
"duration : Double"
)
predictedFeatureName: duration
............
A prediction is created by guard **let durationOutput = try? mlModel!.prediction(from: runFeatures) ** ……
where runFeatures is an instance of a class that provides a set of feature names and the value of each feature to be used in making a prediction. So, for my model that predicts run duration from course, lap number, lap time etc the RunFeatures class is:
class RunFeatures : MLFeatureProvider {
var featureNames: Set = ["course","distance","lapNumber","lapDistance","cumDistance","lapTime","cumTime","duration"]
var course : String = "n/a"
var distance : Double = -0.0
var lapNumber : Double = -0.0
var lapDistance : Double = -0.0
var cumDistance : Double = -0.0
var lapTime : Double = -0.0
var cumTime : Double = -0.0
func featureValue(for featureName: String) -> MLFeatureValue? {
switch featureName {
case "distance":
return MLFeatureValue(double: distance)
case "lapNumber":
return MLFeatureValue(double: lapNumber)
case "lapDistance":
return MLFeatureValue(double: lapDistance)
case "cumDistance":
return MLFeatureValue(double: cumDistance)
case "lapTime":
return MLFeatureValue(double: lapTime)
case "cumTime":
return MLFeatureValue(double: cumTime)
case "course":
return MLFeatureValue(string: course)
default:
return MLFeatureValue(double: -0.0)
}
}
}
Then in my DataModel, prior to prediction, I create an instance of RunFeatures with the input values on which I want to base the prediction:
var runFeatures = RunFeatures()
runFeatures.distance = 3566.0
runFeatures.lapNumber = 1.0
runFeatures.lapDistance = 1001.0
runFeatures.lapTime = 468.0
runFeatures.cumTime = 468.0
runFeatures.cumDistance = 1001.0
runFeatures.course = "Wishing Well Loop"
NOTE there’s no need to provide the output feature (“duration”) here, nor in the featureValue method above but it is required in featureNames.
Then get the prediction with guard let durationOutput = try? mlModel!.prediction(from: runFeatures)
Regards,
Michaela
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
The new TabularData framework in iOS 15, MacOS 12 and watchOS 8 opens up opportunities for easier, more efficient ingestion of data (and for ML possibilities). However, it does not appear to be possible to directly use a DataFrame's rows for a List or ForEach in SwiftUI: the compiler gives an error that the rows do not conform to RandomAccessCollection Protocol. The documentation for Rows does not state compliance with such, even through there are methods which inherit from the protocol.
Using a separate iteration to extract each Row (as a DataFrame.Row) from the DataFrame into a new array works. I make this array identifiable by using the row.index as the id. However, if the DataFrame is large this adds considerably to storage and processing overheads.
Any thoughts on directly using the DataFrame's Rows in SwiftUI?
I have a Multiplatform app using a PersistentCloudKitContainer Model that has a complex object graph and which is still in Development mode (including in CloudKit). An integer attribute of one of the entities needs to be changed to double, for reasons that were not obvious on first analysis of data.
According to the Core Data Migration documentation , which is 10 years old, this case would not be Lightweight Migration and so I would need to create a Migration Policy and Process.
Although I've only recently imported a few legacy records that set the Int attribute (now required to be Double), there are several hundred entities (imported legacy data) with a default Int value (0). Tempting though it is to just change the attribute type from Int to Double and see what happens, recreating the model and hundreds (thousands?) of records if things go wrong would be a real pain in the derriere.
So, given the complexity of my model and the use of CloudKit for multi-device synching, I'm thinking I'll create a new Double attribute and reimport data into that, then when all's fine delete the original Int attribute. This way both sets of changes are Lightweight.
Advice? Thoughts?
Regards, Michaela
I'm doing some summarising of dated data from Coredata using @SectionedFetchRequest and SwiftUI List with Section headers. Summary options are by week, month, quarter and year, with processing by Calendar Component. All works fine (with a bit of extra processing for the week period description) except for quarter, which always returns zero instead of 1 to 4. When I finally decided to look at the documentation https://developer.apple.com/documentation/foundation/calendar/component/quarter there's an Important note saying
"The quarter unit is largely unimplemented, and is not recommended for use.". So why have that enumeration if it's known not to work?
OK, so I now get the month and then determine the quarter with a switch statement.......
According to the documentation for DateComponentsFormatter UnitsStyle .abbreviated https://developer.apple.com/documentation/foundation/datecomponentsformatter/unitsstyle, this style (abbreviated) should create a string like “9h 41m 30s”. In some circumstances, eg MacOS target with import Foundation, the result is "9h 41min. 30s.", which is a sort-of mix between .brief and .abbreviated.
In my SwiftUI multi-platform app (MacOS & iOS) the incorrect (mixed) style is generated for both platforms. Using Playground, the mixed formatting occurs when the platform is set to MacOS, but not iOS with either import UIKit or import Foundation.
Is this a bug, or a "featured" difference between MacOS and iOS?
My multi-platform function is
import Foundation
public func strDuration(_ duration: TimeInterval, style: DateComponentsFormatter.UnitsStyle = .abbreviated) -> String {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.hour, .minute, .second]
formatter.unitsStyle = style
formatter.maximumUnitCount = 3
return formatter.string(from: duration) ?? "n/a"
}
Regards, Michaela
I'm currently developing a multi-platform app where a standalone Watch app collects data, summarises it, then stores the summary in CoreData. The summary gets synchronised across all devices by CloudKit. However, the raw data (e.g. RR interval data from a Polar H10 heart rate monitor) are important from an historical perspective: forming the basis of further analysis, perhaps via AI. Given the limited storage, battery and processing resources on the Watch, it would be inappropriate to persistently store, or perform complex analysis of, the raw data on the Watch.
My preferred solution would be to transfer the raw data to a Mac and, once successfully received, delete it from the Watch (also from CloudKit?). The Mac would then perform further analysis and propagate an additional summary to all other devices. The problem I foresee is that, if using CoreData/CloudKit synchronisation, deleting from the Watch would delete from all other devices. With my currently limited understanding of such synchronisation, I don't see a way of not then automatically deleting the Mac's CoreData records.
Perhaps the solution is to have a separate CoreData container on the watch for the raw data, a separate CloudKit raw data container, and a separate Mac raw data container - then perform the deletes upon receipt of CloudKit update notifications (i.e. not via automatic syncs). The raw data are immutable, so there's no need to deal with updates.
I'd appreciate advice/suggestions on a workable solution.
Regards, Michaela
I'm currently rewriting an app developed over many years in Objective-C, Swift, SwiftUI, and an SQLIte database. Until now, I'd avoided CoreData because I'd been using SQL databases for decades (so was comfortable) and had concerns with CoreData's use of NS types. However, the new app will be multi-platform and share/sync data via CloudKit - which will be a pain in the derriere with SQLite (though not insurmountable). So, I'm going to do a test import of two related entities, 1,000 records and 4,500 records, to see how things go. There are more tables, but this will be just a test.
My intended process is:
Export the SQLite tables, "main" and "details", separately, to CSV. There are linking IDs.
In the new app, load each CSV file into DataFrames using the TabularData framework.
For each row in the "main" DataFrame, filter the "details" DataFrame rows on the "main"'s ID, then create Core Data's "main" entity along with the associated "detail" entities.
Do a context.save() - after each "main" or in batches?
Is there a better (less coding?) way?
My understanding is that I can't just populate each set of entities separately and then expect an automatic creation of the relationships. I've seen reference on the Web of Core Data "linking attributes", rather like joins in SQL, but I see no mention of such in the Xcode DataModel builder, nor class definitions. Am I correct in this understanding?
Regards, Michaela
I had a Big Sur MacMini update (non Beta) get stuck and nothing would fix it, so I then decided to go to Monterey (beta). After some initial problems, related to a Big Sur update waiting in the wings, the Monterey installation started - and then got stuck. The same problem: got so far then no further, time after time. I tried Safe Mode start-ups, but to no avail.
During a shutdown, which also seemed to be blocked, I became aware of HDD noise from the networked backup drive. A backup was in progress! When I turned off automatic backups in TimeMachine (and disconnected the drive), the Monterey install worked - and I suspect the Big Sur update would have done also.
DataFrame(contentsOfJSONFile:url) (and it's MLDataTable equivalent) assumes that the rows to be imported are at the root level of the JSON structure. However, I'm downloading a fairly large dataset that has "header" information, such as date created and validity period, with the targeted array at a subsidiary level. The DataFrame initialiser has JSONReadingOptions, but these don't apply to this situation (i.e. there's nothing that helps).
It seems that I'm faced with the options of 1) stripping the extraneous data from the JSON file, to leave just the array or 2) decoding the JSON file into a bespoke struct then converting its array into a DataFrame - which removes a lot of the benefits of using DataFrame in the first place.
Any thoughts?
Cheers, Michaela
The TabularData DataFrame writeCSV method formats Double (and possibly Integer) numbers that are >= 1000 with a comma thousands separator and surrounds the output in double quotes. Numbers less than 1000 are not double-quoted. If the resulting CSV file is then read by DataFrame contentsOfCSVFile an error is thrown, i.e. a type mismatch. This happens irrespective of whether the types option is set in the CSV read, or not.
There is no option in DataFrame writeCSV to specify no thousands separator, nor any other obvious way of preventing the double-quoting.
Currently, I "fix" the problem by reading the CSV in Numbers, setting the faulty column(s) to Numeric then exporting back to CSV.
Any thoughts on preventing the issue in the first place?
I'm devloping an iOS App for the Polar H7 and H10 Heart Rate sensors and it's crucial that the App monitors the attached device's battery level.For ayone who needs to get the battery level from a BLE device (if it supports such), the Battery Service UUID is 0x180F and the battery level characterisitc UUID is 0x2A19, reporting a one byte value with the battery level as a percentage (UInt8). Details are at https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.battery_service.xmlIn my Swift 3 code, having discovered the Service and the Characteristic (2A19), I do a peripheral.readValue(for: battChar) then a peripheral.setNotifyValue(true, for: battChar). The value is then provided in the didupdatevalue for characterisitc function. Not surprisingly, my Polar devices do not constantly update the battery status but must be interrogated from time to time using the peripheral.readvalue(for: ...) function.I hope this helps someone, somewhere, sometime. 🙂
When an iOS 14 beta device is connected to my MacBook running Big Sur 11 beta 10, files can be transferred to the iOS device via Finder, but not from iOS to the MacBook. No error is given and the transfer drag-and-drop appears to have worked as normal, except the file doesn't get transferred. It worked in the previous beta of Big Sur, although was problematic in earlier betas.