Skip to content

Swift API for integrating mobile applications with Foresight cloud framework.

License

Notifications You must be signed in to change notification settings

Enabyl/SwiftyForesight

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

28 Commits
 
 
 
 
 
 
 
 

Repository files navigation

ForesightSwiftAPILogo

SwiftyForesight

Swift API for integrating mobile applications with Foresight cloud framework.

Contents

Installation

SwiftyForesight is available for installation via CocoaPods. In your Podfile, enter pod 'SwiftyForesight', then from the terminal enter pod install.

User Guide

This library provides an easy interface between iOS devices and the Foresight cloud platform. iOS devices built on the Foresight framework must (1) connect to AWS cloud services, (2) transmit data in the proper format, and (3) download and run custom computational models as they are remotely generated. For these three tasks, this library provides three important classes: CloudManager, LibraData, and LibraModel.

Important Note: All asynchronous functions in SwiftyForesight include completion handlers. Please use these handlers in cases where data transfer should be synchronized with other processes by using DispatchGroup.

AWS Dependencies

As the Foresight framework utilizes Amazon Web Services, important AWS dependencies are installed with the SwiftyForesight CocoaPod, if they aren't installed already. To use this library, ensure that you have obtained information about your AWS resources from an Enabyl representative. Also ensure that you have received your awsconfiguration.json file and have included it in your main project directory.

Importing SwiftyForesight

In any file in which SwiftyForesight classes are used, include import SwiftyForesight in the file header.

CloudManager

The CloudManager class manages the data connection between the iOS application and various AWS resources. It defines a variety of functions used by the LibraData and LibraModels classes to communicate with their respective AWS resources. Instantiate your application's CloudManager object by calling the initializer.

// Instantiate CloudManager object
let cloudManager = CloudManager(identityID: "<name>", userID: "<name>", writeBucket: "<name>", readBucket: "<name>")

An important note is that each user on a device using the Foresight framework should have a unique alphanumeric user ID assigned to them. An easy way to generate this ID is to use UUID.uuidstring() and storing this ID in the Keychain or User Defaults. This ID is used by the Foresight framework to identify user-specific models, generate performance reports, and more, so it is in the developer's best interest to ensure this ID is kept consistent.

This class requires the user to specify their identity ID (found in their awsconfiguration.json file), the S3 bucket name to which they are transmitting data, and the S3 bucket name from which computational models are downloaded. There is only one use case in which you may wish to directly call a CloudManager method, as detailed below.

LibraModel

Initializing

LibraModel is a superclass for all computational models generated by the Foresight framework. Depending on your appilcation, you may have one or more subclass of LibraModel running on your device. The two model subtypes currently supported are SequentialModel (e.g. LSTM) and FeedforwardModel (e.g. Softmax). The below example shows how to instantiate a SequentialModel object, however the same process could be performed for a FeedforwardModel.

// Instantiate SequentialModel object
let myModel = SequentialModel(modelClass: MLModel(), numInputFeatures: 10, localFilepath: <path>, withManager: cloudManager)

The argument modelClass may be used to input a MLModel object if one is already installed in the main application bundle as a placeholder until remote models are downloaded and compiled -- in most cases, this will be left with a placeholder as shown above. The user must specify the number of input features in their model, as well as the local filepath (including .mlmodel filename) where remotely-compiled models will be temporarily stored. Finally, the user should include the CloudManager object defined earlier so that the model can manage its AWS connection. Perform this process for each computational model that will run on your device.

Model Configuration

In order to generate predictions with a LibraModel, the names assigned to the input and output nodes of the model at the time of training/saving must be specified. The default values for input and output nodes are automatically handled by the SwiftyForesight API, however if you set custom names for the input and output nodes of your model you must specify them with the function seFeatures().

Fetching and Using Models

The LibraModel superclass includes two useful methods for performing tasks with these models. These include:

  • fetch(): Fetches and downloads the most recent model from the Foresight server platform.
  • predict(): Generates predictions based on provided input vectors.

Formatting data for prediction is a simple task. For squential models, feature vectors should be provided in list format (i.e. [[Double]]), with each entry being a single feature vector. The total number of feature vectors should correspond with numInputFeatures specified above. For feedforward models, feature vectors should be provided in [Double] format, with the number of elements also corresponding to numInputFeatures. The predict() function will return results in MLMultiArray objects, from which the elements can be extracted to obtain model predictions.

LibraData

Initializing

LibraData objects perform the role of formatting data to be sent to remote server resources for training. For each model that is being remotely trained, a LibraData object should be instantiated and used to format and upload training data. The first step is to instantiate the object as follows.

let myData = LibraData(hasTimestamp: true, featureVectors: 10, labelVectorLength: 3, withManager: cloudManager)

The argument hasTimestamp is used during model training to inform how the first feature vector is treated. The featureVectors argument should specify how many feature vectors the data has NOT including the timestamp column. labelVectorLength specifies the length of the label vector (e.g. for a softmax classifier with three classes, labelVectorLength = 3).

Formatting Data

NOTE that for both feedforward and sequential models, each feature vector reprents the values of a single feature over training samples, not a cross-section of all features at a single timestep. Below is a demonstrative example of how data should be formatted.

// Collect features and labels
let featureVector1 = [1.0, 2.0, 3.0]  // First input feature values at t = 1, 2, 3
let featureVector2 = [4.0, 5.0, 6.0]  // Second input feature values at t = 1, 2, 3
let labelVector1 = [0.0, 1.0]         // First label vector at t = 1
let labelVector2 = [1.0, 0.0]         // Second label vector at t = 2
let labelVector3 = [1.0, 0.0]         // Third label vector at t = 3

// Format features and labels into an array for import to LibraData object
let features = [featureVector1, featureVector2]         // Feature vector
let labels = [labelVector1, labelVector2, labelVector3] // Label vector

Transmitting Data

To format the data for upload, add the feature and label vectors into the LibraData object and upload.

myData.addFeatures(features)  // Add features to object
myData.addLabels(labels)      // Add labels to object
myData.formatData()           // Formatting data for upload
myData.uploadDataToRemote(fromLocalFile: <path>)  // Uploading data to remote

The function formatData() formats the training data and saves it as a local .csv file. This file is automatically given the name <userID>_<eventDate>.csv so that it may be properly processed at a later time. This file is then uploaded to the remote AWS resource with the function uploadDataToRemote() with the same filename. Notice that the local file path must be specified -- this is because the function uploadDataToRemote() is also used by other functions in the SwiftyForesight framework. However, the function formatData() returns the filename in a completion handler, which may then be passed directly to uploadDataToRemote(). Note also that LibraData includes a safeguard to retry all failed uploads at a later time (e.g., when network connection is lost).

// Retrying all failed uploads
myData.retryFailedUploads()

If the user desires to delete all data files associated with failed uploads on the device, LibraData also includes the function clearErrorLogs(), which can be used to clear this data if it begins to consume more memory than is desirable.

Incorporating Metadata

When data files are transmitted to remote resources for model training, it may be useful to include metadata to add context for training algorithms. Metadata is stored in a DynamoDB database. In order to enable metadata, the user must take the following steps.

  1. After initializing your CloudManager, specify your DynamoDB table name with CloudManager.tableName = <YourTableName>.
  2. To add your metadata to your LibraData object, use the function myData.addMetadata().
  3. To upload to DynamoDB, use the function myData.uploadMetadataToRemote().

Data passed into your LibraData object with addMetadata() should be a dictionary with the following format:

// Generating a metadata dictionary
let myDict = [Keys.hash : <userID>, Keys.range: <eventDate>, Keys.m0: <metadata0>, Keys.m1: <metadata1>, ...]

The default DynamoDB table provided by the Foresight framework accepts up to 12 fields. _userID and _eventDate are required hash and range keys, respectively, however the user may choose to store up to 10 additional fields of metadata at their discretion. These fields may be added to the metadata dictionary with the keys _m0 to _m9 respectively. To prevent input errors, the Keys class is provided, which contains static variable holding the proper names of the hash key, range key, and 10 metadata fields. Note that these field names may not be changed, so the user must document and standardize what each metadata field is storing. The best practice guideline for storing metadata is that the <userID> used in this dictionary should be the same as the field CloudManager.userID, since the function removeAllUserMetadata() queries the DynamoDB database based on the value of CloudManager.userID.

Metadata may also be queried with the LibraData function queryData(), which returns all metadata entries between two specified dates. Dates should be formatted as follows in order for the query to work properly.

let myDate = Date()   // Specify date here
let formatter = DateFormatter()
formatter.dateFormat = "yyyyMMhhmmss""
let properDate = formatter.string(from: myDate)

Data Management

To maintain regulatory compliance, it is often necessary to give the user of an application the ability to delete all data and metadata that has been collected remotely. LibraData provides the function clearData() for removing all data that is currently being processed (i.e. neither uploaded nor locally saved). For deleting remote files, we must use CloudManager directly. CloudManager provides the functions removeAllUserFiles() for clearing all data files from remote resources, and the function removeAllUserMetadata() for removing all metadata.

Version History

  • v1.0.0: Initial Version
  • v1.0.1: Automated generation of local and remote filenames in proper format.
  • v1.0.2: Automated handling of DynamoDB table attributes.
  • v1.0.3: Added metadata querying and improved organization of metadata keys.