人工智能实战——人工神经网络(C库iOS交叉编译)

人工智能实战——人工神经网络
更新:项目源码已上传至github:  https://github.com/zhanganyu/XYRobot

最近人工智能辣摸火,让我忍不住也想一探究竟,首先从目前来讲我是一个人工智能领域的门外汉,尽管恶补了几天基本知识,机器学习算法看得差不多,神马线性回归啊,逻辑回归啊,SVM啊,k临近算法啊,决策树啊,随机森林啊,看的我吐血了,不过也不难理解,然后尝试着用一套开源代码FANN(c语言跨平台库)编译到ios中 写一个简单的小机器人

首先是准备工作,把FANN2.2.0的源代码编译成ios 64bit 模拟器的dylib库,其过程不聊了,用到了cmake...等等...

拿到dylib文件后,再拷贝所有的.h到项目中,配置好头文件搜索路径,import一下“fann.h”  编译一下,成功的话 就可以开始写我的小机器人了~~起个名字:小歪
 
人工智能实战——人工神经网络(C库iOS交叉编译)_第1张图片

竟然一次性编译成功,模拟器跑起来了,不过什么代码也没写,先不激动,先写好工具类嘛。
先new一个工具类,用来初始化和保存大脑,并且还要能够训练,执行

//

//  XYRobotManager.h

//  小歪

//

//  Created by reese on 16/3/21.

//  Copyright © 2016 com.ifenduo. All rights reserved.

//


#import 

#include "fann.h"


@interface XYRobotManager : NSObject



//神经网络层数

@property (nonatomicint neuralLayerNumber;


//隐藏神经元个数 (中间层)

@property (nonatomicint hiddenNeuralNumber;


//输入原件个数

@property (nonatomicint inputNum;


//输出原件个数

@property (nonatomicint outputNum;


//预期错误均方差

@property (nonatomicfloat desiredError;


//训练数据存储路径

@property (nonatomicNSString* trainDataPath;


//神经网络保存路径

@property (nonatomicNSString* networkSavingPath;

 

//单例获取

+ (instancetype)sharedManager;


//创建大脑

- (void)createBrain;


//保存大脑

- (void)saveBrain;


//训练

- (void)trainInputDatas:(fann_type *)inputData outputDatas:(fann_type *)outputData dataCount:(int)dataCount;


//执行

- (NSArray *)runInputDatas:(fann_type *)inputData;

 

这里关于NN的配置没有写死,也就是说具体使用的时候是要能修改滴。默认情况下呢,在单例构造完之后设置一下这些配置参数的默认值:

//

//  XYRobotManager.m

//  小歪

//

//  Created by reese on 16/3/21.

//  Copyright © 2016 com.ifenduo. All rights reserved.

//


#import "XYRobotManager.h"


@implementation XYRobotManager
 

//静态c指针 神经网络对象

static struct fann *ann;


+ (instancetype)sharedManager {

    static XYRobotManager *_inst;

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

        _inst = [XYRobotManager new];

        

        //配置神经网络初始参数

        [_inst initConfig];

    });

    return _inst;

}


- (void)initConfig {

    //3层神经元

    _neuralLayerNumber = 3;

    

    //96个内部神经元

    _hiddenNeuralNumber = 96;

    

    //2个输入

    _inputNum = 2;

    

    //1个输出

    _outputNum = 1;

    

    //预期错误均方差

    _desiredError = 0.01;


    //训练数据保存路径

    _trainDataPath = [[XYRobotManager dataPathstringByAppendingPathComponent:@"train.data"];

    

    //大脑保存路径

    _networkSavingPath = [[XYRobotManager dataPath]stringByAppendingPathComponent:@"brain.net"];

    

    

}


+ (NSString *)cachePath {

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectoryNSUserDomainMask,YES);

    

    NSString *documentsDirectory = [paths objectAtIndex:0];

    

    NSString *cacheFolder = [documentsDirectory stringByAppendingPathComponent:@"XYRobot"];

    

    if (![[NSFileManager defaultManagerfileExistsAtPath:cacheFolder]) {

        //如果不存在该文件夹 新建

        [[NSFileManager defaultManagercreateDirectoryAtPath:cacheFolderwithIntermediateDirectories:YES attributes:nil error:nil];

    }

    return cacheFolder;

}


+ (NSString *)dataPath {

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectoryNSUserDomainMask,YES);

    

    NSString *documentsDirectory = [paths objectAtIndex:0];

    

    NSString *dataPath = [documentsDirectory stringByAppendingPathComponent:@"XYRobot"];

    

    if (![[NSFileManager defaultManagerfileExistsAtPath:dataPath]) {

        //如果不存在该文件夹 新建

        [[NSFileManager defaultManagercreateDirectoryAtPath:dataPathwithIntermediateDirectories:YES attributes:nil error:nil];

    }

    return dataPath;

}

 
如果不知道这些参数的意义,说下,默认情况下这个机器人拥有2个输入元件(输入层用来感知环境,好比人的听觉触觉嗅觉)和一个输出元件(表达层用来反馈环境,比如说话),这些元件的输入可以是任意的float数,中间层有96个逻辑组件,相当于一堆神经元,以及各自的连接弧,第一层神经元每个组件的输入是所有连接到这个元件的输入元件的值*权重之和(就叫加权输入/输出吧),下层神经元中组件的加权输出会作为直接连接的上层神经元对应组件的加权输入,最后一层神经元的加权输出会作为表达层每个元件的加权输入…………不过如果不知道神马是NN的话,估计我说了也白说^^

配置好以后得初始化 ,我已经声明好了ann这样一个c指针,可以认为他是一个对象,所以在使用这个工具类之前必须要调用创建大脑方法,否则ann是一个空指针。

创建大脑的方法:


- (void)createBrain {

    NSLog(@"初始化神经网络");

    

    //如果之前保存过,从文件读取

    if ([[NSFileManager defaultManagerfileExistsAtPath:_networkSavingPath]) {

        ann = fann_create_from_file([_networkSavingPathcStringUsingEncoding:NSUTF8StringEncoding]);

    } else {

    

        //创建标准神经网络,配置好对应的参数

        ann =fann_create_standard(_neuralLayerNumber,_inputNum,_hiddenNeuralNumber,_outputNum);

        //给所有元件初始权重(-1~1间的随机值)

        fann_randomize_weights(ann, -1, 1);

        //设置中间层的激活坡度(0~1之间)

        fann_set_activation_steepness_hidden(ann, 1);

        //设置输出层的激活坡度(0~1之间)

        fann_set_activation_steepness_output(ann, 1);

        

        //设置激活函数(激活函数有很多种,具体每一种的效果还没去试)

        fann_set_activation_function_hidden(annFANN_SIGMOID_SYMMETRIC);

        fann_set_activation_function_output(annFANN_SIGMOID_SYMMETRIC);

    }

    

    

}



以及保存大脑的方法(总不能关闭app之后再打开,机器人变了一个"人"对吧)

- (void)saveBrain {

    fann_save(ann, [_networkSavingPath cStringUsingEncoding:NSUTF8StringEncoding]);

}

 接下来要开始训练了,如果不训练的话,这个机器人的输出是随机的。训练过程,这里相当于是监督式学习,是机器学习必不可少的一个阶段,必须要有训练数据才能合理分配每一个神经元/输入组件的权重。
 也就是说我们假设一个目标函数 y = x1 == x2 
这个目标函数符合这个程序的默认设定-->2个输入信号,一个输出信号,实际上就是最简单的让这堆神经元自己去学如何判断两个数相等啊,我们在代码里面绝对没有告诉他,x1==x2,让他自己总结出规律来给出正确的Y

由于输出组件的值域为[-1,1],因此我们通常用来表示概率,或者用于分类(识别)

输出信号在初始状态下得到的是随机的,因为权重是随机分配的,辣么我们得告诉他正确的答案。告诉他一组正确答案,他就得重新调校自己各个组件的权重,最终达到接近于全等运算的过程。也就是函数逼近y' = x1 ? x2 ,?就相当于机器人自己总结出来的规律,函数y'在训练集无穷大的时候无限逼近原始目标函数y= x1 == x2, 这个过程在统计学中叫做拟合。

训练函数如下,前面说了机器学习需要监督式学习,训练的结果需要在训练过程中给出,因此这个函数既有输入也有输出,输出是我们告诉他应该输出的值,如果这个值和他当前的凭感觉输出差太远,则需要不停地训练,一直到拟合我们的输出,当所有元件(专业说法叫选择器)的平均方差值达到我们的预期_desiredError时终止训练:


//inputDataoutputDatasfloat[]类型的数组

- (void)trainInputDatas:(fann_type *)inputData outputDatas:(fann_type *)outputData dataCount:(int)dataCount {

    

    //设置训练算法

    fann_set_training_algorithm(annFANN_TRAIN_RPROP);

    

    //设置终止条件为bit fail

    fann_set_train_stop_function(annFANN_STOPFUNC_BIT);

    

    //设置bit fail limit 即所有训练结果中与预期不符合的个数上限,超出这个上限会一直训练

    fann_set_bit_fail_limit(ann, 1.0f);

    

    //设置训练速度(0~1之间,0为不加速)

    fann_set_learning_momentum(ann, 0.4f);

    

    //由当前函数传递的输入生成一个c指针

    struct fann_train_data* trainData = fann_create_train_from_callback(dataCount, _inputNum,_outputNumcallback);

    

    trainData->input=&inputData;

    trainData->output=&outputData;


    //之前是否存储过训练数据,如果有,和现在的合并(merge)

    if ([[NSFileManager defaultManagerfileExistsAtPath:_trainDataPath]) {

        //已经存储好的训练数据

        struct fann_train_data* trainDataSaved = fann_read_train_from_file([_trainDataPathcStringUsingEncoding:NSUTF8StringEncoding]);

        //合并

        trainData = fann_merge_train_data(trainData, trainDataSaved);

    }

    

    //开始训练,最大次数3000,输出日志间隔:每次都输出 如果不需要重复训练,预期均方差设置为-1即可

    fann_train_on_data(ann, trainData, 3000, 1, _desiredError);

    

    //保存训练数据

    fann_save_train(trainData, [_trainDataPath cStringUsingEncoding:NSUTF8StringEncoding]);

    

    NSLog(@"训练数据路径%@",_trainDataPath);

}



 接下来是执行函数,当我们人为地训练一段时间以后,给一组之前训练中没有训练过的输入,来看他的输出是多少,如果这个神经网络学会了一套规律,那么假设我们给他123 123,他的输出应该是1,假设给他123 100 它的输出应该是0


执行函数:

- (NSArray *)runInputDatas:(fann_type *)inputData {

    fann_type* output = fann_run(ann, inputData);

    

    NSMutableArray* array = [NSMutableArray arrayWithCapacity:_outputNum];

    for (int i = 0; i<_outputNum ; i++) {

        [array addObject:@(output[i])];

        

    }

    return array;

} 

 

代码写完了,让我们来校验一下:

需要一些界面,当然界面很简单,放3个文本框,分别当做输入源1,输入源2,输出源
再放一个label 用来表达这个系统自己的输出

人工智能实战——人工神经网络(C库iOS交叉编译)_第2张图片

controller代码:

//

//  ViewController.m

//  小歪

//

//  Created by reese on 16/3/21.

//  Copyright © 2016 com.ifenduo. All rights reserved.

//


#import "ViewController.h"

#import "XYRobotManager.h"



@interface ViewController ()


@property (weaknonatomicIBOutlet UITextField *input1;

@property (weaknonatomicIBOutlet UITextField *input2;

@property (weaknonatomicIBOutlet UITextField *output1;

@property (weaknonatomicIBOutlet UILabel *output;


@end


@implementation ViewController


- (void)viewDidLoad {

    [super viewDidLoad];


    //创建大脑

    [[XYRobotManager sharedManagercreateBrain];

    

}


- (IBAction)train:(id)sender {

    fann_type input[2] = {_input1.text.floatValue,_input2.text.floatValue};

    fann_type output[1] = {_output1.text.floatValue};

    

    [[XYRobotManager sharedManagertrainInputDatas:input outputDatas:output dataCount:1];

    

}

- (IBAction)test:(id)sender {

    fann_type input[2] = {_input1.text.floatValue,_input2.text.floatValue};

    NSArray *output = [[XYRobotManager sharedManagerrunInputDatas:input];

    [_output setText:[output.firstObject stringValue]];

}


@end


运行程序~~

启动后我们直接执行一下,比如让他判断9 和9是否相等?这个值应该是随机的

人工智能实战——人工神经网络(C库iOS交叉编译)_第3张图片

这样一个随机值说明这个系统处于完全空白状态!各个神经元之间的连接的权重是随机分配的。
辣么我们重启下程序看看是不是这样(没有调用saveBrain方法,那么每次重启app,启动的是新的大脑)


人工智能实战——人工神经网络(C库iOS交叉编译)_第4张图片

我重启了3次, 有一次是1,但是不要被迷惑。。。很可能那一次换两个数就不是1了,那个1只是这个大脑凭初始状态给出的而已,恰好是1

接下来我要教会他9和9就是相等!
那么输入源给2个9,输出给1,点击训练,然后观察控制台:

2016-03-21 10:16:57.359 小歪[1637:22219] 初始化神经网络

2016-03-21 10:29:33.054 小歪[1637:22219] 训练完成

Max epochs     3000. Desired error: 0.0099999998.

Epochs            1. Current error: 0.9998188019. Bit fail 0.

2016-03-21 10:29:33.056 小歪[1637:22219] 训练数据路径/Users/admin/Library/Developer/CoreSimulator/Devices/6233F208-E406-4601-987A-7997263386E5/data/Containers/Data/Application/7BAC0013-A13F-4547-A791-D3E2CB2B917E/Documents/XYRobot/train.data


Desired error: 0.0099999998. 我们的预期均方差是0.01,那么计算机经过这次训练之后的预期均方差是0.9998188019 ,但是根据我们的程序他并没有训练3000次,原因是为什么?因为我们设置了bit fail limit 为1,也就是说训练数据有一条出现误差可以终止训练,因此到这里就结束了。

那么经过了一次训练之后我们来测试下他的水平,让他判断几个数:
 
人工智能实战——人工神经网络(C库iOS交叉编译)_第5张图片人工智能实战——人工神经网络(C库iOS交叉编译)_第6张图片人工智能实战——人工神经网络(C库iOS交叉编译)_第7张图片


可见一次训练是不够的,目前它认为1000=1000, -5 !=-5 ,239=234

接下来我们进行第二次训练 :输入 80 80 输出1

2016-03-21 10:16:57.359 小歪[1637:22219] 初始化神经网络

2016-03-21 10:29:33.054 小歪[1637:22219] 训练完成

Max epochs     3000. Desired error: 0.0099999998.

Epochs            1. Current error: 0.9998188019. Bit fail 0.

2016-03-21 10:29:33.056 小歪[1637:22219] 训练数据路径/Users/admin/Library/Developer/CoreSimulator/Devices/6233F208-E406-4601-987A-7997263386E5/data/Containers/Data/Application/7BAC0013-A13F-4547-A791-D3E2CB2B917E/Documents/XYRobot/train.data

2016-03-21 10:39:58.155 小歪[1637:22219] 训练完成

Max epochs     3000. Desired error: 0.0099999998.

Epochs            1. Current error: 0.0000000000. Bit fail 0.

2016-03-21 10:39:58.158 小歪[1637:22219] 训练数据路径/Users/admin/Library/Developer/CoreSimulator/Devices/6233F208-E406-4601-987A-7997263386E5/data/Containers/Data/Application/7BAC0013-A13F-4547-A791-D3E2CB2B917E/Documents/XYRobot/train.data

 

观察控制台结果发现,仍然只训练了一次,但是这次预期均方差为0.01,实际训练结果为0.000000000的误差,由于达到了我们的预期均方差,所以停止了训练,看来他是有把握了?让我们来调戏下他,测试:
 人工智能实战——人工神经网络(C库iOS交叉编译)_第8张图片

严重通过不了调戏啊,-9和32不相等,而他给的结果无法让人信服,我们继续训练,88,88,输入填1,然后点击训练: 

2016-03-21 10:50:38.147 小歪[3451:52349] 训练完成

Max epochs     3000. Desired error: 0.0099999998.

Epochs            1. Current error: 0.2500000000. Bit fail 1.

Epochs            2. Current error: 0.0132444603. Bit fail 0.

2016-03-21 10:50:38.148  小歪 [3451:52349]  训练数据路径 /Users/admin/Library/Developer/CoreSimulator/Devices/6233F208-E406-4601-987A-7997263386E5/data/Containers/Data/Application/8614765A-20FA-43C8-90F6-A423B80B0235/Documents/XYRobot/train.data

 这次发现他训练了2次,第一次的训练后均方差0.25,判断失误1次,结果进行了第二次训练,第二次训练后均方差仍然不达标,但是判断失误为0,停止了训练,接下来给他更多的训练,比如-100 100 -1, 33 33 1:

2016-03-21 10:54:06.792 小歪[3451:52349] 训练完成

Max epochs     3000. Desired error: 0.0099999998.

Epochs            1. Current error: 0.0000000000. Bit fail 0.

2016-03-21 10:54:06.795 小歪[3451:52349] 训练数据路径/Users/admin/Library/Developer/CoreSimulator/Devices/6233F208-E406-4601-987A-7997263386E5/data/Containers/Data/Application/8614765A-20FA-43C8-90F6-A423B80B0235/Documents/XYRobot/train.data

2016-03-21 10:54:16.583 小歪[3451:52349] 训练完成

Max epochs     3000. Desired error: 0.0099999998.

Epochs            1. Current error: 0.1666666716. Bit fail 1.

Epochs            2. Current error: 0.1666666716. Bit fail 1.

Epochs            3. Current error: 0.3333332837. Bit fail 1.

Epochs            4. Current error: 0.3333333433. Bit fail 2.

Epochs            5. Current error: 0.3333333433. Bit fail 2.

Epochs            6. Current error: 0.3329019248. Bit fail 0.

2016-03-21 10:54:16.584 小歪[3451:52349] 训练数据路径/Users/admin/Library/Developer/CoreSimulator/Devices/6233F208-E406-4601-987A-7997263386E5/data/Containers/Data/Application/8614765A-20FA-43C8-90F6-A423B80B0235/Documents/XYRobot/train.data

2016-03-21 10:54:33.991 小歪[3451:52349] 训练完成

Max epochs     3000. Desired error: 0.0099999998.

Epochs            1. Current error: 0.2831817567. Bit fail 0.

2016-03-21 10:54:33.992 小歪[3451:52349] 训练数据路径/Users/admin/Library/Developer/CoreSimulator/Devices/6233F208-E406-4601-987A-7997263386E5/data/Containers/Data/Application/8614765A-20FA-43C8-90F6-A423B80B0235/Documents/XYRobot/train.data

2016-03-21 10:54:46.735 小歪[3451:52349] 训练完成

Max epochs     3000. Desired error: 0.0099999998.

Epochs            1. Current error: 0.3749229312. Bit fail 1.

Epochs            2. Current error: 0.3737220764. Bit fail 1.

Epochs            3. Current error: 0.2615829706. Bit fail 1.

Epochs            4. Current error: 0.2259529382. Bit fail 1.

Epochs            5. Current error: 0.2499959171. Bit fail 0.

2016-03-21 10:54:46.736 小歪[3451:52349] 训练数据路径/Users/admin/Library/Developer/CoreSimulator/Devices/6233F208-E406-4601-987A-7997263386E5/data/Containers/Data/Application/8614765A-20FA-43C8-90F6-A423B80B0235/Documents/XYRobot/train.data

2016-03-21 10:54:54.423 小歪[3451:52349] 训练完成

Max epochs     3000. Desired error: 0.0099999998.

Epochs            1. Current error: 0.3330805600. Bit fail 0.

2016-03-21 10:54:54.424  小歪 [3451:52349]  训练数据路径 /Users/admin/Library/Developer/CoreSimulator/Devices/6233F208-E406-4601-987A-7997263386E5/data/Containers/Data/Application/8614765A-20FA-43C8-90F6-A423B80B0235/Documents/XYRobot/train.data

2016-03-21 10:56:49.149 小歪[3451:52349] 训练完成

Max epochs     3000. Desired error: 0.0099999998.

Epochs            1. Current error: 0.4682762027. Bit fail 1.

Epochs            2. Current error: 0.5955896378. Bit fail 1.

Epochs            3. Current error: 0.5830196142. Bit fail 1.

Epochs            4. Current error: 0.3932408690. Bit fail 1.

Epochs            5. Current error: 0.3994615376. Bit fail 1.

Epochs            6. Current error: 0.3985652626. Bit fail 1.

Epochs            7. Current error: 0.4392893314. Bit fail 1.

Epochs            8. Current error: 0.2354802340. Bit fail 0.

2016-03-21 10:56:49.152 小歪[3451:52349] 训练数据路径/Users/admin/Library/Developer/CoreSimulator/Devices/6233F208-E406-4601-987A-7997263386E5/data/Containers/Data/Application/8614765A-20FA-43C8-90F6-A423B80B0235/Documents/XYRobot/train.data


经过几次加强训练后,我们再来检验下他的学习成果,判断下0.25 和0.25,39和93,3.9和3.3:
人工智能实战——人工神经网络(C库iOS交叉编译)_第9张图片人工智能实战——人工神经网络(C库iOS交叉编译)_第10张图片人工智能实战——人工神经网络(C库iOS交叉编译)_第11张图片

发现小歪现在对0.25和0.25相等比较确定,而对3.9和3.3,39和93相等持怀疑态度,因为训练强度还不够 所以他只能给一个自己模棱两可的答案~

接下来我给他更多的训练,并且将bit_limit设置为0,即一直训练到没有问题为止
第一次:

2016-03-21 11:06:16.181 小歪[4267:64763] 训练完成

Max epochs     3000. Desired error: 0.0099999998.

Epochs            1. Current error: 0.5999537110. Bit fail 24.

Epochs            2. Current error: 0.4960755408. Bit fail 24.

Epochs            3. Current error: 0.4582657814. Bit fail 24.

Epochs            4. Current error: 0.4193557203. Bit fail 24.

Epochs            5. Current error: 0.5826099515. Bit fail 24.
....

Epochs         2991. Current error: 0.0503493845. Bit fail 24.

Epochs         2992. Current error: 0.0503306128. Bit fail 24.

Epochs         2993. Current error: 0.0503247045. Bit fail 24.

Epochs         2994. Current error: 0.0502951257. Bit fail 24.

Epochs         2995. Current error: 0.0504412763. Bit fail 24.

Epochs         2996. Current error: 0.0503883027. Bit fail 24.

Epochs         2997. Current error: 0.0503233038. Bit fail 24.

Epochs         2998. Current error: 0.0503678434. Bit fail 24.

Epochs         2999. Current error: 0.0503562540. Bit fail 24.

Epochs         3000. Current error: 0.0503364615. Bit fail 24.

第n次

Epochs         2987. Current error: 0.0007500528. Bit fail 28.

Epochs         2988. Current error: 0.0005287924. Bit fail 28.

Epochs         2989. Current error: 0.0006041168. Bit fail 28.

Epochs         2990. Current error: 0.0004783150. Bit fail 28.

Epochs         2991. Current error: 0.0004799517. Bit fail 28.

Epochs         2992. Current error: 0.0004323115. Bit fail 28.

Epochs         2993. Current error: 0.0004352634. Bit fail 28.

Epochs         2994. Current error: 0.0004080634. Bit fail 28.

Epochs         2995. Current error: 0.0004139747. Bit fail 28.

Epochs         2996. Current error: 0.0003935657. Bit fail 28.

Epochs         2997. Current error: 0.0004007270. Bit fail 28.

Epochs         2998. Current error: 0.0003827212. Bit fail 28.

Epochs         2999. Current error: 0.0003909847. Bit fail 28.

Epochs         3000. Current error: 0.0003752450. Bit fail 28.


训练次数越多,小歪的信心应该是越高,那么现在再让他判断下99和99.999,99和99,5和5,5和4:



人工智能实战——人工神经网络(C库iOS交叉编译)_第12张图片人工智能实战——人工神经网络(C库iOS交叉编译)_第13张图片人工智能实战——人工神经网络(C库iOS交叉编译)_第14张图片人工智能实战——人工神经网络(C库iOS交叉编译)_第15张图片


我们设置的预期均方差是0.01,因此小歪给的答案恰好也逼近于正确答案+-0.01,然而我们整个程序并没有告诉他我们的规律是什么(我们在代码里面根本就没有写过x1和x2要全等~~),规律是他自己总结出来的,这就是机器学习的第一步~

今天的小歪训练的很累了,先训练到这里,训练的强度仍然相当低,因此还不足以应用。

至于3层神经网络,96个神经元能做什么,其实已经能做很多的事情了,然而我们还可以开辟更大的空间给小歪~~

下次可能给他N个输入和N个输出,也不一定~
今天的实战就到这里^^

你可能感兴趣的:(Objective-C进阶)