机器学习/自控仿真

机器学习、建模仿真、自动控制、虚拟现实

今日: 0 主题: 62 回复: 444版主:Cirno

分类: (暂无分类)

机器学习并不困难,但从某种程度上来说又很困难。 各种机器学习相关的教材,不管是文字的还是视频的,都要求学习者具备高等数学、线性代数、概率论等数学知识。而这还仅仅是最简单的诸如梯度下降这一类算法的要求,继续向后学习还会遇到更多更高级的数学概念,如果学习者对数学没有一定的“灵性”甚至从小数学就不好,入门机器学习是几乎不可能的事情。 对于有志于此的人们来说,这其实是好事。在机器学习的最前沿,是类似 Google / Facebook / Baidu 这样的大公司,但这完全不妨碍个人产生优秀的研究成果。如今在机器学习上取得的最微小的突破,其影响力也不亚于20年前PHP和JavaScript的横空出世,不亚于40年前出现的图形用户界面和计算机网络。 如果让我用一句话概括机器学习:机器学习,是一门让计算机(或者其他机器)模拟生物“学习”过程的学问。 通常,研究者需要设计具有“学习”能力的数学模型,向模型添加一定的输入,令模型从输入中“学习”输入与输出的对应关系,或者输入与其他输入之间的联系。 举个例子:现有一支机械臂,用其投掷一支飞镖,我们希望让飞镖落点尽量靠近靶心。传统的办法是,设定机械臂的出手速度为某个固定值 \(\vec{v}\) ,然后通过CV等方法求得靶心与机械臂出手点的距离 \(\vec{d}\),再根据当地 \(g\) 值构建抛物线,最后让机械臂按抛物线起点切线 \(\vec{t}\) 抛出飞镖。 如果把出手方向设为输入,飞镖落点设为输出,我们可以利用经典物理定律,由输入直接求得输出(公式省略),也可以从所需的输出反推得到合适的输入。 然而在现实中,运动员只需靠“手感”,就能在不同的距离、不同的出手角度、不经测量计算而准确击中靶心。如果第一次投低了,第二次就增加一些速度,或者提高一点角度……最后就找到了“手感”。这说明:人类虽然不善于计算,但藉由不断地练习,可以令【从眼到手的神经反射回路】为“命中靶心”这个特殊目的调校到最优。 相比之下,若要让一台机器精确地按照某个物理模型投掷飞镖,则困难得多:因为现实世界中的物理模型实在是太复杂了。飞镖的流体动力学模型要怎么仿真?飞镖出手时,机械手与飞镖从开始分离到完全分离经过多少毫秒?是否要给机械手加装精密的速度传感器(加速度计 / CV / 编码器)? 因为完善物理模型的成本实在是太高了,聪明而懒惰的工程师们提出了反馈控制:虽然丢飞镖的过程存在不可忽略且模型复杂的系统误差,但只要飞镖的命中高度与出手切线斜率是正相关的,那么通过不断地【抛掷飞镖->计算落点误差->微调出手斜率】,最终一定能够让系统准确地命中靶心。 于是PID反馈控制被大规模应用。但它的缺点也很明显: 1)必须连续不断地投掷飞镖才能维持反馈,浪费大量飞镖。 2)如果目标在运动,落点误差会一直存在。 这是因为,相比一个完善的物理模型(100%模型预测),PID控制(100%误差驱动)刚好处在另一个极端:PID算法本身并不关心具体控制系统的物理模型细节,只关心输出与期望的误差,并通过输出误差计算相应的输入调整。 于是工程师们提出了二阶PID,前馈控制……大家向反馈控制中加入了越来越多针对系统物理模型的优化,以期得到优良的性能。 后来鲁道夫·卡尔曼(就是刚刚过世的那位)提出了卡尔曼滤波器,可以借助物理模型的已知细节,对输入数据进行修正与预测,并证明了其在一大类问题上的最优性。经过几十年的发展,卡尔曼滤波器已经形成了一个完备的家族。 但要用好卡尔曼滤波器并不容易,如果希望卡尔曼滤波器的效果好,仍然要对系统作非常细致的分析,包括误差从哪里来、各个输入与误差之间如何互相影响……以至于到最后,几乎每一类控制问题,都相应著有关于如何设计针对这类控制问题的卡尔曼滤波器的论文。最终工程师们又回到了起点:要实现完美的控制,仍然需要掌握系统的大量内部细节。 至此,机械手仍然无法像一个人类运动员那样,依靠“手感”投掷飞镖,或者依靠“手感”驾驶飞机。 那么机器学习又是怎样应付这个问题的呢?卖个关子,不然这篇文章就成了机器学习入门了。网上关于机器学习的入门资料很多,有兴趣的同学一定能找到的。 本文作者仅仅是业余研究ML。希望这个新建的板块,能够为同样热爱ML的同学提供一个纯净的交流空间。


搬运过去和近期收集的机器学习相关的东西。 教材及资料汇总 http://cs229.stanford.edu/materials.html ,CS229,斯坦福神课,机器学习基本知识框架,Andrew Ng亲自授课 http://web.stanford.edu/class/cs294a/ CS294A/CS294W Deep Learning and Unsupervised Feature Learning http://cs231n.stanford.edu/ ,CS231,神话延续,机器视觉大牛 Feifei Li 的课,知乎会员杜客做了中文翻译版 https://zhuanlan.zhihu.com/intelligentunit http://www.cs.nyu.edu/~yann/talks/lecun-ranzato-icml2013.pdf , Yann LeCun 深度学习讲义,老先生PPT风格别具一格 http://neuralnetworksanddeeplearning.com/ ,url 说明一切,无须解释,中文翻译: https://hit-scir.gitbooks.io/neural-networks-and-deep-learning-zh_cn/content/index.html http://www.deeplearningbook.org/ Deep Learning 重量级教材 Pattern Recognition and Machine Learning ,经典教材,这里就不放下载链接了 http://ufldl.stanford.edu/wiki/index.php/UFLDL_Tutorial Andrew Ng的另一个进阶教程, 拖到页面最下方,发现有中文版本 开发工具汇总 Sklearn, http://scikit-learn.org/stable/ ,Python机器学习库 http://caffe.berkeleyvision.org/ , Caffe is a deep learning framework made with expression, speed, and modularity in mind, Windows Branch: https://github.com/BVLC/caffe/blob/windows http://deeplearning.net/software/theano/ , Theano is a Python library that allows you to define, optimize, and evaluate mathematical expressions involving multi-dimensional arrays efficiently https://www.tensorflow.org/ , Tensor Flow ,Google Deepmind 开发的开源深度学习库 https://www.cntk.ai/ , CNTK ,微软不甘示弱也推出了自家的开源深度学习库,可能是目前对windows系统最友好的库 https://github.com/PrincetonVision/marvin ,普林斯顿大学Jianxiong Xiao 实验室发布的超轻量级深度学习架构 Marvin , born for hack http://torch.ch/ ,Torch 相关网站 https://www.kaggle.com/ ,最有活力的数据科学社区,举办大量竞赛,提供大量训练数据 https://www.terminal.com/ ,想玩deep learning但暂时还不想四路1080抱回家?云服务解决一切 A Neural Network Playground - TensorFlow ,带你跑步前进进入deep Learning调参的世界 http://cs.stanford.edu/people/karpathy/convnetjs/ : ConvNetJS is a Javascript library for training Deep Learning models (Neural Networks) entirely in your browser Research Paper ImageNet Classification with Deep Convolutional Neural Networks ,相关 PPT 开源代码 Neural Style , This is a torch implementation of the paper A Neural Algorithm of Artistic Style by Leon A. Gatys, Alexander S. Ecker, and Matthias Bethge. neural-style An implementation of neural style in TensorFlow. 训练数据集 ImageNet UC Irvine Machine Learning Repository SUN3D MNIST PASCAL VGG 不定期更新


- 以上是置顶 -

[机器学习笔记#4] Neural Style 算法分析及caffe实验 在之前的帖子中,楼主简要介绍了 Neural Style 算法的原理和实现思路,该方法2015年由三位德国科学家以一份tech report的形式提出,今年他们发表了一篇 CVPR 论文 。 在楼主随后的进一步实验中,发现当初好死不死选caffe是给自己挖了个大坑,caffe作为曾经占据科研界支配地位的工具,用来跑各种pre-trained的model 是相当顺手,但用来实现自定义运算的layer,尤其是用Python,其过程相当之蛋疼,而且也缺乏运算效率。于是推翻重来,改用tensorflow实现。 实现neural style的代码,首先需要一个 pre-trained VGG16 模型,tensorflow 中今年新加入的轻量级图像识别库TF-Slim提供了官方的pre-trained的VGG网络,非常方便。 VGG16 网络结构如下,其特征是大量使用两层级联的具有3x3小卷积核(能分辨上下左右概念的最小尺寸)的卷积层来代替类似Alexnet结构中的单层大卷积核卷积层,而最后的等效 receptive field 是接近的(5x5)。这样的好处一是减少了待训练weight的数量,二是小卷积核之间引入了额外的ReLu层,增加了非线性程度,使得整体的representation power提高(简单理解成用两层的MLP代替单层inear classifier)。 ['vgg_16/conv1/conv1_1', 'vgg_16/conv1/conv1_2', 'vgg_16/pool1', 'vgg_16/conv2/conv2_1', 'vgg_16/conv2/conv2_2', 'vgg_16/pool2', 'vgg_16/conv3/conv3_1', 'vgg_16/conv3/conv3_2', 'vgg_16/conv3/conv3_3', 'vgg_16/pool3', 'vgg_16/conv4/conv4_1', 'vgg_16/conv4/conv4_2', 'vgg_16/conv4/conv4_3', 'vgg_16/pool4', 'vgg_16/conv5/conv5_1', 'vgg_16/conv5/conv5_2', 'vgg_16/conv5/conv5_3', 'vgg_16/pool5', 'vgg_16/fc6', 'vgg_16/fc7', 'vgg_16/fc8'] 在实现中,原paper用conv4_2层输出的activation作为表征原图content的feature,原理是CNN中high level卷积层的neuron主要是被输入图片中物理的轮廓、形状激活,而忽略细节纹理(理解为假如我有两双同款篮球鞋,分别是不同的花纹、颜色,二者的同角度照片在该层的activation相同)。 而对于原图片的style,该paper提出使用卷积层不同filter输出的activation之间的corelation来表征,至于为什么,则出自作者的另一篇paper。这种corelation可以用inner product来计算,得到的feature称为Gram matrix。最终原图的style由 conv1_1,conv2_1,conv3_1,conv4_1,conv5_1的输出共同计算得到。 整个结构如图,出自原paper配图: 生成过程中需要提供两种图片,一张提供content,计算得到content feature;一张提供style,计算得到 style feature (gram matrix)。然后我们的目标图片会被初始化为一张target白噪声图片(随机初始化),该图输出的 content feature 和style feature 于content image 和style image的输出分别进行比较,计算其L2 distance,即得到一个L2 loss function。通过反向传播可以计算该L2 loss 在target图片像素上的gradient,然后对target图片像素进行gradient based optimization,通过迭代循环使其逐渐呈现我们想要的面貌(loss function 最小)。这里很有趣的一点是,该方法中的优化过程,并非针对CNN网络本身,反而是输入图片,虽然有些反直觉,但该方法在各种图片相关的generative model中并不少见,包括效果骇人的deepdream。


1导言 深度学习正在努力让神经网络变得越来越深 [13],因为更深的模型意味着更复杂的处理带来的更好的分类性能,诸如 [4] 等研究已经把神经网络成功地训练到了一百多层。但是我们却把目光放在让模型更宽上,因为我们认为神经网络在这一方向上还有很多的性能供我们挖掘,同时更宽的模型能够更好地并行化以利用计算能力提升带来的红利。 前人已经在这个方面做了许多的重要的工作,Ensemble-based classifiers[12] 描述的是将图像进行不同处理后并输入不同神经网络,最终输出各神经网络输出的平均的一种宽的图像分类神经网络结构,multi-column deep neural network[1] 中阐述的使用不同预处理方式处理输入数据的结构展现了卷积神经网络在这种宽结构上的优越性能。同时,本工作在一定程度上受到了 RCNN[2] 的启发,其使用神经网络对图像不同部位进行分类的方式是我们带步距卷积采样方式的雏形,而使用卷积神经网络作为卷积核的想法则来自于 network in network[11],它使用了多层神经网络替代来原本基于广义线性模型(GLM)的卷积核,带来了更优的分类性能。 我们提出的模型使用带步进的拥有大卷积核卷积层采样,使用小的卷积神经网络替代传统卷积层中的卷积核,使用全局平均池(Global Average-pooling) [11] 平均各卷积核的输出输出最终结果。小卷积神经网络于普通的用于 MNIST 数据集中的传统的卷积神经网络没有什么太大的差别,主要是使用了 Dropout[14] 技术来抑制过分的过拟合并用平均池层替代最大池层,目的是体现本模型所带来的的提升。现阶段我们已经在 MNIST数据集上完成了测试,获得了具有一定说服力的实验结果。 2模型 2.1 带步进卷积 如图 1 所示,输入图像被以带步进卷积的方式切割并输入到小卷积神经网络中,小卷积神经网络的输出结合在一起成为一个特征图,这可以看做是小卷积神经网络充当带步进卷积层的卷积核。最后本模型用全局平均池处理这个特征图输出最终分类结果。 2.2 小卷积神经网络 为了证明我们提出的方法的优势,我们只在 LeNet[8] 的基础上做了了少许改动,包括使用ReLU激活函数以加快拟合速度,增加了神经元的数量以便和现在的其他工作形成对比,在每一层后面都用了Dropout技术以抑制过拟合,把最大池换成了平均池,这些都是现在的卷积神经网络非常常用的技术。 如表 1 所示, 我们使用两种小卷积神经网络来验证我们的模型在更深的神经网络上的性能。 3实验 3.1 训练 在训练过程中,我们使用了 AdamOptimizer[5],共训练 100epoches,初始学习率设置如表 2 所示。 1 3.2MNIST 手写数字数据集 MNIST 手写数字数据集提供了六万的训练集和一万的测试集,它的图片是被规范处理过的 28*28 的灰度图。我们分别测试了我们使用的两种小卷积神经网络在我们的模型上的性能,我们实验的结果如表 3 所示,两种小卷积神经网络的测试结果分别为 0.37和 0.33,可以看出,我们的模型能够为简单的卷积神经网络带来较大的提升,而且更深的小卷积神经网络也能带来更好的效果。 我们认为我们的模型在对比 RCNN-96 和 Maxout In Maxout 这样的模型的时候的时候还是有较大优势的,因为显然的,我们的模型更浅,拥有更少的参数。我们认为 Multi-column Deep Neural Networks 拥有更好的效果是因为它使用了更复杂的基分类器(卷积神经网络)(他们用了 winner-take-all 神经元等技术),我们正在考虑是否使用类似的基分类器。 4讨论 4.1 集成学习 在集成学习中,有如下公式 [7]: $$E = \overline{E} - \overline{A}$$ 其中\(E\)是模型误差,\(\overline{E}\)是各分类器平均误差,\(\overline{A}\)是各分类器的平均分歧(分类器输出与总输出的差)。 我们认为,我们提出的方法所达到的效果在一定程度上来自于我们增加了各分类器的分歧,虽然我们在一定程度上减小了各分类器的分类精度,但平均分歧的增加是大于误差的增加的。 4.2 过拟合 按照[11] 的说法,由于各个小神经网络之间的权值存在共享,所以它们难以形成强 的组合,从而减少了过拟合的可能,但是在我们的模型中却存在难以估量的过拟合,以 至于我们几乎在每一个层中都用了 dropout 来减小过拟合程度(甚至包括输出层,当然, 我们认为在防止过拟合的诸多措施中,输出层的 dropout 在防止过拟合方面有不容小觑 的贡献)。我们认为这些过拟合的原因是各个卷积核对图像上的同一个区域重复采样了 过多次,我们现在还在致力于解决这一问题:一方面,更小的卷积核会使得小卷积神经 网络的输入图像过小,这让小卷积神经网络更难收敛;另一方面,小神经网络内必须使 用卷积来实现更好的图像能力。 4.3 学习率 这样的学习率是极其夸张的,按照道理来说,这样会严重过拟合(实验结果确实如 此),但是事实上只有这样的学习率才能成功地把模型训练出来,所以我们在输出层加 Dropout,以平衡大学习率带来的影响。一些关于 Batch normalization(BN)的实验显 示,在用了 BN 之后,在使用大学习率时收敛速度很快,似乎可以使用正常的学习率进 行训练,但是这时还是难以训练到模型在之前所能达到的效果。 4.4 更多 CNNIC 层 在我们的早期的尝试中,我们曾经尝试使用多个 CNNIC 层来进行分类,但是我们 面临着许多问题:a) 为了使用更多的 CNNIC 层,我们不得不使用为 1 的步进或者较小 步进,这样带来的是计算量上的爆炸式的增长,对于模型来讲是灾难性的。b) 如我们之 前的实验所展现的,使用步进为 1 或者较小步进的 CNNIC 层无法带来任何提升,只有 计算量的增长。 4.5 超参数 这样的一个结构有许许多多的超参数,诸如小卷积神经网络的输入的图像的尺寸、 卷积层步进大小、Dropout 率、学习率和,很多的参数的调节是没有先例可循的,其中 固然存在一些的超参数的调节能够体现模型本身的特性,但是我们并不想把过多的时间 花在这些参数的调节上。 5下一步工作 更多改进 我们正在尝试使用 [10] 中所采用的 Batch normalization 来提升我们的模型 的性能,同时更多的 tricky (雾)也在考虑之中。 更多的数据集上的实验 CIFAR-10/100[6] 是彩色图像数据集,我们将在这个数据集上 进行进一步的实验,以进一步证明模型的优势。 特征图可视化 [11] 中展示了其特征图并对其进行了分析,我们认为这样做有利于人们 理解这一结构。 参考文献 [1] Dan Ciregan, Ueli Meier, and Jurgen Schmidhuber. Multi-column deep neural networks for image classification. computer vision and pattern recognition, pages 3642–3649, 2012. [2] Ross Girshick, Jeff Donahue, Trevor Darrell, and Jitendra Malik. Rich feature hierarchies for accurate object detection and semantic segmentation. pages 580– 587, 2013. [3] Ian J Goodfellow, David Warde-Farley, Mehdi Mirza, Aaron Courville, and Yoshua Bengio. Maxout networks. Computer Science, pages 1319–1327, 2013. [4] Kaiming He, Xiangyu Zhang, Shaoqing Ren, and Jian Sun. Deep residual learning for image recognition. computer vision and pattern recognition, pages 770–778, 2016. [5] Diederik P Kingma and Jimmy Ba. Adam: A method for stochastic optimization. Computer Science, 2014. [6] Alex Krizhevsky. Learning multiple layers of features from tiny images. 2009. [7] Anders Krogh and Jesper Vedelsby. Neural network ensembles, cross validation, and active learning. In NIPS, 1994. [8] Yann Lecun, Leon Bottou, Yoshua Bengio, and Patrick Haffner. Gradient-based learning applied to document recognition. Proceedings of the IEEE, 86(11):2278– 2324, 1998. [9] Ming Liang and Xiaolin Hu. Recurrent convolutional neural network for object recognition. pages 3367–3375, 2015. [10] Zhibin Liao and Gustavo Carneiro. On the importance of normalisation layers in deep learning with piecewise linear activation units. workshop on applications of computer vision, pages 1–8, 2016. [11] Min Lin, Qiang Chen, and Shuicheng Yan. Network in network. international conference on learning representations, 2014. [12] Lior Rokach. Ensemble-based classifiers. Artificial Intelligence Review, 33:1–39,2010. [13] Karen Simonyan and Andrew Zisserman. Very deep convolutional networks for large-scale image recognition. international conference on learning representations,2015. [14] Nitish Srivastava, Geoffrey E Hinton, Alex Krizhevsky, Ilya Sutskever, and Ruslan Salakhutdinov. Dropout: a simple way to prevent neural networks from overfitting. Journal of Machine Learning Research, 15(1):1929–1958, 2014. A 实现 在这个链接中 2 你可以找到本模型的相关代码。 1:训练在由科创基金资助的 NVIDIA GTX1060 6GB 上进行 2: https://github.com/MyWorkShop/Convolutional-Neural-Networks-in-Convolution 本报告PDF版本下载: mid_report.pdf 216k 3次


我从16年10月开始接触机器学习。以独立研究工作者的身份,实验从早做到晚。17年10月份又参加了个RL比赛(具体搜Learning to Run),忙得昏天黑地。 AI是一个发展飞快的领域。 做前沿研究,辛苦且不论,投入产出比低得吓人。 图像特征提取+识别,目前的主流方法是CNN,CNN像积木一样,可以以无穷多的方式组合成一个完整的神经网络,但是只有某些特定的组合方式,在经过训练后能达到较好的性能(比如较高的识别准确度等等)。 连续5年,学界每年都会用新的神经网络架构,刷新若干次图像识别准确率的记录。 图源《2017AI报告》(推荐阅读): http://cdn.aiindex.org/2017-report.pdf 正当大家都以为由He Kaiming提出的ResNet架构已经达到了前人难以企及的高准确率和高参数效率(同样性能下使用更少的自由参数)的时候,WideResNet Xception等架构又接二连三地跳出来。 不过进度虽然飞快,但是对于相关原理的解释(“为什么这样的网络能达到这样的效果”)非常少。正如12月NIPS会议上,与会者提出的那样:目前的神经网络研究给人以炼金术的感觉。 这就有点像医学研究,由于人体实在是太复杂,即便有了现代生物技术的辅助,对于疑难杂症往往还是用统计学方法总结出有效的方案,没有办法像物理学那样提出统一、普遍适用的理论。 不管从个人的经验,还是领域内发展的结果来看,如果想在这些研究方向上做出任何突破,第一步就是要先追赶上其他人的研究(把人家做出来的效果重复一次)。斯坦福的学生用着由Nvidia捐赠的机器,能够比你快100倍地重复出其他人的研究成果。对于捉襟见肘的独立研究者而言,超过SOTA(业界第一)的水平几乎是零概率事件。即便拿下SOTA,风光也不会超过3个月。 好在这个领域的论文都是公开发表且免费传播的——即便第一时间你无法马上重复SOTA的实验,你也仍然可以掌握它的“方法论”和“世界观”,用到日后的工作中去。从这个角度来看,人工智能/机器学习领域的研究其实是目前所有学科里效率最高、进度最快、成果最透明的。 最初选择在这上面花时间,纯粹是因为想掌握各种SOTA酷炫吊炸的技能;一路走来,受益良多,期间把CS本科的课程补了个遍——若非为此,很多教材可能这辈子再不会碰。 对所谓“无知无畏”,有了深层次的体会。最开始学Machine Learning的时候,想法是这东西真有用,原理也简单,那么多人感兴趣想入门,完全应该写教程,写书单,写这个那个,为乡亲们造福……后来随着时间流逝,实验做得越来越多,接触的领域越来越多,才意识到连开给自己的书单都没时间看/看不下去,更别说写什么教程文章了,倒不是怕误人子弟,而是知道一定会误人子弟。有道是,写书的不做研究,做研究的不写书——这句话必须先做了研究才能理解呀。 题外话:我将会参加一月份的AMLD conference。 在茶话读到一篇喷Elsevier的文章,原版之前看过了。学术出版曾经是很专业的工作,手写的论文,出版商要转成铅字,包括数学公式也要帮你排好, 而且还不能犯错(意味着干活的人必须能看懂文章意思) ,到最后印刷成书,人力成本是很高的。 现在很多论文是由作者排版的(LaTeX),出版商的作用就削减了,包括现在还可以下载论文,连纸张都免了。所以大家就抗议价格太贵。 其实可以从政治的角度来看:学术论文是记载知识的,可知识的定义如此广泛,暂时没有用途的知识,将来可能会有价值。所以如果有一本万国期刊,允许所有人自由发表论文,后果就是上面会出现大量类似“吃什么可以祛痘”之类的文章。大家为啥不把文章发到那上面去呢?答案很简单:虽然你可以分辨论文的优劣,但当局分辨不了啊。 学术研究是要消耗经费的,因此提供经费的当局(通常是政府)必然要通过一定的标准来决定是否对一项研究提供经费。如果这个标准选得不好,就会造成经费的浪费。所以学术会议还是得开,论文集还是得出——作为学术界自身通过民主方式(审稿)决定谁能获得经费的一种制度。如果没有这个制度,大家都拿不到经费。大家给学术出版商支付的下载费用,可以视为一种税款,用于维持整个制度的运转。作为这个制度的运作载体,也就是期刊们——也就获得了巨大的政治力量,以至于收钱收得有些难看了(毕竟政府的反射弧长度是20光年)。

我从16年10月开始接触机器学习。以独立研究工作者的身份,实验从早做到晚。17年10月份又参加了个RL比赛(具体搜Learning to Run),忙得昏天黑地。 AI是一个发展飞快的领域。 (附件:279337) 做前沿研究,辛苦且不论,投入产出比低得吓人。 图像特征提取+识别,目前的主流方法是CNN,CNN像积木一样,可以以无穷多的方式组合成一个完整的神经网络,但是只有某些特定的组合方式,在经过训练后能达到较好的性能(比如较高的识别准确度等等)。 连续5年,学界每年都会用新的神经网络架构,刷新若干次图像识别准确率的记录。 (附件:279338) 图源《2017AI报告》(推荐阅读): http://cdn.aiindex.org/2017-report.pdf 正当大家都以为由He Kaiming提出的ResNet架构已经达到了前人难以企及的高准确率和高参数效率(同样性能下使用更少的自由参数)的时候,WideResNet Xception等架构又接二连三地跳出来。 不过进度虽然飞快,但是对于相关原理的解释(“为什么这样的网络能达到这样的效果”)非常少。正如12月NIPS会议上,与会者提出的那样:目前的神经网络研究给人以炼金术的感觉。 这就有点像医学研究,由于人体实在是太复杂,即便有了现代生物技术的辅助,对于疑难杂症往往还是用统计学方法总结出有效的方案,没有办法像物理学那样提出统一、普遍适用的理论。 不管从个人的经验,还是领域内发展的结果来看,如果想在这些研究方向上做出任何突破,第一步就是要先追赶上其他人的研究(把人家做出来的效果重复一次)。斯坦福的学生用着由Nvidia捐赠的机器,能够比你快100倍地重复出其他人的研究成果。对于捉襟见肘的独立研究者而言,超过SOTA(业界第一)的水平几乎是零概率事件。即便拿下SOTA,风光也不会超过3个月。 好在这个领域的论文都是公开发表且免费传播的——即便第一时间你无法马上重复SOTA的实验,你也仍然可以掌握它的“方法论”和“世界观”,用到日后的工作中去。从这个角度来看,人工智能/机器学习领域的研究其实是目前所有学科里效率最高、进度最快、成果最透明的。 最初选择在这上面花时间,纯粹是因为想掌握各种SOTA酷炫吊炸的技能;一路走来,受益良多,期间把CS本科的课程补了个遍——若非为此,很多教材可能这辈子再不会碰。 对所谓“无知无畏”,有了深层次的体会。最开始学Machine Learning的时候,想法是这东西真有用,原理也简单,那么多人感兴趣想入门,完全应该写教程,写书单,写这个那个,为乡亲们造福……后来随着时间流逝,实验做得越来越多,接触的领域越来越多,才意识到连开给自己的书单都没时间看/看不下去,更别说写什么教程文章了,倒不是怕误人子弟,而是知道一定会误人子弟。有道是,写书的不做研究,做研究的不写书——这句话必须先做了研究才能理解呀。 题外话:我将会参加一月份的AMLD conference。 在茶话读到一篇喷Elsevier的文章,原版之前看过了。学术出版曾经是很专业的工作,手写的论文,出版商要转成铅字,包括数学公式也要帮你排好, 而且还不能犯错(意味着干活的人必须能看懂文章意思) ,到最后印刷成书,人力成本是很高的。 现在很多论文是由作者排版的(LaTeX),出版商的作用就削减了,包括现在还可以下载论文,连纸张都免了。所以大家就抗议价格太贵。 其实可以从政治的角度来看:学术论文是记载知识的,可知识的定义如此广泛,暂时没有用途的知识,将来可能会有价值。所以如果有一本万国期刊,允许所有人自由发表论文,后果就是上面会出现大量类似“吃什么可以祛痘”之类的文章。大家为啥不把文章发到那上面去呢?答案很简单:虽然你可以分辨论文的优劣,但当局分辨不了啊。 学术研究是要消耗经费的,因此提供经费的当局(通常是政府)必然要通过一定的标准来决定是否对一项研究提供经费。如果这个标准选得不好,就会造成经费的浪费。所以学术会议还是得开,论文集还是得出——作为学术界自身通过民主方式(审稿)决定谁能获得经费的一种制度。如果没有这个制度,大家都拿不到经费。大家给学术出版商支付的下载费用,可以视为一种税款,用于维持整个制度的运转。作为这个制度的运作载体,也就是期刊们——也就获得了巨大的政治力量,以至于收钱收得有些难看了(毕竟政府的反射弧长度是20光年)。


分类 ( Classification) 以及 分类器 ( Classifier)是机器学习和传统统计学里边一个很重要的概念,可能是我们日常生活中接触到的最多的一种机器学习应用。垃圾邮件过滤,人脸识别,手写字符识别,都是利用了分类的知识。 在这些任务中,我们往往希望给定一个具体输入,通过分类器的处理,能自动将其划到我们预先定义的某个类别中去。比如垃圾邮件过滤,给定邮件内容作为输入,输出 “是垃圾”,“不是垃圾” 两种状态之一;人脸识别,给定照片的特定区域,输出“是我”,“是路人甲”,“是路人A”,等等等等。 但实现这种自动分类功能往往并不简单,举例说,上述提到的几种输入数据,在概率上都是非常随机和分散的,难以抽取出一个统一的分类标准。具体一点,垃圾邮件的内容可能会千奇百怪,关键字五花八门;人脸更不用说,同样是两个鼻子一张嘴,但世间相貌岂止千万种,我自己作为脸盲患者就深感人脸识别不是一件易事;而人的字体和书写习惯也是非常难提炼出一个统一的标准,同样一个字,不同人写出来会非常不同。 但好在我们人类作为万物之灵,处理起这种任务起来虽然偶尔会有困难,但大部分时候可谓是得心应手,原因估计不说大家也知道,因为我们有学习能力。垃圾邮件收得多了,只看一个开头便随手扔进垃圾箱;在人事部混上一年,公司上下几百号面孔一个个熟络得不行;学习了书法鉴赏,几百张字帖看下来,看狂草也一点不虚。 但具体让你讲你怎么做到的,多数人也讲不出个所以然,认识很多张脸的人并不会说:”啊,其实我记下了每个的头围、眼间距、鼻子长度等等的详细尺寸,每次靠精确测量来一一对上号。“ 所以,人类的这种学习分类的能力,是一种模糊的过程,是在经历了足够多的输入作为 训练样本 后,慢慢总结、归纳出的一种概率模型,而非死板的映射。 机器学习中的分类器,就是通过数学工具,让机器也具备这种归纳总结数据的能力。而提到 通过数学工具归纳总结数据 ,难免会让人想到工程学中我们早已熟知的另一个概念——“拟合”。相信大部分人做过这种实验,先测量一堆数据,然后在坐标纸上一个一个画点,然后勾勾画画,这么就弄出来了一条曲线或者直线的模型。顺便一提,回想我大学,最令人发指的是学校竟然让用铅笔跟纸来做这个!美其名曰“熟悉具体步骤”,导致很多人一直到大四毕业设计才知道有 MATLAB 的拟合工具箱这个东西。 所以其实机器学习的很多理念,本质上还是在做 拟合 ( fitting),Andrew Ng 曾在一个讲座上开玩笑说,他最开始接触人工智能的时候,以为自己会接触各种神乎其技的科幻小说里的知识,结果发现他一直在做各种拟合。 而在他的CS229课程中,他讲的第一个线性分类器就是 线性回归 ( Linear Regression ),没错,就是线性回归,高中考纲,而且的的确确是有效果的。我硕士期间修的课程上,教授布置的第一个作业也是用线性回归预测总统大选( 用的伪数据)。






不知道有没有用 s = tf('s'); gs = input('请输入原开环传递函数\n 例如: 1/(s*(0.1*s+1)*(0.05*s+1)) \ngs ='); % 稳态误差 N = input('请输入给定信号表达式\n 例如: 1/s^2 \n N = '); E =s*N/(1+gs); G1 = E; clear s syms s [num,den] = tfdata(G1);%开始转换类型 tf->sym Na = size(den); for i=1:Na(1) for k=1:Na(2) Num=poly2sym(num{i,k},s); Den=poly2sym(den{i,k},s); sym_G1(i,k)=Num/Den; end end assignin('base','sym_G1',sym_G1); E = sym_G1; ess2 = limit(E,s,0,'right'); ess = input('请输入期望的稳态误差 ess = '); K = double(ess2)/ess ;%求得满足稳态误差的最小开环增益 clear s s = tf('s'); disp(['最小开环增益K = ',num2str(K)]) K = input('请输入开环增益K = '); gs=K*gs%更新系统传递函数 figure(1) margin(gs); [gm0 pm0 wg0 wp0] = margin(gs); gm0=20*log10(gm0); disp(['原系统幅值裕度Lg = ',num2str(gm0),'dB ','相角裕度γ = ',num2str(pm0)]) lg = input('请输入目标幅值裕度Lg = '); gama = input('请输入目标相角裕度γ = '); disp(['选用滞后校正 ']) % 截止频率 gama1=gama+5; [mu pu w]=bode(gs); wc=spline(pu,w,(gama1-180));%相对于pu的w的值的规律插值得到相对于(gama1-180)的值 disp(['截止频率wc = ',num2str(wc)]); % 计算β na=polyval(gs.num{1},j*wc); da=polyval(gs.den{1},j*wc); g=na/da; g1=abs(g); h=20*log10(g1); beta=10^(h/20); disp(['β = ',num2str(beta)]); % 计算校正环节参数 T=10/wc disp('校正环节:') gc=(T*s+1)/(beta*T*s+1) disp('校正后系统传递函数:') g=gs*gc disp('校正后系统传伯德图:') figure(2) bode(g); [gm pm wg wp]=margin(g); disp(['校正后系统幅值裕度Lg = ',num2str(gm),'dB ','相角裕度γ = ',num2str(pm)]) %阶跃响应 sys1=feedback(gs,1); sys2=feedback(g,1); figure(3) hold on step(sys1) step(sys2) hold off figure(4) hold on bode(gs) bode(g) hold off 程序写的比较烂 滞后校正参数计算.mlappinstall 111k

不知道有没有用 (附件:277477) s = tf('s'); gs = input('请输入原开环传递函数\n 例如: 1/(s*(0.1*s+1)*(0.05*s+1)) \ngs ='); % 稳态误差 N = input('请输入给定信号表达式\n 例如: 1/s^2 \n N = '); E =s*N/(1+gs); G1 = E; clear s syms s [num,den] = tfdata(G1);%开始转换类型 tf->sym Na = size(den); for i=1:Na(1) for k=1:Na(2) Num=poly2sym(num{i,k},s); Den=poly2sym(den{i,k},s); sym_G1(i,k)=Num/Den; end end assignin('base','sym_G1',sym_G1); E = sym_G1; ess2 = limit(E,s,0,'right'); ess = input('请输入期望的稳态误差 ess = '); K = double(ess2)/ess ;%求得满足稳态误差的最小开环增益 clear s s = tf('s'); disp(['最小开环增益K = ',num2str(K)]) K = input('请输入开环增益K = '); gs=K*gs%更新系统传递函数 figure(1) margin(gs); [gm0 pm0 wg0 wp0] = margin(gs); gm0=20*log10(gm0); disp(['原系统幅值裕度Lg = ',num2str(gm0),'dB ','相角裕度γ = ',num2str(pm0)]) lg = input('请输入目标幅值裕度Lg = '); gama = input('请输入目标相角裕度γ = '); disp(['选用滞后校正 ']) % 截止频率 gama1=gama+5; [mu pu w]=bode(gs); wc=spline(pu,w,(gama1-180));%相对于pu的w的值的规律插值得到相对于(gama1-180)的值 disp(['截止频率wc = ',num2str(wc)]); % 计算β na=polyval(gs.num{1},j*wc); da=polyval(gs.den{1},j*wc); g=na/da; g1=abs(g); h=20*log10(g1); beta=10^(h/20); disp(['β = ',num2str(beta)]); % 计算校正环节参数 T=10/wc disp('校正环节:') gc=(T*s+1)/(beta*T*s+1) disp('校正后系统传递函数:') g=gs*gc disp('校正后系统传伯德图:') figure(2) bode(g); [gm pm wg wp]=margin(g); disp(['校正后系统幅值裕度Lg = ',num2str(gm),'dB ','相角裕度γ = ',num2str(pm)]) %阶跃响应 sys1=feedback(gs,1); sys2=feedback(g,1); figure(3) hold on step(sys1) step(sys2) hold off figure(4) hold on bode(gs) bode(g) hold off 程序写的比较烂 (附件:277478)


ReLU[1]与softplus[2] 图15ReLU与softplus的函数图像 ReLU可表示为 \( f(x)=max(0,x) \) 而softmax可表示为: \( f(x)=ln(1+e^x ) \) 其导数为: \( f^{'}(x)=e^x/(e^x+1)=1/(1+e^{-x} ) \) 这一类的激活函数主要是为了拟合神经元的单侧抑制输出,它们可以加快神经网络的训练速度,更早地得到最终结果。 图16 ReLU对深度卷积神经网络错误率收敛的加速效果[3] Dropout[4] 图16 Dropout模式图 Dropout 技术在于训练部分神经元去拟合识别模型,而使用全部的神经元来进行测试,可以有效地防止过拟合问题(对训练数据集的识别效果过好,以至于无法较好地识别测试数据)。在训练的过程中,随机以一定概率(1-r)忽视部分神经元,进行计算和训练,而在测试(或应用于生产环境)的时候,通过将权值乘以r来计算所有神经元的输出。 Maxout [5] Maxout技术所作的改进主要将神经元的输出改变为(在激活函数前): \( h_i(x) =max_{j∈[1,k]}z_{ij} \) 其中: \( z_{ij}=x^T W_{...ij}+b_{ij} \) 这种使用多个权值计算最后输出最大值的方法改进能够极好地拟合凹函数,对于图像识别等神经网络的非凸模型识别任务拥有极好的提升效果。 图17maxout对非线性性函数的拟合 Network In Network[6] 如图所示,普通的卷积神经网络的输出是直接传递到下一层: 图18普通卷积层 而该论文中的卷积层在传递过程中经过了一个两层的MLP(多层感知器),从而增强卷积层的分类能力。 图19多层感知器卷积层 图20Network In Network结构简图 使用图示结构的神经网络,配合dropout、maxout,它做到了0.45的MNIST错误率。 Reference: [1] Nair V, Hinton G E. Rectified Linear Units Improve Restricted Boltzmann Machines[C]International Conference on Machine Learning. DBLP, 2010:807-814. [2]Dugas C, Bengio Y, Belisle F, et al. Incorporating Second-Order Functional Knowledge for Better Option Pricing[C]. neural information processing systems, 2001: 472-478. [3]KrizhevskyA,SutskeverI,Hinton G E.ImageNet Classification with Deep Convolutional Neural Networks[J].Advances in Neural Information Processing Systems,2012,25(2):2012. [4] Srivastava N, Hinton G, Krizhevsky A, et al. Dropout: a simple way to prevent neural networks from overfitting[J]. Journal of Machine Learning Research, 2014, 15(1):1929-1958. [5]Goodfellow I J, Wardefarley D, Mirza M, et al. MaxoutNetworks[J]. Computer Science, 2013:1319-1327. [6] Lin M, Chen Q, Yan S. Network In Network[J]. Computer Science, 2014.


在科创玩电路的童鞋萌基本都用过电路仿真软件,比如PSPICE,LTSPICE,Multisim之类的玩意,有没有想过这些货是怎么仿真电路的呢? 这些大型的仿真软件其实是基于SPICE的,外加一堆商业零件的电路模型还有画图工具啥的。核心的SPICE是免费的软件,不过是命令行的,日难用( 首先举个最简单的例子,各位刚学电路的时候最常遇到的老三件还记得么?电压源,电流源,电阻! 那就先弄个只有电压源,电流源和电阻的例子! 图中有3个电压未知的节点,记作V1 V2 V3。 先让窝萌回顾下KCL,也就是基尔霍夫电流节点定律,这个定律指明了流入和流出一个节点的电流永远是相等的. 为了方便分析,需要把电压源换成电流源: 一个电压源和一个电阻串联的小电路块可以等效为一个电流源和一个电阻并联的小电路块,电阻的值都一样,电流源的值为电压源电压除以电阻。 图于是就变成了这样: 这个例子可以写出3个KCL等式来: 稍微整理下可以得到: 这里面的G1 G2等代表的是电导,电导是电阻的倒数。 电导的英文叫Conductance,字面上还能理解为两个节点之间的“连接程度”,电导越大,两个节点之间的电阻越小。 这是一个3元1次线性方程,最好的解决方法是写成矩阵形式: 左手侧的电流源的电流是已知量,右边的矩阵里面是节点之间的电导,也是已知量,之后就可以求解所有节点的电压了。 解这个的方法有很多,高斯消元,LU法,QR分解法 知道了节点电压之后就可以求其他有用的量,比如消耗的功率等等。。。 其实上面的矩阵,光看着电路也能写出来,敲门如下: 矩阵是关于对角线对称的,矩阵上的元素可以分为两类,对角线元素和非对角线元素, 对于对角线元素,其值是与所有临近节点和地之间那些电阻的电导之和 对于非对角线元素,其值是负的那两个节点之间电阻的电导 如果两个节点之间没有连接,那么对应的矩阵中的元素是0,比如上面1节点和3节点就没有直接的电阻连接 PS:学会了上面的技巧之后 很多物理题里面日复杂的电路题基本可以做到秒解了 本帖介绍了最简单的纯电阻电路仿真,which is后面一切的基础,下一把介绍非线性电路仿真~




很久不上了,上了写点干货。 You may have dreamed about having your own two-legged (bipedal) robot. Now you may came up with some names, like ASIMO from Honda, or Big Dog from Boston Dynamics. They've accumulated decades of research. But see what these "accumulated research" do in the DARPA Challenge: Pieter Abbeel, Associate Professor, UC Berkeley - RE•WORK Deep Learning Summit 2016 #reworkDL https://www.youtube.com/watch?v=evq4p1zhS7Q They are so ROBOTIC (please watch the video)!! All participants of the 2-million-dollar-competition were less flexible than an 100-year-old human. Why? 因为传统上来说,一个机器人系统是这样工作的: 传感器输入 -> 数学建模 -> 特征提取、识别 -> 任务规划 -> 参数调整 -> 机械输出 每一个环节都需要用到大量(比如,超过KC论坛上所有 活人 总和)的工程知识。因此Robotics成为了美国对外限制出口的专业(留学生朋友们懂)。 但是,搞深度学习的人,比如上面那个视频的主讲Pieter教授不这么看。人要完成这些任务(开门、开车、通过障碍)并不需要进行大量数学计算,只需要通过肢体练习、大脑学习,效果却比任何控制算法都好。因此传统的机器人设计理念,将来必定是死路一条。 他们认为机器人系统应该是这样工作的: 传感器输入 -> 很大的神经网络 -> 机械输出 当然,他们有底气这么说,是因为他们已经用3d仿真加深度增强学习的方法,在计算机上解决了这些问题。 上面这个骨架机器人,以关节角度等作为传感器输入,以前进速度为奖励,学会了如何正确且最优地施加每个关节的力矩,在3D空间中保持快速前进。整个过程不需要机器人基于任何数学模型执行任何数学计算(除了用于模拟及训练神经网络的计算)。 这和深度学习用结构简单的卷积神经网络,在大规模图像识别任务上轻松打败2012年以前所有人工设计特征算法的道理是一样的。我们将会在接下来的几年见证机器人技术如当前的图像、语音技术一样,实现令人惊讶的突破。 Geoff Hinton教授曾经吐槽,要搞清楚大脑是怎么工作的,你不需要10亿欧元(讽刺欧洲大脑项目),你需要一点计算理论。 现在Pieter教授其实也在吐槽:要搞清楚怎么让机器人走路,你不需要每年租场地找一帮机械专业的学生悬赏百万美元(讽刺DARPA),你需要一点深度学习。 Now You May Ask 那这和我们这些吃瓜群众有什么关系呢? 深度学习是计算机科学的分支,增强学习(现在)是深度学习的分支。最近几年有很多中国人(比如本版的某些人)留学美国、加拿大或英国学习计算机科学,并将深度学习的最新进展源源不断地带回国内(因为这个领域不属于robotics,暂时还不受迟钝的美国政府限制),在北上等地的许多行业(语音、图像、人机交互、自动驾驶、数据挖掘)大放异彩。所以如果你对深度学习感兴趣,相信这是一种解决问题的好方法,你可以选择去国外留学。目前这方面的佼佼者包括UCB, UCLA, Stanford等湾区学校,另有U Toronto和NYU等等。 如果你有计算机科学背景,但是在国内上学没有机器学习课程,那么除了申请国外研究生,你还可以直接在网上收看国外名师的视频课程。具体课程我在本版发布过多次,这里再强调一下。 深度学习入门请看Andrew Ng(教授)在Coursera上的Machine Learning,然后是Andrej Karpathy(博士)的在Stanford网站上发表的关于CNN和RNN的课程教学内容。这两个人以热心教育事业著称。 然后做很多深度学习实验,比如语音、图像、文本的分类。 增强学习入门看David Silver(阿法狗)和John Schulman(博士)的视频教程,John Schulman的教程口才一般但是发的关于CEM的python练习一定要做。 增强学习练习请去gym.openai.com,用你的算法跟全世界的人比赛。你应该至少用增强学习算法解决CartPole和Pendulum两个控制问题。我在Pendulum-v0环境的成绩是第二名(ctmakro),因为我用的方法实现的是DeepMind半年前刚出的论文。我在BipedalWalker(双足机器人控制,超难)环境的成绩这两天在跑,预计也能拿到第二名。具体见 https://gym.openai.com/envs/BipedalWalker-v2 目前ImageNet作为深度图像识别的benchmark已经快被(某几位中国同学)爆关,但深度增强学习领域仍不成熟(DeepMind和OpenAI最近仍然在不断发表新成果、新方法,从最早的Policy Gradient到Deep Q-Learning到现在的TRPO),有许多开放的问题尚待解决。无论牛逼与否,每个人都有机会为这个学科作出贡献。OpenAI有gym和universe,DeepMind有DeepMind Lab环境,这都是免费的实验环境。 我没有接受过系统的计算机科学教育,但我有8年编程史,16年计算机操作史,于是我花了3个月的 工作 时间把计算机科学教育补上了(主要是UCB和斯坦福的课程,从操作系统、算法数据结构一直到关系数据库理论)。如果你懂编程,但没有机会接受计算机科学教育,我推荐你也花时间学习这些课程,它们能让你获得对这个学科更高层次的理解(从而把许多痛苦的bug变成轻松的cake),实现后续课程学习效率的数倍提升。 然后我又花了3个月的 工作 时间把机器学习从Linear Regression、Cross Entropy一路学到 Attention Based Model、Deep Reinforcement Learning,途中做了很多实验,现在能非常熟练地用Python操作多维数组(Tensor),并掌握了TensorFlow和Keras等开源图计算框架的使用。当然这和我7年前曾经稍微接触过数字图像处理(DIP)、计算机图形学(CG)有关系,不过我想每一个计算机专业的同学对DIP都不会陌生,如果懂CG就更好。 意思就是说,深度学习(比如对于KC的许多人来说)并不难,只不过(目前以及未来5年)都要从国外进口,所以显得很洋气。只要高中数学扎实,即便高等数学不及格也是可以学会的,很多时候只是我们没有尝试、不敢尝试。所以我亲自试了一下,证明通过网络课程是可以学会的,对人工智能感兴趣的话不学就亏了。 这里有些人不是想制导吗?不是想入轨吗?不是想绑块板砖空中悬停吗?不是想丢个飞镖把无人机砸下来吗?不是想开着高达上路吗?现代控制理论的局限需要靠深度学习方法弥补。


我之前曾经写过文章,说未来社会的很多基础设施不再由政府提供,而是由非常庞大的科技企业提供。比如说大家不再去中国移动或者中国联通办手机卡,而是直接买华为的手机,里面内置了私有基带协议,直接接入华为的网络;大家的身份信息不再向政府登记、查询,而是直接接入BAT的API服务,等等。 现实总是来得比想象快。阿里刚刚发布了他们的新产品“公众趋势预测” https://help.aliyun.com/document_detail/42769.html?spm=a2c17.92424.430516.9.UOm1Oi 大概的功能就是:通过强大的计算力量,实时收集互联网上的一切言论,通过统计方法,分析现在大家都在关注什么,然后反馈给客户。 中国有句古话,叫防民之口甚于防川,意思就是舆论决定政权,谁能控制舆论,谁就能维护政权稳定(反之亦然)。我们的政府要斥巨资养水军、派五毛,是一个道理。从上面链接的介绍来看,现在阿里已经拥有了非常强大的舆论监测能力。下面发生了什么,政府不一定知道,反而阿里比政府还清楚,可以把这个信息卖给政府。而政府买了这个信息以后,既可以用来为人民服务,也可以用来帮人民封口。 当然,阿里并没有直接说“我们在分析的同时也提供监控服务”,但是他们在旁边的一项“直播实时转写”服务中提供了如下功能: 现场演讲场景下、秀场直播场景下,将直播内容的音频实时转写成字幕。对于秀场直播场景,还可以进一步监控违规内容。 也就是说,以后做直播,你讲一个关于习大大的笑话,马上你的频道就可能被切。把这个手段和上面的大规模言论采集加在一起,就变成了“公众舆论实时监控与和谐”。电影《建党伟业》里面,游行学生一人喊口号大家附和的场景,如今不会再发生了。刚喊两个字,武警就出动了。 所以这并不是阴谋论。虽然我相信中国政府不会一觉醒来把中国变成朝鲜,问题是我以前也相信美国人民不会一觉醒来把商人选成总统、英国人民不会一觉醒来退出欧盟、俄罗斯人民不会一觉醒来入侵乌克兰……这些乌龙事件的背后,其实全都是政治家们为了达到特定的目的而对舆论进行操纵。 回到5年前,我发一条微博针砭时弊,我的朋友可以转发评论;如今我发一条微博针砭时弊,第二天就消失了,同时我也进了网监部门的重点关照名单。所以现在中国人已经不怎么针砭时弊了,因为大家都清楚,针砭时弊的东西发了也是白发。针砭时弊大多数都是愤青,但也有社会的进步力量。如果一个社会的进步力量总是被封号,这个社会至少在某些方面是不会取得进步的。我们可以看到,那些对韩春雨提出质疑的学者,态度是多么的客气、多么地不想给自己找麻烦。 同样的,如今我发在科创上的文章,也不能涉及任何社会阴暗面,不能触及任何社会禁区(所以少了很多有趣的东西)。而在有了大规模监控技术之后,这一切都会发生巨大的变化:以前可能是群众举报或者有关部门为了业务在搜索引擎上面搜索,现在这个过程变成了自动的,你刚发完一个帖子,那边论坛值班室电话就响了。这就从量变到质变了。 但这都不是重点。 真正的问题不是政府获得了力量,而是正如我预言的那样,BAT变成了政府。 我们知道,政府的一个权利就是根据法律向人民征税并提供公共服务,这个征税的过程是强制性的,不想交也得交的。而BAT也根据法律(就是你默认同意的《用户条款》)向大家收集了很多信息,为大家提供公共服务(比如百度、微信、淘宝),它们向大家收集的这些信息也都是具有价值的,和税款一样都是真金白银。从这个角度讲,BAT们实际上是扮演了政府的角色。 你可能会说,BAT提供的服务,我不想用可以不用,可以不给他们提供信息呀。但人民群众做不到,而历史是由人民群众书写的。 那这跟我有什么关系呢 中华上下五千年,所有的朝代,所有的帝王,都希望通过各种手段维持政权的稳定,比较著名的案例有秦始皇的焚书坑儒。所以如果你是一名热爱读书的知识分子,从今天开始,不要在网上跟别人分享你的读书体会啦。


摄像头是小蚁的(拆开来看到Xbee,紫蜂通讯。直接买的小米VR,不准备做FPV。两块arduino nano,两块蓝牙主从一体模块,GY-85九轴传感器,舵机云台。硬件差不多布线完成了,软件方面参考 http://tieba.baidu.com/p/4445673292 发现他arduino语句有问题 因为煎饼还处于单片机跑马灯阶段,蓝牙更不懂,所以哪位程序猿帮我看看 头追程序 // Written by Dennis Frie - 2012 // Contact: Dennis.frie@gmail.com // Version 0.08 // Discussion: // http://www.rcgroups.com/forums/showthread.php?t=1677559 /* Channel mapping/config for PPM out: 1 = PPM CHANNEL 1 2 = PPM CHANNEL 2 3 = PPM CHANNEL 3 4 = PPM CHANNEL 4 5 = PPM CHANNEL 5 6 = PPM CHANNEL 6 7 = PPM CHANNEL 7 8 = PPM CHANNEL 8 9 = PPM CHANNEL 9 10 = PPM CHANNEL 10 11 = PPM CHANNEL 11 12 = PPM CHANNEL 12 20 = ANALOG INPUT 0 21 = ANALOG INPUT 1 22 = ANALOG INPUT 2 23 = ANALOG INPUT 3 24 = ANALOG INPUT 4 25 = ANALOG INPUT 5 26 = ANALOG INPUT 6 27 = ANALOG INPUT 7 20 = DIGITAL INPUT 0 21 = DIGITAL INPUT 1 22 = DIGITAL INPUT 2 23 = DIGITAL INPUT 3 24 = DIGITAL INPUT 4 25 = DIGITAL INPUT 5 26 = DIGITAL INPUT 6 27 = DIGITAL INPUT 7 30 = HT pan 31 = HT tilt 32 = HT roll Mapping example: $123456789111CH */ #include <Wire.h> #include "config.h" #include "functions.h" #include "sensors.h" #include "ServoSupport.h" #include <EEPROM.h> extern unsigned char channel_mapping[]; extern unsigned char PpmIn_PpmOut[13]; extern char read_sensors; extern long channel_value[]; extern float gyroOff[]; int counter =0;      // Delay for serial-output. char printPlot = 0;  // print plot for GUI? // External variables (from sensors.h): extern float tiltBeta; extern float panBeta; extern float gyroWeight; extern float GyroWeightPan; extern int maxPulse; extern int servoPanCenter; extern int servoTiltCenter; extern int servoRollCenter; extern int panMaxPulse; extern int panMinPulse; extern int tiltMaxPulse; extern int tiltMinPulse; extern int rollMaxPulse; extern int rollMinPulse; extern float panFactor; extern float tiltFactor;   extern float rollFactor;   extern char tiltInverse; extern char rollInverse; extern char panInverse; extern float magOffset[]; extern int accOffset[]; extern unsigned char htChannels[]; extern char resetValues;           void setup() {      Serial.begin(SERIAL_BAUD);      pinMode(7,OUTPUT);   pinMode(9,OUTPUT);   digitalWrite(2,HIGH);   digitalWrite(3,HIGH);        pinMode(0,INPUT);   pinMode(1,INPUT);   pinMode(2,INPUT);   pinMode(3,INPUT);   pinMode(6,INPUT);   pinMode(7,INPUT);     pinMode(8,INPUT);       //Set button pin to input:   pinMode(BUTTON_INPUT,INPUT);      //Set internal pull-up resistor.   digitalWrite(BUTTON_INPUT,HIGH);      digitalWrite(0,LOW); // pull-down resistor   digitalWrite(1,LOW);   digitalWrite(2,HIGH);   digitalWrite(3,HIGH);           Wire.begin();                  // Start I2C // If the device have just been programmed, write initial config/values to EEPROM:   if (EEPROM.read(0) != 8)  {      //  #if (DEBUG)      Serial.println("New board - saving default values!"); //  #endif                init_sensors();    #if (ALWAYS_CAL_GYRO ==0)          setSensorOffset();    #endif         save Settings();     saveMagData();     saveAccData();     EEPROM.write(0,8);   }         getSettings();                 // Get settings saved in EEPROM      init_sensors();                // Initialise I2C sensors   calMag();   resetCenter();   init_timer_interrupt();        // Start timer interrupt (for sensors)        ServoSupport::Init();   ServoSupport::SetAng(1,9,90);   ServoSupport::SetAng(2,10,90);   ServoSupport::SetAng(3,8,90);   ServoSupport::Start();    } char serial_data[101];           // Array for serial-data unsigned char serial_index =0;   // How many bytes have been received? char string_started = 0;         // Only saves data if string starts with right byte char outputMag = 0; char outputAcc = 0; void loop() {      // Check button   if (digitalRead(BUTTON_INPUT)==0) {    resetValues = 1;   }       // All this is used for communication with GUI     if (Serial.available()) {          if (string_started == 1) {                 // Read incoming byte            serial_data[serial_index++] = Serial.read();                       // If string ends with 'CH" it's channel configuration, that have been received.            // String must always be 12 chars/bytes and ending with CH to be valid.            if (serial_data[serial_index-2] == 'C' && serial_data[serial_index-1] == 'H' && serial_index == 14) {                  // To keep it simple, we will not let the channels be 0-initialised, but start from 1 to match actual channels.                for (unsigned char i=0; i<13; i++) {                   channel_mapping[i+1] = serial_data[i]-48;                                      // Update the dedicated PPM-in -> PPM-out array for faster performance.                   if (serial_data[i]-48 < 14) { //                    PpmIn_PpmOut[i+1] =  serial_data[i]-48;                     PpmIn_PpmOut[serial_data[i]-48] =i+1;                   }                                   }                               Serial.println("Channel mapping received");                               // Reset serial_index and serial_started                serial_index=0;                string_started = 0;            }                                            if (serial_data[serial_index-2] == 'H' && serial_data[serial_index-1] == 'E') {                       // We need 8 parameters:                       // LP_tilt            // LP_pan            // Gyro_weight_tilt            // Gyro_weight_pan            // servo_max            // servo_min            // tilt_servo_gain            // pan_servo_gain                       Serial.println("HT config received:");                       /*            for (unsigned char k = 0; k< 10; k++) {             valuesReceived[k] =0;            }            */            int valuesReceived[20] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};            int comma_index =0;            for (unsigned char k = 0; k < serial_index-2; k++) {                             // Looking for comma                if (serial_data[k] == 44) {                  comma_index++;                }                           else {              valuesReceived[comma_index] = valuesReceived[comma_index]*10 + (serial_data[k]-48);              }                                        // Mainly for debug:              #if (DEBUG)              Serial.print(serial_data[k]);              #endif                        }                      #if (DEBUG)            Serial.println();             for (unsigned char k = 0; k < comma_index+1; k++) {                Serial.print(valuesReceived[k]);                Serial.print(",");                       }            Serial.println();           #endif          tiltBeta        = (float)valuesReceived[0] / 100;            panBeta         = (float)valuesReceived[1] / 100;          gyroWeight      = (float)valuesReceived[2] / 100;          GyroWeightPan   = (float)valuesReceived[3] / 100;          tiltFactor     = (float)valuesReceived[4] / 10;                  panFactor     = (float)valuesReceived[5] / 10;                    rollFactor     = (float)valuesReceived[6] / 10;                         tiltInverse = 1;         rollInverse = 1;         panInverse = 1;                               if (valuesReceived[7] / 100 == 1) {              panInverse = -1;              valuesReceived[7]-=100;          }                     if (valuesReceived[7] / 10 == 1) {           rollInverse = -1;           valuesReceived[7]-=10;          }          if (valuesReceived[7] / 1 == 1) {            tiltInverse = -1;          }                serial_index=0;                string_started = 0;                   servoPanCenter = valuesReceived[8];          panMinPulse = valuesReceived[9];          panMaxPulse = valuesReceived[10];                           servoTiltCenter = valuesReceived[11];          tiltMinPulse = valuesReceived[12];          tiltMaxPulse = valuesReceived[13];                  servoRollCenter = valuesReceived[14];          rollMinPulse = valuesReceived[15];          rollMaxPulse = valuesReceived[16];                             htChannels[0] = valuesReceived[17];                            htChannels[1] = valuesReceived[18];                        htChannels[2] = valuesReceived[19];                                         Serial.println(htChannels[0]);          Serial.println(htChannels[1]);          Serial.println(htChannels[2]);                                   save Settings();                        }                      // Debug info           else if (serial_data[serial_index-5] == 'D' && serial_data[serial_index-4] == 'E' && serial_data[serial_index-3] == 'B' && serial_data[serial_index-2] == 'U' && serial_data[serial_index-1] == 'G') {                  debugOutput();                               serial_index=0;                string_started = 0;           }                                              // Start magnetometer cal           else if (serial_data[serial_index-4] == 'C' && serial_data[serial_index-3] == 'A' && serial_data[serial_index-2] == 'S' && serial_data[serial_index-1] == 'T') {                  outputMag = 1;                               serial_index=0;                string_started = 0;           }                                         // Start accelerometer cal           else if (serial_data[serial_index-4] == 'G' && serial_data[serial_index-3] == 'R' && serial_data[serial_index-2] == 'A' && serial_data[serial_index-1] == 'V') {                  outputAcc = 1;                               serial_index=0;                string_started = 0;           }                         // Stop magnetometer cal           else if (serial_data[serial_index-4] == 'C' && serial_data[serial_index-3] == 'A' && serial_data[serial_index-2] == 'E' && serial_data[serial_index-1] == 'N') {                  outputMag = 0;                               serial_index=0;                string_started = 0;           }                      // Stop accelerometer cal           else if (serial_data[serial_index-4] == 'G' && serial_data[serial_index-3] == 'R' && serial_data[serial_index-2] == 'E' && serial_data[serial_index-1] == 'N') {                  outputAcc = 0;                               serial_index=0;                string_started = 0;           }                                 // Start plotting if PLST received:           else if (serial_data[serial_index-4] == 'P' && serial_data[serial_index-3] == 'L' && serial_data[serial_index-2] == 'S' && serial_data[serial_index-1] == 'T') {                  printPlot = 1;                               serial_index=0;                string_started = 0;           }                   // Stop plotting if PLEN received:                     else if (serial_data[serial_index-4] == 'P' && serial_data[serial_index-3] == 'L' && serial_data[serial_index-2] == 'E' && serial_data[serial_index-1] == 'N') {                  printPlot = 0;                               serial_index=0;                string_started = 0;           }                      // Save settings           else if (serial_data[serial_index-4] == 'S' && serial_data[serial_index-3] == 'A' && serial_data[serial_index-2] == 'V' && serial_data[serial_index-1] == 'E') {                 save Settings();                               serial_index=0;                string_started = 0;           }                                //Calibrate gyro           else if (serial_data[serial_index-4] == 'C' && serial_data[serial_index-3] == 'A' && serial_data[serial_index-2] == 'L' && serial_data[serial_index-1] == 'I') {                  setSensorOffset();                saveSsttings();                                Serial.print("Gyro offset measured:");                 Serial.print(gyroOff[0]);                 Serial.print(",");                   Serial.print(gyroOff[1]);                 Serial.print(",");                       Serial.println(gyroOff[2]);                                   serial_index=0;                string_started = 0;           }             else if (serial_data[serial_index-2] == 'M' && serial_data[serial_index-1] == 'A') {               Serial.println(serial_data);               int valuesReceived[5] = {0,0,0,0,0};               int comma_index =0;                           for (unsigned char k = 0; k < serial_index-2; k++) {                                     // Looking for comma                    if (serial_data[k] == 44) {                      comma_index++;                    }                                   else {                  valuesReceived[comma_index] = valuesReceived[comma_index]*10 + (serial_data[k]-48);                  }                                                                                        }                                    magOffset[0] = valuesReceived[0]-2000;                   magOffset[1] = valuesReceived[2]-2000;                   magOffset[2] = valuesReceived[1]-2000;                                      saveMagData();                             }                                       else if (serial_data[serial_index-3] == 'A' && serial_data[serial_index-2] == 'C' && serial_data[serial_index-1] == 'C') {               Serial.println(serial_data);               int valuesReceived[5] = {0,0,0,0,0};               int comma_index =0;                           for (unsigned char k = 0; k < serial_index-3; k++) {                                     // Looking for comma                    if (serial_data[k] == 44) {                      comma_index++;                    }                                   else {                  valuesReceived[comma_index] = valuesReceived[comma_index]*10 + (serial_data[k]-48);                  }                                                                                        }                   accOffset[0] = valuesReceived[0]-2000;                   accOffset[1] = valuesReceived[1]-2000;                   accOffset[2] = valuesReceived[2]-2000;                                      saveAccData();                             }                                                 // If more than 100 bytes have been received, the string is not valid. Reset and "try again" (wait for $ to indicate start of new string).            else if (serial_index > 100) {                serial_index = 0;                string_started = 0;            }                       }          else if (Serial.read() == '$') {         string_started = 1;     }             }      // if "read_sensors" flag is set high, read sensors and update   if (read_sensors == 1) {              updateSensors();              gyroCalc();         accCalc();         magCalc();         filter();                            if (counter++ >7) {            //          testGyroOutput();        //   testMagOutput(); //           quickTest();                      if (printPlot == 1) {              headtrackerOutput();                              }                    else if (outputMag) {              calMagOutput();           }                      else if (outputAcc) {             calAccOutput();           }                      counter=0;         }               // Will first update read_sensors when everything's done.           read_sensors = 0;              }    } void saveSettings() {    // This is the necessary head-tracler settings: /*          tiltBeta        = (float)valuesReceived[0] / 100;            panBeta         = (float)valuesReceived[1] / 100;          gyroWeight      = (float)valuesReceived[2] / 100;          GyroWeightPan   = (float)valuesReceived[3] / 100;            maxPulse        = valuesReceived[4];          servoCenter     = valuesReceived[5];                  tiltFactor     = (float)valuesReceived[6] / 10;                  panFactor     = (float)valuesReceived[7] / 10;   */              // Chars   EEPROM.write(1,(unsigned char) (tiltBeta*100));   EEPROM.write(2,(unsigned char) (panBeta*100));   EEPROM.write(3,(unsigned char) (gyroWeight*100));   EEPROM.write(4,(unsigned char) (GyroWeightPan*100));      // And some integers   EEPROM.write(5,(unsigned char)maxPulse);   EEPROM.write(6,(unsigned char)(maxPulse>>8));      EEPROM.write(7,(unsigned char)servoPanCenter);   EEPROM.write(8,(unsigned char)(servoPanCenter>>8));        EEPROM.write(9,(unsigned char) (tiltFactor*10));   EEPROM.write(10,(int)((tiltFactor*10))>>8);     EEPROM.write(11,(unsigned char) (panFactor*10));   EEPROM.write(12,(int)((panFactor*10))>>8);     // Channel inverse settings:   EEPROM.write(13,(unsigned char)(tiltInverse+1));   EEPROM.write(14,(unsigned char)(rollInverse+1));       EEPROM.write(15,(unsigned char)(panInverse+1));     EEPROM.write(16,(unsigned char)servoTiltCenter);   EEPROM.write(17,(unsigned char)(servoTiltCenter>>8));     EEPROM.write(18,(unsigned char)servoRollCenter);   EEPROM.write(19,(unsigned char)(servoRollCenter>>8));     EEPROM.write(20,(unsigned char)panMaxPulse);   EEPROM.write(21,(unsigned char)(panMaxPulse>>8));        EEPROM.write(22,(unsigned char)panMinPulse);   EEPROM.write(23,(unsigned char)(panMinPulse>>8));       EEPROM.write(24,(unsigned char)tiltMaxPulse);   EEPROM.write(25,(unsigned char)(tiltMaxPulse>>8));       EEPROM.write(26,(unsigned char)tiltMinPulse);   EEPROM.write(27,(unsigned char)(tiltMinPulse>>8));       EEPROM.write(28,(unsigned char)rollMaxPulse);   EEPROM.write(29,(unsigned char)(rollMaxPulse>>8));       EEPROM.write(30,(unsigned char)rollMinPulse);   EEPROM.write(31,(unsigned char)(rollMinPulse>>8));         EEPROM.write(32,(unsigned char)htChannels[0]);   EEPROM.write(33,(unsigned char)htChannels[1]);   EEPROM.write(34,(unsigned char)htChannels[2]);      // Saving gyro calibration values   int temp = (int)(gyroOff[0]+500.5);   EEPROM.write(35,(unsigned char)temp);   EEPROM.write(36,(unsigned char)(temp>>8));        temp = (int)(gyroOff[1]+500.5);   EEPROM.write(37,(unsigned char)temp);   EEPROM.write(38,(unsigned char)(temp>>8));       temp = (int)(gyroOff[2]+500.5);   EEPROM.write(39,(unsigned char)temp);   EEPROM.write(40,(unsigned char)(temp>>8));          Serial.println("Settings saved!"); } void getSettings() {      tiltBeta = (float)EEPROM.read(1)/100;   panBeta = (float)EEPROM.read(2)/100;   gyroWeight = (float)EEPROM.read(3)/100;   GyroWeightPan = (float)EEPROM.read(4)/100;        maxPulse = EEPROM.read(5)+(EEPROM.read(6)<<8);      servoPanCenter = EEPROM.read(7)+(EEPROM.read(8)<<8);      tiltFactor = (float)(EEPROM.read(9)+(EEPROM.read(10)<<8)) / 10;      panFactor = (float)(EEPROM.read(11)+(EEPROM.read(12)<<8)) / 10;     servoTiltCenter = EEPROM.read(16)+(EEPROM.read(17)<<8);   servoRollCenter = EEPROM.read(18)+(EEPROM.read(19)<<8);        panMaxPulse = EEPROM.read(20)+(EEPROM.read(21)<<8);     panMinPulse = EEPROM.read(22)+(EEPROM.read(23)<<8);          tiltMaxPulse = EEPROM.read(24)+(EEPROM.read(25)<<8);     tiltMinPulse = EEPROM.read(26)+(EEPROM.read(27)<<8);            rollMaxPulse = EEPROM.read(28)+(EEPROM.read(29)<<8);     rollMinPulse = EEPROM.read(30)+(EEPROM.read(31)<<8);              htChannels[0] = EEPROM.read(32);     htChannels[1] = EEPROM.read(33);     htChannels[2] = EEPROM.read(34);          gyroOff[0] = EEPROM.read(35)+(EEPROM.read(36)<<8)-500;   gyroOff[1] = EEPROM.read(37)+(EEPROM.read(38)<<8)-500;   gyroOff[2] = EEPROM.read(39)+(EEPROM.read(40)<<8)-500;           tiltInverse = -1;   rollInverse = -1;   panInverse = -1;      if (EEPROM.read(13) == 2) {       tiltInverse = 1;   }     if (EEPROM.read(14) == 2) {       rollInverse = 1;   }        if (EEPROM.read(15) == 2) {       panInverse = 1;   }   magOffset[0] = EEPROM.read(200)+(EEPROM.read(201)<<8)-2000;       magOffset[1] = EEPROM.read(202)+(EEPROM.read(203)<<8)-2000;       magOffset[2] = EEPROM.read(204)+(EEPROM.read(205)<<8)-2000;            accOffset[0] = EEPROM.read(206)+(EEPROM.read(207)<<8)-2000;       accOffset[1] = EEPROM.read(208)+(EEPROM.read(209)<<8)-2000;       accOffset[2] = EEPROM.read(210)+(EEPROM.read(211)<<8)-2000;          #if (DEBUG)     debugOutput(); #endif } void saveMagData() {   int temp;     temp = (int)(magOffset[0]+2000);   EEPROM.write(200,(unsigned char)temp);   EEPROM.write(201,(unsigned char)(temp>>8));          temp = (int)(magOffset[1]+2000);   EEPROM.write(202,(unsigned char)temp);   EEPROM.write(203,(unsigned char)(temp>>8));          temp = (int)(magOffset[2]+2000);   EEPROM.write(204,(unsigned char)temp);   EEPROM.write(205,(unsigned char)(temp>>8));        Serial.println("Mag-offset saved!");    } void saveAccData() {   int temp;     temp = (int)(accOffset[0]+2000);   EEPROM.write(206,(unsigned char)temp);   EEPROM.write(207,(unsigned char)(temp>>8));          temp = (int)(accOffset[1]+2000);   EEPROM.write(208,(unsigned char)temp);   EEPROM.write(209,(unsigned char)(temp>>8));          temp = (int)(accOffset[2]+2000);   EEPROM.write(210,(unsigned char)temp);   EEPROM.write(211,(unsigned char)(temp>>8));        Serial.println("Acc-offset saved!");   Serial.print(accOffset[0]);   Serial.print(",");   Serial.print(accOffset[1]);   Serial.print(",");     Serial.println(accOffset[2]);    } void debugOutput() {    Serial.println();   Serial.println(); Serial.println(); Serial.println("------ Debug info------");    Serial.print("tiltBeta: "); Serial.println(tiltBeta); Serial.print("panBeta: "); Serial.println(panBeta); Serial.print("gyroWeight: "); Serial.println(gyroWeight); Serial.print("GyroWeightPan: "); Serial.println(GyroWeightPan); Serial.print("servoPanCenter: "); Serial.println(servoPanCenter); Serial.print("servoTiltCenter: "); Serial.println(servoTiltCenter); Serial.print("servoRollCenter: "); Serial.println(servoRollCenter); Serial.print("tiltFactor: "); Serial.println(tiltFactor); Serial.print("panFactor: "); Serial.println(panFactor);   Serial.print("Gyro offset saved "); Serial.print(gyroOff[0]); Serial.print(",");   Serial.print(gyroOff[1]); Serial.print(",");       Serial.println(gyroOff[2]);     Serial.print("Mag offset saved "); Serial.print(magOffset[0]); Serial.print(",");   Serial.print(magOffset[1]); Serial.print(",");       Serial.println(magOffset[2]); Serial.print("Acc offset saved "); Serial.print(accOffset[0]); Serial.print(",");   Serial.print(accOffset[1]); Serial.print(",");       Serial.println(accOffset[2]); SensorInfoPrint();     } 接收机程序 //this programm will put out a PPM signal //////////////////////CONFIGURATION/////////////////////////////// #define chanel_number 9  //set the number of chanels #define default_servo_value 1500  //set the default servo value #define PPM_FrLen 22500  //set the PPM frame length in microseconds (1ms = 1000μs) #define PPM_PulseLen 300  //set the pulse length #define onState 1  //set polarity of the pulses: 1 is positive, 0 is negative #define sigPin 10  //set PPM signal output pin on the arduino ////////////////////////////////////////////////////////////////// /*this array holds the servo values for the ppm signal change theese values in your code (usually servo values move between 1000 and 2000)*/ int ppm[chanel_number]; unsigned int angs,ang1,ang2; char xx; void setup(){        Serial.begin(9600);   //initiallize default ppm values   for(int i=0; i<chanel_number; i++){     ppm[i]= default_servo_value;   }   pinMode(sigPin, OUTPUT);   digitalWrite(sigPin, !onState);  //set the PPM signal pin to the default state (off)      cli();   TCCR1A = 0; // set entire TCCR1 register to 0   TCCR1B = 0;      OCR1A = 100;  // compare match register, change this   TCCR1B |= (1 << WGM12);  // turn on CTC mode   TCCR1B |= (1 << CS11);  // 8 prescaler: 0,5 microseconds at 16mhz   TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt   sei(); } void loop(){   //put main code here   if (Serial.available()) {          angs=Serial.parseInt();  //angs=ang1*180+ang2   ppm[1] = angs/180;   ppm[1] = map(ppm[1],1,179,1000,2000);   ppm[2] = angs%180;   ppm[2] = map(ppm[2],1,179,1000,2000);   }   //ang1=angs/180; ang2=angs%180; xx++; if(50<=xx) {   ppm[0] = analogRead(A0);   ppm[0] = map(ppm[0],0,1023,1000,2000);   xx=0; }   delayMicroseconds(200); } ISR(TIMER1_COMPA_vect){  //leave this alone   static boolean state = true;      TCNT1 = 0;      if(state) {  //start pulse     digitalWrite(sigPin, onState);     OCR1A = PPM_PulseLen * 2;     state = false;   }   else{  //end pulse and calculate when to start the next pulse     static byte cur_chan_numb;     static unsigned int calc_rest;        digitalWrite(sigPin, !onState);     state = true;     if(cur_chan_numb >= chanel_number){       cur_chan_numb = 0;       calc_rest = calc_rest + PPM_PulseLen;//       OCR1A = (PPM_FrLen - calc_rest) * 2;       calc_rest = 0;     }     else{       OCR1A = (ppm[cur_chan_numb] - PPM_PulseLen) * 2;       calc_rest = calc_rest + ppm[cur_chan_numb];       cur_chan_numb++;     }       } } 原概念是位老外Dennis Fire提出的,他的程序只写了头追的传感部分,输出的信号通过无人机的遥控器教练功能发射,不知道如何转蓝牙


下面是Geoff Hinton的《神经网络机器学习》课程视频的截图。 先别管这张图。为什么我要关注机器学习?在众多被冠以“机器学习”这样一个宣传噱头一样的名字的领域中,我最关注的是计算机视觉。对我而言,这一切的渊源都要追溯到2008年。 那时我和93°、小光还有很多网友都在关注一件很有趣的活动,叫做“爆吧”,也就是【大规模自动化贴吧灌水】的意思。当时的百度贴吧是全国人民旺盛精力的释放场所,有很多人喜欢李宇春,很多人喜欢东方神起,这都是现在的小朋友们不能理解的。(写给小朋友们:李宇春就是古代的张杰,东方神起就是古代的EXO) 人对于自己不能理解的事物,总是会加以歧视和偏见。当时很多网友对李宇春和东方神起的粉丝意见很大,有点像现在加州人民对川普的粉丝意见很大。中国人表达意见的主要方式是动手,当时我们动手的主要方式就是往对方的贴吧大量发帖,让对方的贴吧无法正常浏览和讨论。这个过程当然少不了软件助攻,大家开发了各种各样的“爆吧器”,也就是自动发帖软件,每秒钟能发十几帖,一下子就把对方的贴吧给灌满了。完事之后大家都很兴奋,爆吧人民站起来了!!爆吧联合国万岁!!就像远古时期一个部落把另一个部落给团灭了一样的兴奋。 那个时候爆吧界的人自豪感是很强的,就像前段时间帝吧出征的那帮人一样,都觉得自己是在维护民族大义、净化华夏血统。区别是爆吧的人会写程序,帝吧的人只有表情包。 后来百度搞了验证码,要发帖就得先输入验证码。验证码是一个图片,从图片中提取文字对人来说很简单,对计算机来说就很麻烦了,毕竟这帮爆吧的学生没几个搞过数字图像处理。 为了能继续爆吧,大家先是搞明白了百度是怎么生成和消费验证码的,在爆吧之前预先获取几百个验证码,然后一个个手工输入,最后发帖的时候把这些验证码在短时间内全用上。 (后来这成了一个产业,全网各种各样的验证码,由软件收集,然后安排一帮小妹每天坐在电脑前面,一条验证码几分钱地打,服务各种黑产。) 所以当时我和93°一直思考的问题,就是到底怎么用电脑识别验证码。如果能做到这一点,我们的爆吧器岂不轻轻松松就成了全网第一?每一条验证码,那可都是白花花的银子啊。 对于上面那种简单的验证码,其实还是有办法的。最简单的OCR方法就是模板卷积(也就是把已知字母的外观和待测字母的外观进行精确比对),对于已知和固定的字体很有效。所以当时就有各种各样的程序解决这个问题,比如说: (图片来自网络) 从这些方法中我们可以看到,首先要根据验证码图像的特点,对图像进行一些净化处理,去除掉干扰条纹啥的,然后通过一定的统计方法把字母分割开,再通过“字模”(也就是字母模板)逐一匹配,通过像素比对计算匹配度,判断究竟是什么字母。 不过各家的验证码特点是不一样的,所以为了适应各种不同的验证码,就要开发很多种不同的识别程序: 现在我们把这些方法称为传统计算机视觉方法,或者戏称为“中医方法”:虽然《本草纲目》解释的很清楚,大家也都在用,但就是治不了大病。 后来百度的验证码加入了旋转扭曲错位混合字体,干扰的方法也花样翻新,这种幼稚的方法就不起作用了。 比如说上面这张验证码,干扰线跟数字一样粗,用传统方法消除干扰就把数字也给消除了;数字之间都黏在一起,根本没办法分割,更别说套模板了。可是我们用肉眼一看就知道答案是867826。 我当时就想:现有的这些方法,跟人眼的工作原理不一样,所以不能像人眼那样随机应变。有没有接近人眼工作原理的方法?这样不论验证码怎么变,只要人能识别,我们的程序也应该能识别。所以我们就去找计算机图像处理的书。 书里面提了很多方法,比上面那些软件用的方法要复杂一些,不过这些方法实现麻烦,适应性也非常差。 我当时最大的疑惑就是,**明明人脑加肉眼可以轻松解决的问题,为什么计算机却解决不好,还要搞那么麻烦?**为什么连这些教授写的书也解决不好?最重要的是, 为什么这些书不能解释计算机做不到的原因 ? 我最开始以为是自己太笨,没有学到精髓。 直到今年接触到卷积神经网络,领略到其简洁强大,并利用它实现功能之后,我才搞明白问题出在哪:**从2012年AlexNet通过卷积神经网络完虐中医方法,到2016年AI热,这样一个简洁优雅的、一个真正能从根本上解决视觉特征识别问题的、一个可以取代无数脑力劳动的、一个性能几乎没有上限的方法,花了整整4年才从冠军王座进入我(以及许多工程师)的视野。**这让我不由得想起了我以前的一篇文章《酒香就怕巷子深》。 (AlexNet架构) 开始我还觉得,不过晚了4年而已,爆吧那都是8年前的事情了。但当我更加深入这个领域的时候,事实越来越强烈地震撼着我:**今天大家感到神乎其神、“比人还准”、“突然就进步了几十倍”的各种AI应用,所用到的这些技术,早在20年前已有之。**比如说,在08年让我们焦头烂额、翻遍书店都解决不了的验证码识别问题,杨乐坤(Yann LeCun)教授早在90年代就利用卷积神经网络完美解决(印刷+手写数字全自动识别),对应的系统早已投入美国的银行系统使用,用于处理全美10%的手写支票。我们今天感到神奇的DeepDream, DeepArt, 也都不过是杨教授成果的翻版。 (杨乐坤的LeNet5:不管怎么干扰,只要人能识别,它就能识别) 而卷积神经网络的原理,其实比当年我们在书上看到的那些中医方法,还简单。 这意味着什么?这意味着,在美国有一个人经过多年努力,发现并总结了解决问题的正确且优美的方法,把它发表了出来,而在中国的另一个想解决同样问题的年轻人,直到20年之后才接触到这个方法;这意味着20年间,国内(以及国外)无数计算机视觉研究者,无数论文,无数出版物,以及培养出的无数学生和工程师,所掌握和应用的主要方法,也都还是中医方法。搞计算机视觉应用的同学无不感叹:假如当年我们知道杨乐坤,知道卷积神经网络,不知道能省多少事,多实现多少功能。我不敢说这对社会造成了多大的损失,但我知道杨乐坤教授的论文,至少能节省我当年100个小时的无用功。 是因为所有人都是傻瓜吗?是因为教授们学习能力很差吗?都不是。真正的原因是,这种新方法对传统方法、传统理念的冲击,实在是太大了,整个连理论基础都换掉了。2012年借助GPU实现卷积神经网络革命之后,在计算机视觉领域,原来具有传统霸主地位的HOG和SIFT等传统计算机视觉方法几乎全部丢进垃圾桶;而在自然语言处理领域,语言学家们讨论了几十年的各种语言模型,此刻也几乎全部喂狗,所有传统语言学方法加起来的性能,比不上今天三页纸讲完的LSTM网络。 今天的语言学家,在搞机器学习的人看来,跟所谓的“红学家”已经没什么区别了。 其实大家或多或少也发现,这么多年来努力和奋斗的方向搞错了。但是教授们不会承认“我错了”,或者“我的研究没有意义”,因为这意味着惹恼同事、丢掉工作、离开学校。对于以学术为生的人而言,这是不可想象的。所以教授们还是会继续在课堂上把旧的理论传授给学生,指导学生用旧的理论做课题……这时学生就很可怜了;他们往往不知道自己陷入了学术版的庞氏骗局。我有一个同学之前在暨大读语言学,大家每天钻研语言中的各种结构,尝试用各种语言理论分析各种语言的各种属性……差不多都是在浪费时间。近年来神经网络和机器学习在自然语言处理方面取得的巨大突破,无不向我们表明:语言本质上是一种概率分布,根本不应该用逻辑方法分析,而通过这些方法得出诸如汉语和英语孰优孰劣之类的确定性的结论更是在故弄玄虚。 这很自然让我们想起爱因斯坦提出相对论时所受到的、来自学界的广泛而热烈的批评。诚然,科学在曲折前进的过程中,产生这些“江湖”问题是常态;但对于那些懵懵懂懂跟着导师走的学生来说,这确实显得很不公平。而对于像我这样工程思维的人来说,这些人发文章就和犯罪没什么区别了。就因为这些人的一己私利,工程师们减了多少寿啊!! Geoff Hinton教授在他的coursera课程《神经网络机器学习》中,每当谈到近一二十年神经网络和模式识别乃至人工智能领域的进展时,总是要点一两个人名讽刺一番,足见这背后有多大的怨念。 这就是我为什么要关注机器学习:只因阴差阳错,我们认真地学习了太多中医理论,却白痴般地错过了那些真正能帮助我们解决问题的理论进展。现在的我,就像一个刚刚毕业的物理学生,研究的是“以太”,却迎来了相对论;就像一个刚刚毕业的医学生,研究的是“体液”,却迎来了显微镜。 这时我才理解,为什么吴恩达和Geoff费大力气到处演讲、做coursera课程,免费教大家各种机器学习算法——他们希望通过他们的努力,让更多同学接触到真正能解决未来人类面临的主要问题的理论,帮助大家在AI这样一个满地都是恐怖陷阱、空气中弥漫着玄学味道(毫无夸张)的领域,真正学到一点有用的知识。 花上几个月的时间关注一下机器学习吧!你会发现,高数和概率论突然就变得有用了;许多原本无法理解、无法解释的现象,包括视觉,听觉,语言,交流,认知,学习……现在都可以用模型描述(并解决)了。这种兴奋和成就感,不亚于GPS卫星的设计者们发现在加入相对论修正之后,卫星上的时钟终于准确走时了。 最后解释一下开头的那张截图。 Geoff在他的课程PPT中贴出两段话: P.H.温斯顿(时任MIT AI实验室负责人),在他1977年出版的课本《人工智能》中提到: “许多古希腊人支持苏格拉底的观点,认为那些难以解释的联翩思绪来自上帝。在今天,跟所谓的上帝等价的,是那些概率驱动的、行为不定的神经元。神经元行为的随机性的增加,更有可能是癫痫和醉酒的罪魁祸首,而不是聪明和智慧的优势。” 而冯诺依曼(计算机先驱,对量子力学和经济学均有贡献)在他1958年未完成的手稿《计算机与大脑》中却说: “……这一切都将引出一种新的计算理论,它不再受限于过去那种非此即彼的、确定的形式逻辑……许多线索都令我们相信,这种新的形式逻辑系统,将会更加靠近另一个在过去与逻辑很少产生联系的学科,那就是以玻尔兹曼所提出的形式为主的热力学。” 意思是,AI领域的老大当年(1977)在教科书中所坚持的理念,反映了当时该领域研究方向的盲目和无知,也解释了为何当时AI领域人才济济却几乎没有取得任何进展。而那正是Geoff当学生的日子,最后Geoff苦苦坚持几十年才把这个领域扳回来。而其实现在真正取得进展的许多方向,正是先驱冯诺依曼早在1958就提出过的基于概率的系统和方法……所以Geoff也感叹,如果冯老爷子能活久一点,今天的世界也许会完全不一样。


我之前在论坛发过一些机器学习的资源,但是这些资源比较难直接下手,必须要先学高数和概率论。那么我今天就把复杂的机器学习算法,写成高中数学形式。 问题:我有1000张猫的照片,1000张狗的照片,请训练一个系统,令其能够分辨猫和狗。 假如每张照片的大小是32x32,那么一张照片就有1024个像素,每个像素的亮度从0到1可变。我们把这1024个像素标记为x0, x1, x2....x1023. 这样,我们就用1024个变量表示了一张输入图片。 这样就可以定义一个1024元函数:y0,y1 = f(x0,x1,x2.......x1023),其中y0是图片为猫的概率p(猫),y1是图片为狗的概率p(狗)。 我们的任务是,找到合适的函数 f(),使得计算f(猫的图片)得到 y0>y1,计算f(狗的图片)得到 y1>y0. 如果找到了这个函数f(),我们就可以计算 f(某张图片) = f(x0,x1,x2....x1023),如果得到的结果[y0,y1]中 y0>y1,比如[0.9, 0.3]或者[1.4, 0.8]或者[3.2, 1.6],就可以确定这张图片是猫。 所谓机器学习的训练过程,就是寻找函数f()的过程。 为了简化描述,我们把y0和y1称为二维矢量Y,把x0,x1,x2...x1023称为1024维矢量X。 表达式就可以简化为: Y = f(X)。 在下文中,我用大写字母表示矢量和矩阵。矢量和矩阵都是表示一组变量的简便方式(比如用矢量X来表示x0...x1023),是《线性代数》的教学内容。这是本教程涉及的唯一大学内容。 把图片中的1024个像素,转换成两个代表概率的数字,这中间的计算过程一定是非常复杂的。对于人脑和肉眼来说,猫或者狗的影像先通过晶状体汇聚到视网膜,再通过视神经传递到大脑的视神经中枢,再经过几次传递,最终得出猫或者狗的结论。这么复杂的神经传递过程,怎么用数学表达呢?人工智能领域的研究者提出,可以用神经元函数模拟神经元的行为。 下面是一个真正的人类神经元。 刺激从dendrites(树突)传入神经元,然后从axon(轴突)传出。 怎么用数学公式描述它的特性呢?下面我介绍目前机器学习领域最常用的神经元函数。 a = max(w0 * x0 + w1 * x1 + w2 * x2...+ w1023 * x1023 + b, 0) 或者简写为 a = max(W * X + b, 0) 其中a是神经元的输出,X(也就是x0...x1023)是神经元的输入,W(分别是w0...w1023)是神经元的权重,b是神经元的偏置,max是最大值函数。 我们注意到,每个神经元的输入可以有很多,但是输出连接只有一个,这和大脑中神经元的特性是类似的。 权重w的作用,是控制每个输入x对神经元的影响。如果w为负,则每个输入x对神经元的贡献就是负数。通过调节每一个w的大小,我们就可以让神经元对不同的输入x,产生不同的反应。如果某个w等于零,那么对应的输入x对后面的神经元就没有影响,相当于这个神经连接断掉了。 调节权重W(以及偏置b)的过程,称为学习或者训练。 神经元的输出有一个max函数,如果计算结果为正就直接输出,如果计算结果小于0就输出0。这是因为人类神经元只能输出正的刺激信号(脉冲),不能输出负的刺激信号。这只是一种直观的说法,实际上选择这个函数的原因非常复杂,可以去看geoff hinton的教程,里面有更加详细的数学解释。 由于f()的输出是两个实数,我们至少需要两个神经元。这两个神经元和图像的1024个输入都是相连接的。 这样我们就实现了函数f()。上图中,两个神经元的输入权重w的序号重复了,为了明确,我们把第一个神经元的偏置和权重分别称为 b0, w0_0, w0_1, w0_2... 把第二个神经元的偏置和权重分别称为b1, w1_0, w1_1, w1_2... 于是f()的计算方法如下: y0 = a0 = max(x0 * w0_0 + x1 * w0_1 + x2 * w0_2 ...+ x1023 * w0_1023 + b0, 0) y1 = a1 = max(x0 * w1_0 + x1 * w1_1 + x2 * w1_2 ...+ x1023 * w1_1023 + b1, 0) 如果把x0...x1023称为X,把w0_0...w0_1023称为W0,把w1_0...w1_1023称为W1,上式可以简写为: y0 = max(X * W0 + b0, 0) y1 = max(X * W1 + b1, 0) 如果把y0...y1称为Y,把W0和W1称为W(这时W就有1024 * 2=2048个元素了,是一个1024 * 2尺寸的矩阵),b0和b1称为B,上式可以简写为: Y = f(X) = max(X * W + B, 0) 于是,研究者们把W设为2048个随机数,把B设为两个随机数,然后给函数f()输入一张猫的图片。 f(X) = f(猫的图片) = max(猫的图片 * W + B) = [0.5, 0.5] 结果是y0 = 0.5, y1 = 0.5,也就是我们的神经网络分不清这张图片到底是更像猫还是更像狗。这是很正常的,因为我们的W和B是随机设定的。 (由于W和B都是函数f()的参数,下面我们把W和B统称为W。) 研究者随后对W进行了一点随机微调,并重新计算f(猫的图片)。如果调完之后y0增长了或者y1下降了,就说明调节有效;如果调完之后y1增长了或者y0下降了,就说明调的方向错了。 经过一段时间的调节,研究者发现这样很慢,W一共有2048个元素,每调一次,输出只动一点点,要调到什么时候?于是研究者就想了一个办法:用一个函数E来计算误差的大小。 首先,对输出[y0, y1]应用softmax函数。 $$\sigma(z) _{j}={\frac {e^{z _{j}}}{\sum _{k=1}^{K}e^{z _{k}}}}, j=1,2...K$$ 用高中数学来写就是 softmax(Y) = softmax([y0,y1]) = [e^y0, e^y1] / (e^y0 + e^y1) = [e^y0 / (e^y0 + e^y1), e^y1 / (e^y0 + e^y1)] 经过softmax函数之后,y0和y1的相对大小关系不变,但是他们的和保证等于1。比如说原来求得的猫狗概率是[1.2, 0.6],softmax函数之后就得到[0.65, 0.35]。 然后定义误差函数E: Y = f(X) E = - log(softmax(Y)) * y_true E = - log(softmax(f(X))) * y_true 其中 y_true 代表的是输入图像的真正类别,当输入为猫时为[1,0],当输入为狗时为[0,1]。 我们注意到,因为softmax的结果都在0和1之间,应用log函数之后会得到一个负数,softmax所得的结果越小,log得到负数的绝对值就越大。如果softmax后所得结果接近零,log之后会得到一个非常大的负数。前面再加个负号,就得到一个非常大的正数。 所以E越大,说明我们的判断结果错得越离谱。这是一个很合适的误差函数。 当输入为猫时,输出y0(相对于y1)越小,误差函数E的值就越大,如果输出y0远小于y1,说明错的很严重,误差函数E的值会非常大。我们调节W的时候,就可以以E为参考。 上面讲到,研究者想了一个加速的办法。要怎样调节W,才能使得误差E不断降低呢?对了,可以利用E对W的导数。 我们在高中学过y对x的导数,可以写作$$\frac{dy}{dx}$$ 上图中x0处的导数,也就是绿色切线的斜率,是个负数,大概是 -2 。如果把点x0朝导数所指的下坡方向移动,y的值会越来越小。就像下面这样: 如果用公式来描述这个过程: $$x_1 = x_0 - \frac{dy}{dx}(x_0)$$ $$x_2 = x_1 - \frac{dy}{dx}(x_1)$$ $$x_3 = x_2 - \frac{dy}{dx}(x_2)$$ 这相当于不断执行下面的操作: $$x = x - \frac{dy}{dx}(x)$$ 这种【按照y对x在x点的导数来调节x,使得y越来越小】的方法,称为导数下降法。 上面y对x的图像中,y是一维的,我们在一维函数上寻找最小值。如果y是二维的,有两个参数呢?下面是一张2维平面上的图像: 上图中 theta0 和 theta1就是待调节的参数,而J(theta0,theta1)是误差函数。图中演示了两个点是如何通过梯度下降法,找到J()的局部最低点的。 原题中的W是2048个变量,E对W的导数要怎么写?难道要写2048条公式吗? $$\frac{dE}{dw_{0,0}},\frac{dE}{dw_{0,1}}, ... ,\frac{dE}{dw_{1,1023}}$$ 上面这种写法太繁琐了(而且也不正确)。通常我们这样写: $$\frac{\partial E}{\partial w_{ij}}(W)$$ 其中i=0,1 j=0,1,2...1023 称为E对\(w_{ij}\)在W处的偏导数。 之前抛物线函数的例子中,函数y上点x处的斜率是一个数字。而这次函数E上点W的斜率,共有2048个数字,可以记作一个2048维矢量: $$\left(\frac{\partial E}{\partial w_{0,0}}(W),\frac{\partial E}{\partial w_{0,1}}(W), ... ,\frac{\partial E}{\partial w_{1,1023}}(W)\right)$$ 上面这个矢量简称函数E在W处的梯度,它和之前所说的y在x处的斜率是一个意思,只不过由很多个数组成。上面的梯度可以简写为: $$\nabla E(W)=\left({\frac {\partial E}{\partial w_{0,1}}}(W),\ldots ,{\frac {\partial f}{\partial w_{1,1023}}}(W)\right)$$ 因此,只要不断执行: $$W = W - \alpha \nabla E(W)$$ 就可以使得E的值越来越小(假定E是个比较凸的函数)。这种【按照W对E在W处的偏导数来调节W,使得E越来越小】的方法,称为梯度下降法。梯度下降法的一个变种,随机梯度下降法,是目前机器学习领域应用最广泛的优化方法。 其中α是一个可调的量,称为学习率,它决定下降的速度和稳定性。 至此,我们可以把整个训练过程概括为: 确定Y = f(X)的形式,为了与人脑架构匹配,我们在这里用的是神经网络函数,函数的参数(偏置和权重)为W和B,以下将它们统一称为W; 对于给定的一批训练图片X(1000张猫,1000张狗),以及当前的W,计算误差 E = -log(softmax(f(X))) * y_true,其中 y_true 是代表对应图片猫狗分类的标签。 使用梯度下降法,根据E对W在W处的偏导调节W 重复第2步,直到误差E不再下降为止。 用经过训练的f()函数,输入猫狗图片并观察其输出,统计正确率。 为了让例子显得简单,我们只使用了2个神经元,而且从输入直接到输出,中间没有隐藏层,最终得到的分类效果是很差的。 如果你想玩这种最简单的神经网络,可以直接访问google的tensorflow神经网络演示页面,那里的分类器虽然不能分类猫和狗,但可以做一些同样有趣的事情。 http://playground.tensorflow.org/ 在实际的视觉分类应用中,神经元的数量会多得多,而且会一层叠一层(警告:以下是大学内容),比如上面就是一个四层的神经网络,有两个隐藏层。 通过增加层数,网络可以表达更加复杂的非线性关系,从而能更好地概括输入数据中隐含的复杂关系。 对于视觉应用,每一层的输入,也不是直接取输入像素的值,而是用一组二维模板,对像素的值分别进行二维卷积,再叠加求和,层和层之间还有pooling(求局部最大值并组成更小的新图像)操作。 (LeNet架构,它20年前就学会了怎样分辨人类手写的数字,准确率超过99%) 使用二维卷积和pooling,可以令 图像中关键特征获得位移无关性(比如猫的脸在图像中可能会左右移动,这不应该影响最终判断的结果)。 读到这里的同学,以上介绍完了机器学习的基本概念和方法,谢谢你们。如果你觉得这太简单了,下一步的两个可选的教程分别是Andrew Ng的 Machine Learning ,以及Geoff Hinton的 Neural Networks for Machine Learning 。



谈话人: 杨志宏 视觉求索公众号编辑 朱松纯 加州大学洛杉矶分校UCLA统计学和计算机科学教授 Song-Chun Zhu <www.stat.ucla.edu/~sczhu> 时间: 2016年10月 杨 : 朱教授,你在计算机视觉领域耕耘20余年,获得很多奖项, 是很资深的研究人员。近年来你又涉足认知科学、机器人和人工智能。受 《视觉求索公众号》编辑部委托,我想与你探讨一下计算机视觉的起源,这个学科是什么时候创建的, 有哪些创始和代表人物。兼谈一下目前热门的人工智能。 朱 : 好, 我们首先谈一下为什么需要讨论这个问题。 然后, 再来探讨一下计算机视觉的三个重要人物David Marr, King-Sun Fu, Ulf Grenander以及他们的学术思想。我认为他们是这个领域的主要创始人、或者叫有重要贡献的奠基人物。 第一节: 为什么要追溯计算机视觉的源头, 这有什么现实意义? 中国有句很有名的话:“一个民族如果忘记了历史,她也注定将失去未来。” 我认为这句话对一个学科来讲,同样发人深省。我们先来看看现实的状况吧。 首先,假设你当前是一个刚刚进入计算机视觉领域的研究生,很快你会有一种错觉,觉得这个领域好像就是5年前诞生的。 跟踪最新发表的视觉的论文,很少有文章能够引用到5年之前的文献,大部分文献只是2-3年前的,甚至是1年之内的。现在的信息交换比较快,大家都在比一些 Benchmarks,把结果挂到arXiv 网上发布。 很少有一些认真的讨论追溯到10年前,20年前, 或30年前的一些论文,提及当时的一些思想和框架性的东西。现在大家都用同样的方法,只是比拼,你昨天是18.3%的记录(错误率),我今天搞到17.9%了。大家都相当短视,那么研究生毕业以后变成了博士,可能也会带学生做研究,他只知道这几年的历史和流行的方法的话,怎么可能去传承这个学科,让其长期健康发展呢?特别是等当前这一波方法退潮之后,这批人就慢慢失去了根基和源创力。这是一个客观的现象。 因原文转载需要特别授权,附链接: http://mp.weixin.qq.com/s?__biz=MzI3MTM5ODA0Nw==&mid=100000002&idx=2&sn=32face7f1acb17e07f3c38dde41d880e

谈话人: 杨志宏 视觉求索公众号编辑 朱松纯 加州大学洛杉矶分校UCLA统计学和计算机科学教授 Song-Chun Zhu <www.stat.ucla.edu/~sczhu> 时间: 2016年10月 杨 : 朱教授,你在计算机视觉领域耕耘20余年,获得很多奖项, 是很资深的研究人员。近年来你又涉足认知科学、机器人和人工智能。受 《视觉求索公众号》编辑部委托,我想与你探讨一下计算机视觉的起源,这个学科是什么时候创建的, 有哪些创始和代表人物。兼谈一下目前热门的人工智能。 朱 : 好, 我们首先谈一下为什么需要讨论这个问题。 然后, 再来探讨一下计算机视觉的三个重要人物David Marr, King-Sun Fu, Ulf Grenander以及他们的学术思想。我认为他们是这个领域的主要创始人、或者叫有重要贡献的奠基人物。 第一节: 为什么要追溯计算机视觉的源头, 这有什么现实意义? 中国有句很有名的话:“一个民族如果忘记了历史,她也注定将失去未来。” 我认为这句话对一个学科来讲,同样发人深省。我们先来看看现实的状况吧。 首先,假设你当前是一个刚刚进入计算机视觉领域的研究生,很快你会有一种错觉,觉得这个领域好像就是5年前诞生的。 跟踪最新发表的视觉的论文,很少有文章能够引用到5年之前的文献,大部分文献只是2-3年前的,甚至是1年之内的。现在的信息交换比较快,大家都在比一些 Benchmarks,把结果挂到arXiv 网上发布。 很少有一些认真的讨论追溯到10年前,20年前, 或30年前的一些论文,提及当时的一些思想和框架性的东西。现在大家都用同样的方法,只是比拼,你昨天是18.3%的记录(错误率),我今天搞到17.9%了。大家都相当短视,那么研究生毕业以后变成了博士,可能也会带学生做研究,他只知道这几年的历史和流行的方法的话,怎么可能去传承这个学科,让其长期健康发展呢?特别是等当前这一波方法退潮之后,这批人就慢慢失去了根基和源创力。这是一个客观的现象。 因原文转载需要特别授权,附链接: http://mp.weixin.qq.com/s?__biz=MzI3MTM5ODA0Nw==&mid=100000002&idx=2&sn=32face7f1acb17e07f3c38dde41d880e



用浮点运算很浪费时间, 于是就想了一个办法... 屏幕坐标都是整数, 我们不需要尾数, 把一个0.0000000 ~ 1.0000000 的数表示成0x00~0x80,最高位和低7位的中间是小数点, 由于二进制小数表示十进制小数会有误差,只能取近似值, 比如0.7880108,取0.7890625也就是0x65, 要把一个二进制整数调整成0.7890625倍, 只需要用0x65乘上那个整数再右移7位, OK , 按这个思路弄进程序里面运行... 运行效果: http://www.tudou.com/programs/view/TNCI_Cbmb-E/ 点击此处查看视频 KC-LS1u机原理图 : http://bbs.kechuang.org/read/71330 KC-LS1u的C程序代码(乱七八糟的写来测试一下,暂时没进行特别优化, 即兴编写): #include<io.h> int sin[91] =  //格式 : 低8位的最高位为整数,尾数为小数 { 0x00, //sin(0) = 0 无误差 0x02, //sin(1) = 0.0174524,这里取值0.015625 0x04, //sin(2) = 0.0348995,这里取值0.03125 0x07, //sin(3) = 0.052336,这里取值0.0546875 0x09, //sin(4) = 0.0697565,这里取值0.0703125 0x0b, //sin(5) = 0.0871557,这里取值0.0859375 0x0d, //sin(6) = 0.1045285,这里取值0.1015625 0x10, //sin(7) = 0.1218693,这里取值0.125 0x12, //sin(8) = 0.1391731,这里取值0.140625 0x14, //sin(9) = 0.1564344,这里取值0.15625 0x16, //sin(10) = 0.1736482,这里取值0.171875 0x18, //sin(11) = 0.190809,这里取值0.1875 0x1b, //sin(12) = 0.2079117,这里取值0.2109375 0x1d, //sin(13) = 0.2249511,这里取值0.2265625 0x1f, //sin(14) = 0.2419219,这里取值0.2421875 0x21, //sin(15) = 0.258819,这里取值0.2578125 0x23, //sin(16) = 0.2756374,这里取值0.2734375 0x25, //sin(17) = 0.2923717,这里取值0.2890625 0x28, //sin(18) = 0.309017,这里取值0.3125 0x2a, //sin(19) = 0.3255682,这里取值0.328125 0x2c, //sin(20) = 0.3420201,这里取值0.34375 0x2e, //sin(21) = 0.3583679,这里取值0.359375 0x30, //sin(22) = 0.3746066,这里取值0.375 0x32, //sin(23) = 0.3907311,这里取值0.390625 0x34, //sin(24) = 0.4067366,这里取值0.40625 0x36, //sin(25) = 0.4226183,这里取值0.421875 0x38, //sin(26) = 0.4383711,这里取值0.4375 0x3a, //sin(27) = 0.4539905,这里取值0.453125 0x3c, //sin(28) = 0.4694716,这里取值0.46875 0x3e, //sin(29) = 0.4848096,这里取值0.484375 0x40, //sin(30) = 0.5, 无误差 0x42, //sin(31) = 0.5150381,这里取值0.515625 0x44, //sin(32) = 0.5299193,这里取值0.53125 0x46, //sin(33) = 0.544639,这里取值0.546875 0x48, //sin(34) = 0.5591929,这里取值0.5625 0x49, //sin(35) = 0.5735764,这里取值0.5703125 0x4b, //sin(36) = 0.5877852,这里取值0.5859375 0x4d, //sin(37) = 0.601815,这里取值0.6015625 0x4f, //sin(38) = 0.6156615,这里取值0.6171875 0x51, //sin(39) = 0.6293204,这里取值0.6328125 0x52, //sin(40) = 0.6427876,这里取值0.640625 0x54, //sin(41) = 0.656059,这里取值0.65625 0x56, //sin(42) = 0.6691306,这里取值0.671875 0x57, //sin(43) = 0.6819984,这里取值0.6796875 0x59, //sin(44) = 0.6946584,这里取值0.6953125 0x5a, //sin(45) = 0.7071068,这里取值0.7109375 0x5c, //sin(46) = 0.7193398,这里取值0.71875 0x5e, //sin(47) = 0.7313537,这里取值0.734375 0x5f, //sin(48) = 0.7431448,这里取值0.7421875 0x61, //sin(49) = 0.7547096,这里取值0.7578125 0x62, //sin(50) = 0.7660444,这里取值0.765625 0x63, //sin(51) = 0.777146,这里取值0.7734375 0x65, //sin(52) = 0.7880108,这里取值0.7890625 0x66, //sin(53) = 0.7986355,这里取值0.796875 0x68, //sin(54) = 0.809017,这里取值0.8125 0x69, //sin(55) = 0.819152,这里取值0.8203125 0x6a, //sin(56) = 0.8290376,这里取值0.828125 0x6b, //sin(57) = 0.838671,这里取值0.8359375 0x6d, //sin(58) = 0.8480481,这里取值0.8515625 0x6e, //sin(59) = 0.8571673,这里取值0.859375 0x6f, //sin(60) = 0.8660254,这里取值0.8671875 0x70, //sin(61) = 0.8746197,这里取值0.875 0x71, //sin(62) = 0.8829476,这里取值0.8828125 0x72, //sin(63) = 0.8910065,这里取值0.890625 0x73, //sin(64) = 0.8987941,这里取值0.8984375 0x74, //sin(65) = 0.9063078,这里取值0.90625 0x75, //sin(66) = 0.9135455,这里取值0.9140625 0x76, //sin(67) = 0.9205049,这里取值0.921875 0x77, //sin(68) = 0.9271839,这里取值0.9296875 0x78, //sin(69) = 0.9335804,这里取值0.9375 0x78, //sin(70) = 0.9396926,这里取值0.9375 0x79, //sin(71) = 0.9455186,这里取值0.9453125 0x7a, //sin(72) = 0.9510565,这里取值0.953125 0x7a, //sin(73) = 0.9563048,这里取值0.953125 0x7b, //sin(74) = 0.9612617,这里取值0.9609375 0x7c, //sin(75) = 0.9659258,这里取值0.96875 0x7c, //sin(76) = 0.9702957,这里取值0.96875 0x7d, //sin(77) = 0.9743701,这里取值0.9765625 0x7d, //sin(78) = 0.9781476,这里取值0.9765625 0x7e, //sin(79) = 0.9816272,这里取值0.984375 0x7e, //sin(80) = 0.9848078,这里取值0.984375 0x7e, //sin(81) = 0.9876883,这里取值0.984375 0x7f, //sin(82) = 0.9902681,这里取值0.9921875 0x7f, //sin(83) = 0.9925462,这里取值0.9921875 0x7f, //sin(84) = 0.9945219,这里取值0.9921875 0x7f, //sin(85) = 0.9961947,这里取值0.9921875 0x80, //sin(86) = 0.9975641,这里取值1 0x80, //sin(87) = 0.9984295,这里取值1 0x80, //sin(88) = 0.9993908,这里取值1 0x80, //sin(89) = 0.9998477,这里取值1 0x80  //sin(90) = 1,这里取值1 }; int sin_360[361]; int cos_360[361]; void sin_cos_csh() { unsigned int D = 0; while(D != 361) {    if(D < 91) //角度为0到90度 取出sin值和cos值    {       sin_360[D] = sin[D];       cos_360[D] = sin[90 - D];    }    else if(D > 90 && D < 181) //角度为91到180度 取出sin值和cos值    {       sin_360[D] = sin[180 - D];       cos_360[D] = -sin[D - 90];    }    else if(D > 180 && D < 271) //角度为181到270度 取出sin值和cos值    {       sin_360[D] = -sin[D - 180];       cos_360[D] = -sin[270 - D];    }    else //角度为271到360度 取出sin值和cos值    {       sin_360[D] = -sin[360 - D];       cos_360[D] = sin[D - 270];    }    ++D; } } void yuan(VRAMY,VRAMX,XD,YD,ZD,r,VRAM_DATA) {    int dd = 0,Y,X,Z,y,x,_sin,_cos;    int _Y,_X,_Z;    do    {       _sin = sin_360[dd];       _cos = cos_360[dd];       X = _cos*r >> 7;       _Y = _sin*r >> 7;       _sin = sin_360[XD];       _cos = cos_360[XD];       Y = _cos*_Y >> 7;       _Z = _sin*_Y >> 7;       _sin = sin_360[YD];       _cos = cos_360[YD]; //     Z = _cos*_Z - _sin*X >> 7;       _X = _sin*_Z + _cos*X >> 7;       _sin = sin_360[ZD];       _cos = cos_360[ZD];       X = _cos*_X - _sin*Y >> 7;       Y = _sin*_X + _cos*Y >> 7;       y = VRAMY - Y; //转换为实际坐标       x = VRAMX + X;       VRAM[(y << 8) | (x & 0x00ff)] = VRAM_DATA; //写入显存       dd += 10;    }while(dd < 360); } void VRAM_qingping(unsigned char Y,unsigned char _Y,unsigned char DATA) { while(Y != _Y) {    _B = DATA;    _A = 0;    do    {      _A1 = Y;      _A2 = 0x80;      _A0 = _A;      _NOP;      _RAM = _B;      _A = _A + 1;      _A0 = _A;      _NOP;      _RAM = _B;      _A = _A + 1;      _A0 = _A;      _NOP;      _RAM = _B;      _A = _A + 1;      _A0 = _A;      _NOP;      _RAM = _B;      _A = _A + 1;      _A0 = _A;      _NOP;      _RAM = _B;      _A = _A + 1;      _A0 = _A;      _NOP;      _RAM = _B;      _A = _A + 1;      _A0 = _A;      _NOP;      _RAM = _B;      _A = _A + 1;      _A0 = _A;      _NOP;      _RAM = _B;      _A = _A + 1;    }while(_SUB_A_1 != -1);    ++Y; } } void zhongxinzhou(VRAMY,VRAMX,XD,YD,ZD,VRAM_DATA) {    int zz = -32,Y,X,Z,y,x,_sin,_cos;    int _Y,_X,_Z;    do    {       _sin = sin_360[XD];       _cos = cos_360[XD];       Y = - _sin*zz >> 7;       _Z =  _cos*zz >> 7;       _sin = sin_360[YD];       _cos = cos_360[YD]; //     Z = _cos*_Z >> 7;       _X = _sin*_Z >> 7;       _sin = sin_360[ZD];       _cos = cos_360[ZD];       X = _cos*_X - _sin*Y >> 7;       Y = _sin*_X + _cos*Y >> 7;       y = VRAMY - Y; //转换为实际坐标       x = VRAMX + X;       VRAM[(y << 8) | (x & 0x00ff)] = VRAM_DATA; //写入显存       zz += 4;    }while(zz < 32); } main() {    int i,YD = 0,XD = 0,ZD = 0;    VRAM_qingping(0,240,0x00);    sin_cos_csh();    while(1)    {       CTVGA_C &= ~0x01; //请求访问VRAM       while((CTVGA_C & 0x80) == 0x80); //可以访问的时候跳出       VRAM_qingping(120 - 100,120 + 100,0x00);     //  ++YD;    //   ++YD;    //   if(YD == 360) YD = 0;       ++XD;       ++XD;       ++XD;       if(XD == 360) XD = 0;       ++ZD;       if(ZD == 360) ZD = 0;       zhongxinzhou(120,128,XD,YD,ZD,0x1f);       yuan(120,128,XD,YD,ZD,32,0x03);       yuan(120,128,XD,YD,ZD,64,0xe0);       yuan(120,128,XD,YD,ZD,80,0x13);       yuan(120,128,XD,YD,ZD,96,0xe5);       CTVGA_C |= 0x01; //请求VRAM显示       for(i = 0;i != 7000;++i);    } }



环境是node 4.x 先定义一个函数: y = f(x) = 3x^2 + 2x 然后在 [-50,50) 上采样一组数据,并添加白噪声。 function target(x){ return 3 * x * x + 2 * x // y = 3x^2 + 2x } var input = [], output = [] for(i=-50;i<50;i++){ //fill test data input.push(i) output.push(target(i)+Math.random()*3) // with random errors } 此时,input数组包含[-50,50)上的一组输入值,output数组包含对应的输出值。 问题:已知原函数的形式是 y = f(x) = ax^2 + bx,求 a 和 b。 首先定义我们的假设:y = p0x^2 + p1x,接受x为输入,以及params为参数数组。 function hypothesis(x,params){ return params[0] * x * x + params[1] * x } 然后给待求参数指定一个初始值 (p0,p1) = (0,0) var solution = [0,0] 然后我们定义误差函数。误差的定义是[给定参数下,函数的输出值(output)和实际输出值的RMSE] function error_of_hypothesis(params){ var total_square_err = 0.0; for(i in input){ var x = input[i] var difference = hypothesis(x,params) - output[i] total_square_err += difference * difference } return Math.sqrt(total_square_err/input.length) //RMSE } 然后定义[梯度计算及下降]函数。[梯度]就是误差函数在点(p1,p2)处对p1,p2分别求偏导得到的矢量。说白了就是求那个点的“上坡方向”的矢量。 此处求偏导使用了数值方法。 function calculate_gradient_and_descend(stepsize){ // calculate error(p0,p1) var err = error_of_hypothesis(solution) // calculate gradient of error(p0,p1) var delta = 0.00000001 var gradient = [] for(i in solution){ // calculate partial derivative of error(p0,p1) var delta_solution = [] delta_solution[0] = solution[0] delta_solution[1] = solution[1] delta_solution[i] += delta gradient[i] = (error_of_hypothesis(delta_solution) - err)/delta } // descend (p0,p1) for(i in solution){ solution[i] -= gradient[i]*stepsize } return err } 上面可以看到:每运行一次下降函数,solution矢量就会朝当前梯度方向的反方向前进stepsize距离。 这会让我们越来越接近谷底。 下面定义我们的梯度下降循环: function run_descent(iterations,stepsize){ for(var i=0;i<iterations;i++){ var err = calculate_gradient_and_descend(stepsize) if(i%(iterations/10)==0||i>iterations-10){ console.log('-------------'); console.log('gradient descent iteration',i); console.log('RMSE=',err); console.log('solution:',solution,'after descent.'); } } return err } run_descent(1000,0.001) run_descent(1000,0.0001) run_descent(1000,0.00001) run_descent(10000,0.000001) 结果: ------------- gradient descent iteration 9997 RMSE= 1.2756225981845384 solution: [ 3.001084043830482, 2.001937055426506 ] after descent. ------------- gradient descent iteration 9998 RMSE= 1.275622596459923 solution: [ 3.0010840438762454, 2.001937096928974 ] after descent. ------------- gradient descent iteration 9999 RMSE= 1.275622594737627 solution: [ 3.0010840439171016, 2.0019371384060625 ] after descent.


零、前言 神经网络是我这半年来研究的重点,它的接近完美的非线性非类能力和广泛的运用前景是我所期待的(要不然为什么研究那么久)。 于是乎,来给大家讲一讲几种主要的的神经网络结构。 可能大家会问神经网络到底是什么? 综合神经网络的来源、特点和各种解释,它可简单地表述为:人工神经网络是一种旨在模仿人脑结构及其功能的信息处理系统 [1] 。 嗯,有点教科书的古板,但讲的如此简洁、清楚。 一、M-P模型 知道神经网络到底是什么之后,可以来讲一讲它的历史了。 开始的时候,大家只是建立了一个很简单的数学模型,就是下图的M-P模型(神经元的阀值模型),由McCulloch W.S.和 Pitts W.A.在1943年提出。 这是一个模型很简陋,只能展现一个神经元,而且还没有训练方法(在当时,W[权值]是固定的,由人来调节),但以此模型的提出为标志,神经网络拉开了研究的序幕。 它的公式为: $$y_j=f(\sum_{i}(w_{ij}*x_{i}+\theta_j))$$ 其中 $$f(x)=1/[1+exp(-x)]$$ 当然,这个函数是后来人们才提出来的,最初的时候,当x大于0时,f(x)取1,反之为-1。 这种结构使得人工神经网络成为具有学习能力的系统,可以发展知识,以至超过设计者原有的知识水平。[2] 二、Hebb 规则 四十年代末,D. O. Hebb 首先提出了一种神经网络学习算法,称为 Hebb 规则。 这种规则使得神经网络具有了学习的能力。 输出表示为: $$y_j=f(\sum_{i}(w_{ij}*x_{i}))$$ 权值调整: $$w_{ij}(t+1)=w_{ij}(t)+ \eta * y_j * x_i$$ 其中Eta是学习率,取值随环境而定。 下面是代码(很粗暴): class hebb{ public: double w[10][10]; double tran_c; double o[10]; void tran(double *X_in,double *Y_in) { int i,j; for (i=0;i<10;i++) { for(j=0;j<10;j++) { w[i][j]=tran_c*w[i][j]+X_in[i]*Y_in[j]; } } } void rest(){ int i,j; for (i=0;i<10;i++) { for(j=0;j<10;j++) { w[i][j]=0; } } } void run(double *X_in) { int k,l; for (k=0;k<10;k++) { o[k]=0; for(l=0;l<10;l++) { o[k]+=w[l][k]*X_in[i][l]; } } } }; 参考文献: [1] 董军,胡上序。混沌神经网络研究进展和展望。信息与控制,1997,26(5):360-368。 [2]朱大奇,史慧编著。人工神经网络原理及应用。北京:科学出版社,2006

(附件:269645) 图中的深层卷积神经网络是一种较深层级的卷积神经网络,使用两台NVIDIA GTX 580 3GB GPU进行计算和训练(上下的层分别代表两显卡中不同的层),它验证了卷积神经网络在高层数方面的适用性及其优越性,在ILSVRC-2012 数据集中获得了37.5% 17.0%的Top-1 Top-5误差,可以说它运算速度快、分类能力好,具有极强的发展潜力。 (附件:269646) 图中的神经网络类型按照名字来说应该是深度神经网络(Multi-column Deep Neural Networks),但是 http://yann.lecun.com/exdb/mnist/ 上却将其分类为卷积神经网络,故在此处介绍。 输入图像被分成多个小块(Pn)然后这些图像块被输入深度神经网络,然后再对深度神经网络的输出进行处理,成为最终输出结果。 该神经网络可以说是非常优秀的,0.23的MINST数据集错误率(最低),以及NIST SD 19、CASIA 、GTSRB、CIFAR 10、NORB数据集的优秀成绩。 (附件:269647) 这是一个具有残差项的神经网络,某个层的输出会被当作残差项传递到几层后参与运算,残差项的优点是可以较好地减少梯度消失(当神经网络层数过多时,由于其输出函数的特性,输入数据对输出数据的影响会过小,造成误差增大)带来的影响,可以做到152层左右,误差如下图所示(虚线是训练误差,实线是测试误差),可见深层级可以带来较好的效果,但过深层还是会受到梯度下降的影响。 (附件:269648) 该神经网络在ILSVRC2015比赛中取得第一名。


Kalman Filter 之前考虑做飞控玩,所以研究过一下 kalman filter相关的数学模型和实际使用,在此做个整理以及记录编程实验。 本来想归到之前的机器学习笔记中去,毕竟 kalman filter 是属于 Bayes' Filter 实际使用中的一个特例,跟机器学习中的Hidden Markov model (HMM) 有着牵不清扯不断的联系,其求解思路归结起来就是EM (Expectation Maximization),以至于EM维基词条中提供的例子就是kalman filter。但考虑到一般的传统,还是单独归入控制技术更为科学。 首先解释什么是 bayers' filter。 在这个图中,我们有一系列 hidden state variable \(x_t\),hidden 意味着不可直接观测,只能通过间接的 measurement variable \(z_t\) 来获知。举个例子就是,过去的航海者在海上航行时是无法直接知道自己船的位置和速度的(对比于GPS),所以只能间接通过测量岸上的地标用三角测距来获取。这里船的位置、速度就是 \(x_t\),而三角测距读取的角度,就是\(z_t\)。而对于船当前状态的了解,又往往和过往时刻的历史状态有密切关系,比如过去的水手会在海图上不断更新当前位置坐标,以此获取航向、速度信息。最后 \(u_t\) 则是可以影响当前状态的输入控制,比如船长每个时刻的打舵量。 如图所示,在HMM中,每一时刻的状态只和紧邻着的上一时刻的状态有关,每一时刻的测量只和当前时刻的状态有关(虽然啰嗦但还是注明以免混乱)。同时在 kalman filter 中,hidden state variable \(x_t\) 和 measurement variable \(z_t\) 都是假设为服从高斯分布的,这种近似能够对付大部分的实际情况。 写成公式形式就是: $$ P(x_t|x_{0:t-1}, z_{0:t-1}, u_{0:t-1})=P(x_t|x_{t-1},u_{t-1}) $$ $$ P(z_t|x_{0:t-1}, z_{0:t-1}, u_{0:t-1})=P(z_t|x_t) $$ 对 \(P(x_t|x_{t-1},u_{t-1})\) 的描述,在 kalman filter 中被称作 process model ,表示当前状态\(x_t\) 受前一时刻状态 \(x_{t-1}\) 和输入 \(u_{t-1}\) 的影响: $$ x_t=Ax_{t-1}+B u_{t-1}+n_t $$ 其中 \(n_t\) 是加性白噪声,注意这里的状态转换矩阵 \(A\) 形容的是一种线性关系,作为computer visoner,我更喜欢叫做仿射变换,放到运动中,就是最简单的平移关系。但大部分实际情形都是非线性的,简单举例就是普通 kalman filter 可以对平动滤波,但转动就没办法了。解决办法是用 extended kalman filter 或者 unscented kalman filter,他们各自用不同的方式对非线性做了线性近似,然后就可以用普通的 kalman filter 处理。 而对测量过程的描述,称作 measurement model ,同样,还是一种线性关系: $$ z_t = C x_t + v_t $$ 这样一来,如前所述,在 \(t\) 时刻我们将会得到两个对于当前状态的估计,一个是来源于 process model 预测的先验状态 \( {\hat{x_t}}^-\),由上一时刻状态 \(x_{t-1}\) 推导而来,- 表示在 \(t\) 来临以前就可以获取这个状态, 比如四轴飞行器中,通过刚体运动的动力学方程,可以预测下一时刻的飞行姿态;另一个是使用 measurement model 通过测量的 \(z_{t}\) 反推的后验状态 \( {\hat{x_t}}^z\),比如四轴飞行器当前时刻从 IMU 读取的值解算出的姿态。这两个状态变量均服从高斯分布,而高斯分布的一个特性就是, 两个高斯变量的加权和,仍然服从高斯分布 。而通过选取恰当的加权权重,可以使这个新的高斯分布的分散程度( error covariance)小于之前的任一个, 如下图所示。 这使得将 \( {\hat{x_t}}^-\) 和 \( {\hat{x_t}}^z\) 重新组合得到一个更精确状态量预测成为可能。 实际使用的组合方式是这样的: $$ \hat{x}_t={\hat{x}_t}^-+K(z_k-C{\hat{x}_t}^-) $$ 其中 \(K\) 被称作 kalman gain 或者 blending factor,我们将恰当的选择 \(K\) 以使得 \( \hat{x}_t \)的 error covariance 最小。\((z_k-C{\hat{x}_t}^-)\) 被称为测量残差(residual),表示实际测量值 \(z_k\) 与预测测量值 \(C{\hat{x}_t}^-\) 之间的偏移。 下面给出推导过程(感觉像有100年没有推过公式了): 首先我们的 \(\hat{x}_t^-\) 和 \(\hat{x}_t\) 分别服从如下高斯分布的: $$ \hat{x}_t^- \sim \mathcal{N}(\mu_t^- , \Sigma_t^-), \quad \hat{x}_t \sim \mathcal{N}(\mu_t , \Sigma_t) $$ $$ \Sigma_t^-=E[(x_t-\hat{x}_t^-)(x_t-\hat{x}_t^-)^T] $$ $$ \Sigma_t=E[(x_t-\hat{x}_t)(x_t-\hat{x}_t)^T] $$ 其中 \(x_t\) 是实际的状态值(对应于预测值)。 然后 measurement model 有: $$ z_t=Cx_t+v $$ $$ v\sim \mathcal{N}(0 , R) $$ $$ R=E[vv^T] $$ 我们对 \(K\) 优化的目的是使最终的 \(\Sigma_t\) 的 \(trace (Tr)\) 最小,为什么是 \(trace\) 呢? 因为我们知道 \(\Sigma_t\) 是一个对角矩阵,且所有项都大于零,所以它的 \(trace\) 等价于它的 \(L1 \;norm\),所以相当于用 \(L1\; norm\) 作为 loss function 进行了一次数值优化。这里我们令: $$ K=arg \max_{K} \, Tr(E[(x_t-\hat{x}_t)(x_t-\hat{x}_t)^T]) $$ 对右边括号内展开: $$ E[(x_t-\hat{x}_t)(x_t-\hat{x}_t)^T] =E\{ [x_t-\hat{x}_t^--K(z_t-C\hat{x}_t^-)][x_t-\hat{x}_t^--K(z_t-C\hat{x}_t^-)]^T \} $$ $$ =E\{ [x_t-\hat{x}_t^--K[C(x_t-\hat{x}_t^-)+v]][x_t-\hat{x}_t^--K[C(x_t-\hat{x}_t^-)+v]]^T\} $$ $$ =-\Sigma_t^-C^TK^T-KC{\Sigma_t^-}^T+K(C\Sigma_t^-C^T)K^T+KRK^T $$ 然后令: $$ f(K)=Tr[-\Sigma_t^-C^TK^T-KC{\Sigma_t^-}^T+K(C\Sigma_t^-C^T)K^T+KRK^T] $$ $$ \frac{\partial f(K)}{K}=0 $$ 这里要用到几个重要的 \(trace\) 求导公式: $$ \frac{\partial \;Tr(XA) }{\partial X}=A^T $$ $$ \frac{\partial \;Tr(X^TA) }{\partial X}=\frac{\partial \;Tr(AX^T) }{\partial X}=A $$ $$ \frac{\partial \;Tr(XBX^T) }{\partial X}=XB^T+XB $$ 所以最后得到一个结论: $$ K=\frac{ \Sigma_t^- C^T }{C\Sigma_t^-C^T+R} $$ 跟其它资料上给出的形式是一样的(做这一堆展开简直要了命。。) 对这个结果的一个非常有趣的解释是,当测量结果的 error covariance \(R\) 趋近于 0 时,测量值 \(z_t\) 是完全没有误差的,这时有: $$ \lim_{R \to\ 0} K=C^{-1} $$ 代入 prediction 公式中得到: $$ \hat{x}_t=C^{-1}z_t $$ 也就是说只剩下了通过测量得到的部分。 而当预测值的 error covariance \(\Sigma_t^-\) 趋近于 0 时,预测结果 \(\hat{x}_t^-\) 是完全准确的,此时有: $$ \lim_{\Sigma_t^- \to\ 0} K=0 $$ 于是会得到: $$ \hat{x}_t=\hat{x}_t^- $$ 只剩下了通过预测得到的部分。 这里的绝妙之处就在于,\(K\) 会根据测量值 \(z_t\)、预测值 \(\hat{x}_t^-\) 各自的置信程度,来自动调整二者的权重,使得权重偏向于更可信的那一方。 到目前为止,我已经推导出了 kalman filter 的两大重要步骤, Prediction 和 Update ,分别对应 EM 算法中的 Expectation 和 Maximization ,其图示如下: 反复不断重复这两个过程,即是 kalman filter 的实现形式。 Extended Kalman Filter 虽然以上已经成功推导了 kalman filter 的全部公式,但是楼主想做的是四轴姿态估计,原始的 kalman filter 只适用于如下这种线性情况,也就是说,前一时刻状态的高斯分布,经过状态变换后,仍然是高斯分布。 但在强大的微积分面前,一切都是浮云。 人们为了解决非线性,提出了 Extended Kalman Filter (拓展卡尔曼滤波)。其思路是在传递曲线局部用该点的切线方程来代替曲线本身,其实就是高数中的一阶泰勒展开。这样形成了一种局部的线性关系,可以使用原本的 kalman filter 的公式进行运算,只是传递矩阵 \(A\) 不再是恒定的,而是随时刻变化的 \(A_t\),需要在每一时刻根据导数重新计算 。


2015 年德国的几位科研人员基于 google deep dream 的思路,提出了一种用 CNN自动生成带有特定艺术风格的图片的算法,发表了一篇名为 A Neural Algorithm of Artistic Style 的 tech notes, 可以说是 deep dream的美梦+艺术版本。 由于效果实在是太神奇,从那时起开始出现了很多利用此技术来美化用户图片的App和在线应用,比如手机应用 Prisma 和 ostagram ,以及 deepart 。 楼主在读过这篇 tech notes后,被其想法的巧妙所折服,再加上目前网络上开源的版本已经有了基于 Torch, Theano 和 Tensorflow 的,但尚无 caffe版本。 于是产生了用 caffe来重复原文实验的想法。 该文的一个基本思路是 :一张图片的内容(content)和风格(style)是可分离和重组的。于是我们可以用图片A的内容,加上图片B的风格,组合出一张新的图片,使之同时具有二者的内容和风格。 而这里CNN的作用在于,当用CNN识别一张图片时,当图片数据沿着各 convolution layer前向(forward)传播时,其包含的信息将越来越趋于精简,图片中的细节部分,或者说基础纹理和边缘,将逐渐淡去,而这些正是所谓“艺术风格”的体现之处,大概相当于平常说的“笔触”吧;而代表主干内容的结构信息,则会越来越清晰。这是CNN对人类视觉中“分层理解”的工作方式的一种近似模仿。 所以实际中,CNN中的浅层 convolution layer,主要对细节纹理信息进行提取,而这些 layer经过 activation layer 之后的输出,经过处理后,表征了一幅图片的 style 信息;而较深层次的convolution layer,则表征了图片的 content 信息。 具体的实现思路如下: 1. Find Content 基于上述思路,文中首先定义了用于度量一副图片在某一特定 layer \(l\) 上 content 的特征 \(F^l\) ,这个特征直接是选取了CNN在该 layer 后 activation 的输出的 feature map。 假设我们有一张图片 p ,使我们在 layer \(l\) 上得到了一个特征 \(P^l\) , 那么根据内容可分离的观点,这世界上一定存在另一张图片 x ,其在同一CNN同一 layer上输出的特征 \(F^l\) 无限接近于 \(P^l\)。也即是说图片 p , x 拥有相同的 content 。这里我们可以定义如下的 content loss function。 $$ L_{content}(p,x,l)=\frac{1}{2} \sum_{ij} (F^l_{ij}-P^l_{ij})^2 $$ 然后的问题就是要如何找到这张图片?考虑到图片的像素数和灰度级,这里的搜索空间是极其极其巨大的。但我们有 gradient descent 这一法宝在手,可助降妖伏魔。 解决的方法也非常鬼畜: 首先对 x 生成一张白噪声图片,作为初始值,然后用 gradient descent 和 backpropagation,在每一次迭代循环中计算 \(L_{content}(p,x,l)\) 对 x 每一个像素的导数,作为 x 该次 gradient descent 的 update。经过一定的循环次数后, x 便可逐渐收敛为我们想要的目标图片。 由于在 backpropagation 中我们相当于使用了 \(L_{content}(p,x,l)\)作为 output layer,所以相对应的求导过程也需要我们给出。不过这里的 least-square loss 求导非常简单,初中生都会算(分段是因为 VGG使用 ReLu作为 activation layer的关系): $$ \frac{\partial L_{content} }{\partial F^l_{ij}}= \begin{cases} ( F^l_{ij}- P^l_{ij} ) & if & F^l_{ij}>0\\\\ 0 & if & F^l_{ij}<0 \end{cases} $$ (实际上我本人比较想把这里的 least-square loss改成理论上更科学的 cross-entropy loss看看效果) 基于上述理论,楼主编写了如下基于 caffe的实验代码,待我一行行分析: 这一段是设置 caffe相关的环境,为省事直接copy了之前 deep dream的部分。 # imports and basic notebook setup from cStringIO import StringIO import numpy as np import scipy.ndimage as nd import PIL.Image from IPython.display import clear_output, Image, display from google.protobuf import text_format import matplotlib.pyplot as plt from numpy import linalg as LA %matplotlib inline import caffe # If your GPU supports CUDA and Caffe was built with CUDA support, # uncomment the following to run Caffe operations on the GPU. caffe.set_mode_gpu() caffe.set_device(0) # select GPU device if multiple devices exist def showarray(a, fmt='jpeg'): a = np.uint8(np.clip(a, 0, 255)) f = StringIO() PIL.Image.fromarray(a).save(f, fmt) display(Image(data=f.getvalue())) 根据原文,导入已经 train好的 VGG19模型 model_path = '/home/lzhang57/DeepLearning/caffe/models/bvlc_vgg19/' # substitute your path here net_fn = model_path + 'VGG_ILSVRC_19_layers_deploy.prototxt' param_fn = model_path + 'VGG_ILSVRC_19_layers.caffemodel' # Patching model to be able to compute gradients. # Note that you can also manually add "force_backward: true" line to "deploy.prototxt". model = caffe.io.caffe_pb2.NetParameter() text_format.Merge(open(net_fn).read(), model) model.force_backward = True open('tmp.prototxt', 'w').write(str(model)) net = caffe.Classifier('tmp.prototxt', param_fn, mean = np.float32([104.0, 116.0, 122.0]), # ImageNet mean, training set dependent channel_swap = (2,1,0)) # the reference model has channels in BGR order instead of RGB # a couple of utility functions for converting to and from Caffe's input image layout def preprocess(net, img): return np.float32(np.rollaxis(img, 2)[::-1]) - net.transformer.mean['data'] def deprocess(net, img): return np.dstack((img + net.transformer.mean['data'])[::-1]) 读取 content 图片,一只萌猫。并指定用于计算 content feature的参考 layer,这里先设置成 'conv4_2' img = np.float32(PIL.Image.open('data/cat.jpg')) [h,w,c]=img.shape content_img=preprocess(net, img) ### set stop layer ### end='conv4_2' src = net.blobs['data'] src.reshape(1,3,h,w) dst = net.blobs[end] ### generate content image ### src.data[0]=content_img net.forward(end=end) Content=np.copy(dst.data[0]) 生成白噪声图片,并进入 gradient descent 主循环。 ### generate style image target=np.float32(np.random.rand(h,w,3)*255) target_img=preprocess(net, target) ### gradient descent step step=2 src.data[0]=target_img ### optimize target image Loss=[]; max_iter=500 for iter in range(max_iter): ## compute feature net.forward(end=end) Feature=dst.data[0] ## compute gradient tmp=Feature[:]-Content[:]; Loss.append(0.5*LA.norm(Feature-Content)) if( iter>0 and np.abs((Loss[-2]-Loss[-1])/Loss[-2])<1e-4): print('Iteration time=',iter) break tmp[np.argmax(Feature[:]<0)]=0; dst.diff[0]=tmp ## backward to optimize net.backward(start=end) g = src.diff[0] # apply normalized ascent step to the input image src.data[:] -= step_size/np.abs(g).mean()*g vis=deprocess(net,src.data[0]) showarray(vis) plt.plot(Loss) 最后得到的输出图片如下,现在看来还平淡无奇,因为这里只是实现了“生成一张跟参考图片具有相同内容图片”的功能: 迭代过程中的 loss function 收敛情况如下: 为了更好的演示这个过程中发生了什么,这里我将 gradient descent时的中间结果显示如下(成功规避水印): 在我研究 machine learning以前,如果有人告诉我可以像这样把一张白噪声图片只靠做减法,一点一点撸出一只猫来,我是打死也不信的。 今天就做这么多吧,实际上这里只实现了全部功能的 1/3,明天继续,睡觉zzz 更新计划:明天七夕,后天再继续

2015 年德国的几位科研人员基于 google deep dream 的思路,提出了一种用 CNN自动生成带有特定艺术风格的图片的算法,发表了一篇名为 A Neural Algorithm of Artistic Style 的 tech notes, 可以说是 deep dream的美梦+艺术版本。 (附件:268420) 由于效果实在是太神奇,从那时起开始出现了很多利用此技术来美化用户图片的App和在线应用,比如手机应用 Prisma 和 ostagram ,以及 deepart 。 楼主在读过这篇 tech notes后,被其想法的巧妙所折服,再加上目前网络上开源的版本已经有了基于 Torch, Theano 和 Tensorflow 的,但尚无 caffe版本。 于是产生了用 caffe来重复原文实验的想法。 该文的一个基本思路是 :一张图片的内容(content)和风格(style)是可分离和重组的。于是我们可以用图片A的内容,加上图片B的风格,组合出一张新的图片,使之同时具有二者的内容和风格。 而这里CNN的作用在于,当用CNN识别一张图片时,当图片数据沿着各 convolution layer前向(forward)传播时,其包含的信息将越来越趋于精简,图片中的细节部分,或者说基础纹理和边缘,将逐渐淡去,而这些正是所谓“艺术风格”的体现之处,大概相当于平常说的“笔触”吧;而代表主干内容的结构信息,则会越来越清晰。这是CNN对人类视觉中“分层理解”的工作方式的一种近似模仿。 所以实际中,CNN中的浅层 convolution layer,主要对细节纹理信息进行提取,而这些 layer经过 activation layer 之后的输出,经过处理后,表征了一幅图片的 style 信息;而较深层次的convolution layer,则表征了图片的 content 信息。 具体的实现思路如下: 1. Find Content 基于上述思路,文中首先定义了用于度量一副图片在某一特定 layer \(l\) 上 content 的特征 \(F^l\) ,这个特征直接是选取了CNN在该 layer 后 activation 的输出的 feature map。 假设我们有一张图片 p ,使我们在 layer \(l\) 上得到了一个特征 \(P^l\) , 那么根据内容可分离的观点,这世界上一定存在另一张图片 x ,其在同一CNN同一 layer上输出的特征 \(F^l\) 无限接近于 \(P^l\)。也即是说图片 p , x 拥有相同的 content 。这里我们可以定义如下的 content loss function。 $$ L_{content}(p,x,l)=\frac{1}{2} \sum_{ij} (F^l_{ij}-P^l_{ij})^2 $$ 然后的问题就是要如何找到这张图片?考虑到图片的像素数和灰度级,这里的搜索空间是极其极其巨大的。但我们有 gradient descent 这一法宝在手,可助降妖伏魔。 解决的方法也非常鬼畜: 首先对 x 生成一张白噪声图片,作为初始值,然后用 gradient descent 和 backpropagation,在每一次迭代循环中计算 \(L_{content}(p,x,l)\) 对 x 每一个像素的导数,作为 x 该次 gradient descent 的 update。经过一定的循环次数后, x 便可逐渐收敛为我们想要的目标图片。 由于在 backpropagation 中我们相当于使用了 \(L_{content}(p,x,l)\)作为 output layer,所以相对应的求导过程也需要我们给出。不过这里的 least-square loss 求导非常简单,初中生都会算(分段是因为 VGG使用 ReLu作为 activation layer的关系): $$ \frac{\partial L_{content} }{\partial F^l_{ij}}= \begin{cases} ( F^l_{ij}- P^l_{ij} ) & if & F^l_{ij}>0\\\\ 0 & if & F^l_{ij}<0 \end{cases} $$ (实际上我本人比较想把这里的 least-square loss改成理论上更科学的 cross-entropy loss看看效果) 基于上述理论,楼主编写了如下基于 caffe的实验代码,待我一行行分析: 这一段是设置 caffe相关的环境,为省事直接copy了之前 deep dream的部分。 # imports and basic notebook setup from cStringIO import StringIO import numpy as np import scipy.ndimage as nd import PIL.Image from IPython.display import clear_output, Image, display from google.protobuf import text_format import matplotlib.pyplot as plt from numpy import linalg as LA %matplotlib inline import caffe # If your GPU supports CUDA and Caffe was built with CUDA support, # uncomment the following to run Caffe operations on the GPU. caffe.set_mode_gpu() caffe.set_device(0) # select GPU device if multiple devices exist def showarray(a, fmt='jpeg'): a = np.uint8(np.clip(a, 0, 255)) f = StringIO() PIL.Image.fromarray(a).save(f, fmt) display(Image(data=f.getvalue())) 根据原文,导入已经 train好的 VGG19模型 model_path = '/home/lzhang57/DeepLearning/caffe/models/bvlc_vgg19/' # substitute your path here net_fn = model_path + 'VGG_ILSVRC_19_layers_deploy.prototxt' param_fn = model_path + 'VGG_ILSVRC_19_layers.caffemodel' # Patching model to be able to compute gradients. # Note that you can also manually add "force_backward: true" line to "deploy.prototxt". model = caffe.io.caffe_pb2.NetParameter() text_format.Merge(open(net_fn).read(), model) model.force_backward = True open('tmp.prototxt', 'w').write(str(model)) net = caffe.Classifier('tmp.prototxt', param_fn, mean = np.float32([104.0, 116.0, 122.0]), # ImageNet mean, training set dependent channel_swap = (2,1,0)) # the reference model has channels in BGR order instead of RGB # a couple of utility functions for converting to and from Caffe's input image layout def preprocess(net, img): return np.float32(np.rollaxis(img, 2)[::-1]) - net.transformer.mean['data'] def deprocess(net, img): return np.dstack((img + net.transformer.mean['data'])[::-1]) 读取 content 图片,一只萌猫。并指定用于计算 content feature的参考 layer,这里先设置成 'conv4_2' (附件:268425) img = np.float32(PIL.Image.open('data/cat.jpg')) [h,w,c]=img.shape content_img=preprocess(net, img) ### set stop layer ### end='conv4_2' src = net.blobs['data'] src.reshape(1,3,h,w) dst = net.blobs[end] ### generate content image ### src.data[0]=content_img net.forward(end=end) Content=np.copy(dst.data[0]) 生成白噪声图片,并进入 gradient descent 主循环。 ### generate style image target=np.float32(np.random.rand(h,w,3)*255) target_img=preprocess(net, target) ### gradient descent step step=2 src.data[0]=target_img ### optimize target image Loss=[]; max_iter=500 for iter in range(max_iter): ## compute feature net.forward(end=end) Feature=dst.data[0] ## compute gradient tmp=Feature[:]-Content[:]; Loss.append(0.5*LA.norm(Feature-Content)) if( iter>0 and np.abs((Loss[-2]-Loss[-1])/Loss[-2])<1e-4): print('Iteration time=',iter) break tmp[np.argmax(Feature[:]<0)]=0; dst.diff[0]=tmp ## backward to optimize net.backward(start=end) g = src.diff[0] # apply normalized ascent step to the input image src.data[:] -= step_size/np.abs(g).mean()*g vis=deprocess(net,src.data[0]) showarray(vis) plt.plot(Loss) 最后得到的输出图片如下,现在看来还平淡无奇,因为这里只是实现了“生成一张跟参考图片具有相同内容图片”的功能: (附件:268421) 迭代过程中的 loss function 收敛情况如下: (附件:268422) 为了更好的演示这个过程中发生了什么,这里我将 gradient descent时的中间结果显示如下(成功规避水印): (附件:268424) 在我研究 machine learning以前,如果有人告诉我可以像这样把一张白噪声图片只靠做减法,一点一点撸出一只猫来,我是打死也不信的。 今天就做这么多吧,实际上这里只实现了全部功能的 1/3,明天继续,睡觉zzz 更新计划:明天七夕,后天再继续


原文 初稿译者:@龙星镖局 友情指导:@winsty,@妖僧老馮, @jarszm,@潘布衣 郑重声明:本文素材是Yann LeCun 收集整理,英文链接。虽然是简单的翻译,但本文没有机器学习领域的深厚知识是领会不到精髓的。由于知识水平和美国文化理解不够,文中仍有多处自觉没有真正体会LeCun的幽默。欢迎大家能继续批评指正。译文中若有不当之处,是译者水平有限所致,跟指导者没有任何关系。 第0条 Radford Neal:我不觉得贝叶斯是解决所有问题的最好方法。Geoff Hinton:我完全听不到你在胡扯什么,因为我对你说这句话的先验为0. Radford Neal: I don't necessarily think that the Bayesian method is the best thing to do in all cases... Geoff Hinton: Sorry Radford, my prior probability for you saying this is zero, so I couldn't hear what you said. 备注:先验是贝叶斯理论的一个关键环节,判读一个事情是否成立的后验概率等于似然(类条件概率)乘以先验。由于Hinton说先验为0,所以就等于说Neal说的东西基本就是胡扯了。另外一种可能,Hinton对贝叶斯这一套似乎一直也有点不太满意,Neal忽然说贝叶斯没那么好,他可能很惊讶这种论调,终于有人能和他一样见解了。 第1条 Hinton不需要隐藏单元,因为当他靠近时隐藏单元会自己藏起来。 Geoff Hinton doesn't need to make hidden units. They hide by themselves when he approaches. 备注:隐藏层是深层神经网络的中间层,起到学习feature的关键作用。这里暗指Hinton玩转了深度学习,不需要隐藏层就能起到数据变换的效果。[潘布衣]将approaches直译出来会更生动,会显得更神奇。 第2条 Hinton不是不同意你,只是跟你稍有差异。 Geoff Hinton doesn't disagree with you, he contrastively diverges (from Vincent Vanhoucke) 备注:contrastively diverges缩写为CD,中文翻译为“对比散度”。[妖僧老馮]contrastively diverges 是图模型中的一种对梯度的逼近方法,深度学习中仅在RBM/DBN这类中有用到,由于目前RBM/DBN已经不大用了所以。。。[龙星镖局]也可理解为“暗指很少同意人”? 第3条 深度信念网络确实对Hinton深信不疑。 Deep Belief Nets actually believe deeply in Geoff Hinton. 备注:Deep Belief Nets是深度学习里的一类算法统称,这里算法相信人,暗指Hinton在深度学习领域的超级影响力。一个形象的比喻:中国人爱说有钱能使鬼推磨,有时为了强调钱的作用,还可以说有钱能使磨推鬼。这里就是强调Hinton之于深度学习的作用。 第4条 Hinton发现了人脑是如何工作的。哦,确切地讲,是过去25年来每年都会发现一次。 Geoff Hinton discovered how the brain really works. Once a year for the last 25 years. 备注:学界经常拿人脑工作机制来解释深度学习是如何work的,这也是Hinton说自己研究的东西牛逼的主要例证,但人脑机制一直是悬而未决的研究课题,此处略有嘲讽他乱扯人脑的意思。 第5条 马尔可夫随机场确信Hinton是难缠的。 Markov random fields think Geoff Hinton is intractable. 备注:马尔可夫随机场做精确推断已被证明是#P问题,比NP问题还要难的一类。此处暗指Hinton也是个难缠的家伙。 第6条 如果你胆敢挑战Hinton,他分分钟就能把你的熵最大化。你的自由能甚至在你到达平衡前就会消失。 If you defy Geoff Hinton, he will maximize your entropy in no time. Your free energy will be gone even before you reach equilibrium. 备注:最大熵原理简单来说各种情形都有可能发生,此处表示让人怀疑自己所确信的。自由能和能量平衡则是RBM(受限波尔滋曼机)的基本概念。这句话暗指Hinton这个大权威被你挑战时,会给你点color see see。 第7条 Hinton能让你的肠子悔青。 Geoff Hinton can make you regret without bounds. 备注:bound是机器学习研究中理论分析算法work的重要参考指标,在英语里有“边界/边疆”的意思。without bounds有点“Hinton说啥就是啥,根本不需要证明”的意思。 第8条 Hinton能让你轻松减肥(是你的体重,遗憾的是不是我的)。 Geoff Hinton can make your weight decay (your weight, but unfortunately not mine). 备注:weight decay是机器学习里一个重要概念,是正则项/dropout等有效的工作原理,在英语里则有减肥的意思。括号里则是Yann LeCun自嘲的话,因为他自己很早就是一个大胖子了,据说一直想减肥但一直未成功。 第9条 Hinton不需要支持向量,因为他可以用小拇指撑起高维超平面。 Geoff Hinton doesn't need support vectors. He can support high-dimensional hyperplanes with his pinky finger. 备注:支持向量是SVM(支持向量机)分界面上的样本点,也可以认为是SVM划分分类面的决定因素。此处有Hinton对SVM不屑一顾的意思。 第10条 可能有人还不知道Hinton接触贝叶斯派时内心其实是认为他们是有罪的。 A little-known fact about Geoff Hinton: he frequents Bayesians with prior convictions (with thanks to David Schwab). 备注:[winsty] 这里可能暗指统计学里的frequenist 和 bayesian学派。这里的意思应该是说通过prior conviction将贝叶斯频率化派,往往prior是soft的,这里用conviction这个词感觉是说只相信prior不管likelihood。[妖僧老馮]这里的frequents的双关意思是接近(动词)。hinton不大信贝叶斯这套玩意,所以这句话的双关的意思是hinton接近这帮贝叶斯学者的时候内心已经认为他们有罪。 第11条 所有敢接近Hinton的核函数醒来时都会很纠结。 All kernels that ever dared approaching Geoff Hinton woke up convolved. 备注:核函数是支持向量机处理非线性的重要手段,支持向量机和深度学习长期两派,相互打压。convolved在深度学习里是卷积,大众使用时也有点沮丧/纠结的意思。 第12条 大多数农场通常都会被美丽的原野环绕。Hinton的农场则是在一个超级大平原上,被一个破旧的农田围绕着,谷仓里还堆满了谷子粒。 Most farmhouses are surrounded by nice fields. Geoff Hinton's farmhouse lies in a hyper-plain, surrounded by a mean field, and has kernels in la grange. 备注:此句有多处双关,hyper-plain, mean field, kernels, lagrange都是机器学习里的概念,同时又分别对应英语里的“平原”,“破旧的农田”,“谷粒”,而La grange对应法语里的“谷仓”。[winsty] 应该是说Lagrangian form的SVM可以推导出kernel trick。[Jarszm] kernel双关谷粒,还用了一个法语梗,把Lagrange拆成la grange是法语里“那个谷仓”的意思 第13条 Hinton用过的唯一的一个kernel就是事实本身。 The only kernel Geoff Hinton has ever used is a kernel of truth. 备注:kernel of truth, 英语解释为A singular element of truth,大致相当于中文里的“真理”。 第14条 一碰到Hinton,支持向量就精神失常了,并且最佳分割面也不灵光了。 After an encounter with Geoff Hinton, support vectors become unhinged and suffer optimal hyper-pain (with thanks to Andrew Jamieson). 备注:一般认为经典的SVM使用的是hinge loss,而unhinged对应英语里精神失常。此处有用到了SVM和神经网络的恩恩怨怨作梗,又黑了一把SVM。 第15条 Hinton举一反三的能力是无边无际的。 Geoff Hinton's generalizations are boundless. 备注:generalization是机器学习评估模型在新样本上的表现(泛化能力)的重要指标,在英语里则可对应中文里“举一反三”的泛化能力。而bound则是机器学习中理论上分析算法work的重要概念,在英语里则对应无边际的意思。这句有点在说Hinton老爷子也很扯的意思。 第16条 Hinton直接跳到第三代贝叶斯了。 Geoff Hinton goes directly to third Bayes. 备注:贝叶斯领域一直有人在研究,但Hinton对贝叶斯不太感冒,这里说Hinton直接用第三代了,在黑bayes的同时,也暗指Hinton已经成精了。 第17条 永远不要打断Hinton讲话,否则你只能默默承受讨价还价带给老爷子的愤怒了。 Never interrupt one of Geoff Hinton's talks: you will suffer his wrath if you maximize the bargin。 备注:maximize the bargin 是暗指SVM的最大间隔么?在英语里则是讨价还价的意思。这句话又用到了SVM和神经网络的梗,大概就是说少在老爷子面前提SVM,要不然愤怒给你看。 特别声明:感谢微博上所有好友的建议和评论,收益很多。


sklearn是python的一个ML库。其文档非常详细,详细程度可称教材级别。使用简便。 运作流程 音频输入==>STFT==>寻峰==>泛音提取==>使用sklearn进行聚类==>输出 有关STFT 本应用中由于输入的音频文件为44k的CD采样率,因此采用的STFT使用44100的原采样率,不再重新采样。并使用8000点的帧长度,步进2000点(即帧重叠6000点),使用hanning窗,以达到足够高的频率分辨度同时足以显示出乐曲的动态变化。 详细: http://bbs.kechuang.org/t/78789 寻峰 这里没有使用特别复杂的寻峰算法,只是最简单的二阶导数+阈值 泛音提取 乐音中各次泛音成分的比例关系反映了声音的音色特征。因此提取前n次泛音与基频的比例,取对数(消除距离不均)后放入n维向量中进行聚类。由于高次数的泛音十分弱,易被噪音干扰,所能提供的特征信息也不多,因此n过大反而不利于分类。经多次测试,n的取值应在4附近,效果比较理想。 同时,由于和弦结构会对泛音产生一定干扰(例如,其中五度音程的两个音,二、三次泛音将会重叠在一起),因此应使用尽量简单的乐曲。 聚类 聚类使用sklearn库的SpectralClustering算法。样本应采用乐器种类较多,音色差别较大的乐曲,经实际测试,对部分乐器种类较少的乐曲分类效果不好。n_clusters的值应按照实际音色种类选择。 测试散点图 这几张图是各种测试参数的结果中效果比较好的几张。 泛音向量维度 3~5 n_clusers 5~7 fig.35 fig.30 fig.29 fig.27 从散点图中可看出,由于过于简单的寻峰算法,导致较弱的音丢失严重(例如最开头一段),较强的部分噪音比较严重(例如fig.36后部) 图中总体上还是能够反映出乐曲中各种乐器的使用情况的。 注 测试音频: StarSky.mp3 你的浏览器可能不支持audio标签播放音乐。升级吧。 mp3格式,请转换成wav后使用。(linux用户请使用mpg123而不是mpg321,python的wave库似乎无法读取mpg321所转换出的wav格式。。。。然而使用ubuntu的音乐播放器,视频播放器和octave读取都完全正常) 代码: https://github.com/gym487/MLProj/ 按规定论坛上传一份: MLProj.rar 5.07M



nkc Development Server  https://github.com/kccd/nkc

科创研究院 (c)2005-2016

蜀ICP备11004945号-2 川公网安备51010802000058号