diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9d643e2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,57 @@ +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ +!*.xcworkspace/contents.xcworkspacedata + +## Other +*.moved-aside +*.xcuserstate + +## Sourcery +default.profraw + +## Obj-C/Swift specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +## Packages/ +.build/ + +## CocoaPods +Pods/ + +## Carthage +Carthage/Build + +## fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output + +## FBSnapshotTestCase +FailureDiffs/ + +## Deployment scripts +.setup_travis_deployment.sh +.match_key + +## AppCode +.idea/ \ No newline at end of file diff --git a/HandWriting-Learner.xcodeproj/project.pbxproj b/HandWriting-Learner.xcodeproj/project.pbxproj index 9b6fa9f..7b6fc50 100644 --- a/HandWriting-Learner.xcodeproj/project.pbxproj +++ b/HandWriting-Learner.xcodeproj/project.pbxproj @@ -145,17 +145,17 @@ B5E8EF701C84A32800D977DF /* HandWriting-Learner */ = { isa = PBXGroup; children = ( + B5E8EF911C84A33D00D977DF /* HistoryCell */, B5E8EFAA1C84A45A00D977DF /* NeuronalNetworkManager */, - B5E8EFA71C84A3D400D977DF /* UIImage-Extension */, B5E8EFA41C84A35F00D977DF /* Swift-AI */, - B5E8EF911C84A33D00D977DF /* HistoryCell */, + B5E8EFA71C84A3D400D977DF /* UIImage-Extension */, B5E8EF711C84A32800D977DF /* AppDelegate.swift */, - B50C71631C8F1BA7003D6EC1 /* SetupViewController.swift */, - B5E8EF731C84A32800D977DF /* ViewController.swift */, - B5E8EF751C84A32800D977DF /* Main.storyboard */, B5E8EF781C84A32800D977DF /* Assets.xcassets */, - B5E8EF7A1C84A32800D977DF /* LaunchScreen.storyboard */, B5E8EF7D1C84A32800D977DF /* Info.plist */, + B5E8EF7A1C84A32800D977DF /* LaunchScreen.storyboard */, + B5E8EF751C84A32800D977DF /* Main.storyboard */, + B50C71631C8F1BA7003D6EC1 /* SetupViewController.swift */, + B5E8EF731C84A32800D977DF /* ViewController.swift */, ); path = "HandWriting-Learner"; sourceTree = ""; @@ -174,7 +174,7 @@ children = ( B5E8EF921C84A34600D977DF /* HistoryTableViewCell.swift */, ); - name = HistoryCell; + path = HistoryCell; sourceTree = ""; }; B5E8EFA41C84A35F00D977DF /* Swift-AI */ = { @@ -184,12 +184,12 @@ B5E8EF951C84A35A00D977DF /* FFNN.swift */, B5E8EF961C84A35A00D977DF /* Matrix.swift */, B5E8EF971C84A35A00D977DF /* Operations.swift */, - B5E8EF981C84A35A00D977DF /* Random.swift */, B5E8EF991C84A35A00D977DF /* README.md */, + B5E8EF981C84A35A00D977DF /* Random.swift */, B5E8EF9A1C84A35A00D977DF /* Storage.swift */, B5E8EF9B1C84A35A00D977DF /* Vector.swift */, ); - name = "Swift-AI"; + path = "Swift-AI"; sourceTree = ""; }; B5E8EFA71C84A3D400D977DF /* UIImage-Extension */ = { @@ -197,7 +197,7 @@ children = ( B5E8EFA51C84A3AF00D977DF /* UIImage+Processing.swift */, ); - name = "UIImage-Extension"; + path = "UIImage-Extension"; sourceTree = ""; }; B5E8EFAA1C84A45A00D977DF /* NeuronalNetworkManager */ = { @@ -205,7 +205,7 @@ children = ( B5E8EFA81C84A45700D977DF /* FFNNManager.swift */, ); - name = NeuronalNetworkManager; + path = NeuronalNetworkManager; sourceTree = ""; }; /* End PBXGroup section */ @@ -253,14 +253,16 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0720; - LastUpgradeCheck = 0720; + LastUpgradeCheck = 0910; ORGANIZATIONNAME = "Alban Perli"; TargetAttributes = { B5E8EF6D1C84A32800D977DF = { CreatedOnToolsVersion = 7.2.1; + LastSwiftMigration = 0910; }; B5E8EF811C84A32800D977DF = { CreatedOnToolsVersion = 7.2.1; + LastSwiftMigration = 0910; TestTargetID = B5E8EF6D1C84A32800D977DF; }; }; @@ -363,6 +365,7 @@ B5E8EF761C84A32800D977DF /* Base */, ); name = Main.storyboard; + path = .; sourceTree = ""; }; B5E8EF7A1C84A32800D977DF /* LaunchScreen.storyboard */ = { @@ -371,6 +374,7 @@ B5E8EF7B1C84A32800D977DF /* Base */, ); name = LaunchScreen.storyboard; + path = .; sourceTree = ""; }; /* End PBXVariantGroup section */ @@ -384,13 +388,21 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -428,13 +440,21 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -453,6 +473,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 9.2; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; VALIDATE_PRODUCT = YES; }; name = Release; @@ -465,6 +486,8 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.alban-perli.HandWriting-Learner"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -476,6 +499,8 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.alban-perli.HandWriting-Learner"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; }; name = Release; }; @@ -487,6 +512,8 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.alban-perli.HandWriting-LearnerTests"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/HandWriting-Learner.app/HandWriting-Learner"; }; name = Debug; @@ -499,6 +526,8 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.alban-perli.HandWriting-LearnerTests"; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_SWIFT3_OBJC_INFERENCE = On; + SWIFT_VERSION = 4.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/HandWriting-Learner.app/HandWriting-Learner"; }; name = Release; diff --git a/HandWriting-Learner/AppDelegate.swift b/HandWriting-Learner/AppDelegate.swift index d42a9d0..278629e 100644 --- a/HandWriting-Learner/AppDelegate.swift +++ b/HandWriting-Learner/AppDelegate.swift @@ -14,33 +14,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? - func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true } - func applicationWillResignActive(application: UIApplication) { - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. - } - - func applicationDidEnterBackground(application: UIApplication) { - // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. - } - - func applicationWillEnterForeground(application: UIApplication) { - // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. - } - - func applicationDidBecomeActive(application: UIApplication) { - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - } - - func applicationWillTerminate(application: UIApplication) { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. - } - - } diff --git a/HandWriting-Learner/HistoryTableViewCell.swift b/HandWriting-Learner/HistoryCell/HistoryTableViewCell.swift similarity index 89% rename from HandWriting-Learner/HistoryTableViewCell.swift rename to HandWriting-Learner/HistoryCell/HistoryTableViewCell.swift index 7ddc2f3..35039b9 100644 --- a/HandWriting-Learner/HistoryTableViewCell.swift +++ b/HandWriting-Learner/HistoryCell/HistoryTableViewCell.swift @@ -20,7 +20,7 @@ class HistoryTableViewCell: UITableViewCell { // Initialization code } - override func setSelected(selected: Bool, animated: Bool) { + override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) // Configure the view for the selected state diff --git a/HandWriting-Learner/FFNNManager.swift b/HandWriting-Learner/NeuronalNetworkManager/FFNNManager.swift similarity index 75% rename from HandWriting-Learner/FFNNManager.swift rename to HandWriting-Learner/NeuronalNetworkManager/FFNNManager.swift index 18223a1..0c9e47e 100644 --- a/HandWriting-Learner/FFNNManager.swift +++ b/HandWriting-Learner/NeuronalNetworkManager/FFNNManager.swift @@ -14,17 +14,17 @@ class FFNNManager: NSObject { // Initialize neural network with pre-trained weights (may need to change activation function if yours was trained with a different one) var network:FFNN? - func createNetwork(inputs: Int, hidden: Int, outputs:Int, learningRate: Float, momentum:Float){ + func createNetwork(_ inputs: Int, hidden: Int, outputs:Int, learningRate: Float, momentum:Float){ // Initialize neural network with pre-trained weights (may need to change activation function if yours was trained with a different one) - network = FFNN(inputs: inputs, hidden:hidden , outputs: outputs, learningRate: learningRate, momentum: momentum, weights:nil, activationFunction: .Sigmoid, errorFunction: .Default(average: false)) + network = FFNN(inputs: inputs, hidden:hidden , outputs: outputs, learningRate: learningRate, momentum: momentum, weights:nil, activationFunction: .Sigmoid, errorFunction: .default(average: false)) } - func createNetworkFromFileName(fileName:String){ + func createNetworkFromFileName(_ fileName:String){ network = FFNN.fromFile(fileName) } - func trainNetwork(inputs:[Float],answer:[Float], epoch:Int)->(output:[Float],error:Float){ + func trainNetwork(_ inputs:[Float],answer:[Float], epoch:Int)->(output:[Float],error:Float){ guard network != nil else { return ([],-1.0) @@ -40,7 +40,7 @@ class FFNNManager: NSObject { } - func predictionForInput(input:[Float])->[Float]{ + func predictionForInput(_ input:[Float])->[Float]{ return try! network!.update(inputs: input) } @@ -49,11 +49,11 @@ class FFNNManager: NSObject { return network!.getWeights() } - func setNewWeight(weights:[Float]){ + func setNewWeight(_ weights:[Float]){ try! network!.resetWithWeights(weights) } - func saveNetworkWithName(name:String){ + func saveNetworkWithName(_ name:String){ FFNNManager.instance.network?.writeToFile(name) } diff --git a/HandWriting-Learner/SetupViewController.swift b/HandWriting-Learner/SetupViewController.swift index cdb6121..572be1a 100644 --- a/HandWriting-Learner/SetupViewController.swift +++ b/HandWriting-Learner/SetupViewController.swift @@ -24,35 +24,35 @@ class SetupViewController: UIViewController { @IBOutlet weak var momentumTxtField: UITextField! @IBOutlet weak var learningRateTxtField: UITextField! - @IBAction func loadBtnClicked(sender: UIButton) { + @IBAction func loadBtnClicked(_ sender: UIButton) { if !networkNameIsEmpty() { FFNNManager.instance.createNetworkFromFileName(self.networkName) - self.performSegueWithIdentifier("toNetwork", sender: self) + self.performSegue(withIdentifier: "toNetwork", sender: self) }else{ - let alert = UIAlertController(title: AlertTextConst.kAlertTitle, message: AlertTextConst.kLoadAlertText, preferredStyle: .Alert) - alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil)) + let alert = UIAlertController(title: AlertTextConst.kAlertTitle, message: AlertTextConst.kLoadAlertText, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil)) - self.presentViewController(alert, animated: true, completion: nil) + self.present(alert, animated: true, completion: nil) } } - @IBAction func newNetworkBtnClicked(sender: AnyObject) { + @IBAction func newNetworkBtnClicked(_ sender: AnyObject) { if !networkNameIsEmpty() && !learningRateIsEmpty() && !momentumIsEmpty() { FFNNManager.instance.createNetwork(30*30, hidden: 601, outputs: 4, learningRate: Float(learningRate)!, momentum: Float(momentum)!) - self.performSegueWithIdentifier("toNetwork", sender: self) + self.performSegue(withIdentifier: "toNetwork", sender: self) }else{ - let alert = UIAlertController(title: AlertTextConst.kAlertTitle, message: AlertTextConst.kCreateAlertText, preferredStyle: .Alert) - alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil)) + let alert = UIAlertController(title: AlertTextConst.kAlertTitle, message: AlertTextConst.kCreateAlertText, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil)) - self.presentViewController(alert, animated: true, completion: nil) + self.present(alert, animated: true, completion: nil) } } @@ -104,13 +104,13 @@ class SetupViewController: UIViewController { // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation - override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { // Get the new view controller using segue.destinationViewController. // Pass the selected object to the new view controller. if segue.identifier == "toNetwork"{ - let vc : ViewController = segue.destinationViewController as! ViewController + let vc : ViewController = segue.destination as! ViewController vc.currentNetworkName = networkName } diff --git a/HandWriting-Learner/FFNN+Storage.swift b/HandWriting-Learner/Swift-AI/FFNN+Storage.swift similarity index 83% rename from HandWriting-Learner/FFNN+Storage.swift rename to HandWriting-Learner/Swift-AI/FFNN+Storage.swift index 79e9cb5..1da25c4 100755 --- a/HandWriting-Learner/FFNN+Storage.swift +++ b/HandWriting-Learner/Swift-AI/FFNN+Storage.swift @@ -16,25 +16,25 @@ extension FFNN: Storage { /// Reads a FFNN from file. /// - Parameter filename: The name of the file, located in the default Documents directory. - public static func fromFile(filename: String) -> FFNN? { + public static func fromFile(_ filename: String) -> FFNN? { return self.read(self.getFileURL(filename)) } /// Reads a FFNN from file. /// - Parameter url: The `NSURL` for the file to read. - public static func fromFile(url: NSURL) -> FFNN? { + public static func fromFile(_ url: URL) -> FFNN? { return self.read(url) } /// Writes the FFNN to file. /// - Parameter filename: The name of the file to write to. This file will be written to the default Documents directory. - public func writeToFile(filename: String) { + public func writeToFile(_ filename: String) { self.write(FFNN.getFileURL(filename)) } /// Writes the FFNN to file. /// - Parameter url: The `NSURL` to write the file to. - public func writeToFile(url: NSURL) { + public func writeToFile(_ url: URL) { self.write(url) } diff --git a/HandWriting-Learner/FFNN.swift b/HandWriting-Learner/Swift-AI/FFNN.swift similarity index 79% rename from HandWriting-Learner/FFNN.swift rename to HandWriting-Learner/Swift-AI/FFNN.swift index 3cdf906..e945c98 100755 --- a/HandWriting-Learner/FFNN.swift +++ b/HandWriting-Learner/Swift-AI/FFNN.swift @@ -14,10 +14,10 @@ import Foundation /// An enum containing all errors that may be thrown by FFNN. -public enum FFNNError: ErrorType { - case InvalidInputsError(String) - case InvalidAnswerError(String) - case InvalidWeightsError(String) +public enum FFNNError: Error { + case invalidInputsError(String) + case invalidAnswerError(String) + case invalidWeightsError(String) } /// An enum containing all supported activation functions. @@ -42,9 +42,9 @@ public enum ActivationFunction: String { /// An enum containing all supported error functions. public enum ErrorFunction { /// Default error function (sum) - case Default(average: Bool) + case `default`(average: Bool) /// Cross Entropy function (Cross Entropy) - case CrossEntropy(average: Bool) + case crossEntropy(average: Bool) } @@ -75,10 +75,10 @@ public final class FFNN { } /// The activation function to use during update cycles. - private var activationFunction : ActivationFunction = .Sigmoid + fileprivate var activationFunction : ActivationFunction = .Sigmoid /// The error function used for training - private var errorFunction : ErrorFunction = .Default(average: false) + fileprivate var errorFunction : ErrorFunction = .default(average: false) /** The following private properties are allocated once during initializtion, in order to prevent frequent memory allocations for temporary variables during the update and backpropagation cycles. @@ -88,58 +88,58 @@ public final class FFNN { /// (1 - momentumFactor) * learningRate. /// Used frequently during backpropagation. - private var mfLR: Float + fileprivate var mfLR: Float /// The number of input nodes, INCLUDING the bias node. - private let numInputNodes: Int + fileprivate let numInputNodes: Int /// The number of hidden nodes, INCLUDING the bias node. - private let numHiddenNodes: Int + fileprivate let numHiddenNodes: Int /// The total number of weights connecting all input nodes to all hidden nodes. - private let numHiddenWeights: Int + fileprivate let numHiddenWeights: Int /// The total number of weights connecting all hidden nodes to all output nodes. - private let numOutputWeights: Int + fileprivate let numOutputWeights: Int /// The current weights leading into all of the hidden nodes, serialized in a single array. - private var hiddenWeights: [Float] + fileprivate var hiddenWeights: [Float] /// The weights leading into all of the hidden nodes from the previous round of training, serialized in a single array. /// Used for applying momentum during backpropagation. - private var previousHiddenWeights: [Float] + fileprivate var previousHiddenWeights: [Float] /// The current weights leading into all of the output nodes, serialized in a single array. - private var outputWeights: [Float] + fileprivate var outputWeights: [Float] /// The weights leading into all of the output nodes from the previous round of training, serialized in a single array. /// Used for applying momentum during backpropagation. - private var previousOutputWeights: [Float] + fileprivate var previousOutputWeights: [Float] /// The most recent set of inputs applied to the network. - private var inputCache: [Float] + fileprivate var inputCache: [Float] /// The most recent outputs from each of the hidden nodes. - private var hiddenOutputCache: [Float] + fileprivate var hiddenOutputCache: [Float] /// The most recent output from the network. - private var outputCache: [Float] + fileprivate var outputCache: [Float] /// Temporary storage while calculating hidden errors, for use during backpropagation. - private var hiddenErrorSumsCache: [Float] + fileprivate var hiddenErrorSumsCache: [Float] /// Temporary storage while calculating hidden errors, for use during backpropagation. - private var hiddenErrorsCache: [Float] + fileprivate var hiddenErrorsCache: [Float] /// Temporary storage while calculating output errors, for use during backpropagation. - private var outputErrorsCache: [Float] + fileprivate var outputErrorsCache: [Float] /// Temporary storage while updating hidden weights, for use during backpropagation. - private var newHiddenWeights: [Float] + fileprivate var newHiddenWeights: [Float] /// Temporary storage while updating output weights, for use during backpropagation. - private var newOutputWeights: [Float] + fileprivate var newOutputWeights: [Float] /// The output error indices corresponding to each output weight. - private var outputErrorIndices = [Int]() + fileprivate var outputErrorIndices = [Int]() /// The hidden output indices corresponding to each output weight. - private var hiddenOutputIndices = [Int]() + fileprivate var hiddenOutputIndices = [Int]() /// The hidden error indices corresponding to each hidden weight. - private var hiddenErrorIndices = [Int]() + fileprivate var hiddenErrorIndices = [Int]() /// The input indices corresponding to each hidden weight. - private var inputIndices = [Int]() + fileprivate var inputIndices = [Int]() /// Initializes a feed-forward neural network. - public init(inputs: Int, hidden: Int, outputs: Int, learningRate: Float = 0.7, momentum: Float = 0.4, weights: [Float]? = nil, activationFunction: ActivationFunction = .Default, errorFunction: ErrorFunction = .Default(average: false)) { + public init(inputs: Int, hidden: Int, outputs: Int, learningRate: Float = 0.7, momentum: Float = 0.4, weights: [Float]? = nil, activationFunction: ActivationFunction = .Default, errorFunction: ErrorFunction = .default(average: false)) { if inputs < 1 || hidden < 1 || outputs < 1 || learningRate <= 0 { print("Warning: Invalid arguments passed to FFNN initializer. Inputs, hidden, outputs and learningRate must all be positive and nonzero. Network will not perform correctly.") } @@ -158,15 +158,15 @@ public final class FFNN { self.momentumFactor = momentum self.mfLR = (1 - momentum) * learningRate - self.inputCache = [Float](count: self.numInputNodes, repeatedValue: 0) - self.hiddenOutputCache = [Float](count: self.numHiddenNodes, repeatedValue: 0) - self.outputCache = [Float](count: outputs, repeatedValue: 0) + self.inputCache = [Float](repeating: 0, count: self.numInputNodes) + self.hiddenOutputCache = [Float](repeating: 0, count: self.numHiddenNodes) + self.outputCache = [Float](repeating: 0, count: outputs) - self.outputErrorsCache = [Float](count: self.numOutputs, repeatedValue: 0) - self.hiddenErrorSumsCache = [Float](count: self.numHiddenNodes, repeatedValue: 0) - self.hiddenErrorsCache = [Float](count: self.numHiddenNodes, repeatedValue: 0) - self.newOutputWeights = [Float](count: self.numOutputWeights, repeatedValue: 0) - self.newHiddenWeights = [Float](count: self.numHiddenWeights, repeatedValue: 0) + self.outputErrorsCache = [Float](repeating: 0, count: self.numOutputs) + self.hiddenErrorSumsCache = [Float](repeating: 0, count: self.numHiddenNodes) + self.hiddenErrorsCache = [Float](repeating: 0, count: self.numHiddenNodes) + self.newOutputWeights = [Float](repeating: 0, count: self.numOutputWeights) + self.newHiddenWeights = [Float](repeating: 0, count: self.numHiddenWeights) self.activationFunction = activationFunction self.errorFunction = errorFunction @@ -180,9 +180,9 @@ public final class FFNN { self.inputIndices.append(weightIndex % self.numInputNodes) } - self.hiddenWeights = [Float](count: self.numHiddenWeights, repeatedValue: 0) + self.hiddenWeights = [Float](repeating: 0, count: self.numHiddenWeights) self.previousHiddenWeights = self.hiddenWeights - self.outputWeights = [Float](count: outputs * self.numHiddenNodes, repeatedValue: 0) + self.outputWeights = [Float](repeating: 0, count: outputs * self.numHiddenNodes) self.previousOutputWeights = self.outputWeights if let weights = weights { @@ -202,10 +202,10 @@ public final class FFNN { /// Propagates the given inputs through the neural network, returning the network's output. /// - Parameter inputs: An array of `Float`s, each element corresponding to one input node. /// - Returns: The network's output after applying the given inputs, as an array of `Float`s. - public func update(inputs inputs: [Float]) throws -> [Float] { + public func update(inputs: [Float]) throws -> [Float] { // Ensure that the correct number of inputs is given guard inputs.count == self.numInputs else { - throw FFNNError.InvalidAnswerError("Invalid number of inputs given: \(inputs.count). Expected: \(self.numInputs)") + throw FFNNError.invalidAnswerError("Invalid number of inputs given: \(inputs.count). Expected: \(self.numInputs)") } // Cache the inputs @@ -241,14 +241,14 @@ public final class FFNN { /// Trains the network by comparing its most recent output to the given 'answers', adjusting the network's weights as needed. /// - Parameter answer: The 'correct' desired output for the most recent update to the network, as an array of `Float`s. /// - Returns: The total calculated error from the most recent update. - public func backpropagate(answer answer: [Float]) throws -> Float { + public func backpropagate(answer: [Float]) throws -> Float { // Verify valid answer guard answer.count == self.numOutputs else { - throw FFNNError.InvalidAnswerError("Invalid number of outputs given in answer: \(answer.count). Expected: \(self.numOutputs)") + throw FFNNError.invalidAnswerError("Invalid number of outputs given in answer: \(answer.count). Expected: \(self.numOutputs)") } // Calculate output errors - for (outputIndex, output) in self.outputCache.enumerate() { + for (outputIndex, output) in self.outputCache.enumerated() { switch self.activationFunction { case .Softmax: // FIXME: This isn't working correctly @@ -263,7 +263,7 @@ public final class FFNN { self.outputWeights, 1, &self.hiddenErrorSumsCache, 1, vDSP_Length(1), vDSP_Length(self.numHiddenNodes), vDSP_Length(self.numOutputs)) - for (errorIndex, error) in self.hiddenErrorSumsCache.enumerate() { + for (errorIndex, error) in self.hiddenErrorSumsCache.enumerated() { self.hiddenErrorsCache[errorIndex] = self.activationDerivative(self.hiddenOutputCache[errorIndex]) * error } @@ -293,7 +293,7 @@ public final class FFNN { vDSP_mmov(newHiddenWeights, &hiddenWeights, 1, vDSP_Length(numHiddenWeights), 1, 1) // Sum and return the output errors - return self.outputErrorsCache.reduce(0, combine: { (sum, error) -> Float in + return self.outputErrorsCache.reduce(0, { (sum, error) -> Float in return sum + abs(error) }) } @@ -310,16 +310,16 @@ public final class FFNN { /// - errorThreshold: A `Float` indicating the maximum error allowed per epoch of validation data, before the network is considered 'trained'. /// This value must be determined by the user, because it varies based on the type of data used and the desired accuracy. /// - Returns: The final calculated weights of the network after training has completed. - public func train(inputs inputs: [[Float]], answers: [[Float]], testInputs: [[Float]], testAnswers: [[Float]], errorThreshold: Float) throws -> [Float] { + public func train(inputs: [[Float]], answers: [[Float]], testInputs: [[Float]], testAnswers: [[Float]], errorThreshold: Float) throws -> [Float] { guard errorThreshold > 0 else { - throw FFNNError.InvalidInputsError("Error threshold must be greater than zero!") + throw FFNNError.invalidInputsError("Error threshold must be greater than zero!") } // TODO: Allow trainer to exit early or regenerate new weights if it gets stuck in local minima // Train forever until the desired error threshold is met while true { - for (index, input) in inputs.enumerate() { + for (index, input) in inputs.enumerated() { try self.update(inputs: input) try self.backpropagate(answer: answers[index]) } @@ -342,9 +342,9 @@ public final class FFNN { /// - Parameter weights: An array of `Float`s, to be used as the weights for the network. /// - Important: The number of weights must equal numHidden * (numInputs + 1) + numOutputs * (numHidden + 1), /// or the weights will be rejected. - public func resetWithWeights(weights: [Float]) throws { + public func resetWithWeights(_ weights: [Float]) throws { guard weights.count == self.numHiddenWeights + self.numOutputWeights else { - throw FFNNError.InvalidWeightsError("Invalid number of weights provided: \(weights.count). Expected: \(self.numHiddenWeights + self.numOutputWeights)") + throw FFNNError.invalidWeightsError("Invalid number of weights provided: \(weights.count). Expected: \(self.numHiddenWeights + self.numOutputWeights)") } self.hiddenWeights = Array(weights[0.. NSURL { - let manager = NSFileManager.defaultManager() - let dirURL = try! manager.URLForDirectory(.DocumentDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: false) - return dirURL.URLByAppendingPathComponent(filename) + public static func getFileURL(_ filename: String) -> URL { + let manager = FileManager.default + let dirURL = try! manager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) + return dirURL.appendingPathComponent(filename) } /// Reads a FFNN stored in a file at the given URL. - public static func read(url: NSURL) -> FFNN? { - guard let data = NSData(contentsOfURL: url) else { + public static func read(_ url: URL) -> FFNN? { + guard let data = try? Data(contentsOf: url) else { return nil } - guard let storage = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? [String : AnyObject] else { + guard let storage = NSKeyedUnarchiver.unarchiveObject(with: data) as? [String : AnyObject] else { return nil } @@ -393,44 +393,44 @@ public extension FFNN { let weights = hiddenWeights + outputWeights - let n = FFNN(inputs: numInputs, hidden: numHidden, outputs: numOutputs, learningRate: learningRate, momentum: momentumFactor, weights: weights, activationFunction: activation!, errorFunction: .Default(average: false)) + let n = FFNN(inputs: numInputs, hidden: numHidden, outputs: numOutputs, learningRate: learningRate, momentum: momentumFactor, weights: weights, activationFunction: activation!, errorFunction: .default(average: false)) return n } /// Writes the current state of the FFNN to a file at the given URL. - public func write(url: NSURL) { + public func write(_ url: URL) { var storage = [String : AnyObject]() - storage["inputs"] = self.numInputs - storage["hidden"] = self.numHidden - storage["outputs"] = self.numOutputs - storage["learningRate"] = self.learningRate - storage["momentum"] = self.momentumFactor - storage["hiddenWeights"] = self.hiddenWeights - storage["outputWeights"] = self.outputWeights - storage["activationFunction"] = self.activationFunction.rawValue - - let data: NSData = NSKeyedArchiver.archivedDataWithRootObject(storage) - data.writeToURL(url, atomically: true) + storage["inputs"] = self.numInputs as AnyObject + storage["hidden"] = self.numHidden as AnyObject + storage["outputs"] = self.numOutputs as AnyObject + storage["learningRate"] = self.learningRate as AnyObject + storage["momentum"] = self.momentumFactor as AnyObject + storage["hiddenWeights"] = self.hiddenWeights as AnyObject + storage["outputWeights"] = self.outputWeights as AnyObject + storage["activationFunction"] = self.activationFunction.rawValue as AnyObject + + let data: Data = NSKeyedArchiver.archivedData(withRootObject: storage) + try? data.write(to: url, options: [.atomic]) } /// Computes the error over the given training set. - private func error(result: [[Float]], expected: [[Float]]) throws -> Float { + fileprivate func error(_ result: [[Float]], expected: [[Float]]) throws -> Float { var errorSum: Float = 0 switch self.errorFunction { - case .Default(let average): - for (inputIndex, input) in result.enumerate() { + case .default(let average): + for (inputIndex, input) in result.enumerated() { let outputs = try self.update(inputs: input) - for (outputIndex, output) in outputs.enumerate() { + for (outputIndex, output) in outputs.enumerated() { errorSum += abs(self.activationDerivative(output) * (expected[inputIndex][outputIndex] - output)) } } if average { errorSum /= Float(result.count) } - case .CrossEntropy(let average): - for (inputIndex, input) in result.enumerate() { + case .crossEntropy(let average): + for (inputIndex, input) in result.enumerated() { let outputs = try self.update(inputs: input) - for (outputIndex, output) in outputs.enumerate() { + for (outputIndex, output) in outputs.enumerated() { errorSum += crossEntropy(output, b: expected[inputIndex][outputIndex]) } } @@ -443,36 +443,38 @@ public extension FFNN { } /// Applies the activation function to the hidden layer nodes. - private func activateHidden() { + fileprivate func activateHidden() { switch self.activationFunction { case .None: - for (var i = self.numHidden; i > 0; --i) { + + for i in ((0 + 1)...self.numHidden).reversed().dropLast() { self.hiddenOutputCache[i] = 0.0 } + self.hiddenOutputCache[0] = 1.0 case .Default: - for (var i = self.numHidden; i > 0; --i) { + for i in ((0 + 1)...self.numHidden).reversed().dropLast() { self.hiddenOutputCache[i] = sigmoid(self.hiddenOutputCache[i - 1]) } self.hiddenOutputCache[0] = 1.0 case .Linear: - for (var i = self.numHidden; i > 0; --i) { + for i in ((0 + 1)...self.numHidden).reversed().dropLast() { self.hiddenOutputCache[i] = linear(self.hiddenOutputCache[i - 1]) } self.hiddenOutputCache[0] = 1.0 case .Sigmoid, .Softmax: // For Softmax, apply Sigmoid activation for hidden layers - for (var i = self.numHidden; i > 0; --i) { + for i in ((0 + 1)...self.numHidden).reversed().dropLast() { self.hiddenOutputCache[i] = sigmoid(self.hiddenOutputCache[i - 1]) } self.hiddenOutputCache[0] = 1.0 case .RationalSigmoid: - for (var i = self.numHidden; i > 0; --i) { + for i in ((0 + 1)...self.numHidden).reversed().dropLast() { self.hiddenOutputCache[i] = rationalSigmoid(self.hiddenOutputCache[i - 1]) } self.hiddenOutputCache[0] = 1.0 case .HyperbolicTangent: - for (var i = self.numHidden; i > 0; --i) { + for i in ((0 + 1)...self.numHidden).reversed().dropLast() { self.hiddenOutputCache[i] = hyperbolicTangent(self.hiddenOutputCache[i - 1]) } self.hiddenOutputCache[0] = 1.0 @@ -480,7 +482,7 @@ public extension FFNN { } /// Applies the activation function to the output layer nodes. - private func activateOutput() { + fileprivate func activateOutput() { switch self.activationFunction { case .None: for i in 0.. Float { + fileprivate func activationDerivative(_ output: Float) -> Float { switch self.activationFunction { case .None: return 0.0 @@ -543,7 +545,7 @@ public extension FFNN { } /// Randomizes all of the network's weights, from each layer. - private func randomizeWeights() { + fileprivate func randomizeWeights() { for i in 0.. Float { +private func randomWeight(numInputNodes: Int) -> Float { let range = 1 / sqrt(Float(numInputNodes)) let rangeInt = UInt32(2_000_000 * range) let randomFloat = Float(arc4random_uniform(rangeInt)) - Float(rangeInt / 2) @@ -568,48 +570,48 @@ private func randomWeight(numInputNodes numInputNodes: Int) -> Float { // MARK: Error Functions -private func crossEntropy(a: Float, b: Float) -> Float { +private func crossEntropy(_ a: Float, b: Float) -> Float { return log(a) * b } // MARK: Activation Functions and Derivatives /// Linear activation function (raw sum) -private func linear(x: Float) -> Float { +private func linear(_ x: Float) -> Float { return x } /// Derivative for the linear activation function -private func linearDerivative(y: Float) -> Float { +private func linearDerivative(_ y: Float) -> Float { return 1.0 } /// Sigmoid activation function -private func sigmoid(x: Float) -> Float { +private func sigmoid(_ x: Float) -> Float { return 1 / (1 + exp(-x)) } /// Derivative for the sigmoid activation function -private func sigmoidDerivative(y: Float) -> Float { +private func sigmoidDerivative(_ y: Float) -> Float { return y * (1 - y) } /// Rational sigmoid activation function -private func rationalSigmoid(x: Float) -> Float { +private func rationalSigmoid(_ x: Float) -> Float { return x / (1.0 + sqrt(1.0 + x * x)) } /// Derivative for the rational sigmoid activation function -private func rationalSigmoidDerivative(y: Float) -> Float { +private func rationalSigmoidDerivative(_ y: Float) -> Float { let x = -(2 * y) / (y * y - 1) return 1 / ((x * x) + sqrt((x * x) + 1) + 1) } /// Hyperbolic tangent activation function -private func hyperbolicTangent(x: Float) -> Float { +private func hyperbolicTangent(_ x: Float) -> Float { return tanh(x) } /// Derivative for the hyperbolic tangent activation function -private func hyperbolicTangentDerivative(y: Float) -> Float { +private func hyperbolicTangentDerivative(_ y: Float) -> Float { return 1 - (y * y) } diff --git a/HandWriting-Learner/Matrix.swift b/HandWriting-Learner/Swift-AI/Matrix.swift similarity index 77% rename from HandWriting-Learner/Matrix.swift rename to HandWriting-Learner/Swift-AI/Matrix.swift index 093dd1e..889ae67 100755 --- a/HandWriting-Learner/Matrix.swift +++ b/HandWriting-Learner/Swift-AI/Matrix.swift @@ -7,27 +7,27 @@ import Accelerate -public class Matrix { +open class Matrix { - public let columns: Int - public let rows: Int - public let shape: (Int, Int) - public let size: Int + open let columns: Int + open let rows: Int + open let shape: (Int, Int) + open let size: Int var flat: Vector - public var vectorView: Vector { + open var vectorView: Vector { get { return self.flat } } - public var description: String { + open var description: String { get { return self.flat.flat.description } } - public var transpose: Matrix { + open var transpose: Matrix { get { let m = Matrix(rows: self.columns, columns: self.rows) vDSP_mtransD(self.flat.flat, 1, &m.flat.flat, 1, vDSP_Length(self.rows), vDSP_Length(self.columns)) @@ -44,7 +44,7 @@ public class Matrix { } /// Returns/sets the item at the given row and column index. - public subscript(row: Int, column: Int) -> Double { + open subscript(row: Int, column: Int) -> Double { get { return self.flat.flat[row * self.columns + column] } @@ -56,9 +56,9 @@ public class Matrix { // TODO: Guard against invalid indices for row/column accessors. /// Returns the receiver's row at the given index. - public func row(index: Int) -> Vector { + open func row(_ index: Int) -> Vector { var v = self.flat.flat - var r = [Double](count: self.columns, repeatedValue: 0) + var r = [Double](repeating: 0, count: self.columns) for column in 0.. Vector{ + open func column(_ index: Int) -> Vector{ var v = self.flat.flat - var c = [Double](count: self.rows, repeatedValue: 0) + var c = [Double](repeating: 0, count: self.rows) for row in 0.. Matrix { + open func copy() -> Matrix { let c = Matrix(rows: self.rows, columns: self.columns) c.flat = self.flat.copy() return c } -} \ No newline at end of file +} diff --git a/HandWriting-Learner/Operations.swift b/HandWriting-Learner/Swift-AI/Operations.swift similarity index 88% rename from HandWriting-Learner/Operations.swift rename to HandWriting-Learner/Swift-AI/Operations.swift index a7690ba..105a5d4 100755 --- a/HandWriting-Learner/Operations.swift +++ b/HandWriting-Learner/Swift-AI/Operations.swift @@ -11,7 +11,7 @@ import Accelerate /// Vector negation (negates each element of the receiver). public prefix func - (m: Vector) -> Vector { - var n = [Double](count: m.size, repeatedValue: 0.0) + var n = [Double](repeating: 0.0, count: m.size) vDSP_vnegD(m.flat, 1, &n, 1, vDSP_Length(m.size)) let v = Vector(size: m.size) v.flat = n @@ -20,7 +20,7 @@ public prefix func - (m: Vector) -> Vector { /// Vector addition. public func + (lhs: Vector, rhs: Vector) -> Vector { - var s = [Double](count: lhs.size, repeatedValue: 0.0) + var s = [Double](repeating: 0.0, count: lhs.size) vDSP_vaddD(lhs.flat, 1, rhs.flat, 1, &s, 1, vDSP_Length(lhs.size)) let v = Vector(size: lhs.size) v.flat = s @@ -29,7 +29,7 @@ public func + (lhs: Vector, rhs: Vector) -> Vector { /// Vector subtraction. public func - (lhs: Vector, rhs: Vector) -> Vector { - var s = [Double](count: lhs.size, repeatedValue: 0.0) + var s = [Double](repeating: 0.0, count: lhs.size) vDSP_vsubD(rhs.flat, 1, lhs.flat, 1, &s, 1, vDSP_Length(lhs.size)) let v = Vector(size: lhs.size) v.flat = s @@ -39,7 +39,7 @@ public func - (lhs: Vector, rhs: Vector) -> Vector { /// Element-wise vector addition. public func + (lhs: Vector, rhs: Double) -> Vector { var scalar = rhs - var s = [Double](count: lhs.size, repeatedValue: 0.0) + var s = [Double](repeating: 0.0, count: lhs.size) vDSP_vsaddD(lhs.flat, 1, &scalar, &s, 1, vDSP_Length(lhs.size)) let v = Vector(size: lhs.size) v.flat = s @@ -48,7 +48,7 @@ public func + (lhs: Vector, rhs: Double) -> Vector { /// Element-wise vector subtraction. public func - (lhs: Vector, rhs: Double) -> Vector { - var s = [Double](count: lhs.size, repeatedValue: 0.0) + var s = [Double](repeating: 0.0, count: lhs.size) var scalar = -rhs vDSP_vsaddD(lhs.flat, 1, &scalar, &s, 1, vDSP_Length(lhs.size)) let v = Vector(size: lhs.size) @@ -58,7 +58,7 @@ public func - (lhs: Vector, rhs: Double) -> Vector { /// Element-wise vector multiplication. public func * (lhs: Vector, rhs: Double) -> Vector { - var s = [Double](count: lhs.size, repeatedValue: 0.0) + var s = [Double](repeating: 0.0, count: lhs.size) var scalar = -rhs vDSP_vsmulD(lhs.flat, 1, &scalar, &s, 1, vDSP_Length(lhs.size)) let v = Vector(size: lhs.size) @@ -68,7 +68,7 @@ public func * (lhs: Vector, rhs: Double) -> Vector { /// Element-wise vector division. public func / (lhs: Vector, rhs: Double) -> Vector { - var s = [Double](count: lhs.size, repeatedValue: 0.0) + var s = [Double](repeating: 0.0, count: lhs.size) var scalar = -rhs vDSP_vsdivD(lhs.flat, 1, &scalar, &s, 1, vDSP_Length(lhs.size)) let v = Vector(size: lhs.size) @@ -112,7 +112,7 @@ public func * (lhs: Matrix, rhs: Matrix) -> Matrix { assert(lhs.columns == rhs.rows, "Matrix sizes don't match") let v1 = lhs.flat.flat let v2 = rhs.flat.flat - var r = [Double](count: lhs.rows * rhs.columns, repeatedValue: 1) + var r = [Double](repeating: 1, count: lhs.rows * rhs.columns) vDSP_mmulD(v1, 1, v2, 1, &r, 1, vDSP_Length(lhs.rows), vDSP_Length(rhs.columns), vDSP_Length(rhs.rows)) let m = Matrix(rows: lhs.rows, columns: rhs.columns) m.flat.flat = r diff --git a/HandWriting-Learner/README.md b/HandWriting-Learner/Swift-AI/README.md similarity index 100% rename from HandWriting-Learner/README.md rename to HandWriting-Learner/Swift-AI/README.md diff --git a/HandWriting-Learner/Random.swift b/HandWriting-Learner/Swift-AI/Random.swift similarity index 80% rename from HandWriting-Learner/Random.swift rename to HandWriting-Learner/Swift-AI/Random.swift index 8f67256..17a70ed 100755 --- a/HandWriting-Learner/Random.swift +++ b/HandWriting-Learner/Swift-AI/Random.swift @@ -12,7 +12,7 @@ class Random { static let maximumInteger = UInt32.max - static func random(number : UInt32) -> UInt32 { + static func random(_ number : UInt32) -> UInt32 { return arc4random_uniform(number) } @@ -20,7 +20,7 @@ class Random { return Double(random(maximumInteger))/Double(maximumInteger) } - static func randomBounded(min: Double, max: Double) -> Double { + static func randomBounded(_ min: Double, max: Double) -> Double { let amplitude = max - min let offset = min return offset + amplitude * Random.numberFromZeroToOne() diff --git a/HandWriting-Learner/Storage.swift b/HandWriting-Learner/Swift-AI/Storage.swift similarity index 61% rename from HandWriting-Learner/Storage.swift rename to HandWriting-Learner/Swift-AI/Storage.swift index 2a5f203..73e93a2 100755 --- a/HandWriting-Learner/Storage.swift +++ b/HandWriting-Learner/Swift-AI/Storage.swift @@ -14,10 +14,10 @@ import Foundation public protocol Storage { - typealias StorageType - func writeToFile(filename: String) - func writeToFile(url: NSURL) - static func fromFile(filename: String) -> StorageType? - static func fromFile(url: NSURL) -> StorageType? + associatedtype StorageType + func writeToFile(_ filename: String) + func writeToFile(_ url: URL) + static func fromFile(_ filename: String) -> StorageType? + static func fromFile(_ url: URL) -> StorageType? -} \ No newline at end of file +} diff --git a/HandWriting-Learner/Vector.swift b/HandWriting-Learner/Swift-AI/Vector.swift similarity index 80% rename from HandWriting-Learner/Vector.swift rename to HandWriting-Learner/Swift-AI/Vector.swift index d8eb58c..ee363d2 100755 --- a/HandWriting-Learner/Vector.swift +++ b/HandWriting-Learner/Swift-AI/Vector.swift @@ -8,13 +8,13 @@ import Accelerate -public class Vector { +open class Vector { /// The vector as an array of `Double`. var flat = [Double]() /// Converts the receiver into a `Matrix` with one row and `size` columns. - public var matrixView: Matrix { + open var matrixView: Matrix { get { let m = Matrix(rows: 1, columns: self.size) m.flat.flat = self.flat @@ -23,25 +23,25 @@ public class Vector { } /// The size of the vector (total number of elements). - public var size: Int { + open var size: Int { get { return self.flat.count } } /// The textual representation of the vector. - public var description: String { + open var description: String { get { return self.flat.description } } public init(size: Int) { - self.flat = [Double](count: size, repeatedValue: 0.0) + self.flat = [Double](repeating: 0.0, count: size) } /// Returns/sets the element value at the given index. - public subscript(index: Int) -> Double { + open subscript(index: Int) -> Double { get { return self.flat[index] } @@ -52,17 +52,17 @@ public class Vector { // TODO: Finish this. /// Computes the dot product of the receiver with another vector. - public func dot(v: Vector) -> Double { + open func dot(_ v: Vector) -> Double { var c: Double = 0.0 vDSP_dotprD(self.flat, 1, self.flat, 1, &c, vDSP_Length(self.size)) return 0.0 } /// Returns a new `Vector` that is a copy of the receiver. - public func copy() -> Vector { + open func copy() -> Vector { let v = Vector(size: self.size) v.flat = self.flat return v } -} \ No newline at end of file +} diff --git a/HandWriting-Learner/UIImage+Processing.swift b/HandWriting-Learner/UIImage-Extension/UIImage+Processing.swift similarity index 63% rename from HandWriting-Learner/UIImage+Processing.swift rename to HandWriting-Learner/UIImage-Extension/UIImage+Processing.swift index c3ee6a5..31e35bb 100644 --- a/HandWriting-Learner/UIImage+Processing.swift +++ b/HandWriting-Learner/UIImage-Extension/UIImage+Processing.swift @@ -8,10 +8,10 @@ extension UIImage { let height = Int(self.size.height) var pixelsArray = [Float]() - let pixelData = CGDataProviderCopyData(CGImageGetDataProvider(self.CGImage)) + let pixelData = self.cgImage?.dataProvider?.data let data: UnsafePointer = CFDataGetBytePtr(pixelData) - let bytesPerRow = CGImageGetBytesPerRow(self.CGImage) - let bytesPerPixel = (CGImageGetBitsPerPixel(self.CGImage) / 8) + let bytesPerRow = self.cgImage?.bytesPerRow + let bytesPerPixel = ((self.cgImage?.bitsPerPixel)! / 8) var position = 0 for _ in 0.. UIImage { + func toSize(_ newSize: CGSize) -> UIImage { UIGraphicsBeginImageContext(newSize) - self.drawInRect(CGRectMake(0, 0, newSize.width, newSize.height)) + self.draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)) let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() - return newImage + return newImage! } // Extract sub image based on the given frame // x,y top left | x,y bottom right | pixels margin above this frame - func extractFrame(var topLeft: CGPoint, var bottomRight: CGPoint, pixelMargin: CGFloat) ->UIImage { + func extractFrame(_ topLeft: CGPoint, bottomRight: CGPoint, pixelMargin: CGFloat) ->UIImage { + var topLeft = topLeft, bottomRight = bottomRight topLeft.x = topLeft.x - pixelMargin topLeft.y = topLeft.y - pixelMargin bottomRight.x = bottomRight.x + pixelMargin bottomRight.y = bottomRight.y + pixelMargin - let size:CGSize = CGSizeMake(bottomRight.x-topLeft.x, bottomRight.y-topLeft.y) - let rect = CGRectMake(-topLeft.x, -topLeft.y, self.size.width, self.size.height) + let size:CGSize = CGSize(width: bottomRight.x-topLeft.x, height: bottomRight.y-topLeft.y) + let rect = CGRect(x: -topLeft.x, y: -topLeft.y, width: self.size.width, height: self.size.height) UIGraphicsBeginImageContext(size) - self.drawInRect(rect) + self.draw(in: rect) let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() - return newImage + return newImage! } -} \ No newline at end of file +} diff --git a/HandWriting-Learner/ViewController.swift b/HandWriting-Learner/ViewController.swift index f56ea42..9a0f145 100644 --- a/HandWriting-Learner/ViewController.swift +++ b/HandWriting-Learner/ViewController.swift @@ -43,7 +43,7 @@ class ViewController: UIViewController,UITableViewDataSource { let framedImg = self.framedImg() - let resizedImg = framedImg!.toSize(CGSizeMake(30, 30)) + let resizedImg = framedImg!.toSize(CGSize(width: 30, height: 30)) return (resizedImg.pixelsArray().pixelValues,resizedImg) } @@ -56,8 +56,8 @@ class ViewController: UIViewController,UITableViewDataSource { let freeSpaceAroundDrawinFrame:CGFloat = 5.0 - let topLeft = CGPointMake(CGFloat(xPoints.minElement()!), CGFloat(yPoints.minElement()!)) - let bottomRight = CGPointMake(CGFloat(xPoints.maxElement()!), CGFloat(yPoints.maxElement()!)) + let topLeft = CGPoint(x: CGFloat(xPoints.min()!), y: CGFloat(yPoints.min()!)) + let bottomRight = CGPoint(x: CGFloat(xPoints.max()!), y: CGFloat(yPoints.max()!)) return self.imageView.image!.extractFrame(topLeft,bottomRight: bottomRight, pixelMargin: freeSpaceAroundDrawinFrame) } @@ -91,11 +91,11 @@ class ViewController: UIViewController,UITableViewDataSource { } let trainingValues = FFNNManager.instance.trainNetwork(pixelArray!, answer:answer, epoch: 1) - let result = String(trainingValues.output) + let result = String(describing: trainingValues.output) self.result.text = result - historyDatasource.insert(NSDictionary(dictionaryLiteral: ("img",img),("result",result)), atIndex: 0) + historyDatasource.insert(NSDictionary(dictionaryLiteral: ("img",img),("result",result)), at: 0) tableView.reloadData() @@ -122,7 +122,7 @@ class ViewController: UIViewController,UITableViewDataSource { print(output) - let maxValue = output.maxElement()! + let maxValue = output.max()! // Not sure enough if maxValue < 0.8 { @@ -132,7 +132,7 @@ class ViewController: UIViewController,UITableViewDataSource { // Convert the max value (> 0.8) to int to get // the index value of the switch - let index = output.indexOf(maxValue)! as Int + let index = output.index(of: maxValue)! as Int self.segmented.selectedSegmentIndex = index @@ -153,22 +153,22 @@ class ViewController: UIViewController,UITableViewDataSource { //MARK: UI Actions - @IBAction func trainingModeSwitchDidChangeValue(sender: UISwitch) { + @IBAction func trainingModeSwitchDidChangeValue(_ sender: UISwitch) { - if sender.on { + if sender.isOn { actionButton.title = "Train" }else { actionButton.title = "Ask" } - self.tableView.hidden = !sender.on - self.result.hidden = !sender.on + self.tableView.isHidden = !sender.isOn + self.result.isHidden = !sender.isOn } - @IBAction func actionBtnClicked(sender: AnyObject) { + @IBAction func actionBtnClicked(_ sender: AnyObject) { - if trainingSwitch.on { + if trainingSwitch.isOn { trainNeurons() }else{ askNeurons() @@ -184,10 +184,10 @@ class ViewController: UIViewController,UITableViewDataSource { } - @IBAction func saveNetworkBtnClicked(sender: AnyObject) { + @IBAction func saveNetworkBtnClicked(_ sender: AnyObject) { self.imageView.image = nil FFNNManager.instance.saveNetworkWithName(currentNetworkName) - self.dismissViewControllerAnimated(true, completion: nil) + self.dismiss(animated: true, completion: nil) } @@ -197,7 +197,7 @@ class ViewController: UIViewController,UITableViewDataSource { // Do any additional setup after loading the view, typically from a nib self.imageView.layer.borderWidth = 1.0 - self.imageView.layer.borderColor = UIColor.grayColor().CGColor + self.imageView.layer.borderColor = UIColor.gray.cgColor self.tableView.dataSource = self @@ -211,50 +211,50 @@ class ViewController: UIViewController,UITableViewDataSource { //MARK: TableView Datasource - func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCellWithIdentifier("historyCell", forIndexPath: indexPath) as! HistoryTableViewCell + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "historyCell", for: indexPath) as! HistoryTableViewCell let dico = historyDatasource[indexPath.row] cell.sentImg.image = dico["img"] as? UIImage cell.sentImg.layer.borderWidth = 1.0 - cell.sentImg.layer.borderColor = UIColor.grayColor().CGColor + cell.sentImg.layer.borderColor = UIColor.gray.cgColor cell.predictionLabel.text = dico["result"] as? String return cell } - func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return historyDatasource.count } //MARK: Drawing methods - override func touchesBegan(touches: Set, - withEvent event: UIEvent?){ + override func touchesBegan(_ touches: Set, + with event: UIEvent?){ isSwiping = false let touch = touches.first - lastPoint = touch!.locationInView(imageView) + lastPoint = touch!.location(in: imageView) xPoints.append(Float(lastPoint.x)) yPoints.append(Float(lastPoint.y)) } - override func touchesMoved(touches: Set, - withEvent event: UIEvent?){ + override func touchesMoved(_ touches: Set, + with event: UIEvent?){ isSwiping = true; let touch = touches.first - let currentPoint = touch!.locationInView(imageView) + let currentPoint = touch!.location(in: imageView) UIGraphicsBeginImageContext(self.imageView.frame.size) - self.imageView.image?.drawInRect(CGRectMake(0, 0, self.imageView.frame.size.width, self.imageView.frame.size.height)) - CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y) - CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), currentPoint.x, currentPoint.y) - CGContextSetLineCap(UIGraphicsGetCurrentContext(),CGLineCap.Round) - CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 9.0) - CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(),0.0, 0.0, 0.0, 1.0) - CGContextStrokePath(UIGraphicsGetCurrentContext()) + self.imageView.image?.draw(in: CGRect(x: 0, y: 0, width: self.imageView.frame.size.width, height: self.imageView.frame.size.height)) + UIGraphicsGetCurrentContext()?.move(to: CGPoint(x: lastPoint.x, y: lastPoint.y)) + UIGraphicsGetCurrentContext()?.addLine(to: CGPoint(x: currentPoint.x, y: currentPoint.y)) + UIGraphicsGetCurrentContext()?.setLineCap(CGLineCap.round) + UIGraphicsGetCurrentContext()?.setLineWidth(9.0) + UIGraphicsGetCurrentContext()?.setStrokeColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0) + UIGraphicsGetCurrentContext()?.strokePath() self.imageView.image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() lastPoint = currentPoint @@ -262,18 +262,18 @@ class ViewController: UIViewController,UITableViewDataSource { yPoints.append(Float(lastPoint.y)) } - override func touchesEnded(touches: Set, - withEvent event: UIEvent?){ + override func touchesEnded(_ touches: Set, + with event: UIEvent?){ if(!isSwiping) { // This is a single touch, draw a point UIGraphicsBeginImageContext(self.imageView.frame.size) - self.imageView.image?.drawInRect(CGRectMake(0, 0, self.imageView.frame.size.width, self.imageView.frame.size.height)) - CGContextSetLineCap(UIGraphicsGetCurrentContext(), CGLineCap.Round) - CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 9.0) - CGContextSetRGBStrokeColor(UIGraphicsGetCurrentContext(), 0.0, 0.0, 0.0, 1.0) - CGContextMoveToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y) - CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), lastPoint.x, lastPoint.y) - CGContextStrokePath(UIGraphicsGetCurrentContext()) + self.imageView.image?.draw(in: CGRect(x: 0, y: 0, width: self.imageView.frame.size.width, height: self.imageView.frame.size.height)) + UIGraphicsGetCurrentContext()?.setLineCap(CGLineCap.round) + UIGraphicsGetCurrentContext()?.setLineWidth(9.0) + UIGraphicsGetCurrentContext()?.setStrokeColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0) + UIGraphicsGetCurrentContext()?.move(to: CGPoint(x: lastPoint.x, y: lastPoint.y)) + UIGraphicsGetCurrentContext()?.addLine(to: CGPoint(x: lastPoint.x, y: lastPoint.y)) + UIGraphicsGetCurrentContext()?.strokePath() self.imageView.image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() } diff --git a/HandWriting-LearnerTests/HandWriting_LearnerTests.swift b/HandWriting-LearnerTests/HandWriting_LearnerTests.swift index 51a95c1..3b9a591 100644 --- a/HandWriting-LearnerTests/HandWriting_LearnerTests.swift +++ b/HandWriting-LearnerTests/HandWriting_LearnerTests.swift @@ -28,7 +28,7 @@ class HandWriting_LearnerTests: XCTestCase { func testPerformanceExample() { // This is an example of a performance test case. - self.measureBlock { + self.measure { // Put the code you want to measure the time of here. } }