最近做了一个课程作业,用matlab编程一个分类器,由于是控制和弱电出身,
对于这种数据处理的程序不是很熟悉,所以很是费劲,而这个作业数据只是两维的特征,最后出来的效果还是不如matlab自带的程序。
深感机器学习的算法非常抽象,而且实际实现时,有很多细节资料上没有提到。
而且如果要优化算法效率、提高适用性和可靠性,更是要对算法理解很透彻,感觉这点很难,因为对数学要求特别高,而且数据特征维数大了,根本不知道该如何调试·····
求高人们指点一二!! 修改
对于这种数据处理的程序不是很熟悉,所以很是费劲,而这个作业数据只是两维的特征,最后出来的效果还是不如matlab自带的程序。
深感机器学习的算法非常抽象,而且实际实现时,有很多细节资料上没有提到。
而且如果要优化算法效率、提高适用性和可靠性,更是要对算法理解很透彻,感觉这点很难,因为对数学要求特别高,而且数据特征维数大了,根本不知道该如何调试·····
求高人们指点一二!! 修改
21 个回答
机器学习方向的博士生,个人有几点体会。
1. 尽最大可能复用别人的代码。哪怕编译等等很麻烦,也可以在调试上省很多很多时间。血的教训。
2. 多把数据可视化出来,很多问题你看数字很难有直观感受,但往往一画出来就会发现哪里错了。这在高维数据下尤其明显。
3. 调试的时候和普通程序一样,看到结果不对就逆推, 找到问题是从哪个地方开始引入,从哪句话开始实际数值和你的期待不一样的。定位问题就成功了一大半。 可以用非常简单的数据代入验证,这样你也知道正确的结果是什么。
4. 关于工具的选择,原则实是先确认算法是对的,再调优实现。具体说先用matlab/python之类方便可视化的工具快速迭代找到一个能得到正确结果的算法。然后再用c++/mex优化速度。否则一个跑的很快的屎还是一坨屎。
1. 尽最大可能复用别人的代码。哪怕编译等等很麻烦,也可以在调试上省很多很多时间。血的教训。
2. 多把数据可视化出来,很多问题你看数字很难有直观感受,但往往一画出来就会发现哪里错了。这在高维数据下尤其明显。
3. 调试的时候和普通程序一样,看到结果不对就逆推, 找到问题是从哪个地方开始引入,从哪句话开始实际数值和你的期待不一样的。定位问题就成功了一大半。 可以用非常简单的数据代入验证,这样你也知道正确的结果是什么。
4. 关于工具的选择,原则实是先确认算法是对的,再调优实现。具体说先用matlab/python之类方便可视化的工具快速迭代找到一个能得到正确结果的算法。然后再用c++/mex优化速度。否则一个跑的很快的屎还是一坨屎。
作为一个纯做机器学习,主要搞probabilistic graphical model的苦逼Ph.D,
大多数时间实际上是在推公式好吧摔!!!anyway,说说自己编写、调试机器学习程序的经验吧。
1,要用print !
大多数机器学习方法是优化问题,一般是迭代求解,算法一般就是gradient descent的各种变种。 自己编写的时候,尽量打印每轮迭代时model的信息,包括目标函数值、训练数据上的错误率,甚至测试数据上的错误率,probabilistic model经常打印log-likelihood。举个例子,如果迭代中,目标函数值增加了(diverge 了),先检查是否是learning rate设的过大(最常见),gradient的正负搞反了(也很常见。。),如果不是,基本就要断点调试了。再比如, 如果训练错误率一直在下降,测试错误率开始上升,那就是过拟合了。
2,先在toy/simulated的data上测试通过
real data一般有不少预处理的问题,数据本身也有很多不符合模型假设的“个性”,很多时候,会影响你对机器学习模型、算法本身正确性的判断。最重要的,这样调试大大节省了时间。
3,不要吝啬注释
机器学习核心算法的程序,通常非常短小,但是如果没有注释,可读性会很差。因为代码背后是公式。对绝大多数凡人来说,公式本身就已晦涩,从代码里面读出公式,搞清楚程序在干吗,更是坑爹。所以,为了自己跟他人的身心健康,请加注释。
4,如果专注机器学习算法本身,复用别人的代码要慎重。
当你自己提了一个模型、算法,想投NIPS、ICML、AIStats、UAI, 需要对比前人的算法时,如果不是特别难,建议自己动手实现前人的方法(实际上,这是当年老板给我的忠告)。。首先,如果你只是把别人的代码当做一个黑盒子,很难得到自己的方法为啥好,别人的方法为啥差的insight,如果不幸自己的方法差,也很难找到改进自己方法的窍门。再者,最新提出的方法,一般没有可用的高质量代码。注意!!如果你的主要目标不是提出新的算法,而是想在某个应用上做出好的正确率,那么按 @grapeot说的,尽可能复用别人的代码。
5,先编写简单的机器学习算法练手
比如logistic regression, 比如EM for Gaussian Mixture, 比如SVM。先用最简单的实现。logistic regression就用gradient descent,linear svm就用sub-gradient decent。加了各种kernel的svm就用naive的quadratic programming,熟悉一些了,也可以用SMO。磨刀不误砍柴工,能学到很多。
6, 机器学习程序中的小bug很难发现
难发现的原因在于,有小bug的程序工作的很好,没有任何错误的征兆~~甚至有些时候,带小bug的程序,会比没bug的程序得到的正确率更高...(有多年ML经验的人,肯定遇到过这样的情况)。 原因是小bug会起到某种regularization的作用,防止了过拟合。所以尽人力后,顺其自然吧。。。
p.s.题外话,这个现象曾在12年NIPS的时候,被一个机器学习的大牛Dietterich拿来调侃巨牛Hinton提出的dropout方法。Hinton当时说,如果我的方法work得好,是因为有bug,那一定是世上最好的bug~~因为我的程序总是在赢各种竞赛。。
1,要用print !
大多数机器学习方法是优化问题,一般是迭代求解,算法一般就是gradient descent的各种变种。 自己编写的时候,尽量打印每轮迭代时model的信息,包括目标函数值、训练数据上的错误率,甚至测试数据上的错误率,probabilistic model经常打印log-likelihood。举个例子,如果迭代中,目标函数值增加了(diverge 了),先检查是否是learning rate设的过大(最常见),gradient的正负搞反了(也很常见。。),如果不是,基本就要断点调试了。再比如, 如果训练错误率一直在下降,测试错误率开始上升,那就是过拟合了。
2,先在toy/simulated的data上测试通过
real data一般有不少预处理的问题,数据本身也有很多不符合模型假设的“个性”,很多时候,会影响你对机器学习模型、算法本身正确性的判断。最重要的,这样调试大大节省了时间。
3,不要吝啬注释
机器学习核心算法的程序,通常非常短小,但是如果没有注释,可读性会很差。因为代码背后是公式。对绝大多数凡人来说,公式本身就已晦涩,从代码里面读出公式,搞清楚程序在干吗,更是坑爹。所以,为了自己跟他人的身心健康,请加注释。
4,如果专注机器学习算法本身,复用别人的代码要慎重。
当你自己提了一个模型、算法,想投NIPS、ICML、AIStats、UAI, 需要对比前人的算法时,如果不是特别难,建议自己动手实现前人的方法(实际上,这是当年老板给我的忠告)。。首先,如果你只是把别人的代码当做一个黑盒子,很难得到自己的方法为啥好,别人的方法为啥差的insight,如果不幸自己的方法差,也很难找到改进自己方法的窍门。再者,最新提出的方法,一般没有可用的高质量代码。注意!!如果你的主要目标不是提出新的算法,而是想在某个应用上做出好的正确率,那么按 @grapeot说的,尽可能复用别人的代码。
5,先编写简单的机器学习算法练手
比如logistic regression, 比如EM for Gaussian Mixture, 比如SVM。先用最简单的实现。logistic regression就用gradient descent,linear svm就用sub-gradient decent。加了各种kernel的svm就用naive的quadratic programming,熟悉一些了,也可以用SMO。磨刀不误砍柴工,能学到很多。
6, 机器学习程序中的小bug很难发现
难发现的原因在于,有小bug的程序工作的很好,没有任何错误的征兆~~甚至有些时候,带小bug的程序,会比没bug的程序得到的正确率更高...(有多年ML经验的人,肯定遇到过这样的情况)。 原因是小bug会起到某种regularization的作用,防止了过拟合。所以尽人力后,顺其自然吧。。。
p.s.题外话,这个现象曾在12年NIPS的时候,被一个机器学习的大牛Dietterich拿来调侃巨牛Hinton提出的dropout方法。Hinton当时说,如果我的方法work得好,是因为有bug,那一定是世上最好的bug~~因为我的程序总是在赢各种竞赛。。
建议lz去Coursera听几节Machine Learning的课Coursera.org,然后做一下对应的作业。作业就是用MATLAB编的,如果你能把作业里面的代码都理解好了。我觉得应该就算入门了吧~ 而且你可以趁此机会熟悉熟悉MATLAB的矩阵化运算,以及其他的常用指令。
至于难不难,我觉得学控制的同学,应该有足够的数学基础(线性代数+概率论)去理解的哈。
同学你也可以参考一下我的代码zihaolucky/Machine-Learning-and-UFLDL · GitHub . Lecture和code都有了。不过,如果你要跟那门课,还是自己完成吧,有honor code规则的哈。
至于vczh同学提到的“那些模型训练出来的系数是不可理解的,调试他们干什么……”.确实如此,机器学习,就是学习出那些参数,以最好地贴近数据模式啊。这个是算法。需要优化的,可能是你程序的速度。
至于难不难,我觉得学控制的同学,应该有足够的数学基础(线性代数+概率论)去理解的哈。
同学你也可以参考一下我的代码zihaolucky/Machine-Learning-and-UFLDL · GitHub . Lecture和code都有了。不过,如果你要跟那门课,还是自己完成吧,有honor code规则的哈。
至于vczh同学提到的“那些模型训练出来的系数是不可理解的,调试他们干什么……”.确实如此,机器学习,就是学习出那些参数,以最好地贴近数据模式啊。这个是算法。需要优化的,可能是你程序的速度。
首先关于语言的选择,个人认为还是Matlab比较合适,因为目前在学术领域,用Matlab的人感觉还是相对多一点的。题主如果在写代码的过程中遇到了问题,也比较方便向别人请教。
目测开源的机器学习库也是Matlab更多。当然如果对程序速度有严格要求,就可以考虑C++。
其次对于题主你来讲,最重要的问题是要明确自己的 目的。
说实话,如果只是一个课程作业的话,完全没必要深入到很多算法的细节,只要了解了算法的大体流程即可,再选择别人实现好的算法,多次测试,一般结果是可以让人满意的。
但如果题主是想继续在机器学习领域深入研究的话,了解算法细节就是必不可少的了。不过即便这样,还是要从简单入手逐渐加深难度。否则学习曲线太陡,一般人都hold不住。
(另外自己感觉机器学习还算是入门容易精通难的,深入研究充满了大量细节和数学,研究者们也在一直outperform其他人……( ̄ε ̄;))
所以,综上,题主 最好从简单入手!( ̄ε ̄;)
先了解基本算法思路,再使用现成代码进行实验!
在此,我 强烈推荐一个模式识别库 PRTools5!在这里下载: Software - Pattern Recognition Tools,作者叫 R.P.W. Duin。
这个库最大的优点就是简单方便,而且实现了大量大量常用算法,对于初学者,完全可以上手就用,避免陷入细节的泥潭。而且都经过大量测试,基本没有严重bug。简单举几个栗子:
首先,需要构造两个数据集:
其中,prdataset是PRTools5中的函数,目的就是构造数据集……
X是训练数据矩阵,每一行是一个数据点(一个object),每列是一个特征(feature)。Y是一个列向量,代表数据X的标签。怎么样,简单吧……
之后,就可以进行分类(classification)了!
其中,w1, w2, w3, w4 就是训练好的分类器了。knnc是knn…qdc是Quadratic Bayes Normal Classifier,svc是svc……naivebc是Naive Bayes。
至于各种参数呢?函数都已经自动选择了,当然题主根据自己要处理的具体问题,也完全可以手动指定。
之后就可以测试结果了,我们选择B作为测试集。
这里的e就是classification error……当然也可以选择其他函数,使用其他判断标准。
但 鉴于题主的数据是二维的,因此可视化是一个很好很强大的手段。(二维的!这年头哪还有这么好的事└(T_T;)┘……)。以下举例:
此处使用了PRTools5自带的数据生成函数,A、B都是二维数据,2 classes,每类中有50(400)个objects。
……不用解释。
结果如下:
这时真相大白,原来gendatb是生成banana形状的数据…………而且显然,对于这种数据,knn的效果最好。
总之PRTools5是一个非常强大的模式识别库。
而且这时再看,机器学习也就不麻烦且不抽象了吧~~
其次对于题主你来讲,最重要的问题是要明确自己的 目的。
说实话,如果只是一个课程作业的话,完全没必要深入到很多算法的细节,只要了解了算法的大体流程即可,再选择别人实现好的算法,多次测试,一般结果是可以让人满意的。
但如果题主是想继续在机器学习领域深入研究的话,了解算法细节就是必不可少的了。不过即便这样,还是要从简单入手逐渐加深难度。否则学习曲线太陡,一般人都hold不住。
(另外自己感觉机器学习还算是入门容易精通难的,深入研究充满了大量细节和数学,研究者们也在一直outperform其他人……( ̄ε ̄;))
所以,综上,题主 最好从简单入手!( ̄ε ̄;)
先了解基本算法思路,再使用现成代码进行实验!
在此,我 强烈推荐一个模式识别库 PRTools5!在这里下载: Software - Pattern Recognition Tools,作者叫 R.P.W. Duin。
这个库最大的优点就是简单方便,而且实现了大量大量常用算法,对于初学者,完全可以上手就用,避免陷入细节的泥潭。而且都经过大量测试,基本没有严重bug。简单举几个栗子:
首先,需要构造两个数据集:
A = prdataset(X1, Y1)
B = prdataset(X2, Y2)
X是训练数据矩阵,每一行是一个数据点(一个object),每列是一个特征(feature)。Y是一个列向量,代表数据X的标签。怎么样,简单吧……
之后,就可以进行分类(classification)了!
w1 = knnc(A);
w2 = qdc(A);
w3 = svc(A);
w4 = naivebc(A);
% ......
至于各种参数呢?函数都已经自动选择了,当然题主根据自己要处理的具体问题,也完全可以手动指定。
之后就可以测试结果了,我们选择B作为测试集。
e1 = testd(B*w1);
e2 = testd(B*w2);
e3 = testd(B*w3);
e4 = testd(B*w4);
% ......
但 鉴于题主的数据是二维的,因此可视化是一个很好很强大的手段。(二维的!这年头哪还有这么好的事└(T_T;)┘……)。以下举例:
A = gendatb([50 50]);
B = gendatb([400 400]);
w1 = knnc(A);
w2 = qdc(A);
w3 = svc(A);
w4 = naivebc(A);
% ......
scatterd(B); % 做测试数据散点图
hold on
h1 = plotc(w1); % 做出分类器边界图,以下类似
h2 = plotc(w2, 'b');
h3 = plotc(w3, 'g');
h4 = plotc(w4, 'y');
legend([h1, h2, h3, h4], 'knnc', 'qdc', 'svc', 'naivebc')
这时真相大白,原来gendatb是生成banana形状的数据…………而且显然,对于这种数据,knn的效果最好。
总之PRTools5是一个非常强大的模式识别库。
而且这时再看,机器学习也就不麻烦且不抽象了吧~~
不知道题主要写编写的是哪个分类器,不同分类器的编写复杂度相差非常大。像KNN,几句话就能写出来,而像SVM,要复杂得多。
相对于其它语言,我觉得MATLAB是我遇到的最容易上手的。最大的优势是方便查看各变量值与调试。
说说我认为一些有用的技巧:
1. 比较复杂的算法,可以先用数学语言把大概流程写出来,这样编程的时候有个参照。对算法流程非常清楚,是将它代码实现的必要条件。
2. 由简入难。先实现算法的一些基本功能,而扩展功能留到后面做。
3. 边写边调试,尤其是自己对编程语言不太熟悉的情况下,犯错的机率非常大。如果写了几百行再调试,那绝对是个巨大的工程。所以建议每写个几行,就F5运行下,查看下各个变量的值,看与自己想像的是否一样。这样便于即时发现问题,改正问题。
另外, 刚开始编写,不建议把代码写在function里,然后用主程序调用,这样不方便查看变量值与调试。我倾向先在主程序里把代码功能实现,调试确定没问题了,再把它转移到一个function里,保持主程序代码的简洁。当然这种方法对Python, C之类的语言没有用处。
4. 自己构造一个简单的样例来调试程序。一个简单的样例,自己能清楚的判断程序输出结果是否正确。而如果是很复杂的样例,判断起来就麻烦得多。
暂时想到的就这么多,希望能有帮助。
相对于其它语言,我觉得MATLAB是我遇到的最容易上手的。最大的优势是方便查看各变量值与调试。
说说我认为一些有用的技巧:
1. 比较复杂的算法,可以先用数学语言把大概流程写出来,这样编程的时候有个参照。对算法流程非常清楚,是将它代码实现的必要条件。
2. 由简入难。先实现算法的一些基本功能,而扩展功能留到后面做。
3. 边写边调试,尤其是自己对编程语言不太熟悉的情况下,犯错的机率非常大。如果写了几百行再调试,那绝对是个巨大的工程。所以建议每写个几行,就F5运行下,查看下各个变量的值,看与自己想像的是否一样。这样便于即时发现问题,改正问题。
另外, 刚开始编写,不建议把代码写在function里,然后用主程序调用,这样不方便查看变量值与调试。我倾向先在主程序里把代码功能实现,调试确定没问题了,再把它转移到一个function里,保持主程序代码的简洁。当然这种方法对Python, C之类的语言没有用处。
4. 自己构造一个简单的样例来调试程序。一个简单的样例,自己能清楚的判断程序输出结果是否正确。而如果是很复杂的样例,判断起来就麻烦得多。
暂时想到的就这么多,希望能有帮助。
机器学习coding比较蛋疼啊
我比较笨 一般是这么搞得
1,找个别人写好的程序1先跑,这时候一般都是matlab等好调试的语言 跑对了ok下一步
2,理解程序1内在算法,根据需求改进成自己的版本2,语言跟上面相同,这个如果效果还行,就可以作为正确答案
3,按照自己的版本2改写成c等高效语言,然后跟版本2每一步每个值对每个值的跟踪调试,遇到随机数的地方就设置成一样的值,挑对了再改回去,保证每个地方跟正确答案一致,这样出来的东西应该正确性就可以保证了。就是过程有点蛋疼了
我比较笨 一般是这么搞得
1,找个别人写好的程序1先跑,这时候一般都是matlab等好调试的语言 跑对了ok下一步
2,理解程序1内在算法,根据需求改进成自己的版本2,语言跟上面相同,这个如果效果还行,就可以作为正确答案
3,按照自己的版本2改写成c等高效语言,然后跟版本2每一步每个值对每个值的跟踪调试,遇到随机数的地方就设置成一样的值,挑对了再改回去,保证每个地方跟正确答案一致,这样出来的东西应该正确性就可以保证了。就是过程有点蛋疼了
选好模型和最优化的算法。从最简单和常见的模型开始(logistic regression,decision tree ,KNN等等);MATLAB里的分类器用的最优化算法基本都是很sophisticated的,简单的梯度下降什么的肯定搞不过人家的。
准备好测试用的程序和数据,包括测试在训练数据和测试数据上的误差,画learning curve,ROC曲线啦,etc。这些测试结果就像仪表盘一样,让你清楚自己现在所处的情况。
cross validation (n-fold, loocv,etc)+ grid search 来选参数,libsvm 就是这么做的。(跑程序的时候你可以去看看电影什么的)
不过你这个是二维的特征啊,直接画个图出来看看呗。
准备好测试用的程序和数据,包括测试在训练数据和测试数据上的误差,画learning curve,ROC曲线啦,etc。这些测试结果就像仪表盘一样,让你清楚自己现在所处的情况。
cross validation (n-fold, loocv,etc)+ grid search 来选参数,libsvm 就是这么做的。(跑程序的时候你可以去看看电影什么的)
不过你这个是二维的特征啊,直接画个图出来看看呗。
感觉语言选好自己喜欢的就可以啦...不过还是推荐python现在theano,scikit-learn什么的都很好用.
我觉得要先理解一些基础概念,知道为什么这么做
理解了的话就可以尽量参考一下已有的代码(对象和类的定义)
然后试着"想象一下"矩阵运算是怎么操作的,用手多写写shape之间转换关系?
应该还要有一台运算速度快的电脑吧
最主要还是理解概念,为什么这么做,这么做会怎么样吧
推荐Andrew Ng的Cousera课程
目前正好讲到了机器学习系统设计和修复~
我觉得要先理解一些基础概念,知道为什么这么做
理解了的话就可以尽量参考一下已有的代码(对象和类的定义)
然后试着"想象一下"矩阵运算是怎么操作的,用手多写写shape之间转换关系?
应该还要有一台运算速度快的电脑吧
最主要还是理解概念,为什么这么做,这么做会怎么样吧
推荐Andrew Ng的Cousera课程
目前正好讲到了机器学习系统设计和修复~
既然你已经知道 MATLAB 有这个功能了,那么就看看 MATLAB 的帮助文档是怎么实现这个算法的吧。
-------------接下来是黑 MATLAB 的分割线---------------
哈哈,看不到源代码吧,哈哈, MATLAB 是闭源软件!还不趁早用 Python!
-------------接下来是黑 MATLAB 的分割线---------------
哈哈,看不到源代码吧,哈哈, MATLAB 是闭源软件!还不趁早用 Python!
陈应佳、匿名用户、Michael Jackson 赞同
个人体会:补充一些其他的
简单算法多用别人的程序,然而以后题主需要写别人没有现成的复杂的程序的时候,有这么几点个人体会:
1、 很多算法是基于数学优化设计的,存在优化目标,比如概率模型里的最大似然估计或最大后验,然后使用迭代的算法(EM,梯度下降,牛顿法等等)进行优化,因此在迭代的过程中可以把优化的目标输出出来,看看是不是真的得到了优化。
2、有的时候优化了没错但优化的时候就优化到NAN去了(写的Java)...这种时候需要采用倒推的方法一步一步回溯,看是哪里出现了第一个NAN,是不是对应的求解公式有没有写错,尤其是数学公式里的各种下标有没有弄混。
3、如之前各位答案说的,把中间结果画出来看看
4、机器学习算法调试确实是一个很难的问题,记得以前看过一个段子说某deep learning大牛设计了算法号称有性能提升,然后开源了代码发了论文,结果被人检查出来代码里有错误,把bug改了之后性能就下降了。不知道是谁是真是假...
不知道题主说的适用性和可靠性是什么意思。运行效率的话,我认为更多的是对数据结构和算法的理解,对算法复杂度的分析和优化,以及编程的优化,需要题主多多练习编程。
简单算法多用别人的程序,然而以后题主需要写别人没有现成的复杂的程序的时候,有这么几点个人体会:
1、 很多算法是基于数学优化设计的,存在优化目标,比如概率模型里的最大似然估计或最大后验,然后使用迭代的算法(EM,梯度下降,牛顿法等等)进行优化,因此在迭代的过程中可以把优化的目标输出出来,看看是不是真的得到了优化。
2、有的时候优化了没错但优化的时候就优化到NAN去了(写的Java)...这种时候需要采用倒推的方法一步一步回溯,看是哪里出现了第一个NAN,是不是对应的求解公式有没有写错,尤其是数学公式里的各种下标有没有弄混。
3、如之前各位答案说的,把中间结果画出来看看
4、机器学习算法调试确实是一个很难的问题,记得以前看过一个段子说某deep learning大牛设计了算法号称有性能提升,然后开源了代码发了论文,结果被人检查出来代码里有错误,把bug改了之后性能就下降了。不知道是谁是真是假...
不知道题主说的适用性和可靠性是什么意思。运行效率的话,我认为更多的是对数据结构和算法的理解,对算法复杂度的分析和优化,以及编程的优化,需要题主多多练习编程。
学习一下Learning Theory,了解bias和variance的区别以及产生原因对设计好的feature,以及debug ML算法非常重要。
如果英语比较好的话, 强烈推荐看一下Stanford教授Andrew Ng关于应用以及debug ML算法的建议 (slides在这里)。这是Stanford计算机系Machine Learning课程的材料,这里面的提供的方法在实际使用ML算法的过程中很有效。
如果英语比较好的话, 强烈推荐看一下Stanford教授Andrew Ng关于应用以及debug ML算法的建议 (slides在这里)。这是Stanford计算机系Machine Learning课程的材料,这里面的提供的方法在实际使用ML算法的过程中很有效。
那些代码其实一点也不抽象,必须理解他们到非常不抽象的地步,并且深刻理解课题,以及课题和这些代码的关系。ML的结果经常会有很多难以理解的时候,只能多看多想, 有时候要换换features 能得到更好的结果。但是一切的根源来自对问题的深刻理解。