
AutoML
tf.multinomial(logits, num_samples) 第一个参数logits可以是一个数组,每个元素的值表示对应index的选择概率。 假设logits有两个元素,即[0.6,0.4],这表示的意思是取 0 的概率是0.6, 取 1 的概率是0.4。 第二个参数num_samples表示抽样的个数。 例如:tf.multinomial(tf.log([[0.01]]),3) 不管重复运行多少次结果都是 [0,0,0]tf.multinomial(tf.log([[0.1, 0.6]]),3) 结果可能 [0,0,0],也可能是[0,1,1],当然也有其他可能。
假设现在有图像数据imgs和对应标签targets。数据维度分别如下 imgs.shape = (num, channel, width, height) targets.shape = (num, class) 因为通常我们需要将数据打散,这样的好处是可以让模型训练更具鲁棒性,那么如何同时打散data和target,而且还需要保持对应顺序不变呢?方法如下 # 得到打乱后的index from random import shuffle index = [i for i in range(len(imgs))] shuffle(index) imgs = imgs[index, :, :, :] targets = targets[index, :] 要注意的是数据的维度要保持正确,也就是上面的:数量要正确,假如在mnist数据集上,target的维度是(num,)维度,所以此时应该写成targets = targets[index]即可。 MARSGGBO原创 2018-10-31
目前在研究Automated Machine Learning,其中有一个子领域是实现网络超参数自动化搜索,而常见的搜索方法有Grid Search、Random Search以及贝叶斯优化搜索。前两者很好理解,这里不会详细介绍。本文将主要解释什么是体统(沉迷延禧攻略2333),不对应该解释到底什么是贝叶斯优化。 I Grid Search & Random Search 我们都知道神经网络训练是由许多超参数决定的,例如网络深度,学习率,卷积核大小等等。所以为了找到一个最好的超参数组合,最直观的的想法就是Grid Search,其实也就是穷举搜索,示意图如下。 但是我们都知道机器学习训练模型是一个非常耗时的过程,而且现如今随着网络越来越复杂,超参数也越来越多,以如今计算力而言要想将每种可能的超参数组合都实验一遍(即Grid Search)明显不现实,所以一般就是事先限定若干种可能,但是这样搜索仍然不高效。 所以为了提高搜索效率,人们提出随机搜索,示意图如下。虽然随机搜索得到的结果互相之间差异较大,但是实验证明随机搜索的确比网格搜索效果要好。 II Bayesian Optimization 假设一组超参数组合是\(X={x_1,x_2,...,x_n}\)(\(x_n\)表示某一个超参数的值),而这组超参数与最后我们需要优化的损失函数存在一个函数关系,我们假设是\(f(X)\)。 而目前机器学习其实是一个黑盒子(black box),即我们只知道input和output,所以上面的函数\(f\)很难确定。所以我们需要将注意力转移到一个我们可以解决的函数上去,下面开始正式介绍贝叶斯优化。 假设我们有一个函数\(f:\cal{X}→\Bbb{R}\),我们需要在\(X\subseteq\cal{X}\)内找到 \(x^*=\underset{x\in X}{\operatorname{argmin}}f(x) \tag{1}\) 当\(f\)是凸函数且定义域\(X\)也是凸的时候,我们可以通过已被广泛研究的凸优化来处理,但是\(f\)并不一定是凸的,而且在机器学习中\(f\)通常是expensive black-box function,即计算一次需要花费大量资源。那么贝叶斯优化是如何处理这一问题的呢? 1. 详细算法 Sequential model-based optimization (SMBO) 是贝叶斯优化的最简形式,其算法思路如下: 下面详细介绍一下上图中的算法: 1. Input: \(f\): 就是那个所谓的黑盒子 \(\cal{X}\):是输入数据,例如图像、语音等。 \(S\):是Acquisition Function(采集函数),这个函数的作用是用来选择公式(1)中的\(x\),后面会详细介绍这个函数。 \(\cal{M}\):是基于输入数据假设的模型,即已知的输入数据\(x\)都是在这个模型上的,可以用来假设的模型有很多种,例如随机森林,Tree Parzen Estimators(想要了解这两种的可以阅读参考文献[1])等,但是本文主要介绍高斯模型。 2. InitSamples(f,x)→D 这一步骤就是初始化获取数据集\(\cal{D}={(X_1,Y_1),...,(X_n,Y_n)}\),其中\(Y_i=f(X_i)\),这些都是已知的。 3. 循环选参数\(T\)次 因为每次选出参数\(x\)后都需要计算\(f(x)\),而正如前面介绍的没计算一次函数\(f\),都会消耗大量资源,所以一般需要固定选参次数(或者是函数评估次数)。 \(p(y|x,D)←FITMODEL(M,D)\) 首先我们预先假设了模型\(\cal{M}\)服从高斯分布,且已知了数据集\(\cal{D}\),所以可以通过计算得出具体的模型具体函数表示。假设下图中的绿色实现就是基于数据集\(\cal{D}\)经过计算后的服从高斯分布模型。可以看到Each additional band of green is another half standard deviation on the output distribution. 那么高斯分布是如何计算的呢? 因为我们已经假设\(f\)~\(GP(μ,K)\)。 (GP:高斯过程,μ:均值 K:协方差kernel,)。所以预测也是服从正态分布的,即有\(p(y|x,D)=\cal{N}(y|\hat{μ},\hat{σ}^2)\) \(x_i←\underset{x\in X}{\operatorname{argmax}}S(X,p(y|X,D))\) 现在已经将假设的模型计算出来了,那么下一步我们需要基于假设模型的基础上选择满足公式(1)的参数了,也就是选择\(X\),那么如何选择呢?这就涉及到了Acquisition Function,为了让文章篇幅更易阅读,想了解Acquisition Function移步到文末。 \(y_i←f(x_i)\) 既然参数选出来了,那么当然就是要计算咯。例如我们通过上述步骤已经选出了一组超参数\(x_i\),那么我们下一步就是将超参数带入网络中去进行训练,最后得到输出\(y_i\)。这一步骤虽然expensive,但是没办法还是得走啊。 \(D←D \bigcup{(x_i,y_i)}\) 更新数据集。 2. Acquisition Function Acquisition Function的选择可以有很多种,下面将分别介绍不同的AC function。 1) Probability of improvement 假设\(f'=min \, f\),这个\(f'\)表示目前已知的\(f\)的最小值。 然后定义utility function如下:\[ u(x) = \begin{cases} o, & \text{if $f(x)>f'$} \\ 1, & \text{if $f(x)≤f'$ } \end{cases} \] 其实也可以把上面的\(u(x)\)理解成一个reward函数,如果f(x)不大于f'就有奖励,反之没有。 probability of improvement acquisition function定义为the expected utility as a function of x: \[ \begin{align} a_{PI}(x)=E[u(x)|x,D] & = \int_{-∞}^{f'}\cal{N}(f;μ(x),K(x,x))df \notag{} \\ & = \cal{\Phi}(f';μ(x),K(x,x)) \notag{} \end{align} \] 之后只需要求出\(a(x)\)的最大值即可求出基于高斯分布的满足要求的\(x\)。 2) Excepted improvement 上面的AC function有个缺点就是找到的\(x\)可能是局部最优点,所以有了Excepted improvement。\(f'\)的定义和上面一样,即\(f'=min \, f\)。utility function定义如下: \[u(x)=max(0,f'-f(x))\] 因为我们最初的目的是找到使得f(x)最小的x,所以这个utility function的含义很好理解,即接下来找到的\(f(x)\)比已知最小的\(f'\)越小越好,然后选出小的程度最大的那个\(f(x)\)和\(f'\)之间的差距的绝对值作为奖励,如果没有更小的那么奖励则为0. AC function定义如下: \[ \begin{align} a_{EI}(x)=E[u(x)|x,D] & = \int_{-∞}^{f'}(f'-f)\cal{N}(f;μ(x),K(x,x))df \notag{} \\ & = (f'-μ(x))\cal{\Phi}(f';μ(x),K(x,x)) \, + \, K(x,x)\cal{N}(f';μ(x),K(x,x)) \notag{} \end{align} \] 通过计算使得\(a_{EI}\)值最大的点即为最优点。 上式中有两个组成部分。要使得上式值最大则需要同时优化左右两个部分: 左边需要尽可能的减少\(μ(x)\) 右边需要尽可能的增大方差(或协方差)\(K(x,x)\) 但是二者并不同能是满足,所以这是一个exploitation-exploration tradeoff。 3) Entropy search 4) Upper confidence bound Reference [1] Sigopt.com. Bayesian Optimization Primer (2018). [online] Available at: https://sigopt.com/static/pdf/SigOpt_Bayesian_Optimization_Primer.pdf [Accessed 26 Oct. 2018]. [2] Cse.wustl.edu. Bayesian Optimization (2018). [online] Available at: https://www.cse.wustl.edu/~garnett/cse515t/spring_2015/files/lecture_notes/12.pdf [Accessed 26 Oct. 2018]. [3] Anon,How does Bayesian optimization work? (2018). [online] Available at: https://www.quora.com/How-does-Bayesian-optimization-work [Accessed 26 Oct. 2018]. MARSGGBO原创 2018-10-28
转载链接:https://www.zhihu.com/question/51325408/answer/125426642来源:知乎 这个问题无外乎有三个难点: 什么是sum 什么是reduce 什么是维度(indices, 现在均改为了axis和numpy等包一致) sum很简单,就是求和,那么问题就是2和3,让我们慢慢来讲。其实彻底讲清楚了这个问题,很多关于reduce,维度的问题都会恍然大悟。 0. 到底操作哪个维度?? sum这个操作完全可以泛化为任意函数,我们就以sum为例,来看看各种情况。 首先是1维(按照tensorflow的说法其实是0维,后面会说)就是这样: a = 1 sum(a) => 1 那么看看2维的情况,为了看的更清楚,特意写成了矩阵的形式: a = [[1,2], [3,4]] sum(a) => ??? 仔细观察,那么问题来了,sum(a)到底应该是多少?有人说,当然是[3, 7](“横着加”[[1+2],[3+4]]),有人说 不应该是[4, 6](“竖着加”[[1+3],[2+4]]) 吗?还有人或说,不应该是10(全加在一起)吗? 谁是对的? 都是对的。 所以,对于多维数组元素的相加,如果不指定“如何加”,结果是未定义的,之所以有些时候没有指定也可以得到结果,是因为不同的软件或框架有默认的行为。对于tensorflow,默认行为是最后一种,也就是全加在一起。 1. 什么是维度?什么是轴(axis)?如何索引轴(axis)? 注:对Axis比较熟悉的读者可跳过这部分解释,只看加粗字体。 这是一个很大的问题,到底什么是维度呢?维基百科说: 维度,又称维数,是数学中独立参数的数目。在物理学和哲学的领域内,指独立的时空坐标的数目。0维是一点,没有长度。1维是线,只有长度。2维是一个平面,是由长度和宽度(或曲线)形成面积。3维是2维加上高度形成“体积面”。虽然在一般人中习惯了整数维,但在分形中维度不一定是整数,可能会是一个非整的有理数或者无理数。 妈呀,好复杂,我只是想写个tensorflow代码呀。 那么,编程时,你就可以简单的认为: 维度是用来索引一个多维数组中某个具体数所需要最少的坐标数量。 把这句话多读几遍,我想你肯定会有所顿悟。这里之所以说第一个1维的例子时0维,是因为,一个数字根本不需要索引,因为就只有一个呀。所有不同维度的形式如下: 0维,又称0维张量,数字,标量:1 1维,又称1维张量,数组,vector:[1, 2, 3] 2维,又称2维张量,矩阵,二维数组:[[1,2], [3,4]] 3维,又称3维张量,立方(cube),三维数组:[ [[1,2], [3,4]], [[5,6], [7,8]] ] n维:你应该get到点了吧~ 再多的维只不过是是把上一个维度当作自己的元素 1维的元素是标量,2维的元素是数组,3维的元素是矩阵。 从0维到3维,边看边念咒语“维度是用来索引一个多维数组中某个具体数所需要最少的坐标。” 在纸上写写看,想要精确定位一个数字,需要几个数字呢?比如上面例子中的3维数组,我们想要3这个数字,至少要3个数字定位,它的坐标是(0为索引起点):[0, 1, 0] 好了,现在就能说了,什么是轴(axis),如何索引axis(代码中常用的变量名,后文就用axis代表轴)。 什么是axis,编程时,你就可以简单的认为: axis是多维数组每个维度的坐标。 同样,把这句话多读几遍,我想你一定有体悟。 还拿3维来说,数字3的坐标是[0, 1, 0],那么第一个数字0的axis是0,第二个数字1的axis是1,第三个数字0的axis是2。 让我们再看看我们是如何得到3这个数字的: 找到3所在的2维矩阵在这个3维立方的索引:0 找到3所在的1维数组在这个2维矩阵的索引:1 找到3这个数这个1维数组的索引:0 (这里最好写在纸上看一看,括号比较多。) 也就是说,对于[ [[1,2], [3,4]], [[5,6], [7,8]] ]这个3维情况,[[1,2],[[5,6]], [[3,4], [7,8]]这两个矩阵(还记得吗,高维的元素低一个维度,因此三维立方的元素是二维矩阵)的axis是0,[1,2],[3,4],[5,6],[7,8]这4个数组(二维矩阵的元素是一维数组)的axis是1,而1,2,3,4,5,6,7,8这8个数的axis是2。 越往里axis就越大,依次加1。 那么,对于3维的情况,令a = [ [[1,2], [3,4]], [[5,6], [7,8]] ],tf.reduce_sum(a, axis=1)应该输出[[ 4, 6], [12, 14]],这就是处在axis=1的4个数组相加的结果,并reduce掉了一个维度。 这里需要注意的是,axis可以为负数,此时表示倒数第axis个维度,这和Python中列表切片的用法类似。 那么什么是reduce呢? 2. 什么是reduce reduce这个词字面上来讲,大多称作“归约”,但这个词太专业了,以至于第一眼看不出来意思。我更倾向于解释为“塌缩”,这样就形象多了。对一个n维的情况进行reduce,就是将执行操作的这个维度“塌缩”。还是上面tf.reduce_sum(a, axis=1)的例子,输出[[ 4, 6], [12, 14]]是二维,显然是被“塌缩”了,塌缩的哪个维度呢?就是被操作的维度,第2个维度,也就是axis=1(0开始索引)。tf.reduce_sum(a, axis=1)具体执行步骤如下: 找到a中axis=1的元素,也就是[1,2],[3,4],[5,6],[7,8]这4个数组(两两一组,因为前两个和后两个的地位相同) 在axis=1的维度进行相加也就是[1,2]+[3,4]=[4,6],[5,6]+[7,8]=[12, 14] “塌缩”这一维度,也就是说“掉一层方括号”,得出[[ 4, 6], [12, 14]] 接下来是一个附加问题: 3. 什么是keepdims 上面的reduce已经解释了,“塌缩”的是被操作的维度,那么keepdims也就是保持维度,直观来看就是“不掉一层方括号”,不掉哪层方括号呢?就是本来应该被塌缩的那一层(详细解释见评论)。tf.reduce_sum(a, axis=1, keepdims=True)得出[[[ 4, 6]], [[12, 14]]],可以看到还是3维。这种尤其适合reduce完了要和别的同维元素相加的情况。
因为课程作业的要求需要完成一篇IEEE格式的论文,所以选择入门LaTeX。但是期间遇到了各种各样莫名其妙的坑。前前后后挣扎了两个多星期终于完成了IEEE模板的设置。下面详细记录一下让我深恶痛绝的心路历程。 一、软件的选择 网上有很多LaTeX软件,在线编辑器推荐Overleaf。但是我个人还是更喜欢离线写东西,所以尝试过各种编辑器,例如VSCode等等,这些编辑器都需要自己搭环境才能用,反正对于我们这种初学者而言门槛较高,而且浪费时间,所以下面介绍一个LaTeX组合可以让你直接上手体验LaTeX,而不需要挣扎在LaTeX的门口。 要想离线使用LaTeX,首先需要一个编辑器,也就是敲LaTeX的软件,这里强烈推荐 TextStudio。这个软件是开源免费的,而且界面是我找过的软件中还过得去的。。因为感觉其他的也都不怎么好看。 但是光有编辑器还不行啊,你还得有编译器,这里推荐使用 MiKTeX。怎么理解这个软件的作用呢,就好像你要运行python代码,你得安装官网提供的Python3.6或者Anaconda之后才能编译python代码啊,之前没搞懂这个关系,一直以为跟markdown一样,结果并不是。 所以综上,要想使用LaTeX,你得有编辑器和编译器才行啊。 二、模板 废话不多说直接上模板。模板最初只需要如下三个文件: temp.tex: 保存LaTeX的文件 temp.bib: 保存参考文献的文件,其实也可以将参考文献写在*.tex中,但是我个人更喜欢把他们分开,因为这样逻辑更清晰。 ieeeconf.cls: IEEE样式模板。 以上文件可在如下网址下载: 谷歌云盘 百度云盘 提取码:cepb github 最终效果: 下面是示例。 1. temp.tex \documentclass[a4paper, 10pt, conference]{ieeeconf} \usepackage[utf8]{inputenc} \usepackage{dtk-logos} % for BibTeX stylized logo \overrideIEEEmargins \title{\LARGE \bf The review of Automated Machine learning } \author{He Xin$^{1}$ and Wang Zhichun$^{2}$ } \begin{document} \maketitle %\thispagestyle{empty} %\pagestyle{empty} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \begin{abstract} Test test testTest test testTest test testTest test test Test test testTest test testTest test testTest test test \end{abstract} \section{INTRODUCTION} As we all know(\cite{xie_genetic_2017}), deep learning, which has been used in a lot of research fields including image classification, image recognition, machine translation, has achieved remarkable achievements in those tasks. Take the image classification as an example, AlexNet () outperformed traditional computer vision methods on ImageNet (Russakovsky et al., 2015), which was in turn outperformed by VGG nets (Simonyan \& Zisserman, 2015), then ResNets (He et al., 2016) etc. \section{METHODS} As we all know(\cite{xie_genetic_2017}), deep learning, which has been used in a lot of research fields including image classification, image recognition, machine translation, has achieved remarkable achievements in those tasks. Take the image classification as an example, AlexNet () outperformed traditional computer vision methods on ImageNet (Russakovsky et al., 2015), which was in turn outperformed by VGG nets (Simonyan \& Zisserman, 2015), then ResNets (He et al., 2016) etc. \subsection{Bayesian Optimization} Test test testTest test testTest test testTest test test As we all know(\cite{xie_genetic_2017}), deep learning, which has been used in a lot of research fields including image classification, image recognition, machine translation, has achieved remarkable achievements in those tasks. Take the image classification as an example, AlexNet () outperformed traditional computer vision methods on ImageNet (Russakovsky et al., 2015), which was in turn outperformed by VGG nets (Simonyan \& Zisserman, 2015), then ResNets (He et al., 2016) etc. \subsection{Gradient-based} Test test testTest test testTest test testTest test test As we all know(\cite{xie_genetic_2017}), deep learning, which has been used in a lot of research fields including image classification, image recognition, machine translation, has achieved remarkable achievements in those tasks. Take the image classification as an example, AlexNet () outperformed traditional computer vision methods on ImageNet (Russakovsky et al., 2015), which was in turn outperformed by VGG nets (Simonyan \& Zisserman, 2015), then ResNets (He et al., 2016) etc. \subsection{Meta Learning} Test test testTest test testTest test testTest test test As we all know(\cite{xie_genetic_2017}), deep learning, which has been used in a lot of research fields including image classification, image recognition, machine translation, has achieved remarkable achievements in those tasks. Take the image classification as an example, AlexNet () outperformed traditional computer vision methods on ImageNet (Russakovsky et al., 2015), which was in turn outperformed by VGG nets (Simonyan \& Zisserman, 2015), then ResNets (He et al., 2016) etc. \subsection{Evolutionary Algorithm} Test test testTest test testTest test testTest test test As we all know(\cite{xie_genetic_2017}), deep learning, which has been used in a lot of research fields including image classification, image recognition, machine translation, has achieved remarkable achievements in those tasks. Take the image classification as an example, AlexNet () outperformed traditional computer vision methods on ImageNet (Russakovsky et al., 2015), which was in turn outperformed by VGG nets (Simonyan \& Zisserman, 2015), then ResNets (He et al., 2016) etc. \subsection{Reinforcement Learning} Test test testTest test testTest test testTest test test Test test testTest test testTest test testTest test test Test test testTest test testTest test testTest test test \section{Comparison and Analysis} Test test testTest test testTest test testTest test test Test test testTest test testTest test testTest test test Test test testTest test testTest test testTest test test \subsection{Units} Test test testTest test testTest test testTest test test Test test testTest test testTest test testTest test test Test test testTest test testTest test testTest test test \begin{itemize} \item Test test test \item Test test test \end{itemize} \section{CONCLUSIONS} Test test testTest test testTest test testTest test test Test test testTest test testTest test testTest test test Test test testTest test testTest test testTest test test \addtolength{\textheight}{-12cm} % This command serves to balance the column lengths % on the last page of the document manually. It shortens % the textheight of the last page by a suitable amount. % This command does not take effect until the next page % so it should come on the page before the last. Make % sure that you do not shorten the textheight too much. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \section*{APPENDIX} Test test Test test testTest test testTest test testTest test test Test test testTest test testTest test testTest test test \section*{ACKNOWLEDGMENT} Test test testTest test testTest test testTest test test Test test testTest test testTest test testTest test test Test test %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \nocite{*} \bibliographystyle{ieeetran} \bibliography{temp} \end{document} 2. temp.bib @article{xie_genetic_2017, title = {Genetic {CNN}}, url = {http://arxiv.org/abs/1703.01513}, abstract = {The deep Convolutional Neural Network (CNN) is the state-of-the-art solution for large-scale visual recognition. Following basic principles such as increasing the depth and constructing highway connections, researchers have manually designed a lot of fixed network structures and verified their effectiveness.}, language = {en}, urldate = {2018-10-22}, journal = {arXiv:1703.01513 [cs]}, author = {Xie, Lingxi and Yuille, Alan}, month = mar, year = {2017}, note = {arXiv: 1703.01513}, keywords = {Computer Science - Computer Vision and Pattern Recognition}, file = {Xie 和 Yuille - 2017 - Genetic CNN.pdf:E\:\\Zotero_storage\\storage\\A73TXSBC\\Xie 和 Yuille - 2017 - Genetic CNN.pdf:application/pdf} } 3. ieeeconf.cls 这个文件太大,建议去上面的链接中下载。 MARSGGBO原创 2018-10-23
参考:清华大学ISATAP隧道配置方法 简单介绍一下windows下的配置方法,其他系统的配置方法详见上面提供的个链接。 首先打卡管理员权限的命令行窗口,依次输入如下命令即可 netsh int ipv6 isatap set router isatap.tsinghua.edu.cn netsh int ipv6 isatap set state enable 我因为不在大陆,所以平时是可以正常访问谷歌的,但是一旦我连接这个ipv6之后,访问谷歌就会变得抽风,一下能上,一下又访问不了。所以如果不想连接ipv6的话,可以输入如下命令回归到正常: netsh int ipv6 isatap set state disable
1 TPU分类和收费标准 1.1 分类和计费说明 地区 抢占式TPU Cloud TPU 美国 $1.35/hour $4.5/hour 欧洲 $1.485/hour $4.95/hour 亚太区地区 $1.566/hour $5.22/hour 抢占式 TPU 是 Cloud TPU 在需要将资源分配给另一项任务时,可以随时终止(抢占)的 TPU。抢占式 TPU 的费用要比普通 TPU 低廉得多。 TPU 以 1 秒钟为增量单位进行计费。 为了连接到 TPU,我们必须配置一台虚拟机(单独结算)。要注意的是虚拟机和TPU是分别计费的。 也就是说仅在启动 TPU 之后,Cloud TPU 的计费才会开始;在停止或删除 TPU 之后,计费随即停止。运行 ctpu pause 或 gcloud compute tpus stop 即可停止 TPU。同样,只有在虚拟机激活之后,我们才会向您收取虚拟机费用。 如果虚拟机已停止,而 Cloud TPU 未停止,您需要继续为 Cloud TPU 付费。如果 Cloud TPU已停止或删除,而虚拟机未停止,则您需要继续为虚拟机付费。 1.2 实用查询链接 Compute Engine 价格表 Compute Engine 价格计算器 1.3 价格计算实例 以下示例解释了如何计算一项训练作业的总费用,该作业使用美国区域的 TPU 资源和 Compute Engine 实例。 一家机器学习研究机构通过创建 Compute Engine 实例预配了一台虚拟机,他们选择的是 n1-standard-2 机器类型。他们还创建了一项 TPU 资源,其 Compute Engine 实例和 TPU 资源的累计使用时间都是 10 小时。为了计算该训练作业的总费用,这家机器学习研究机构必须将以下几项相加在一起: 所有 Compute Engine 实例的总费用 所有 Cloud TPU 资源的总费用 资源 每小时每台机器的价格(美元 ) 机器数量 计费小时数 各资源总费用 训练作业总费用 Compute Engine n1-standard-2 实例 $0.095 1 10 $0.95 _ Cloud TPU 资源 $4.50 1 10 $45.00 _ $45.95 使用抢占式 TPU 的价格示例 在以下示例中,使用的资源和时长与上例相同,但这一次该研究机构决定使用抢占式 TPU 来节省成本。抢占式 TPU 的费用是每小时 $1.35,而非普通 TPU 的每小时 $4.50。 资源 每小时每台机器的价格(美元 ) 机器数量 计费小时数 各资源总费用 训练作业总费用 Compute Engine n1-standard-2 实例 $0.095 1 10 $0.95 - 抢占式 TPU $1.35 1 10 $13.50 - $14.45 2 使用步骤 2.1 创建GCP project 点击链接Google Cloud Platform之后会进入这样一个界面: 点击创建项目,输入项目名,等一会项目就会创建成功,有时可能需要刷新一下网页项目才会出现。 2.2 创建Cloud Storage bucket Cloud Storage 简单来说就是用来存储模型训练数据和训练结果的。官方的解释是它是适用于非结构化对象的一种功能强大且经济有效的存储解决方案,非常适合托管实时网页内容、存储用于分析的数据、归档和备份等各种服务。 注意:要想使用Cloud Storage,需要启用结算功能。 2.2.1 创建存储分区 存储分区用于保存您要在 Cloud Storage中存储的对象(任何类型的文件)。 首先在控制台左侧选择 【存储】(如下图示(中英文))就能进入Cloud Storage页面了, 之后点击 【创建存储分区】 输入storage名即可创建完成,注意名称需要是unique的,否则无法创建成功。 2.2.2 上传和共享对象 要开始使用您的存储分区,只需上传对象并开放其访问权限即可。 2.2.3 清理 在最后一步中,您将删除之前为本教程创建的存储分区和对象。 2.3 打开Cloud Shell,使用ctpu工具 Shell在控制台右上角,如下图示: 输入ctpu print-config可以查看配置信息。我的输入结果是这样的: ctpu configuration: name: hkbuautoml project: test01-219602 zone: us-central1-b If you would like to change the configuration for a single command invocation, please use the command line flags. 2.3.1 创建Computer Engine VM和TPU 命令为:ctpu up [optional: --name --zone] 注意: name只能用小写字母和数字组成,大写字母或者其他字符都会报错。 这里我创建了一个名为tputest的tpu。输入y确认创建。 上面的ctpu up命令主要做了如下几件事: 开启Computer Engine和Cloud TPU服务 创建预装有最新稳定版本TensorFlow的Computer Engine VM。其中默认的Zone是us-central1-b。 使用TensorFlow的相应版本创建Cloud TPU,并将Cloud TPU的名称作为环境变量( TPU _ NAME )传递给Computer Engine VM。 通过向Cloud TPU服务帐户授予特定的IAM角色(见下图),确保您的Cloud TPU可以从GCP项目中获得所需的资源。 执行其他的检查。 将您登录到新的Compute Engine VM。 2.3.2 检查是否登录成功 当成功登录VM后,我们可以看到shell prompt已经由username@project 转变成username@tpuname。 2.3.3 运行一个TensorFlow程序 创建代码文件pico cloud-tpu.py 示例代码如下 import os import tensorflow as tf from tensorflow.contrib import tpu from tensorflow.contrib.cluster_resolver import TPUClusterResolver def axy_computation(a, x, y): return a * x + y inputs = [ 3.0, tf.ones([3, 3], tf.float32), tf.ones([3, 3], tf.float32), ] tpu_computation = tpu.rewrite(axy_computation, inputs) tpu_grpc_url = TPUClusterResolver( tpu=[os.environ['TPU_NAME']]).get_master() with tf.Session(tpu_grpc_url) as sess: sess.run(tpu.initialize_system()) sess.run(tf.global_variables_initializer()) output = sess.run(tpu_computation) print(output) sess.run(tpu.shutdown_system()) print('Done!') 运行代码,结果如下: [array([[4., 4., 4.], [4., 4., 4.], [4., 4., 4.]], dtype=float32)] Done! 2.3.4 释放资源 代码跑完后切记要释放资源,否则系统会继续计费。释放资源方法如下: 1. 断开与Computer Engine VM的连接: (vm)$ exit 成功断开之后shell prompt会变成项目名而不是VM名。 2. 删除Computer Engine VM和Cloud TPU $ ctpu delete !!!特别注意:如果在创建VM的时候指定了name,name在删除的时候同业也要指定name。我在删除的时候没有加name,虽然命令行结果显示删除成功,但是后面我在控制台查看资源使用情况,发现VM实例依旧存在。所以最保险的办法是命令输完后,去控制台看看实例是否还存在。 3. 删除Storage 命令为:gsutil rm -r gs://Your-storage-name 更详细的资料可参考官方文档。 MARSGGBO原创 2018-10-16
原文链接:Meta Learning单排小教学 虽然Meta Learning现在已经非常火了,但是还有很多小伙伴对于Meta Learning不是特别理解。考虑到我的这个AI游乐场将充斥着Meta Learning的分析解读及各种原创思考,所以今天Flood就和大家做一个Meta Learning小教学,希望能够用最简短,最通俗的语言来让大家明白Meta Learning的概念,Meta Learning的几种研究方法,以及Meta Learning未来的发展,带大家上分!相信这个Meta Learning小教学是目前全网唯一的中文教程。 当然要看懂这个小教学,大家至少要对深度学习和增强学习有一定了解。下面我们就开始吧! 1.Meta Learning到底研究什么? 要回答这个问题,我们先来问另一个问题: Deep Learning是在研究什么? 简单一句话就是Deep Learning研究一个从x到y的映射mapping,只是这个映射函数f是用一个端到端的深度神经网络来表示: 如果是计算机视觉中的图像识别,那么x就是图片,y就是标签; 如果是自然语言处理中的文本翻译,那么x就是比如中文,y就是英文; 如果是深度增强学习中的玩Atari游戏,那么x就是屏幕画面,y就是输出的动作。 所以,我们可以看到,深度学习研究的就是通过深度神经网络来学习一个针对某一特定任务task的模型。通过大量的样本进行训练,训练完,这个模型就可以用在特定任务上。 说完Deep Learning研究什么,那Meta Learning呢? Meta Learning研究Task! Meta Learning的目的是希望学习很多很多的task,然后有了这些学习经验之后,在面对新的task的时候可以游刃有余,学的快又学的好! 为什么叫Meta呢?Deep Learning是在Task里面研究,现在Meta Learning是在Task外面,更高层级来研究。也就是在Meta Learning的问题上,Task是作为样本来输入的。这里我们将用Few-Shot Learning的问题加以解释。 但在此之前,我们要先从人类智能的角度,仿生学的角度说说Meta Learning的意义! 2. 为什么研究Meta Learning? Deep Learning现在的研究,训练一个模型,都是从0开始,也就是所谓的train from scratch。但是对比一下人,我们人是从0开始学习嘛?没有,人就算是刚出生的婴儿,都已经有一套本能的智能来引导他们去学习,而之后随着人不断长大,我们就慢慢的不再从0开始学习了,都是基于我们已有的知识学习,也就是所谓的先验知识Prior。比如我们没有玩过炉石传说这个游戏,但是我们通过我们的先验知识知道这是卡牌游戏,然后我们理解了游戏规则,我们也就可以上手来玩了。所以,我们人要玩一个新游戏是非常快的,而现在Deep Learning,Deep Reinforcement Learning在做什么?在让一个婴儿从0开始学习玩游戏。这不符合现实,而实际上我们也发现从0开始学习需要巨量的时间,虽然最后学会了甚至学的比人更好,但是这很不类人,很不智能。 因此,我们的研究需要转换一下思路了,我们为什么要从0开始学习呢?我们为什么不先学习一个Prior先验知识呢?如果我们构建的深度学习系统能够学到先验知识,并且能够利用这些知识,我们就可以在新的问题上学的更快更好!那么,这个就是Meta Learning要做的事情了,非常有意义,非常符合人工智能的发展路线。更进一步的,如果我们能够实现lifelong learning终身学习,那么我们的人工智能系统就可以越来越智能了。 有了这样的背景知识,我们就更好的理解Meta Learning要干什么了?我们不是要学一个具体的模型,我们要学的是一个先验知识。这里我特别喜欢用金庸武侠的武功修炼比喻,如果说Deep Learning是学习具体的外功的话,Meta Learning我们要学的是内功。 相信大家都看过倚天屠龙记,里面张无忌自从学习了九阳神功之后,学任何武功什么太极拳,乾坤大挪移都变得特别快。Meta Learning就是我们的九阳神功。 那么从AI的发展角度看,人工智能的发展肯定是一个由外向内的过程,简单的x到y的映射这种其实一点都不知道,想想人的情感,人的思维,一句话可以解毒出无数种可能,怎么可能是简单的x到y呢? 也因此,Meta Learning是AI研究的必然趋势。 3. Few-Shot Learning,一个引子 有了上面的知识基础,我们现在就要深入一点,具体的看一个典型的Meta learning问题,也就是Few-Shot Learning少样本学习! 这里我引用了Google Brain的Hugo Larochelle的一张ppt来加以说明。我们知道一般的图像识别比如imagenet,就是给你1000个类的训练集进行训练,每一个类有600张图片,然后训练好神经网络后,我们要用1000个类里面新的图片对这个神经网络进行测试,看能够识别多好。那么Few-Shot Learning的要求就是少样本,我们希望给你5个类的训练集进行训练,每一个类只有1张或者几张图片,总之非常少,然后训练好神经网络之后,我们要用着5个类里面的新的图片进行测试,看识别效果。 那么我们直接看这个问题,肯定觉得不可能啊,只用几张图片怎么训练?不得分分钟过拟合吗?没关系,我们有Meta Learning的设定,这训练样本和测试样本都只是一个task,我们可以用很多别的task进行训练,学习一个Prior,然后我们希望在新的task上能够学出效果来。 所以就如上图所示,在Meta Learning上,我们不再直接叫train和test了,而是叫Meta-train和Meta-test。在上图中,每一行都是一个task,包含了task的train set和test set,图中展示就是所谓的5way 1shot 设定,也就是一个task包含5个类,每一个类一个训练样本,然后给你2个测试样本测试。我们可以把每一个task当做一个meta learning的训练样本。我们要通过多种task的训练,从而在Meta-test的时候也就是在新的task上取得好效果。 相信通过上面的解释,大家对于Meta Learning这个问题设定是理解的了。下面我们就来看看怎么来学这些task。 4.Meta Learning的三种解决办法 我们还是根据Few-Shot Learning的问题设定来说明怎么学。 我们看到,现在输入到神经网络的数据变了,实际上就是多了一个D_train 训练集,也就是这个task拥有的东西。这个训练集比如上面说的5way 1shot,也就是5张图片和5个对应的标签。我们希望我们的神经网络能够仅根据这几张图片得到一个针对这几张图片的神经网络,从而能够对新的样本进行识别。所以,这里就产生一个问题: 怎么使用D_train来影响我们的神经网络呢? 1)HyperNetwork 生成参数 HyperNetwork是一个蛮有名的网络,简单说就是用一个网络来生成另外一个网络的参数。那么我们这里非常直接,我们的设想就是希望用一个hypernetwork输入训练集数据,然后给我输出我的对应模型也就是上图f的参数,我们希望输出的这个参数能够使得在测试图片上取得好的识别效果。那么,有了这样设计,这个hypernetwork其实就是一个meta network。大家可以看到,本来基本的做法是用训练集直接训练这个模型f,但是现在我们用这个hypernetwork不训练了,直接给你输出参数,这等价于hypernetwork学会了如何学习图像识别,这也是为什么meta learning也同时叫做learning to learn的原因。我们通过hypernetwork学会学习。训练好了这个模型,连反向传播梯度下降都不要了,直接给你参数,是不是很酷? 那怎么训练这个神经网络呢? 这里有个所谓的episodic training!一个episode就是包含了一个task,有训练集有测试集。我们使用训练集输入到hypernetwork,得到f的参数,然后使用测试集输入到f 得到预测的标签,最后用测试集的样本标签得到模型的loss,之后就用梯度下降进行训练。所以我们可以看到,整个模型是端到端的。通过大量的episodic training,也就是大量的task进行训练,我们就可以训练出一个模型出来。 需要稍微说明一下这个task哪里来呢?我们采样出来的。比如我们给你100个类的训练样本,每一个类可能有600个图片,也就是imagenet的规模。但是我们这里不是直接batch这些样本进行训练,而是随机采样出一个task,比如选5个类,每一个类1个图片,然后再每一个类选5个图片作为task的测试集。这样采样是为了模拟meta-test阶段的task构造。 那么因为meta-train和train和乱,常常会混掉,所以大家约定俗成的把task里面的训练集称为support set,把task里面的测试集称为query set。而Meta training set和Meta test set分别直接称为training set和test set。 使用Hypernetwork在我看来是最直接表现meta learning的一种做法。但是现在实际上我们好像没有看到直接用hypernetwork做few shot learning的paper,而都是间接的使用,或者简单使用一下。具体的优缺点我们还是等下再总结。先说第二个方法。 2)Conditional Neural Network 条件神经网络 这个做法其实就是上图啦,我们直接把D_train当做条件输入到f中,那么这个f本身就变成一个meta network了。也就是条件神经网络实际上能够得到和上面的hypernetwork一样的意义。因为我们可以想,只要条件D_train变了,那么y_test肯定也就变了。所以这里就非常非常直接了。把数据全部输入进去,让神经网络自己学就行了,不外乎就是去设计一个合适的网络结构而已。那么,这里最最简单粗暴的网络结构就是SNAIL算法使用temporal convolutional network,也就是wavenet的架构: 当然,我们也可以巧妙一点的设计,比如我的relation net,不把标签y作为输入: 事实上基于条件神经网络的做法,效果是最好的。 3)MAML 基于梯度的做法 现在我们来说MAML,这个可以单独算一个不一样的做法。 这个思路只能说也太简单了,就是为什么不用D_train先用一般的监督学习方式更新一下网络,然后再使用更新参数后的f来使用D_test进行训练呢?这样的话,我们也通过D_train更新了网络了,只是我们希望我们只需用极少的step训练D_train就能使D_test取得好的效果。 所以,MAML的核心步骤就是 (1)采集Task,得到D_train和D_test (2)使用D_train对神经网络f训练少数几步,得到新的参数 (3)利用新的参数训练D_test,然后使得梯度下降更新一开始的参数。 这里最不好理解的可能就是二次梯度了,也就是我们的目的还是要训练一开始的参数,只是中间我们要先计算梯度得到新的参数,然后用新的参数再算一次梯度,这样对于一开始的原始参数就要算两次梯度。这是MAML比较麻烦的地方,也可以认为是一种缺点。当然,现在使用Pytorch可以分分钟实现这种二次梯度的操作。 可以说MAML还是非常巧妙的,一种不一样的方法,当然了已经被Chelsea Finn发扬光大了。 5. 三种解决办法有什么优缺点呢? 下面我们来谈谈三种办法的优缺点。先说HyperNetwork生成参数的做法。这种做法最大的问题就在于参数空间是很大的,所以要生成合适的参数特别是巨量的参数其实是比较困难的,所以目前绝大多数生成参数的做法都是只生成少量参数,比如一层的MLP,或者对于参数的空间进行一定的限制,比如就在[-1,1]之间,否则空间太多,有无数种选择输出一样的结果,就很难训了。但是采样HyperNetwork又有其灵活性,意味着我们可以只更新少部分参数,而不用全部。 接下来就是条件神经网络了。这又有什么问题呢?我觉得在性能上绝对会是最好的,很直接,但是不好看,一直要拖着一个条件,网络很大。不管是生成参数还是MAML,他们的模型网络就是独立的,之后只要输入x就行了,而条件神经网络每次都要输入条件,很烦啊。 那么MAML呢?可能最烦人的就是二次梯度了,这意味着MAML的训练会很慢,那么就很难hold住大网络了。实际上MAML目前对于大的网络结构比如Resnet效果并不好。然后MAML是使用D_train的Loss来更新整个网络,对比HyperNetwork缺少灵活性。这个Loss就是最好的吗?不见得。如果D_train是无监督数据,那怎么办?所以MAML是有局限性的。 目前各种各样的Meta Learning研究,在方法论上都逃不出这三种方法。要么改改网络结构,要么结合一下上面的方法,比如先MAML再生成参数,或者hypernetwork和conditional neural network混着用等等。那么什么才是终极必杀呢?可能还是要具体问题具体看吧,对于不同的问题采用不同办法效果会不一样。这些都值得我们去探索。 6. Meta Learning的潜在威力! 讲完了前面的方法论,相信大家对于Meta Learning已经有所了解了。但是大家肯定会疑问,难道Meta Learning只是在做Few-Shot Learning这种有点boring的任务吗? 当然不是。 首先我们可以改成增强学习的task,对于增强学习的task,这里的D_train就是历史信息了: 所谓的历史信息也就是之前的transitions(state,action,reward,next_state)。把历史信息输入进去,原来的reinforcement learning就分分钟变成meta reinforcement learning啦。 那么,Meta Learning也仅仅止步于此吗? 也当然不是! 扩宽思维极限的时候到了! 谁说D_train和D_test要一样呢!!! 这才是Meta Learning最最重要的思想!一个task里面的训练集和测试集可以完全不一样。只要通过D_test的loss signal能够传递到D_train中,整个网络就可以端到端的训练。 比如D_train可以是完全的无监督数据(没有标签),那么我们同样可以构造一个hypernetwork来更新我们的模型,然后用D_test有标签的数据来训练。这样训练完之后,我们就得到了一个能够无监督学习的hypernetwork了。 实际上D_train和D_test的差异性可以进一步变大,训练可以是增强学习,测试可以是模仿学习,训练可以是语音,测试可以是图像,等等,会不会很有趣呢? 最后,我们可以举一个更形象一点的idea:比如有一个虚拟世界,里面可以生成虚拟人在里面生活,我们的train过程就是让这个虚拟人在里面爱干啥干啥,可以玩比如1年的游戏时间,然后我们的test过程要求这个虚拟人通过200的智商测试。那么通过巨量的虚拟meta training之后,我们就能得到一个这样的人工智能系统,这个虚拟人懂得自己去学习了,而我们还完全不知道他怎么学的。 So, this is Meta Learning! The Future of AGI!
网上讲高斯过程回归的文章很少,且往往从高斯过程讲起,我比较不以为然:高斯过程回归(GPR), 终究是个离散的事情,用连续的高斯过程( GP) 来阐述,简直是杀鸡用牛刀。所以我们这次直接从离散的问题搞起,然后把高斯过程逆推出来。 这篇博客有两个彩蛋,一个是揭示了高斯过程回归和Ridge回归的联系,另一个是介绍了贝叶斯优化具体是怎么搞的。后者其实值得单独写一篇博客,我在这里就是做一个简单介绍好了,但没准就不写了,想想就累。 高斯过程回归 的 Intuition 假设有一个未知的函数f : R–> R , 在训练集中,我们有3个点 x_1, x_2, x_3, 以及这3个点对应的结果,f1,f2,f3. (如图) 这三个返回值可以有噪声,也可以没有。我们先假设没有。 so far so good. 没什么惊讶的事情。 高斯过程回归的关键假设是: 给定 一些 X 的值,我们对 Y 建模,并假设 对应的这些 Y 值 服从 联合正态分布! (更正式的定义后面会说到) 换言之,对于上面的例子,我们的假设是: 一般来说,这个联合正态分布的均值向量不用操心,假设成0 就蛮好。(讲到后面你就知道为什么了) 所以关键是,这个模型的 协方差矩阵K 从哪儿来。 为了解答这个问题,我们进行了另一个重要假设: 如果两个x 比较相似(eg, 离得比较近),那么对应的y值的相关性也就较高。换言之,协方差矩阵是 X 的函数。(而不是y的函数) 具体而言,对于上面的例子,由于x3和x2离得比较近,所以我们假设 f3和f2 的correlation 要比 f3和f1的correlation 高。 话句话说,我们可以假设协方差矩阵的每个元素为对应的两个x值的一个相似性度量: 那么问题来了,这个相似性怎么算?如何保证这个相似性度量所产生的矩阵是一个合法的协方差矩阵? 好,现在不要往下看了,你自己想3分钟。你也能想出来的。 提示:合法的协方差矩阵就是 (symmetric) Positive Semi-definite Matrix (。。。。。。。。。。。。思考中) 好了时间到。 答案: Kernel functions ! 矩阵A正定是指,对任意的X≠0恒有X^TAX>0。 矩阵A半正定是指,对任意的X≠0恒有X^TAX≥0。 判定A是半正定矩阵的充要条件是:A的所有顺序主子式大于或等于零。 如果你了解SVM的话,就会接触过一个著名的Mercer Theorem,(当然如果你了解泛函分析的话也会接触过 ),这个M定理是在说:一个矩阵是Positive Semi-definite Matrix当且仅当该矩阵是一个Mercer Kernel . 所以我们在svm里用过的任何Kernel都能拿过来用! 举个栗子,在高斯过程回归里,一种非常常见的Kernel就是SVM里面著名的高斯核(但是为了让命名不是那么混淆,文献中一般把这个Kernel称作 squared exponential kernel. 具体而言就是 回归分析 好了,现在可以做回归分析了: 如果我们现在又有了一个新的点 x* 这个新的点对应的f* 怎么求?(如下图) 根据假设,我们假设 f* 和 训练集里的 f1, f2, f3 同属于一个 (4维的)联合正态分布! 也就是说,不仅 f1,f2,f3属于 一个3 维的联合正态分布(参数可以算出来),而且 f* 和 f1,f2,f3属于(另一个)4维的联合正态分布,用数学的语言来表达就是: 首先我们来看一看,这个4 x 4 的 矩阵能不能算出来: 黄色的大K,是依据训练集的3维联合分布算出来的,绿色的K*, 是测试点x* 分别和每一个训练集的x 求出来的。所以整个联合分布我们都知道了。 接下来的事情就好办了,我们既然已经知道(f,f*)的联合分布P(f, f*)的所有参数, 如何求p(f*) ?好消息是这个联合分布是正态的,我们直接用公式就能搞出来下面的结果(using the marginalization property): 不难求出f* 隶属于一个1维的正态分布, 参数是: 所以这是一种贝叶斯方法,和OLS回归不同,这个方法给出了预测值所隶属的整个(后验)概率分布的。再强调一下,我们得到的是f* 的整个分布!不是一个点估计,而是整个分布啊同志们。 In addition, 不仅可以得到 f*这一个点的分布,我们对这个未知的 函数 也可以进行推断!换言之,如果把一个函数想成一个变量,那么高斯过程回归可以求出这个函数的分布来。(distribution over functions)不幸的是,我们的计算机只能存储离散的数据,怎么表示一个连续的函数呢? 好办,我们对一个区间里面均匀地硬造出来1万个测试点x*, 然后求出这些测试点和训练集所对应的y(一个巨高维的向量)的联合分布,然后在这个巨高维的联合分布里采样一次,就得到了函数的(近似的)一个样本。 比如训练集就三个点,测试集1万个x,图中的每一个红点就分别是这些点f* 的均值,(当点很多的时候,就可以假设是一个“连续”的函数了)而蓝色的线代表一个或两个标准差的bound. 我们如果从这个分布中采样10次,就可以得到10个巨高维的向量,也就是从这个后验概率中sample出来的10个函数的sample. plot出来长这个样子: 含有已知数据(训练集)的地方,这些函数都离的很近(variance很低),没有数据的时候,这个spread就比较大。 也许你会问:我为毛要搞出来函数的分布?我为毛要关心这个variance. 在很多问题中,我们不仅仅需要知道预测值的点估计,而且要知道这个估计有多少信心在里面(这也是贝叶斯方法的好处之一) 举个例子:Multiple Bandit Problem假设 我们已经有了几个油井,每个油井的价值不一样,我们在这个二维平面上,利用高斯过程回归,对每一个地理位置估计一个该位置对应的出油量。而开发每一口井是有成本的,在预算有限的情况下,如果想尽可能少地花钱,我们就需要定义一个效益函数,同高斯过程回归的预测结果相结合,来指导我们下一次在哪儿打井。这个效益函数往往是 预测值 和 方差 的一个函数。以上这个例子,就是高斯过程回归在贝叶斯优化中的一个典型应用。有时间专门写一篇。 高斯过程 好了,现在终于可以讲一讲高斯过程了。 高斯过程是在函数上的正态分布。(Gaussian distribution over functions)具体而言就是 我们具体用的时候,模型假设是酱紫的: 我们观察到一个训练集 D 给定一个测试集 X* ( X* 是一个 N* x D 的矩阵, D是每一个点的维度)我们希望得到 一个 N* 维的预测向量 f*. 高斯过程回归的模型假设是 然后根据贝叶斯回归的方法,我们可以求出来 f*的后验概率: This is it. 要啥有啥了。 有噪声的高斯过程 下面着重说一下有噪声情况下的结果,以及此情况下和Ridge Regression的神秘联系。 当观测点有噪声时候,即, y = f(x) + noise, where noise ~N(0, sigma^2) 我们有 发现没,唯一区别就是 K 变成 了 Ky,也就是多加了一个sigma。 这个很像是一种regularization. 确实如此。 好了,下面就说说这个 GPR的 insight,这个模型到底想干什么 如果只有一个测试点,那么输出的f* 就是隶属于一个1维的正态分布了,具体而言: 再看,我们回想一下Ridge Regression (小编注:下图argmax应为argmin) 我们仔细观察一下上面那个蓝色的框框 所以说,ridge回归是一种最最最最简单的高斯过程回归,核函数就是简单的点积! 而高斯过程的核函数可以有很多,除了上面提到的squared exponential, 有整整一本书都在讲各种kernel和对应的随机过程 所以高斯过程是一个非常包罗万象的根基,类似于小无相功。 高斯过程回归(GPR)和贝叶斯线性回归类似,区别在于高斯过程回归中用核函数代替了贝叶斯线性回归中的基函数(其实也是核函数,线性核)。 来看看多维高斯分布的一些重要性质,第一个性质为两个相互独立的多维高斯分布A和B的和也是一个多维高斯分布C,且C的均值和方差都为A和B均值方差的和。第二个性质为:两个多维高斯分布之和构成的分布C而言,在已知一部分观察值C1的条件下,另一部分观察值C2的概率分布是一个多维高斯分布,且可以用A和B中对应的信息来表示。 如下: 由贝叶斯线性回归和高斯过程回归的对比可知,贝叶斯线性回归是高斯过程回归中的一个子集,只是它用的是线性核而已,通过两者的公式就可以看出它们之间的关系: 上面是贝叶斯线性回归,下面是高斯过程回归。 简单例子: 假设现在已经观察到了6个样本点,x为样本点特征(一维的),y为样本输出值。现在新来了一个样本点,要求是用高斯回归过程来预测新来样本点的输出值。这些样本点显示如下; 其中前面6个点是已知输出值的训练样本,其值为: 第7个点是需要预测的样本,红色的垂直条形线表示观察输出值的误差,绿色的垂直条形线为用高斯过程回归的误差。 用GPR解该问题的流程大概如下(对应前面讲的一些基础知识): 1. 选择适当的u(均值函数)和k(核函数),以及噪声变量σ,其中核函数的选择尤其重要,因为它体现了需处理问题的先验知识,应根据不同的应用而选择不同的核。 2. 计算出训练样本的核矩阵(6*6),如下: 3. 计算需预测的点 与训练样本6个点的核值向量,如下: 4. 自己和自己的核值为 且此时整个样本的多维高斯分布表达式为: 5. 通过前面m和D的公式,求得m=0.95,D=0.21. 6. 画出最终结果如下: 这个例子来源于论文Gaussian Processes for Regression A Quick Introduction中,它的核函数等参数选择和基础知识部分的不同,但这里主要是对GPR的应用有个简单的宏观上的理解,让大脑对GPR应用有个初步的印象,否则有了那么多的公式推导但不会应用又有什么用呢? http://www.cnblogs.com/tornadomeet/archive/2013/06/14/3135380.html http://dataunion.org/17089.html http://www.cnblogs.com/tornadomeet/archive/2013/06/15/3137239.html
1. 深度可分离卷积(depthwise separable convolution) 在可分离卷积(separable convolution)中,通常将卷积操作拆分成多个步骤。而在神经网络中通常使用的就是深度可分离卷积(depthwise separable convolution)。 举个例子,假设有一个3×3大小的卷积层,其输入通道为16、输出通道为32。 那么一般的操作就是用32个3×3的卷积核来分别同输入数据卷积,这样每个卷积核需要3×3×16个参数,得到的输出是只有一个通道的数据。之所以会得到一通道的数据,是因为刚开始3×3×16的卷积核的每个通道会在输入数据的每个对应通道上做卷积,然后叠加每一个通道对应位置的值,使之变成了单通道,那么32个卷积核一共需要(3×3×16)×32 =4068个参数。 1.1 标准卷积与深度可分离卷积的不同 用一张来解释深度可分离卷积,如下: 可以看到每一个通道用一个filter卷积之后得到对应一个通道的输出,然后再进行信息的融合。而以往标准的卷积过程可以用下面的图来表示: 1.2 深度可分离卷积的过程 而应用深度可分离卷积的过程是①用16个3×3大小的卷积核(1通道)分别与输入的16通道的数据做卷积(这里使用了16个1通道的卷积核,输入数据的每个通道用1个3×3的卷积核卷积),得到了16个通道的特征图,我们说该步操作是depthwise(逐层)的,在叠加16个特征图之前,②接着用32个1×1大小的卷积核(16通道)在这16个特征图进行卷积运算,将16个通道的信息进行融合(用1×1的卷积进行不同通道间的信息融合),我们说该步操作是pointwise(逐像素)的。这样我们可以算出整个过程使用了3×3×16+(1×1×16)×32 =656个参数。 1.3 深度可分离卷积的优点 可以看出运用深度可分离卷积比普通卷积减少了所需要的参数。重要的是深度可分离卷积将以往普通卷积操作同时考虑通道和区域改变成,卷积先只考虑区域,然后再考虑通道。实现了通道和区域的分离。 2 分组卷积(Group convolution) Group convolution 分组卷积,最早在AlexNet中出现,由于当时的硬件资源有限,训练AlexNet时卷积操作不能全部放在同一个GPU处理,因此作者把feature maps分给多个GPU分别进行处理,最后把多个GPU的结果进行融合。 2.1 什么是分组卷积 在说明分组卷积之前我们用一张图来体会一下一般的卷积操作。 从上图可以看出,一般的卷积会对输入数据的整体一起做卷积操作,即输入数据:H1×W1×C1;而卷积核大小为h1×w1,一共有C2个,然后卷积得到的输出数据就是H2×W2×C2。这里我们假设输出和输出的分辨率是不变的。主要看这个过程是一气呵成的,这对于存储器的容量提出了更高的要求。 但是分组卷积明显就没有那么多的参数。先用图片直观地感受一下分组卷积的过程。对于上面所说的同样的一个问题,分组卷积就如下图所示。 可以看到,图中将输入数据分成了2组(组数为g),需要注意的是,这种分组只是在深度上进行划分,即某几个通道编为一组,这个具体的数量由(C1/g)决定。因为输出数据的改变,相应的,卷积核也需要做出同样的改变。即每组中卷积核的深度也就变成了(C1/g),而卷积核的大小是不需要改变的,此时每组的卷积核的个数就变成了(C2/g)个,而不是原来的C2了。然后用每组的卷积核同它们对应组内的输入数据卷积,得到了输出数据以后,再用concatenate的方式组合起来,最终的输出数据的通道仍旧是C2。也就是说,分组数g决定以后,那么我们将并行的运算g个相同的卷积过程,每个过程里(每组),输入数据为H1×W1×C1/g,卷积核大小为h1×w1×C1/g,一共有C2/g个,输出数据为H2×W2×C2/g。 2.2 分组卷积具体的例子 从一个具体的例子来看,Group conv本身就极大地减少了参数。比如当输入通道为256,输出通道也为256,kernel size为3×3,不做Group conv参数为256×3×3×256。实施分组卷积时,若group为8,每个group的input channel和output channel均为32,参数为8×32×3×3×32,是原来的八分之一。而Group conv最后每一组输出的feature maps应该是以concatenate的方式组合。 Alex认为group conv的方式能够增加 filter之间的对角相关性,而且能够减少训练参数,不容易过拟合,这类似于正则的效果。 3 空洞(扩张)卷积(Dilated/Atrous Convolution) 空洞卷积(dilated convolution)是针对图像语义分割问题中下采样会降低图像分辨率、丢失信息而提出的一种卷积思路。利用添加空洞扩大感受野,让原本3 x3的卷积核,在相同参数量和计算量下拥有5x5(dilated rate =2)或者更大的感受野,从而无需下采样。扩张卷积(dilated convolutions)又名空洞卷积(atrous convolutions),向卷积层引入了一个称为 “扩张率(dilation rate)”的新参数,该参数定义了卷积核处理数据时各值的间距。换句话说,相比原来的标准卷积,扩张卷积(dilated convolution) 多了一个hyper-parameter(超参数)称之为dilation rate(扩张率),指的是kernel各点之前的间隔数量,【正常的convolution 的 dilatation rate为 1】。 图说空洞卷积的概念 (a)图对应3x3的1-dilated conv,和普通的卷积操作一样。(b)图对应3x3的2-dilated conv,实际的卷积kernel size还是3x3,但是空洞为1,需要注意的是空洞的位置全填进去0,填入0之后再卷积即可。【此变化见下图】(c)图是4-dilated conv操作。 在上图中扩张卷积的感受野可以由以下公式计算得到;其中i+1表示dilated rate。 比如上图中(a),dilated=1,F(dilated) = 3×3;图(b)中,dilated=2,F(dilated)=7×7;图(c)中,dilated=4, F(dilated)=15×15。 dilated=2时具体的操作,即按照下图在空洞位置填入0之后,然后直接卷积就可以了。 空洞卷积的动态过程 在二维图像上直观地感受一下扩张卷积的过程: 上图是一个扩张率为2的3×3卷积核,感受野与5×5的卷积核相同,而且仅需要9个参数。你可以把它想象成一个5×5的卷积核,每隔一行或一列删除一行或一列。 在相同的计算条件下,空洞卷积提供了更大的感受野。空洞卷积经常用在实时图像分割中。当网络层需要较大的感受野,但计算资源有限而无法提高卷积核数量或大小时,可以考虑空洞卷积。 Dilated Convolution感受野指数级增长 对于标准卷积核情况,比如用3×3卷积核连续卷积2次,在第3层中得到1个Feature点,那么第3层这个Feature点换算回第1层覆盖了多少个Feature点呢? 第3层: 第2层: 第1层: 第一层的一个5×5大小的区域经过2次3×3的标准卷积之后,变成了一个点。也就是说从size上来讲,2层3*3卷积转换相当于1层5*5卷积。题外话,从以上图的演化也可以看出,一个5×5的卷积核是可以由2次连续的3×3的卷积代替。 但对于dilated=2,3*3的扩张卷积核呢? 第3层的一个点: 第2层: 可以看到第一层13×13的区域,经过2次3×3的扩张卷积之后,变成了一个点。即从size上来讲,连续2层的3×3空洞卷积转换相当于1层13×13卷积。 转置卷积 转置卷积(transposed Convolutions)又名反卷积(deconvolution)或是分数步长卷积(fractially straced convolutions)。反卷积(Transposed Convolution, Fractionally Strided Convolution or Deconvolution)的概念第一次出现是Zeiler在2010年发表的论文Deconvolutional networks中。 转置卷积和反卷积的区别 那什么是反卷积?从字面上理解就是卷积的逆过程。值得注意的反卷积虽然存在,但是在深度学习中并不常用。而转置卷积虽然又名反卷积,却不是真正意义上的反卷积。因为根据反卷积的数学含义,通过反卷积可以将通过卷积的输出信号,完全还原输入信号。而事实是,转置卷积只能还原shape大小,而不能还原value。你可以理解成,至少在数值方面上,转置卷积不能实现卷积操作的逆过程。所以说转置卷积与真正的反卷积有点相似,因为两者产生了相同的空间分辨率。但是又名反卷积(deconvolutions)的这种叫法是不合适的,因为它不符合反卷积的概念。 转置卷积的动态图 △卷积核为3×3、步幅为2和无边界扩充的二维转置卷积 需要注意的是,转置前后padding,stride仍然是卷积过程指定的数值,不会改变。 例子 由于上面只是理论的说明了转置卷积的目的,而并没有说明如何由卷积之后的输出重建输入。下面我们通过一个例子来说明感受下。 比如有输入数据:3×3,Reshape之后,为A :1×9,B(可以理解为滤波器):9×4(Toeplitz matrix) 那么A*B=C:1×4;Reshape C=2×2。所以,通过B 卷积,我们从输入数据由shape=3×3变成了shape=2×2。反过来。当我们把卷积的结果拿来做输入,此时A:2×2,reshape之后为1×4,B的转置为4×9,那么A*B=C=1×9,注意此时求得的C,我们就认为它是卷积之前的输入了,虽然存在偏差。然后reshape为3×3。所以,通过B的转置 - “反卷积”,我们从卷积结果shape=2×2得到了shape=3×3,重建了分辨率。 也就是输入feature map A=[3,3]经过了卷积B=[2,2] 输出为 [2,2] ,其中padding=0,stride=1,反卷积(转置卷积)则是输入feature map A=[2,2],经过了反卷积滤波B=[2,2].输出为[3,3]。其中padding=0,stride=1不变。那么[2,2]的卷积核(滤波器)是怎么转化为[4,9]或者[9,4]的呢?通过Toeplitz matrix。 至于这其中Toeplitz matrix是个什么东西,此处限于篇幅就不再介绍了。但即使不知道这个矩阵,转置卷积的具体工作也应该能够明白的。 --------------------- 本文来自 chaolei_9527 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/chaolei3/article/details/79374563?utm_source=copy
使用numpy解决图像维度变换问题 numpy python numpy函数介绍 1. np.transpose(input, axes=None) 在机器学习中经常会碰到各种图像数据集,有的是按照num*height*width*channel来存储的,而有的则是num*channel*height*width。然后每次碰到这种问题都会想半天该怎么相互变换。 也想过自己手敲代码实现,但是一方面速度肯定没别人的方法好,另一方面还不一定是对的233。其实numpy已经帮我们都弄好了,我们只要使用np.transpose方法即可。 例如输入数据是a.shape = 1000*32*32*3(num*height*width*channel) 我们只需要使用如下代码即可达到要求。 a = np.transpose(a, [0,3,1,2]) 2. np.concatenate((a1, a2, …), axis=0) 参数1:表示需要叠加的一组矩阵,可以是元组形式 (a1, a2, …),也可以是列表形式[a1,a2,…]。另外,各个矩阵的维度必须保持一致! 参数2:axis默认为0,当然也可以设置为其他的值。 3. np.newaxis 这个可以用于扩展一个新的维度,例如假设我们的标签y.shape=(10,),我们想把它变成 (10,1) 该怎么做?很简单: y = y[:, np.newaxis] # 其实也可以这样 y = np.reshape(y, [len(y),1]) 效果图如下: 4. np.mean & np.var np.mean(a, axis=None, dtype=None, out=None, keepdims=<no value>) np.var(a, axis=None, dtype=None, out=None, ddof=0, keepdims=<no value>) 很简单,这两个方法分别用来计算均值和方差,在图像数据预处理的时候很有帮助。可以在应用场景二中来看看具体的特殊用法。 实践出真知之应用场景一 现假设我们有一组二维图像数据集,其大小为3*2*2 (num*height*width)。我们现在想将这个二维数据集转化成3维的,而且最后维度顺序要是num*height*width*channel,我们应该咋办呢? 1. 扩展出一个新的维度用来表示channel 新增加一个维度,建议先将channel增加在第二个维度,这样更好理解,而且数据不会被打乱。 2.叠加channel 3.维度转置 4.验证是否正确 最后来验证一下这一波操作是否正确: 可以看到是正确的!!! 5.整体演示代码 实践出真知之应用场景二 假设我们现有三维数据集,a.shape=(4,2,2,3) (num, height, width, channel)。而在数据预处理之前呢,我们一般都需要将图像数据每个像素点的值除以255,之后再减去每个维度的均值,再除以方差。 但是怎么得到每个维度的均值和方差呢? 因为channel在最后一维,所以我们需要指定需要计算均值和方差的维度,即 0,1,2。另外还有一个很重要的就是我们需要讲keepdims这个参数设置为True,它的作用是让输出的结果保持相同的维度,比如说数据集是4维的,那么输出的均值也会是4维。由下图可以看到,如果keepdims=False,那么均值和方差则会变成1维列表 MARSGGBO原创 2018-8-5
TensorFlow tf.app&tf.app.flags用法介绍 TensorFlow tf.app argparse tf.app.flags 下面介绍 tf.app.flags.FLAGS的使用,主要是在用命令行执行程序时,需要传些参数,其实也就可以理解成对argparse库进行的封装,示例代码如下 #coding:utf-8 # 学习使用 tf.app.flags 使用,全局变量 # 可以再命令行中运行也是比较方便,如果只写 python app_flags.py 则代码运行时默认程序里面设置的默认设置 # 若 python app_flags.py --train_data_path <绝对路径 train.txt> --max_sentence_len 100 # --embedding_size 100 --learning_rate 0.05 代码再执行的时候将会按照上面的参数来运行程序 import tensorflow as tf FLAGS = tf.app.flags.FLAGS # tf.app.flags.DEFINE_string("param_name", "default_val", "description") tf.app.flags.DEFINE_string("train_data_path", "/desktop/train.txt", "training data dir") tf.app.flags.DEFINE_string("log_dir", "./logs", " the log dir") tf.app.flags.DEFINE_integer("max_sentence_len", 80, "max num of tokens per query") tf.app.flags.DEFINE_integer("embedding_size", 50, "embedding size") tf.app.flags.DEFINE_float("learning_rate", 0.001, "learning rate") def main(unused_argv): train_data_path = FLAGS.train_data_path print("train_data_path", train_data_path) print("*" * 30) max_sentence_len = FLAGS.max_sentence_len print("max_sentence_len", max_sentence_len) print("*" * 30) embdeeing_size = FLAGS.embedding_size print("embedding_size", embdeeing_size) print("*" * 30) abc = tf.add(max_sentence_len, embdeeing_size) init = tf.global_variables_initializer() with tf.Session() as sess: sess.run(init) print("abc", sess.run(abc)) # 使用这种方式保证了,如果此文件被其他文件 import的时候,不会执行main 函数 if __name__ == '__main__': tf.app.run() # 解析命令行参数,调用main 函数 main(sys.argv) 两种调用方式: 方式一: python tf_app_flag.py 结果如下: 方式二: python app_flags.py --train_data_path ./test.py --max_sentence_len 100 --embedding_size 100 --learning_rate 0.05 tf.app.run() 该函数一般都是出现在这种代码中: if __name__ == '__main__': tf.app.run() 上述第一行代码表示如果当前是从其它模块调用的该模块程序,则不会运行main函数!而如果就是直接运行的该模块程序,则会运行main函数。 具体第二行的功能从源码开始分析,源码如下: flags_passthrough=f._parse_flags(args=args)这里的parse_flags就是我们tf.app.flags源码中用来解析命令行参数的函数。所以这一行就是解析参数的功能; 下面两行代码也就是tf.app.run的核心意思:执行程序中main函数,并解析命令行参数! 参考: TensorFlow 中 tf.app.flags.FLAGS 的用法介绍 Deep Learning学习笔记(六)详解tf.app.flags()和tf.app.run()的源码 MARSGGBO原创 2018-8-5
向量和矩阵的各种范数比较(1范数、2范数、无穷范数等等 范数 norm 矩阵 向量 一、向量的范数 首先定义一个向量为:a=[-5,6,8, -10] 1.1 向量的1范数 向量的1范数即:向量的各个元素的绝对值之和,上述向量a的1范数结果就是:29,MATLAB代码实现为:norm(a,1); 1.2 向量的2范数 向量的2范数即:向量的每个元素的平方和再开平方根,上述a的2范数结果就是:15,MATLAB代码实现为:norm(a,2); 1.3 向量的无穷范数 1.向量的负无穷范数即:向量的所有元素的绝对值中最小的:上述向量a的负无穷范数结果就是:5,MATLAB代码实现为:norm(a,-inf); 2…向量的正无穷范数即:向量的所有元素的绝对值中最大的:上述向量a的负无穷范数结果就是:10,MATLAB代码实现为:norm(a,inf); 二、矩阵的范数 首先我们将介绍数学中矩阵的范数的情况,也就是无论哪个学科都统一的一种规定。。。 例如矩阵A = [ -1 2 -3; 4 -6 6] 2.1 矩阵的1范数 矩阵的1范数即:矩阵的每一列上的元素绝对值先求和,再从中取个最大的,(列和最大),上述矩阵A的1范数先得到[5,8,9],再取最大的最终结果就是:9,MATLAB代码实现为:norm(A,1); 2.2 矩阵的2范数 矩阵的2范数即:矩阵ATAATAA^{T}A的最大特征值开平方根,上述矩阵A的2范数得到的最终结果是:10.0623,MATLAB代码实现为:norm(A,2); 2.3 矩阵的无穷范数 矩阵的1范数即:矩阵的每一行上的元素绝对值先求和,再从中取个最大的,(行和最大),上述矩阵A的1范数先得到[6;16],再取最大的最终结果就是:16,MATLAB代码实现为:norm(A,inf); 接下来我们要介绍机器学习的低秩,稀疏等一些地方用到的范数,一般有核范数,L0范数,L1范数(有时很多人也叫1范数,这就让初学者很容易混淆),L21范数(有时也叫2范数),F范数。。。上述范数都是为了解决实际问题中的困难而提出的新的范数定义,不同于前面的矩阵范数。 2.4 矩阵的核范数 矩阵的核范数即:矩阵的奇异值(将矩阵svd分解)之和,这个范数可以用来低秩表示(因为最小化核范数,相当于最小化矩阵的秩——低秩),上述矩阵A最终结果就是:10.9287, MATLAB代码实现为:sum(svd(A)) 2.5 矩阵的L0范数 矩阵的L0范数即:矩阵的非0元素的个数,通常用它来表示稀疏,L0范数越小0元素越多,也就越稀疏,上述矩阵A最终结果就是:6 2.6 矩阵的L1范数 矩阵的L1范数即:矩阵中的每个元素绝对值之和,它是L0范数的最优凸近似,因此它也可以表示稀疏,上述矩阵A最终结果就是:22,MATLAB代码实现为:sum(sum(abs(A))) 2.7 矩阵的F范数 矩阵的F范数即:矩阵的各个元素平方之和再开平方根,它通常也叫做矩阵的L2范数,它的有点在它是一个凸函数,可以求导求解,易于计算,上述矩阵A最终结果就是:10.0995,MATLAB代码实现为:norm(A,‘fro’) 2.8 矩阵的L21范数 矩阵的L21范数即:矩阵先以每一列为单位,求每一列的F范数(也可认为是向量的2范数),然后再将得到的结果求L1范数(也可认为是向量的1范数),很容易看出它是介于L1和L2之间的一种范数,上述矩阵A最终结果就是:17.1559,MATLAB代码实现为: norm(A(:,1),2) + norm(A(:,2),2) + norm(A(:,3),2) 本文转载自 1024Michael 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/Michael__Corleone/article/details/75213123?utm_source=copy MARSGGBO原创 2018-8-5
知乎上的这个回答超级赞!!!!!!!! 但是不能转载嘤嘤嘤。 https://www.zhihu.com/question/20852004
线性代数复习 Linear Algebra 线性代数 行列式 行列式的性质 记行列式如下: 行列式与它的转置行列式相等,即 互换行列式的两行(列),行列式变号。 推论:如果行列式有两行(列)完全相同,则此行列式为零。即,若,则 行列式的某一行(列)中所有的元素都乘以同一个倍数,等于用数乘以此行列式。下图有:。 推论:行列式的某一行(列)中所有元素的公因子可以提到行列式符号的外面. 行列式中如果有两行(列)元素成比例,则此行列式为零。 若行列式的某一列(行)的元素都是两数之和,那么该行列式是两个行列式之和。 把行列式的某一列(行)的各元素乘以同一个倍数然后加到另一列(行)对应的元素上去,行列式不变。 MARSGGBO原创 2018-8-5
机器学习中数据读取是很重要的一个环节,TensorFlow也提供了很多实用的方法,为了避免以后时间久了又忘记,所以写下笔记以备日后查看。 最普通的正常情况 首先我们看看最普通的情况: # 创建0-10的数据集,每个batch取个数。 dataset = tf.data.Dataset.range(10).batch(6) iterator = dataset.make_one_shot_iterator() next_element = iterator.get_next() with tf.Session() as sess: for i in range(2): value = sess.run(next_element) print(value) 输出结果 [0 1 2 3 4 5] [6 7 8 9] 由结果我们可以知道TensorFlow能很好地帮我们自动处理最后一个batch的数据。 datasets.batch(batch_size)与迭代次数的关系 但是如果上面for循环次数超过2会怎么样呢?也就是说如果 **循环次数*批数量 > 数据集数量** 会怎么样?我们试试看: dataset = tf.data.Dataset.range(10).batch(6) iterator = dataset.make_one_shot_iterator() next_element = iterator.get_next() with tf.Session() as sess: >>==for i in range(3):==<< value = sess.run(next_element) print(value) 输出结果 [0 1 2 3 4 5] [6 7 8 9] --------------------------------------------------------------------------- OutOfRangeError Traceback (most recent call last) D:\Continuum\anaconda3\lib\site-packages\tensorflow\python\client\session.py in _do_call(self, fn, *args) 1277 try: ... ...省略若干信息... ... OutOfRangeError (see above for traceback): End of sequence [[Node: IteratorGetNext_64 = IteratorGetNext[output_shapes=[[?]], output_types=[DT_INT64], _device="/job:localhost/replica:0/task:0/device:CPU:0"](OneShotIterator_28)]] 可以知道超过范围了,所以报错了。 datasets.repeat() 为了解决上述问题,repeat方法登场。还是直接看例子吧: dataset = tf.data.Dataset.range(10).batch(6) dataset = dataset.repeat(2) iterator = dataset.make_one_shot_iterator() next_element = iterator.get_next() with tf.Session() as sess: for i in range(4): value = sess.run(next_element) print(value) 输出结果 [0 1 2 3 4 5] [6 7 8 9] [0 1 2 3 4 5] [6 7 8 9] 可以知道repeat其实就是将数据集重复了指定次数,上面代码将数据集重复了2次,所以这次即使for循环次数是4也依旧能正常读取数据,并且都能完整把数据读取出来。同理,如果把for循环次数设置为大于4,那么也还是会报错,这么一来,我每次还得算repeat的次数,岂不是很心累?所以更简便的办法就是对repeat方法不设置重复次数,效果见如下: dataset = tf.data.Dataset.range(10).batch(6) dataset = dataset.repeat() iterator = dataset.make_one_shot_iterator() next_element = iterator.get_next() with tf.Session() as sess: for i in range(6): value = sess.run(next_element) print(value) 输出结果: [0 1 2 3 4 5] [6 7 8 9] [0 1 2 3 4 5] [6 7 8 9] [0 1 2 3 4 5] [6 7 8 9] 此时无论for循环多少次都不怕啦~~ datasets.shuffle(buffer_size) 仔细看可以知道上面所有输出结果都是有序的,这在机器学习中用来训练模型是浪费资源且没有意义的,所以我们需要将数据打乱,这样每批次训练的时候所用到的数据集是不一样的,这样啊可以提高模型训练效果。 另外shuffle前需要设置buffer_size: 不设置会报错, buffer_size=1:不打乱顺序,既保持原序 buffer_size越大,打乱程度越大,演示效果见如下代码: dataset = tf.data.Dataset.range(10).shuffle(2).batch(6) dataset = dataset.repeat(2) iterator = dataset.make_one_shot_iterator() next_element = iterator.get_next() with tf.Session() as sess: for i in range(4): value = sess.run(next_element) print(value) 输出结果: [1 0 2 4 3 5] [7 8 9 6] [1 2 3 4 0 6] [7 8 9 5] 注意:shuffle的顺序很重要,一般建议是最开始执行shuffle操作,因为如果是先执行batch操作的话,那么此时就只是对batch进行shuffle,而batch里面的数据顺序依旧是有序的,那么随机程度会减弱。不信你看: dataset = tf.data.Dataset.range(10).batch(6).shuffle(10) dataset = dataset.repeat(2) iterator = dataset.make_one_shot_iterator() next_element = iterator.get_next() with tf.Session() as sess: for i in range(4): value = sess.run(next_element) print(value) 输出结果: [0 1 2 3 4 5] [6 7 8 9] [0 1 2 3 4 5] [6 7 8 9] MARSGGBO原创 2018-8-5
前言 前段时间更新自己电脑上的tf1.4到1.9,没想到踩了这么多坑。。。特意记录下来希望可以帮到大家 删除旧版本 如果你电脑上没有安装旧版本的tf,就可以忽略这一步。我是因为想要升级到最新版本,所以需要先卸载旧版本。旧版本是用anaconda安装的,卸载很简单,首先进入安装tf的环境,我的环境是“tensorflow”: activate tensorflow 然后卸载TensorFlow就好,或者直接删除这个tensorflow的环境,卸载tensorflow可以使用: (tensorflow)pip uninstall tensorflow-gpu 前提是你安装的时候使用的是pip指令。直接删除环境可以使用: conda env remove -n tensorflow 安装CUDA 要安装CUDA需要满足下面的四个条件: A CUDA-capable GPU (一个可以运行CUDA的显卡) A supported version of Microsoft Windows(一个支持CUDA的系统) A supported version of Microsoft Visual Studio(一个支持CUDA的VisualStudio) the NVIDIA CUDA Toolkit (available at http://developer.nvidia.com/cuda-downloads) (CUDA的安装文件) 这里需要注意的是,安装tensorflow-gpu1.9必须严格安装CUDA9.0,任何9.X都不可以,比如CUDA9.2什么的都不行,因为tensorflow会去寻找xxxx90.dll,如果是CUDA9.2之类的,这些dll的名称是xxxx92.dll,会报出找不到的错误。 还有一点需要注意,如果你的电脑上没有VS,那么你需要先去安装,请先自行下载安装VS2015,或者在我给出的链接中下载: Visual Studio 2015 VS的安装需要一定的时间,请耐心等待。 如果你觉得前三个条件满足,那么就可以下载CUDA9.0了,下载地址是CUDA9.0,选择如下图所示,然后下载: 下载完成后,双击安装。这里又有一个坑,我的显卡是GTX 750M,直接选择“精简”模式的安装会报错导致不能安装,报错的原因是Visual Studio Intergration不能正确安装导致的失败。 你可以选择直接精简安装试试运气,也可以跟着我下面的教程选择“自定义”安装,下面介绍如何自定义安装: 在刚刚双击安装文件后,会让你找一个解压临时文件的路径,保存下这个路径: 抽取完成先进入这个路径,把里面的CUDAVisualStudioIntegration文件夹拷贝到其他地方保存,然后继续安装 选择自定义安装,点击下一步 除了Visual Studio Intergration其他的都打对勾,如下图所示: 完成后,我们需要手动安装之前没有安装的Visual Studio Intergration,进入以下路径: C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\V140\BuildCustomizations 将我们之前保存的那个CUDAVisualStudioIntegration文件夹中的\extras\visualstudiointegration\MSBuildExtensions下面的所有文件拷贝到上面的那个路径中去即可。 完成后,进入这个路径中,其中ProgramData是隐藏文件夹,看不到的话就去文件夹选项里勾选查看隐藏文件 C:\ProgramData\NVIDIA Corporation\CUDA Samples\v9.0\5_Simulations\nbody 因为之前安装的是VS2015,所以选择15的sln 接下来会打开VS2015,如果你的电脑上缺某些组件,这个时候VS会自动提醒你需要补充安装,安装即可。安装完成后在屏幕上方调成debug和x64 右击右侧的“解决方案”,选择生成解决方案进行编译 成功后进入路径 C:\ProgramData\NVIDIA Corporation\CUDA Samples\v9.0\bin\win64\Debug 双击运行刚刚编译得到的“nbody.exe”,得到下图所示表示安装成功CUDA PS:没意外的话不需要设置环境变量,安装的时候自动设置好了 安装cuDnn cuDnn也请严格按照tf官网上的要求下载7.0版本的,不要下载7.X的,下载地址cuDnn 然后解压缩得到3个文件夹,将这3个文件夹里面的文件复制到CUDA安装路径下相应的文件夹里面即可。CUDA默认安装路径为:C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v9.0,具体来说: 复制 \cuda\bin\cudnn64_7.dll 到 C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v9.0\bin. 复制 \cuda\ include\cudnn.h 到 C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v9.0\include. 复制 \cuda\lib\x64\cudnn.lib 到 C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v9.0\lib\x64. 安装tensorflow-gpu 相比于之前繁琐的步骤,这里反而是最简单了的。请安装Anaconda,Anaconda实质上是一个Python包和版本管理工具。首先去官网下载Anaconda,然后安装,注意在某一个选项中勾选将anaconda加入到PATH中去(虽然anaconda不建议这么做,但是方便啊!),其他的全部选择默认选项即可,最多改一改安装路径,其他按照默认选项安装。 1、 用anaconda创建一个虚拟环境 C:> conda create -n tensorflow python=3.6 2、 在 Anaconda 中,您可以使用 conda 来创建一个虚拟环境。但是,在 Anaconda 内部,我们建议使用 pip install 命令来安装 TensorFlow,而不要使用 conda install 命令。 根据tf官方文件的建议,我们使用pip指令安装,但是同时,我们不要执行tf官方文档中的安装指令,因为那样安装太慢了,我们这里用豆瓣镜像去安装最新版本的tensorflow,瞬间就能安装好 (tensorflow)C:> pip install --index-url https://pypi.douban.com/simple tensorflow-gpu 3、 测试 这里使用PyCharm这个IDE来进行测试。首先创建一个空的Python工程,然后在setting中设置Python解释器为Python3.5的那个,然后创建一个空的Python文件,文件编入如下代码: import tensorflow as tfhello = tf.constant("Hello!TensorFlow") sess = tf.Session() print(sess.run(hello)) 如果输出如下的话,就是成功安装了tensorflow了: 结语 至此就成功安装完毕啦,真的是坑好多。。。希望可以帮到你,如果出现其他意外,欢迎来信讨论解决 原文链接:https://antdlx.com/win10_tf19/
Title 文章标题 Summary 写完笔记之后最后填,概述文章的内容,以后查阅笔记的时候先看这一段。 Research Objective 作者的研究目标 Problem Statement 问题陈述,要解决什么问题? Method(s) 解决问题的方法/算法是什么? Evaluation 作者如何评估自己的方法,有没有问题或者可以借鉴的地方 Conclusion 作者给了哪些strong conclusion, 又给了哪些weak conclusion? Notes 在这些框架外额外需要记录的笔记。 MARSGGBO原创 2018-8-5 markdown代码如下: # Title 文章标题 # Summary 写完笔记之后最后填,概述文章的内容,以后查阅笔记的时候先看这一段。 # Research Objective 作者的研究目标 # Problem Statement 问题陈述,要解决什么问题? # Method(s) 解决问题的方法/算法是什么? # Evaluation 作者如何评估自己的方法,有没有问题或者可以借鉴的地方 # Conclusion 作者给了哪些strong conclusion, 又给了哪些weak conclusion? # Notes 在这些框架外额外需要记录的笔记。 <center> <img src="" style="border:5px solid black;border-radius:15px;"> </center> <b style="color:tomato;"></b> <footer style="color:white;;background-color:rgb(24,24,24);padding:10px;border-radius:10px;"><br> <h3 style="text-align:center;color:tomato;font-size:16px;" id="autoid-2-0-0"><br> <b>MARSGGBO</b><b style="color:white;"><span style="font-size:25px;"></span>原创</b><br> <br><br> <br><br> <b style="color:white;"><br> 2018-8-5<p></p> </b><p><b style="color:white;"></b><br> </p></h3><br> </footer>
无耻的但是肯定会对你有帮助的托福广告 本文其实是一篇无耻的广告软文,如果看了本文想购买资料的可邮件联系我。 邮件:(marsggbo@foxmail.com) 出于良心,在上广告之前先给要考托福的同学们一点小小的备考建议: 阅读 最重要的就是单词!!!! 最重要的就是单词!!!! 最重要的就是单词!!!! 相信我背完单词后你会来感激我的。 但是说到背单词,还是有一些方法的,或者说有一些能够加快背单词的效率的方法。 第一步:选择合适的单词书 这里强烈推荐 《词以类记》,主意要买托福版本的。当然如果你觉得不喜欢用书来背单词,你也可以和我一样用APP来背单词。市面上各种背单词的APP我都尝试过,最后还是选择了 墨墨背单词(官方麻烦把广告费打我账上)。 之所以选择墨墨背单词是因为以下原因(你也可以基于这些原因选择合适自己的软件): 界面舒服:这对于我来说很重要,看起来舒服的话用起来也舒心不是? 功能专一:这个软件不像其他的APP一样各种乱七八糟的功能(我是在2017.09-2018.01期间用的,不知道现在怎么样了),不像知米背单词一样各种乱七八糟而功能,又是英文小说,又是自习室啥的。 有多种词库:其中就有《词以类记》这个词库,所以你可以直接用这个APP被托福单词啦 打卡激励:这个打卡是需要看持续时间长短的,比如说坚持一个星期有奖励什么的,虽说奖励不怎么样,但是还是挺有成就感的。扇贝也有打卡功能,但是给我的感觉是打不打卡没有太所谓。 评论:!!!!这是最吸引我的地方!!!!现在看视频,看文章,看知乎都特喜欢看评论,因为评论里的人才太多了。你看看别人的记单词方法会很受用的。而且你可以直接fork别人的评论。 点词翻译:这可能是众多单词软件没有的功能,比如说他给的例句里有单词不认识,没关系你点一下就会出现单词的详细解释,用起来贼方便!!!更牛的是评论里的单词也可以点哦!!!妈妈再也不用担心我的学习,真的是哪里不会点哪里。 第二步:每天的单词记忆量尽量多 是的,每天的单词记忆量要:尽量多!!!尽量多!!!尽量多!!! 不要跟我说背多了过段时间会忘记,说得好像少背一点你能全记住似的。 每天背100个都不算多。之前因为备考时间紧,所以有时候最多一天500个,当然这500个不全是新单词。 所以这里就体现出了APP的好处,尤其是墨墨背单词可以给你反复推送新旧单词,旧单词一直被会枯燥,新单词就会因为不认识就抵触。但是新旧交替之后你会感觉还行。 第三步:根据解题技巧刷题 这里的解题技巧就不详细说了,你可以上网看其他的视频,也可以去新东方上培训课,那里的老师都会说的。(以后也考虑写一写解题技巧~~敬请期待吼) 但是千万不要以为去上了课你的阅读成绩就能蹭蹭蹭往上走。 不给油,再牛逼的火箭也飞不起来不是?(此处请勿抬杠,谢谢合作!) 听力 精听 + 跟读。 分数不太高,不在这里误人子弟。 口语+写作 这两个其实是很相似的,因为很多口语备考时积累的素材都是可以直接套用到写作中去的,而且还能拿不错的分数呢~~~ 这一块就是我推荐的资料的重点!!!超级适合那种和我一样拿着题目毫无头绪,然后面对一个问题还真的就老老实实想如何回答,但是就是不知道怎么回答的同学。 比如说口语题目:你认为工资高但假期短的工作 和 工资低但假期长的工作 哪个更好? 如果是最开始的我,我肯定会想很久,我会想工资高吧,钱就多,钱一多吧我就能享受一些,但是假期又短这可怎么享受呢?工资低吧,但是至少假期长啊,但是工资低咋活啊。然后一番思想斗争之后觉得哪个都不太好,就会想说工资适中,假期适中就好。 -_- 答应我,以后不要像我一样好吗。这样很容易精神分裂的,而且明明是二选一,你来个第三者,你让评分的机器很无语啊(有老师说口语评分基本上是机器人评分) 当你看了我给你的这份资料后,你会条件反射的想出好几个关键词,然后围绕这个关键词套答案就可以了。 比如说你可以以“压力”为关键词展开。既然选择了从“压力”切题,那么我们就应该选择“工资低但假期长”,不要考虑你的选择存在哪些问题,你只需要说清楚你的优势在哪即可。就把这个话题当成平常说话,你平常表达自己的观点难道还会考虑各个因素吗?就好像你粉了你的爱豆,你肯定只会向周围人安利ta的好啊,不好的都自带滤镜过滤了好吗? 下面是以“压力”切题的思路: 众所周知,现在大家压力都很大,学生的学习压力大,上班族的工作压力大。而从长远角度看,压力对于一个人来说无论是身体还是心理都会产生极大的负面影响。 而如果我们有一个假期长的工作,我们可以更好的放松自己,让自己有更多的时间和家人朋友在一块,这才是生活的意义。 大致的解题思路就是上面那样的,你只要翻译成英文,换个题目稍微改改几个单词即可。上面只写了短短几句话,你也可以稍微加几句套话基本就够了。最开始你可能会觉得时间太长了,主观题1min30s。但是当你熟悉套路之后,你会觉得时间根本不够用。而且就算时间太长,你把语速降下来也是OK的。 看到这如果对这份资料动心的,可以用邮件私戳我吼~~~ 邮件:(marsggbo@foxmail.com) 当然也欢迎单纯想问一些考试细节的同学 MARSGGBO原创 2018-8-5
定义 无偏估计:估计量的均值等于真实值,即具体每一次估计值可能大于真实值,也可能小于真实值,而不能总是大于或小于真实值(这就产生了系统误差)。 估计量评价的标准 (1)无偏性 如上述 (2)有效性 有效性是指估计量与总体参数的离散程度。如果两个估计量都是无偏的,那么离散程度较小的估计量相对而言是较为有效的。即虽然每次估计都会大于或小于真实值,但是偏离的程度都更小的估计更优。 (3)一致性 又称相合性,是指随着样本容量的增大,估计量愈来愈接近总体参数的真值。 为什么方差的分母是n-1? 结论: 首先这个问题本身概念混淆了。 如果已知全部的数据,那么均值和方差可以直接求出。但是对一个随机变量X,需要估计它的均值和方差,此时才用分母为n-1的公式来估计他的方差,因此分母是n-1才能使对方差的估计(而不是方差)是无偏的。 因此,这个问题应该改为,为什么随机变量的方差的估计的分母是n-1? 如果我们已经知道了全部的数据,那就可以求出均值μ,σ,此时就是常规的分母为n的公式直接求,但这并不是估计! 现在,对于一个随机变量X,我们要去估计它的期望和方差。 期望的估计就是样本的均值\(\overline{X}\) 现在,在估计的X的方差的时候,如果我们预先知道真实的期望μ,那么根据方差的定义: \[E[(X_i-μ)^2]=\frac{1}{n}\sum_i^n{(X_i-μ)^2}=σ^2\] 这时分母为n的估计是正确的,就是无偏估计! 但是,在实际估计随机变量X的方差的时候,我们是不知道它的真实期望的,而是用期望的估计值\(\overline{X}\)去估计方差,那么: 由上式可知,只有除非\(\overline{X}=μ\),否则必有 \[ \frac{1}{n}\sum_i^n(X_i-\overline{X})^2 < \frac{1}{n} \sum_i^n(X_i-μ)^2 \] 上面不等式中的右边才算是方差的"正确估计"! 这也就说明了为什么不能使用\(frac{1}{n}\) 所以把分母从n换成n-1,就是把对方差的估计稍微放大一点点。至于为什么是n-1,而不是n-2,n-3,...,有严格的数学证明。 无偏估计虽然在数学上更好,但是并不总是“最好”的估计,在实际中经常会使用具有其它重要性质的有偏估计。 原文链接:无偏估计 MARSGGBO原创 2018-8-4
在计算loss的时候,最常见的一句话就是 tf.nn.softmax_cross_entropy_with_logits ,那么它到底是怎么做的呢? 首先明确一点,loss是代价值,也就是我们要最小化的值 tf.nn.softmax_cross_entropy_with_logits(logits, labels, name=None) 除去name参数用以指定该操作的name,与方法有关的一共两个参数: 第一个参数logits:就是神经网络最后一层的输出,如果有batch的话,它的大小就是[batchsize,num_classes],单样本的话,大小就是num_classes 第二个参数labels:实际的标签,大小同上 具体的执行流程大概分为两步: 第一步是先对网络最后一层的输出做一个softmax,这一步通常是求取输出属于某一类的概率,对于单样本而言,输出就是一个num_classes大小的向量([Y1,Y2,Y3...]其中Y1,Y2,Y3...分别代表了是属于该类的概率) 第二步是softmax的输出向量[Y1,Y2,Y3...]和样本的实际标签做一个交叉熵,公式如下: \[H_{y'}(y)=-\sum_i{y'_ilog(y_i)}\] 其中\(y'_i\)指代实际的标签中第i个的值(用mnist数据举例,如果是3,那么标签是[0,0,0,1,0,0,0,0,0,0],除了第4个值为1,其他全为0) \(y_i\)就是softmax的输出向量[Y1,Y2,Y3...]中,第i个元素的值 显而易见,预测越准确,结果的值越小(别忘了前面还有负号),最后求一个平均,得到我们想要的loss 注意!!!这个函数的返回值并不是一个数,而是一个向量,如果要求交叉熵,我们要再做一步tf.reduce_sum操作,就是对向量里面所有元素求和,最后才得到,如果求loss,则要做一步tf.reduce_mean操作,对向量求均值! 理论讲完了,上代码 import tensorflow as tf #our NN's output logits=tf.constant([[1.0,2.0,3.0],[1.0,2.0,3.0],[1.0,2.0,3.0]]) #step1:do softmax y=tf.nn.softmax(logits) #true label y_=tf.constant([[0.0,0.0,1.0],[0.0,0.0,1.0],[0.0,0.0,1.0]]) #step2:do cross_entropy cross_entropy = -tf.reduce_sum(y_*tf.log(y)) #do cross_entropy just one step cross_entropy2=tf.reduce_sum(tf.nn.softmax_cross_entropy_with_logits(logits, y_))#dont forget tf.reduce_sum()!! with tf.Session() as sess: softmax=sess.run(y) c_e = sess.run(cross_entropy) c_e2 = sess.run(cross_entropy2) print("step1:softmax result=") print(softmax) print("step2:cross_entropy result=") print(c_e) print("Function(softmax_cross_entropy_with_logits) result=") print(c_e2) 输出结果是: step1:softmax result= [[ 0.09003057 0.24472848 0.66524094] [ 0.09003057 0.24472848 0.66524094] [ 0.09003057 0.24472848 0.66524094]] step2:cross_entropy result= 1.22282 Function(softmax_cross_entropy_with_logits) result= 1.2228 最后大家可以试试e^1/(e^1+e^2+e^3)是不是0.09003057,发现确实一样!!这也证明了我们的输出是符合公式逻辑的 原文链接:【TensorFlow】tf.nn.softmax_cross_entropy_with_logits的用法 MARSGGBO原创 2018-7-30
摘要 本文提出了一种新方法,可以基于简单的爬山过程自动搜索性能良好的CNN架构,该算法运算符应用网络态射,然后通过余弦退火进行短期优化运行。 令人惊讶的是,这种简单的方法产生了有竞争力的结果,尽管只需要与训练单个网络相同数量级的资源。例如使用该算法,在单个GPU上训练12个小时就可以将CIFAR-10数据集的错误率降低到6%一下,训练一整天后能够降低到5%左右。 1.介绍 背景不再详述,我们可以知道的是传统的优化算法并不能实现神经网络架构的自动搜索是因为其架构搜索空间是 离散的(例如层数、层类型等), 有条件的(例如,定义层的参数的数量取决于层类型)。因此,依赖于可微分或者独立参数的算法是不可行的。 由于上述原因也就使得进化算法和增强算法得到广泛应用,但是这些方法要么消耗巨大,要么无法获得优异的表现。 本文的贡献如下: 提出了一种基线方法,该方法 随机构造出网络,并使用SGDR对这些网络训练。该基线方法在CIFAR-10的测试集上能够实现6%-7%的错误率。 正式确定并扩展了网络态射的工作(Chen et al。,2015; Wei et al。,2016; Cai et al。,2017),以提供流行的网络构建模块,例如跳过连接和批量规范化。 提出 Neural Architecture Search by Hillclimbing (NASH),该算法已在摘要中介绍。 2.相关工作 Hyperparameter optimization: random search (Bergstra & Bengio, 2012) Bayesian optimization (Bergstra et al., 2011; Snoek et al., 2012) bandit-based approaches (Li et al., 2016a) evolutionary strategies (Loshchilov & Hutter, 2016) Automated architecture search: (Bergstra et al., 2011; Mendoza et al., 2016)将架构搜索视为超参数搜索,使用标准的超参数优化算法来优化架构。 (Baker et al., 2016; Zoph & Le, 2017; Cai et al., 2017)三个方法都采用训练增强学习代理(reinforcement learning agent)的方式。 Baker et al. (2016)通过训练一个RL Agent来按顺序选择层类型(卷积,池化,全连接)和它们的参数。 Zoph & Le (2017)使用递归神经网络控制器来按顺序生成用于表示网络架构的字符串。 上述两个方法都是从头训练所生成的网络,并且耗时较长。为了解决这些问题,Cai et al. (2017)提出在RL中应用网络变换/态射的概念。 Real et al. (2017) 和 Suganuma et al. (2017)使用进化算法从小网络来迭代生成强有力的网络。诸如插入一层、修改一层的参数、增加跳跃连接都视为“突变”。其中前者使用大量的计算资源(250GPUs,10天),后者则由于处理大量网络而被限制在相对较小的网络上。前面两种算法中的网络容量会随时间不断增大,而, Saxena & Verbeek(2016)则是在一开始就训练一个大型的网络,然后在最后做剪枝。 Network morphism/ transformation.:这是Chen et al. (2015)在迁移学习基础上提出来的。作者介绍了一个函数保留操作,该函数可以使得网络更深("Net2Deeper")或者更宽("Net2Wider"),目的是加速培训和探索网络架构。Wei et al. (2016) 提出了其它的操作,如用于处理非幂等激活函数、改变内核大小,并引入网络态射概念。 3. 网络态射(Network Morphism) 令\(N(X)\)表示定义在\(X \subset{R^n}\)一组神经网络。网络态射是指从参数为\(w∈R^k\)的神经网络\(f^w∈N(x)\)映射为参数为\(\tilde{w}∈R^j\)的神经网络\(g^{\tilde{w}}∈N(x)\),即: \[f^w(x)=g^{\tilde{w}(x)} \, for \, every \, x ∈ X \tag{1}\] 下面会给出几个网络态射的例子以及用于构建神经网络的操作(如添加一个卷积层)是如何表示为网络态射的。为方便说明,令\(f_i^{w_i}(x)\)表示神经网络\(f^w(x)\)的某一个部分,如可能是某一层或者是子集网络。 3.1 Network morphism Type I 使用下式代替\(f_i^{w_i}(x)\) \[\tilde{f_i}^{\tilde{w}_i}(x)=Af_i^{w_i}(x)+b \tag{2}\] 其中\(\tilde{w}_i=(w_i,A,b)\)。显然当\(A=1,b=0\)时,公式(2)则退化成公式(1)。 这种态射可以用于添加全连接层或者卷积层,因为这些层都是简单的线性映射。Chen et al. (2015) 称这个态射为"Net2DeeperNet"。 除了上面的替换方式,也可以有 \[\tilde{f_i}^{\tilde{w}_i}(x)=C(Af_i^{w_i}(x)+b)+d \tag{3}\] 其中\(\tilde{w}_i=(w_i,C,d)\)。\(A,b\)是固定的,不可学习的。当\(C=A^{-1},d=-Cb\)时公式(3)变成了公式(1)。批标准化层(或者其他标准化层)可以用上面的表达式表示:\(A,b\)表示批量统计数据,\(C,d\)表示可学习的缩放和位移。 3.2 Network morphism Type II 假设\(f_i^{w_i}(x)=Ah^{w_h}(x)+b\),其中\(h\)为任意函数。 我们使用如下式子代替\(f_i^{w_i}(x)\): 其中\(\tilde{h}^{w_{\tilde{h}}} (x)\)是任意函数。新参数\(\tilde{w}_i=(w_i,w_{\tilde{h}},\tilde{A})\)。同理,当\(\tilde{A}=0\)时依然可以得到公式(1)。 公式(4)可以有两种对NN的修改方式: 1.加宽某一层,例如当\(\tilde{h}=h\)时,宽度加倍。 2.可以事先跳跃连接(skip connections)。例如假设\(h(x)\)表示一系列的网络层,即\(h(x)=h_n(x)◦···h_0(x)\),那么当\(\tilde{h}(x)=x\)时则实现了从\(h_0\)到\(h_n\)的跳跃连接。 3.3 Network morphism Type III 根据定义,每一个幂等函数\(f_i^{w_i}(x)\)都可以用下面的式子代替: \[f_i^{(w_i,\tilde{w}_i)}=f_i^{w_i}◦f_i^{\tilde{w}_i} \tag{5}\] 初始化\(\tilde{w}_i=w_i\)。 幂等(idempotent、idempotence)是一个数学与计算机学概念,常见于抽象代数中。 在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。 例如f(f(x))=f(x),那么f(x)就是幂等函数。 3.4 Network morphism Type IV 使用下式代替\(f_i^{w_i}(x)\): \[ \tilde{f}_i^{\tilde{w}_i}(x)=λf_i^{w_i}(x)+(1-λ)h^{w_h}(x),\,\, \tilde{w}_i=(w_i,λ,w_h) \tag{6} \] \(h\)为任意函数。 这种态射可用于合并任何函数,尤其是任何非线性函数。 4. ARCHITECTURE SEARCH BY NETWORK MORPHISMS 1. 最开始使用一个小型的预训练模型 2. 将网络态射应用到该初始化网络中,经过训练后可以生成表现更加,更大的网络。所生成的网络可看作是“child”网络,初始网络可看作是“parent”网络。 3. 在上面步骤生成的子网络中找到表现最优秀的网络,然后在该网络上继续生成子网络,不断迭代优化。 以上方法即为 Neural Architecture Search by Hill-climbing (NASH) 下图展示了NASH的步骤: NASH的算法步骤如下: 在实现上图中的算法时,函数ApplyNetMorph(model, n)应用\(n\)个网络态射,每个都是从下面三种情况中均匀随机采样的: 使网络更深,即加上"Conv-Batchnorm-Relu"模块。模块所加的位置和kernel大小(\(∈{3,5}\))都是均匀采样的。通道的数量与前一个最近的卷积通道数相等。 使网络更宽,即通过使用 网络态射II增加通道数量。需要拓宽的卷积层和拓宽因子(\(∈{2,4}\))都是均匀采样的。 通过分别使用网络态射类型II或IV,添加从第i层到第j层的跳跃连接(通过 concatenation 或 addition - 均匀采样)。 层i和j也都是均匀地采样。 5. 实验与结果 具体的实验结果可查阅原论文 Simple And Efficient Architecture Search For Neural Networks。 MARSGGBO原创 2018-7-27
这或许是众多OIer最大的误区之一。 你会经常看到网上出现“这怎么做,这不是NP问题吗”、“这个只有搜了,这已经被证明是NP问题了”之类的话。你要知道,大多数人此时所说的NP问题其实都是指的NPC问题。他们没有搞清楚NP问题和NPC问题的概念。NP问题并不是那种“只有搜才行”的问题,NPC问题才是。好,行了,基本上这个误解已经被澄清了。下面的内容都是在讲什么是P问题,什么是NP问题,什么是NPC问题,你如果不是很感兴趣就可以不看了。接下来你可以看到,把NP问题当成是 NPC问题是一个多大的错误。 还是先用几句话简单说明一下时间复杂度。时间复杂度并不是表示一个程序解决问题需要花多少时间,而是当问题规模扩大后,程序需要的时间长度增长得有多快。也就是说,对于高速处理数据的计算机来说,处理某一个特定数据的效率不能衡量一个程序的好坏,而应该看当这个数据的规模变大到数百倍后,程序运行时间是否还是一样,或者也跟着慢了数百倍,或者变慢了数万倍。不管数据有多大,程序处理花的时间始终是那么多的,我们就说这个程序很好,具有O(1)的时间复杂度,也称常数级复杂度;数据规模变得有多大,花的时间也跟着变得有多长,这个程序的时间复杂度就是O(n),比如找n个数中的最大值;而像冒泡排序、插入排序等,数据扩大2倍,时间变慢4倍的,属于O(n^2)的复杂度。还有一些穷举类的算法,所需时间长度成几何阶数上涨,这就是O(a^n)的指数级复杂度,甚至O(n!)的阶乘级复杂度。不会存在O(2n^2)的复杂度,因为前面的那个“2”是系数,根本不会影响到整个程序的时间增长。同样地,O (n^3+n^2)的复杂度也就是O(n^3)的复杂度。因此,我们会说,一个O(0.01n^3)的程序的效率比O(100*n^2)的效率低,尽管在n很小的时候,前者优于后者,但后者时间随数据规模增长得慢,最终O(n^3)的复杂度将远远超过O(n^2)。我们也说,O(n^100)的复杂度小于O(1.01^n)的复杂度。 容易看出,前面的几类复杂度被分为两种级别,其中后者的复杂度无论如何都远远大于前者:一种是O(1),O(log(n)),O(n^a)等,我们把它叫做多项式级的复杂度,因为它的规模n出现在底数的位置;另一种是O(a^n)和O(n!)型复杂度,它是非多项式级的,其复杂度计算机往往不能承受。当我们在解决一个问题时,我们选择的算法通常都需要是多项式级的复杂度,非多项式级的复杂度需要的时间太多,往往会超时,除非是数据规模非常小。 自然地,人们会想到一个问题:会不会所有的问题都可以找到复杂度为多项式级的算法呢?很遗憾,答案是否定的。有些问题甚至根本不可能找到一个正确的算法来,这称之为“不可解问题”(Undecidable Decision Problem)。The Halting Problem就是一个著名的不可解问题,在我的Blog上有过专门的介绍和证明。再比如,输出从1到n这n个数的全排列。不管你用什么方法,你的复杂度都是阶乘级,因为你总得用阶乘级的时间打印出结果来。有人说,这样的“问题”不是一个“正规”的问题,正规的问题是让程序解决一个问题,输出一个“YES”或“NO”(这被称为判定性问题),或者一个什么什么的最优值(这被称为最优化问题)。那么,根据这个定义,我也能举出一个不大可能会有多项式级算法的问题来:Hamilton回路。问题是这样的:给你一个图,问你能否找到一条经过每个顶点一次且恰好一次(不遗漏也不重复)最后又走回来的路(满足这个条件的路径叫做Hamilton回路)。这个问题现在还没有找到多项式级的算法。事实上,这个问题就是我们后面要说的NPC问题。 下面引入P类问题的概念:如果一个问题可以找到一个能在多项式的时间里解决它的算法,那么这个问题就属于P问题。P是英文单词多项式的第一个字母。哪些问题是P类问题呢?通常NOI和NOIP不会出不属于P类问题的题目。我们常见到的一些信息奥赛的题目都是P问题。道理很简单,一个用穷举换来的非多项式级时间的超时程序不会涵盖任何有价值的算法。 接下来引入NP问题的概念。这个就有点难理解了,或者说容易理解错误。在这里强调(回到我竭力想澄清的误区上),NP问题不是非P类问题。NP问题是指可以在多项式的时间里验证一个解的问题。NP问题的另一个定义是,可以在多项式的时间里猜出一个解的问题。比方说,我RP很好,在程序中需要枚举时,我可以一猜一个准。现在某人拿到了一个求最短路径的问题,问从起点到终点是否有一条小于100个单位长度的路线。它根据数据画好了图,但怎么也算不出来,于是来问我:你看怎么选条路走得最少?我说,我RP很好,肯定能随便给你指条很短的路出来。然后我就胡乱画了几条线,说就这条吧。那人按我指的这条把权值加起来一看,嘿,神了,路径长度98,比100小。于是答案出来了,存在比100小的路径。别人会问他这题怎么做出来的,他就可以说,因为我找到了一个比100 小的解。在这个题中,找一个解很困难,但验证一个解很容易。验证一个解只需要O(n)的时间复杂度,也就是说我可以花O(n)的时间把我猜的路径的长度加出来。那么,只要我RP好,猜得准,我一定能在多项式的时间里解决这个问题。我猜到的方案总是最优的,不满足题意的方案也不会来骗我去选它。这就是NP问题。当然有不是NP问题的问题,即你猜到了解但是没用,因为你不能在多项式的时间里去验证它。下面我要举的例子是一个经典的例子,它指出了一个目前还没有办法在多项式的时间里验证一个解的问题。很显然,前面所说的Hamilton回路是NP问题,因为验证一条路是否恰好经过了每一个顶点非常容易。但我要把问题换成这样:试问一个图中是否不存在Hamilton回路。这样问题就没法在多项式的时间里进行验证了,因为除非你试过所有的路,否则你不敢断定它“没有Hamilton回路”。 之所以要定义NP问题,是因为通常只有NP问题才可能找到多项式的算法。我们不会指望一个连多项式地验证一个解都不行的问题存在一个解决它的多项式级的算法。相信读者很快明白,信息学中的号称最困难的问题——“NP问题”,实际上是在探讨NP问题与P类问题的关系。 很显然,所有的P类问题都是NP问题。也就是说,能多项式地解决一个问题,必然能多项式地验证一个问题的解——既然正解都出来了,验证任意给定的解也只需要比较一下就可以了。关键是,人们想知道,是否所有的NP问题都是P类问题。我们可以再用集合的观点来说明。如果把所有P类问题归为一个集合P中,把所有 NP问题划进另一个集合NP中,那么,显然有P属于NP。现在,所有对NP问题的研究都集中在一个问题上,即究竟是否有P=NP?通常所谓的“NP问题”,其实就一句话:证明或推翻P=NP。 NP问题一直都是信息学的巅峰。巅峰,意即很引人注目但难以解决。在信息学研究中,这是一个耗费了很多时间和精力也没有解决的终极问题,好比物理学中的大统一和数学中的歌德巴赫猜想等。 目前为止这个问题还“啃不动”。但是,一个总的趋势、一个大方向是有的。人们普遍认为,P=NP不成立,也就是说,多数人相信,存在至少一个不可能有多项式级复杂度的算法的NP问题。人们如此坚信P≠NP是有原因的,就是在研究NP问题的过程中找出了一类非常特殊的NP问题叫做NP-完全问题,也即所谓的 NPC问题。C是英文单词“完全”的第一个字母。正是NPC问题的存在,使人们相信P≠NP。下文将花大量篇幅介绍NPC问题,你从中可以体会到NPC问题使P=NP变得多么不可思议。 为了说明NPC问题,我们先引入一个概念——约化(Reducibility,有的资料上叫“归约”)。 简单地说,一个问题A可以约化为问题B的含义即是,可以用问题B的解法解决问题A,或者说,问题A可以“变成”问题B。《算法导论》上举了这么一个例子。比如说,现在有两个问题:求解一个一元一次方程和求解一个一元二次方程。那么我们说,前者可以约化为后者,意即知道如何解一个一元二次方程那么一定能解出一元一次方程。我们可以写出两个程序分别对应两个问题,那么我们能找到一个“规则”,按照这个规则把解一元一次方程程序的输入数据变一下,用在解一元二次方程的程序上,两个程序总能得到一样的结果。这个规则即是:两个方程的对应项系数不变,一元二次方程的二次项系数为0。按照这个规则把前一个问题转换成后一个问题,两个问题就等价了。同样地,我们可以说,Hamilton回路可以约化为TSP问(Travelling Salesman Problem,旅行商问题):在Hamilton回路问题中,两点相连即这两点距离为0,两点不直接相连则令其距离为1,于是问题转化为在TSP问题中,是否存在一条长为0的路径。Hamilton回路存在当且仅当TSP问题中存在长为0的回路。 “问题A可约化为问题B”有一个重要的直观意义:B的时间复杂度高于或者等于A的时间复杂度。也就是说,问题A不比问题B难。这很容易理解。既然问题A能用问题B来解决,倘若B的时间复杂度比A的时间复杂度还低了,那A的算法就可以改进为B的算法,两者的时间复杂度还是相同。正如解一元二次方程比解一元一次方程难,因为解决前者的方法可以用来解决后者。 很显然,约化具有一项重要的性质:约化具有传递性。如果问题A可约化为问题B,问题B可约化为问题C,则问题A一定可约化为问题C。这个道理非常简单,就不必阐述了。 现在再来说一下约化的标准概念就不难理解了:如果能找到这样一个变化法则,对任意一个程序A的输入,都能按这个法则变换成程序B的输入,使两程序的输出相同,那么我们说,问题A可约化为问题B。 当然,我们所说的“可约化”是指的可“多项式地”约化(Polynomial-time Reducible),即变换输入的方法是能在多项式的时间里完成的。约化的过程只有用多项式的时间完成才有意义。 好了,从约化的定义中我们看到,一个问题约化为另一个问题,时间复杂度增加了,问题的应用范围也增大了。通过对某些问题的不断约化,我们能够不断寻找复杂度更高,但应用范围更广的算法来代替复杂度虽然低,但只能用于很小的一类问题的算法。再回想前面讲的P和NP问题,联想起约化的传递性,自然地,我们会想问,如果不断地约化上去,不断找到能“通吃”若干小NP问题的一个稍复杂的大NP问题,那么最后是否有可能找到一个时间复杂度最高,并且能“通吃”所有的 NP问题的这样一个超级NP问题?答案居然是肯定的。也就是说,存在这样一个NP问题,所有的NP问题都可以约化成它。换句话说,只要解决了这个问题,那么所有的NP问题都解决了。这种问题的存在难以置信,并且更加不可思议的是,这种问题不只一个,它有很多个,它是一类问题。这一类问题就是传说中的NPC 问题,也就是NP-完全问题。NPC问题的出现使整个NP问题的研究得到了飞跃式的发展。我们有理由相信,NPC问题是最复杂的问题。再次回到全文开头,我们可以看到,人们想表达一个问题不存在多项式的高效算法时应该说它“属于NPC问题”。此时,我的目的终于达到了,我已经把NP问题和NPC问题区别开了。到此为止,本文已经写了近5000字了,我佩服你还能看到这里来,同时也佩服一下自己能写到这里来。 NPC问题的定义非常简单。同时满足下面两个条件的问题就是NPC问题。首先,它得是一个NP问题;然后,所有的NP问题都可以约化到它。证明一个问题是 NPC问题也很简单。先证明它至少是一个NP问题,再证明其中一个已知的NPC问题能约化到它(由约化的传递性,则NPC问题定义的第二条也得以满足;至于第一个NPC问题是怎么来的,下文将介绍),这样就可以说它是NPC问题了。 既然所有的NP问题都能约化成NPC问题,那么只要任意一个NPC问题找到了一个多项式的算法,那么所有的NP问题都能用这个算法解决了,NP也就等于P 了。因此,给NPC找一个多项式算法太不可思议了。因此,前文才说,“正是NPC问题的存在,使人们相信P≠NP”。我们可以就此直观地理解,NPC问题目前没有多项式的有效算法,只能用指数级甚至阶乘级复杂度的搜索。 顺便讲一下NP-Hard问题。NP-Hard问题是这样一种问题,它满足NPC问题定义的第二条但不一定要满足第一条(就是说,NP-Hard问题要比 NPC问题的范围广)。NP-Hard问题同样难以找到多项式的算法,但它不列入我们的研究范围,因为它不一定是NP问题。即使NPC问题发现了多项式级的算法,NP-Hard问题有可能仍然无法得到多项式级的算法。事实上,由于NP-Hard放宽了限定条件,它将有可能比所有的NPC问题的时间复杂度更高从而更难以解决。 不要以为NPC问题是一纸空谈。NPC问题是存在的。确实有这么一个非常具体的问题属于NPC问题。下文即将介绍它。 下文即将介绍逻辑电路问题。这是第一个NPC问题。其它的NPC问题都是由这个问题约化而来的。因此,逻辑电路问题是NPC类问题的“鼻祖”。 逻辑电路问题是指的这样一个问题:给定一个逻辑电路,问是否存在一种输入使输出为True。 什么叫做逻辑电路呢?一个逻辑电路由若干个输入,一个输出,若干“逻辑门”和密密麻麻的线组成。看下面一例,不需要解释你马上就明白了。 ┌───┐ │ 输入1├─→┐ ┌──┐ └───┘ └─→┤ │ │ or ├→─┐ ┌───┐ ┌─→┤ │ │ ┌──┐ │ 输入2├─→┤ └──┘ └─→┤ │ & nbsp;└───┘ │ ┌─→┤AND ├──→输出 └────────┘┌→┤ │ ┌───┐ ┌──┐ │ └──┘ │ 输入3├─→┤ NOT├─→────┘ └───┘ └──┘ 这是个较简单的逻辑电路,当输入1、输入2、输入3分别为True、True、False或False、True、False时,输出为True。 有输出无论如何都不可能为True的逻辑电路吗?有。下面就是一个简单的例子。 ┌───┐ │输入1 ├→─┐ ┌──┐ └───┘ └─→┤ │ │AND ├─→┐ ┌─→┤ │ │ │ └──┘ │ ┌──┐ │ └→┤ │ ┌───┐ │ │AND ├─→输出 │输入2 ├→─┤ ┌──┐ ┌→┤ │ └───┘ └→┤NOT ├→──┘ └──┘ └──┘ 上面这个逻辑电路中,无论输入是什么,输出都是False。我们就说,这个逻辑电路不存在使输出为True的一组输入。 回到上文,给定一个逻辑电路,问是否存在一种输入使输出为True,这即逻辑电路问题。 逻辑电路问题属于NPC问题。这是有严格证明的。它显然属于NP问题,并且可以直接证明所有的NP问题都可以约化到它(不要以为NP问题有无穷多个将给证明造成不可逾越的困难)。证明过程相当复杂,其大概意思是说任意一个NP问题的输入和输出都可以转换成逻辑电路的输入和输出(想想计算机内部也不过是一些 0和1的运算),因此对于一个NP问题来说,问题转化为了求出满足结果为True的一个输入(即一个可行解)。 有了第一个NPC问题后,一大堆NPC问题就出现了,因为再证明一个新的NPC问题只需要将一个已知的NPC问题约化到它就行了。后来,Hamilton 回路成了NPC问题,TSP问题也成了NPC问题。现在被证明是NPC问题的有很多,任何一个找到了多项式算法的话所有的NP问题都可以完美解决了。因此说,正是因为NPC问题的存在,P=NP变得难以置信。P=NP问题还有许多有趣的东西,有待大家自己进一步的挖掘。攀登这个信息学的巅峰是我们这一代的终极目标。现在我们需要做的,至少是不要把概念弄混淆了。 原文链接:http://www.matrix67.com/blog/archives/105
摘要 神经网络在多个领域都取得了不错的成绩,但是神经网络的合理设计却是比较困难的。在本篇论文中,作者使用 递归网络去省城神经网络的模型描述,并且使用 增强学习训练RNN,以使得生成得到的模型在验证集上取得最大的准确率。 在 CIFAR-10数据集上,基于本文提出的方法生成的模型在测试集上得到结果优于目前人类设计的所有模型。测试集误差率为3.65%,比之前使用相似结构的最先进的模型结构还有低0.09%,速度快1.05倍。 在 Penn Treebank数据集上,根据本文算法得到的模型能够生成一个新颖的 recurrent cell,其要比广泛使用的 LSTM cell或者其他基线方法表现更好。在 Penn Treebank测试集上取得62.4的perplexity,比之前的最好方法还有优秀3.6perplexity。这个 recurrent cell也可以转移到PTB的字符语言建模任务中,实现1.214的perplexity。 1.介绍 深度神经网络在许多具有挑战性的任务重都取得了不俗的成绩。在这成绩背后涉及到的技术则是从特征设计迁移到的结构设计,例如从SIFT、HOG(特征设计)到AlexNet、VGGNet、GoogleNet、ResNet等(结构设计)。 各种优秀的网络结构使得多种任务处理起来简单不少,但是设计网络结构仍然需要大量的专业知识并且需要耗费大量时间。 为了解决上述问题,本文提出 Neural Architecture Search,以期望找到合适的网络结构。大致原理图如下: RNN作为一个 controller去生成模型的描述符,然后根据描述符得到模型,进而得到该模型在数据集上的准确度。接着将该准确度作为 奖励信号(reward signal)对controller进行更新。如此不断迭代找到合适的网络结构。 2.相关工作 超参数优化在机器学习中是个重要的研究话题,也被广泛使用。但是,该方法很难去生成一个长度可变的参数配置,即灵活性不高。虽然 贝叶斯优化可以搜索得到非固定长度的结构,但是与本文提出方法相比在通用性和可变性上都稍逊一筹。 现代神经进化算法虽然可以很灵活的生成模型,但是在大规模数据上实用性不高。 program synthesis and inductive programming的思想是searching a program from examples,Neural Architecture Search与其有一些相似的地方。 与本文方法相关的方法还有 meta-learning、使用一个神经网络去学习用于其他网络的梯度下降更新(Andrychowicz et al., 2016)、以及 使用增强学习去找到用于其他网络的更新策略(Li & Malik, 2016) 3.方法 本节将从下面3个方面介绍所提出的方法: 1.介绍递归网络如何通过使用policy gradient method最大化生成框架的准确率 2.介绍几个改善方法,如skip connection(增加复杂度)、parameter server(加速训练)等 3.介绍如何生成递归 3.1 Generate Model Descriptions With A Controller Recurrent Neural Network 用于生成模型描述的RNN结构如下,所生成的超参数是一系列的 token。 在实验中,如果层数超过一定数量,生成模型就会被停止。这种情况下,或者在收敛时,所生成模型在测试集上得到的准确率会被记录下来。 3.2 Training With Reinforcement RNN的参数用\(θ_c\)表示。controller所预测的一系列tokens记为一系列的actions,即\(a_{1:T}\),这些tokens是为了子网络(Child network)设计结构。子网络在验证集上得到的准确率用\(R\)表示,该准确率作为 reward signal,并且会用到增强学习来训练controller。 通过求解最大化reward找到最优的结构,reward表达式如下: \[J(θ_c)=E_{P(a_{1:T;θ_c})}[R]\] 因为奖励信号\(R\)是不可微分的,所以我们需要一个策略梯度方法来迭代更新\(θ_c\)。在本文中,使用到来自 Williams (1992) 的增强学习规则: \[\nabla_{θ_c}J(θ_c)=\sum_{t=1}^{T}E_{P(a_{1:T;θ_c})}[\nabla_{θ_c}logP(a_t|a_{(t-1):1};θ_c)R]\] 根据经验上式约等于: \[\frac{1}{m} \sum_{k=1}^{m} \sum_{t=1}^{T} \nabla_{θ_c}logP(a_t|a_{(t-1):1};θ_c)R_k\] 其中\(m\)是controller在一个batch中采样得到的结构的数量,\(T\)是controller用于预测和设计神经网络结构的超参数的数量。 \(R_k\)表示第k个网络结构在验证集上的准确度。 上述的更新算法是对梯度的无偏估计,但是有很高的方差。为了降低方差,文中使用如下基线函数: \[\frac{1}{m} \sum_{k=1}^{m} \sum_{t=1}^{T} \nabla_{θ_c}logP(a_t|a_{(t-1):1};θ_c)(R_k-b)\] 只要\(b\)不依懒于当前的action,那么其仍是无偏梯度估计,且\(b\)是前面的结构准确率的 指数平均数指标(Exponential Moving Average, EMA) EMA(Exponential Moving Average)是指数平均数指标,它也是一种趋向类指标,指数平均数指标是以指数式递减加权的移动平均。 其公式为: EMA_{today}=α * Price_{today} + ( 1 - α ) * EMA_{yesterday}; 其中,α为平滑指数,一般取作2/(N+1)。 Accelerate Training with Parallelism and Asynchronous Updates 使用并行算法和异步更新来加速训练 每一次用于更新controller的参数\(θ_c\)的梯度都对应于一个子网络训练达到收敛。但是因为子网络众多,且每次训练收敛耗时长,所以使用 分布式训练和异步参数更新的方法来加速controller的学习速度。 训练模型如上图所示,一共有\(S\)个 Parameter Server用于存储 \(K\)个 Controller Replica的共享参数。然后每个 Controller Replica 生成\(m\)个并行训练的自网络。 controller会根据\(m\)个子网络结构在收敛时得到的结果收集得到梯度值,然后为了更新所有 Controller Replica,会把梯度值传递给 Parameter Server。 在本文中,当训练迭代次数超过一定次数则认为子网络收敛。 3.3 Increase Architecture Complexity With Skip Connections And Other Layer Types 3.1节中的示意图为了方便说明,所以其中的网络结构较为简单。本节则会介绍一种方法能够使得controller生成的网络结构假如 skip connections(如ResNet结构) 或者 branching layers(层分叉,如GoogleNet结构)。 为实现准确预测connections,本文采用了 (Neelakantan et al., 2015) 中的基于注意力机制的set-selection type attention方法。 在\(N\)层,根据sigmoid函数判断与其前面的\(N-1\)个层是否相连。sigmoid函数如下: \[P(Layer\,j\,is\,an\,input\,to\,layer\,i)=sigmoid(v^T tanh(W_{prev}*h_j+W_{curr}*h_i))\] 上式中\(h_j\)表示controller在第\(j\)层的隐藏状态(\(j\)的大小是从0到\(N-1\))。 下面介绍如何应对有的层可能没有输入或输出的情况: 1.如果没有输入,那么原始图像作为输入 2.在最后一层,将所有还没有connected层的输出concatenate起来作为输入。 3.如果需要concatenated的输入层有不同的size,那么小一点的层通过补0来保证一样大小 3.4 GENERATE RECURRENT CELL ARCHITECTURES 下图展示了生成递归单元结构的具体细节。 由图可知采用了树结构来描述网络结构,这样也便于遍历每个节点。 每棵树由两个叶子节点(用0,1表示)和一个中间节点(用2表示)组成。 4. 实验与结果 具体的实验结果可查阅原论文 NEURAL ARCHITECTURE SEARCH WITH REINFORCEMENT LEARNING。 5.读后感 【The First Step-by-Step Guide for Implementing Neural Architecture Search with Reinforcement Learning Using TensorFlow】这篇文章很详细的给出了如何实现NASnet的方法以及源代码,通过阅读代码能更好地理解本论文的思路。 NAS在生成网络的时候之前需要固定网络的结构,或者是说需要固定网络的层数。 以生成CNN网络为例,代码中默认最大层数参数max_layers=2,当然也可以人为修改。 而controller其实就是一个RNN网络,其输出数据表示某一层中各个节点的参数,各个参数是按顺序输出的。例如代码中是按照 [cnn_filter_size,cnn_num_filters,max_pool_ksize,cnn_dropout_rates] 输出的(貌似并没有实现skip-connection)。 伪代码: state = np.array([[10.0, 128.0, 1.0, 1.0]*max_layers], dtype=np.float32) # 初始化state for episode in range(MAX_EPISODES): action = RLnet.get_action(state) # 增强学习网络根据当前状态获取下一步的动作,其中是使用原论文所给的NAScell来对动作进行预测的。 reward, pre_accuracy = net_manager.get_reward(action) # 根据生成的动作得到对应的网络,然后将该网络在训练集上训练至收敛,再将收敛后的网络在验证集上运行得到准确度,根据一定的准则将准确度转化为reward。 reward = update(reward) # 更新reward state = update(action) # 根据action更新state,在例子中是state=action[0] 从上面的伪代码可以看出每次采样得到的模型都需要在训练集上训练到收敛,然后再根据在验证集上得到的reward更新。所以NAS其本质是在离散搜索空间进行搜索,而且网络拓扑结构是固定的,并且训练时间较长,不过思路比较简单好懂。 MARSGGBO原创 2018-7-21
以下内容是对AutoML技术现状与未来展望讲座的总结。 1.机器学习定义 《西瓜书》中的直观定义是:利用经验来改善系统的性能。(这里的经验一般是指数据) Mitchell在《Machine Learning》一书中的较为形式化的定义是一个程序通过给它一些数据,它能够提升在某个任务上的某种度量。(如下图示) 下图很清楚明了的展示了机器学习所做的事情,不再赘述。 2.AutoML技术回顾 很多时候在某一领域使用机器学习得到了效果很好的模型,但是若要在另一个领域使用该模型则不一定适用,而且通常需要大量的专业知识。正是由于受到这样的限制,所以才有了AutoML技术的发展。 2.1 AutoML研究的主要场景 2.1.1 静态闭环AutoML a. 介绍 第一个场景是静态闭环AutoML,该场景是目前用的最多的场景。该场景是给定数据和度量标准之后,通过AutoML选择出效果最好的模型。该过程没有任何的人工干预,都是算法自动选择的。 下图展示了机器学习的基本流程图,可以看到主要有数据预处理、特征处理、模型训练等过程,并且每个过程都包含多种方法。 b. 存在的问题 而AutoML的本质工作是将各个过程的方法进行选择、组合、优化。 但是AutoML存在如下问题: 由于我们通常并不知道所优化的参数和预期效果之间是什么样的显示表达,所以 目标函数形式未知。 由于可能的组合方式太多,所以 搜索空间巨大 正是由于组合方式太多,而且每一个组合都需要从头做数据预处理,特征处理,模型训练等操作,所以 函数计算代价巨大。 c. 解决办法 1.基础搜索方法 该方法其实就是网格搜索,即将各种参数排列成矩阵的形式,然后使用 笛卡尔积(\(A×B = {(x,y)|x∈A∧y∈B}\)) 将所有的组合可能遍历一遍。 该方法有两个缺陷: 随着超参数的规模越来越大,组合的可能性会指数增加,从而导致计算量大大增加。 有的参数重要,但是有的并不重要,但是网格搜索会无差别组合,所以在不重要参数上浪费大量时间和计算资源。所以通常会采用随机搜索的方法来增加搜索效率,并且不会存在指数爆炸,组合爆炸的问题。 2.基于采样的方法 上面介绍的网格搜索和随机搜索实现起来简单,而且使用比较多,但是它们搜索起来比较盲目。 所以有了基于采样的方法以期望避免搜索盲目。 该方法是基于某种策略去产生一组可能的参数候选组合,然后对候选组合进行评估。评估之后我们可以得到反馈,基于这个反馈我们会进一步优化搜索策略,以此迭代去解决优化问题。 这样的一个优化过程是一个黑盒函数,学术界也叫做“零阶优化”,因为在这一过程中我们只能获取函数值,无法获取到它的导数信息。 具体的实现方法有如下四种: 1) 基于模型的零阶优化 如图示,该方法也是通过采样,评估,反馈等迭代操作来得到优化结果,包含两个重要部件:一是用什么样的模型,而是采用什么样的采样策略。 而常用的优化方法有两种:贝叶斯优化和 随机坐标收缩。 贝叶斯优化是被研究的最多的一种方法之一,而最常见的是采用高斯过程来建模。但是高斯过程在求解的时候需要一个三次方操作,所以当数据点特别多的时候计算效率是非常低下的。所以就有贝叶斯神经网络来解决复杂度问题。 另外,高斯过程要求参数必须是连续空间的,而且还有一些其他的限制,所以需要用到随机森林来解决参数类型受限问题。 对应的参数选择策略标准有: 选择概率提升较大的点 选择提升幅度大的点 通过交叉熵选择 GP_UCB(不了解。。。) 贝叶斯模型存在一个致命的错误,那就是它依赖于很强的模型假设(表示我们对函数空间的认知)。 为了解决贝叶斯的缺点,有人提出可以通过分类的方式来解决,即将好的点和坏的点区分开来,而不是对模型进行假设,该方法就是随机坐标收缩(RACOS, RAndomized Coordinate Shrinking)。 该方法采用的模型是使用框将好的点选中,坏的点在框外。而框需要满足两个条件:一是尽可能的随机,而是框要尽可能的“瘦”,最瘦就是瘦成一条垂直于坐标轴的直线。 2) 局部搜索 该方法是指从一个点出发,在它的邻域内进行搜索。 最常见的局部搜索方法是 爬山法。即寻找可能性最大的一个方向后,往该方向前进。该方法能够收敛,但是可能会陷在局部最优解或者停在比较平的地方。 为了解决陷在局部最优问题,迭代式局部搜索应运而生。它的思想是在找到局部最优点后,对局部最优点有一些扰动,然后重新开始一轮局部搜索。 3) 启发式算法 该类方法相较于前两种缺乏坚实的理论支撑,主要是根据对生物,自然界的观察,去模拟一些生物或者自然现象,从而进行优化。 4) 强化学习 该方法是有一种杀鸡用牛刀的感觉,因为强化学习自身的优化就是一个比较大的问题。 3.基于梯度的方法 2.1.2 外部知识辅助AutoML 该场景其实也是静态场景,只不过该场景会从其他任务迁移一些已经做过的知识来作为辅助。 2.1.3 动态环境AutoML 上面两种场景都是静态场景,而现实应用中每天的数据都是不断产生的,任务度量也是不断变化的,所以就有了动态环境AutoML。 例如常见的推荐系统,每天有新用户注册,又有老用户离开。并且用户的喜好也不断发生变化,这就是典型的动态场景。 2.2 AutoML热点研究方向 AutoML热点研究方向主要有两个:效率和 泛化性 2.2.1 效率 常见的提高效率的方法有如下: 将串行的计算方式改成 同步并行或者 异步串行 提前停止模型训练,避免模型过拟合等现象的产生 使用预训练模型进行热启动 混合优化目标,即将计算代价和损失函数结合起来作为优化目标 2.2.2 泛化性 还有一个研究热点是训练模型的泛化性。因为机器学习的本质是希望所训练得到的模型能够对多个任务都有效,即在从未见过的样本上也能表现优秀。 评估 以基于采样的优化为例,假设我们通过采样得到了一些数据点,然后进行超参数评估。这个评估是怎么做的呢? 一般我们会从原数据集中选择一部分数据作为验证集,然后查看验证集的效果如何。但是这个验证集是否能代表未来的数据集呢?答案是不确定的。 所以有些工作就需要去研究怎么做更合理的评估。 我们知道AutoML是从众多模型中选择出在某一数据集上表现最好的一个作为最终的输出模型,那么这就意味着其他的模型都浪费掉了。那些模型虽然表现不是最好的,但是可能也不差,而且可能在其他数据集上表现会更好。所以我们可以试着做集成学习,以此来提高泛化性。 2.3 从理论角度看AutoML 世上没有免费的午餐。 有很多理论都证明不存在一种通用的算法能解决所有问题。 2.4 AutoML应用 视频中主讲人打了下广告,介绍了由第四范式主办的AutoML比赛。 3. AutoML未来展望 算法效率的提升 未来展望一个大方向是算法效率的提升。而算法效率又分为时间复杂度和样本复杂度。 时间复杂度很好理解,它主要是希望能够对全流程进行优化,如下图示,不再赘述。 样本复杂度则是指降低收集样本的成本等。因为收集高质量的有标签的样本是很昂贵而且很困难的,所以可行的办法是才用迁移学习来解决。周志华老师也提出了 学件的概念,即将以往训练的 模型和对该模型的 归约组合成学件,以供后续任务的使用。 算法 AutoML理论 MARSGGBO原创 2018-7-14
本文为Awesome-AutoML-Papers的译文。 1、AutoML简介 Machine Learning几年来取得的不少可观的成绩,越来越多的学科都依赖于它。然而,这些成果都很大程度上取决于人类机器学习专家来完成如下工作: 数据预处理 Preprocess the data 选择合适的特征 Select appropriate features 选择合适的模型族 Select an appropriate model family 优化模型参数 Optimize model hyperparameters 模型后处理 Postprocess machine learning models 分析结果 Critically analyze the results obtained 随着大多数任务的复杂度都远超非机器学习专家的能力范畴,机器学习应用的不断增长使得人们对现成的机器学习方法有了极大的需求。因为这些现成的机器学习方法使用简单,并且不需要专业知识。我们将由此产生的研究领域称为机器学习的逐步自动化。 AutoML借鉴了机器学习的很多知识,主要包括: 贝叶斯优化 Bayesian optimization 结构化数据的大数据的回归模型 Regression models for structured data and big data 元学习 Meta learning 迁移学习 Transfer learning 组合优化 Combinatorial optimization. 2、目录 Papers Automated Feature Engineering Expand Reduce Hierarchical Organization of Transformations Meta Learning Reinforcement Learning Architecture Search Evolutionary Algorithms Local Search Meta Learning Reinforcement Learning Transfer Learning Hyperparameter Optimization Bayesian Optimization Evolutionary Algorithms Lipschitz Functions Local Search Meta Learning Particle Swarm Optimization Random Search Transfer Learning Performance Prediction Performance Prediction Frameworks Miscellaneous Tutorials Bayesian Optimization Meta Learning Articles Bayesian Optimization Meta Learning Slides Bayesian Optimization Books Meta Learning Projects Prominent Researchers Papers Automated Feature Engineering Expand Reduce 2017 | AutoLearn — Automated Feature Generation and Selection | Ambika Kaul, et al. | ICDM | PDF 2017 | One button machine for automating feature engineering in relational databases | Hoang Thanh Lam, et al. | arXiv | PDF 2016 | Automating Feature Engineering | Udayan Khurana, et al. | NIPS | PDF 2016 | ExploreKit: Automatic Feature Generation and Selection | Gilad Katz, et al. | ICDM | PDF 2015 | Deep Feature Synthesis: Towards Automating Data Science Endeavors | James Max Kanter, Kalyan Veeramachaneni | DSAA | PDF Hierarchical Organization of Transformations 2016 | Cognito: Automated Feature Engineering for Supervised Learning | Udayan Khurana, et al. | ICDMW | PDF Meta Learning 2017 | Learning Feature Engineering for Classification | Fatemeh Nargesian, et al. | IJCAI | PDF Reinforcement Learning 2017 | Feature Engineering for Predictive Modeling using Reinforcement Learning | Udayan Khurana, et al. | arXiv | PDF 2010 | Feature Selection as a One-Player Game | Romaric Gaudel, Michele Sebag | ICML | PDF Architecture Search Evolutionary Algorithms 2017 | Large-Scale Evolution of Image Classifiers | Esteban Real, et al. | PMLR | PDF 2002 | Evolving Neural Networks through Augmenting Topologies | Kenneth O.Stanley, Risto Miikkulainen | Evolutionary Computation | PDF Local Search 2017 | Simple and Efficient Architecture Search for Convolutional Neural Networks | Thomoas Elsken, et al. | ICLR | PDF Meta Learning 2016 | Learning to Optimize | Ke Li, Jitendra Malik | arXiv | PDF Reinforcement Learning 2018 | Efficient Neural Architecture Search via Parameter Sharing | Hieu Pham, et al. | arXiv | PDF 2017 | Neural Architecture Search with Reinforcement Learning | Barret Zoph, Quoc V. Le | ICLR | PDF Transfer Learning 2017 | Learning Transferable Architectures for Scalable Image Recognition | Barret Zoph, et al. | arXiv | PDF Frameworks 2017 | Google Vizier: A Service for Black-Box Optimization | Daniel Golovin, et al. | KDD |PDF 2017 | ATM: A Distributed, Collaborative, Scalable System for Automated Machine Learning | T. Swearingen, et al. | IEEE | PDF 2015 | AutoCompete: A Framework for Machine Learning Competitions | Abhishek Thakur, et al. | ICML | PDF Hyperparameter Optimization Bayesian Optimization 2016 | Bayesian Optimization with Robust Bayesian Neural Networks | Jost Tobias Springenberg, et al. | NIPS | PDF 2016 | Scalable Hyperparameter Optimization with Products of Gaussian Process Experts | Nicolas Schilling, et al. | PKDD | PDF 2016 | Taking the Human Out of the Loop: A Review of Bayesian Optimization | Bobak Shahriari, et al. | IEEE | PDF 2016 | Towards Automatically-Tuned Neural Networks | Hector Mendoza, et al. | JMLR | PDF 2016 | Two-Stage Transfer Surrogate Model for Automatic Hyperparameter Optimization | Martin Wistuba, et al. | PKDD | PDF 2015 | Efficient and Robust Automated Machine Learning | PDF 2015 | Hyperparameter Optimization with Factorized Multilayer Perceptrons | Nicolas Schilling, et al. | PKDD | PDF 2015 | Hyperparameter Search Space Pruning - A New Component for Sequential Model-Based Hyperparameter Optimization | Martin Wistua, et al. | PDF 2015 | Joint Model Choice and Hyperparameter Optimization with Factorized Multilayer Perceptrons | Nicolas Schilling, et al. | ICTAI | PDF 2015 | Learning Hyperparameter Optimization Initializations | Martin Wistuba, et al. | DSAA | PDF 2015 | Scalable Bayesian optimization using deep neural networks | Jasper Snoek, et al. | ACM | PDF 2015 | Sequential Model-free Hyperparameter Tuning | Martin Wistuba, et al. | ICDM | PDF 2013 | Auto-WEKA: Combined Selection and Hyperparameter Optimization of Classification Algorithms | PDF 2013 | Making a Science of Model Search: Hyperparameter Optimization in Hundreds of Dimensions for Vision Architectures | J. Bergstra | JMLR | PDF 2012 | Practical Bayesian Optimization of Machine Learning Algorithms | PDF 2011 | Sequential Model-Based Optimization for General Algorithm Configuration(extended version) | PDF Evolutionary Algorithms 2018 | Autostacker: A Compositional Evolutionary Learning System | Boyuan Chen, et al. | arXiv | PDF 2017 | Large-Scale Evolution of Image Classifiers | Esteban Real, et al. | PMLR | PDF Lipschitz Functions 2017 | Global Optimization of Lipschitz functions | C´edric Malherbe, Nicolas Vayatis | arXiv | PDF Local Search 2009 | ParamILS: An Automatic Algorithm Configuration Framework | Frank Hutter, et al. | JAIR | PDF Meta Learning 2008 | Cross-Disciplinary Perspectives on Meta-Learning for Algorithm Selection | PDF Particle Swarm Optimization 2017 | Particle Swarm Optimization for Hyper-parameter Selection in Deep Neural Networks | Pablo Ribalta Lorenzo, et al. | GECCO | PDF 2008 | Particle Swarm Optimization for Parameter Determination and Feature Selection of Support Vector Machines | Shih-Wei Lin, et al. | Expert Systems with Applications | PDF Random Search 2016 | Hyperband: A Novel Bandit-Based Approach to Hyperparameter Optimization | Lisha Li, et al. | arXiv | PDF 2012 | Random Search for Hyper-Parameter Optimization | James Bergstra, Yoshua Bengio | JMLR | PDF 2011 | Algorithms for Hyper-parameter Optimization | James Bergstra, et al. | NIPS | PDF Transfer Learning 2016 | Efficient Transfer Learning Method for Automatic Hyperparameter Tuning | Dani Yogatama, Gideon Mann | JMLR | PDF 2016 | Flexible Transfer Learning Framework for Bayesian Optimisation | Tinu Theckel Joy, et al. | PAKDD | PDF 2016 | Hyperparameter Optimization Machines | Martin Wistuba, et al. | DSAA | PDF 2013 | Collaborative Hyperparameter Tuning | R´emi Bardenet, et al. | ICML | PDF Miscellaneous 2018 | Accelerating Neural Architecture Search using Performance Prediction | Bowen Baker, et al. | ICLR | PDF 2017 | Automatic Frankensteining: Creating Complex Ensembles Autonomously | Martin Wistuba, et al. | SIAM | PDF Tutorials Bayesian Optimization 2010 | A Tutorial on Bayesian Optimization of Expensive Cost Functions, with Application to Active User Modeling and Hierarchical Reinforcement Learning | PDF Meta Learning 2008 | Metalearning - A Tutorial | PDF Articles Bayesian Optimization 2016 | Bayesian Optimization for Hyperparameter Tuning | Link Meta Learning 2017 | Why Meta-learning is Crucial for Further Advances of Artificial Intelligence? | Link 2017 | Learning to learn | Link Slides Automated Feature Engineering Automated Feature Engineering for Predictive Modeling | Udyan Khurana, etc al. | PDF Hyperparameter Optimization Bayesian Optimization Bayesian Optimisation | PDF A Tutorial on Bayesian Optimization for Machine Learning | PDF Books Meta Learning 2009 | Metalearning - Applications to Data Mining | Springer | PDF Projects Advisor | Python | Open Source | Code auto-sklearn | Python | Open Source | Code Auto-WEKA | Java | Open Source | Code Hyperopt | Python | Open Source | Code Hyperopt-sklearn | Python | Open Source | Code SigOpt | Python | Commercial | Link SMAC3 | Python | Open Source | Code RoBO | Python | Open Source | Code BayesianOptimization | Python | Open Source | Code Scikit-Optimize | Python | Open Source | Code HyperBand | Python | Open Source | Code BayesOpt | C++ | Open Source | Code Optunity | Python | Open Source | Code TPOT | Python | Open Source | Code ATM | Python | Open Source | Code Cloud AutoML | Python | Commercial| Link H2O | Python | Commercial | Link DataRobot | Python | Commercial | Link MLJAR | Python | Commercial | Link MateLabs | Python | Commercial | Link MARSGGBO原创 2018-7-14
Problem set 1 // Homework 1 // Color to Greyscale Conversion //A common way to represent color images is known as RGBA - the color //is specified by how much Red, Green, and Blue is in it. //The 'A' stands for Alpha and is used for transparency; it will be //ignored in this homework. //Each channel Red, Blue, Green, and Alpha is represented by one byte. //Since we are using one byte for each color there are 256 different //possible values for each color. This means we use 4 bytes per pixel. //Greyscale images are represented by a single intensity value per pixel //which is one byte in size. //To convert an image from color to grayscale one simple method is to //set the intensity to the average of the RGB channels. But we will //use a more sophisticated method that takes into account how the eye //perceives color and weights the channels unequally. //The eye responds most strongly to green followed by red and then blue. //The NTSC (National Television System Committee) recommends the following //formula for color to greyscale conversion: //I = .299f * R + .587f * G + .114f * B //Notice the trailing f's on the numbers which indicate that they are //single precision floating point constants and not double precision //constants. //You should fill in the kernel as well as set the block and grid sizes //so that the entire image is processed. #include "reference_calc.cpp" #include "utils.h" #include <stdio.h> __global__ void rgba_to_greyscale(const uchar4* const rgbaImage, unsigned char* const greyImage, int numRows, int numCols) { //TODO //Fill in the kernel to convert from color to greyscale //the mapping from components of a uchar4 to RGBA is: // .x -> R ; .y -> G ; .z -> B ; .w -> A // //The output (greyImage) at each pixel should be the result of //applying the formula: output = .299f * R + .587f * G + .114f * B; //Note: We will be ignoring the alpha channel for this conversion //First create a mapping from the 2D block and grid locations //to an absolute 2D location in the image, then use that to //calculate a 1D offset int x = blockIdx.x * blockDim.x + threadIdx.x; int y = blockIdx.y * blockDim.y + threadIdx.y; if(x < numCols && y < numRows) { greyImage[y * numRows + x] = 0.299f * rgbaImage[y * numRows + x].x + 0.587f * rgbaImage[y * numRows + x].y + 0.114f * rgbaImage[y * numRows + x].z; } } void your_rgba_to_greyscale(const uchar4 * const h_rgbaImage, uchar4 * const d_rgbaImage, unsigned char* const d_greyImage, size_t numRows, size_t numCols) { //You must fill in the correct sizes for the blockSize and gridSize //currently only one block with one thread is being launched const dim3 blockSize(32, 8); //TODO const dim3 gridSize( (numRows+blockSize.x-1)/blockSize.x, (numCols+blockSize.y-1)/blockSize.y, 1); //TODO rgba_to_greyscale<<<gridSize, blockSize>>>(d_rgbaImage, d_greyImage, numRows, numCols); cudaDeviceSynchronize(); checkCudaErrors(cudaGetLastError()); } Problem set 2 // Homework 2 // Image Blurring // // In this homework we are blurring an image. To do this, imagine that we have // a square array of weight values. For each pixel in the image, imagine that we // overlay this square array of weights on top of the image such that the center // of the weight array is aligned with the current pixel. To compute a blurred // pixel value, we multiply each pair of numbers that line up. In other words, we // multiply each weight with the pixel underneath it. Finally, we add up all of the // multiplied numbers and assign that value to our output for the current pixel. // We repeat this process for all the pixels in the image. // To help get you started, we have included some useful notes here. //**************************************************************************** // For a color image that has multiple channels, we suggest separating // the different color channels so that each color is stored contiguously // instead of being interleaved. This will simplify your code. // That is instead of RGBARGBARGBARGBA... we suggest transforming to three // arrays (as in the previous homework we ignore the alpha channel again): // 1) RRRRRRRR... // 2) GGGGGGGG... // 3) BBBBBBBB... // // The original layout is known an Array of Structures (AoS) whereas the // format we are converting to is known as a Structure of Arrays (SoA). // As a warm-up, we will ask you to write the kernel that performs this // separation. You should then write the "meat" of the assignment, // which is the kernel that performs the actual blur. We provide code that // re-combines your blurred results for each color channel. //**************************************************************************** // You must fill in the gaussian_blur kernel to perform the blurring of the // inputChannel, using the array of weights, and put the result in the outputChannel. // Here is an example of computing a blur, using a weighted average, for a single // pixel in a small image. // // Array of weights: // // 0.0 0.2 0.0 // 0.2 0.2 0.2 // 0.0 0.2 0.0 // // Image (note that we align the array of weights to the center of the box): // // 1 2 5 2 0 3 // ------- // 3 |2 5 1| 6 0 0.0*2 + 0.2*5 + 0.0*1 + // | | // 4 |3 6 2| 1 4 -> 0.2*3 + 0.2*6 + 0.2*2 + -> 3.2 // | | // 0 |4 0 3| 4 2 0.0*4 + 0.2*0 + 0.0*3 // ------- // 9 6 5 0 3 9 // // (1) (2) (3) // // A good starting place is to map each thread to a pixel as you have before. // Then every thread can perform steps 2 and 3 in the diagram above // completely independently of one another. // Note that the array of weights is square, so its height is the same as its width. // We refer to the array of weights as a filter, and we refer to its width with the // variable filterWidth. //**************************************************************************** // Your homework submission will be evaluated based on correctness and speed. // We test each pixel against a reference solution. If any pixel differs by // more than some small threshold value, the system will tell you that your // solution is incorrect, and it will let you try again. // Once you have gotten that working correctly, then you can think about using // shared memory and having the threads cooperate to achieve better performance. //**************************************************************************** // Also note that we've supplied a helpful debugging function called checkCudaErrors. // You should wrap your allocation and copying statements like we've done in the // code we're supplying you. Here is an example of the unsafe way to allocate // memory on the GPU: // // cudaMalloc(&d_red, sizeof(unsigned char) * numRows * numCols); // // Here is an example of the safe way to do the same thing: // // checkCudaErrors(cudaMalloc(&d_red, sizeof(unsigned char) * numRows * numCols)); // // Writing code the safe way requires slightly more typing, but is very helpful for // catching mistakes. If you write code the unsafe way and you make a mistake, then // any subsequent kernels won't compute anything, and it will be hard to figure out // why. Writing code the safe way will inform you as soon as you make a mistake. // Finally, remember to free the memory you allocate at the end of the function. //**************************************************************************** #include "utils.h" __global__ void gaussian_blur(const unsigned char* const inputChannel, unsigned char* const outputChannel, int numRows, int numCols, const float* const filter, const int filterWidth) { // TODO // NOTE: Be sure to compute any intermediate results in floating point // before storing the final result as unsigned char. // NOTE: Be careful not to try to access memory that is outside the bounds of // the image. You'll want code that performs the following check before accessing // GPU memory: // // if ( absolute_image_position_x >= numCols || // absolute_image_position_y >= numRows ) // { // return; // } // NOTE: If a thread's absolute position 2D position is within the image, but some of // its neighbors are outside the image, then you will need to be extra careful. Instead // of trying to read such a neighbor value from GPU memory (which won't work because // the value is out of bounds), you should explicitly clamp the neighbor values you read // to be within the bounds of the image. If this is not clear to you, then please refer // to sequential reference solution for the exact clamping semantics you should follow. int x = blockIdx.x * blockDim.x + threadIdx.x; int y = blockIdx.y * blockDim.y + threadIdx.y; if( x < numRows && y < numCols){ int pos = y * numCols + x; assert((filterWidth % 2) == 1); for(int filterRow = -filterWidth /2 ; filterRow <= filterWidth / 2; ++filterRow) for(int filterCol = -filterWidth / 2; filterCol <= filterWidth / 2; ++filterCol){ int neighborRows = min(numRows - 1,max(0,y + filterRow)); int neighborCols = min(numCols - 1, max(0,x + filterCol)); int pos = neighborCols * numCols + neighborRows; int neighbor = static_cast<float>(inputChannel[pos]); int filter_pos = (filterRow + filterWidth / 2)* filterWidth; outputChannel[pos] += outputChannel[neighbor] * filter[filter_pos]; } } } //This kernel takes in an image represented as a uchar4 and splits //it into three images consisting of only one color channel each __global__ void separateChannels(const uchar4* const inputImageRGBA, int numRows, int numCols, unsigned char* const redChannel, unsigned char* const greenChannel, unsigned char* const blueChannel) { // TODO // // NOTE: Be careful not to try to access memory that is outside the bounds of // the image. You'll want code that performs the following check before accessing // GPU memory: // // if ( absolute_image_position_x >= numCols || // absolute_image_position_y >= numRows ) // { // return; // } int x = blockIdx.x * blockDim.x + threadIdx.x; int y = blockIdx.y * blockDim.y + threadIdx.y; if(x < numCols && y < numRows){ redChannel[y * numCols + x ] = inputImageRGBA[y * numCols + x].x; greenChannel[y * numCols + x ] = inputImageRGBA[y * numCols + x].y; blueChannel[y * numCols + x ] = inputImageRGBA[y * numCols + x].z; } } //This kernel takes in three color channels and recombines them //into one image. The alpha channel is set to 255 to represent //that this image has no transparency. __global__ void recombineChannels(const unsigned char* const redChannel, const unsigned char* const greenChannel, const unsigned char* const blueChannel, uchar4* const outputImageRGBA, int numRows, int numCols) { const int2 thread_2D_pos = make_int2( blockIdx.x * blockDim.x + threadIdx.x, blockIdx.y * blockDim.y + threadIdx.y); const int thread_1D_pos = thread_2D_pos.y * numCols + thread_2D_pos.x; //make sure we don't try and access memory outside the image //by having any threads mapped there return early if (thread_2D_pos.x >= numCols || thread_2D_pos.y >= numRows) return; unsigned char red = redChannel[thread_1D_pos]; unsigned char green = greenChannel[thread_1D_pos]; unsigned char blue = blueChannel[thread_1D_pos]; //Alpha should be 255 for no transparency uchar4 outputPixel = make_uchar4(red, green, blue, 255); outputImageRGBA[thread_1D_pos] = outputPixel; } unsigned char *d_red, *d_green, *d_blue; float *d_filter; void allocateMemoryAndCopyToGPU(const size_t numRowsImage, const size_t numColsImage, const float* const h_filter, const size_t filterWidth) { //allocate memory for the three different channels //original checkCudaErrors(cudaMalloc(&d_red, sizeof(unsigned char) * numRowsImage * numColsImage)); checkCudaErrors(cudaMalloc(&d_green, sizeof(unsigned char) * numRowsImage * numColsImage)); checkCudaErrors(cudaMalloc(&d_blue, sizeof(unsigned char) * numRowsImage * numColsImage)); //TODO: //Allocate memory for the filter on the GPU //Use the pointer d_filter that we have already declared for you //You need to allocate memory for the filter with cudaMalloc //be sure to use checkCudaErrors like the above examples to //be able to tell if anything goes wrong //IMPORTANT: Notice that we pass a pointer to a pointer to cudaMalloc checkCudaErrors(cudaMalloc(&d_filter,sizeof(float) * filterWidth * filterWidth)); //TODO: //Copy the filter on the host (h_filter) to the memory you just allocated //on the GPU. cudaMemcpy(dst, src, numBytes, cudaMemcpyHostToDevice); //Remember to use checkCudaErrors! checkCudaErrors(cudaMemcpy(d_filter,h_filter,sizeof(float) * filterWidth * filterWidth,cudaMemcpyHostToDevice)); } void your_gaussian_blur(const uchar4 * const h_inputImageRGBA, uchar4 * const d_inputImageRGBA, uchar4* const d_outputImageRGBA, const size_t numRows, const size_t numCols, unsigned char *d_redBlurred, unsigned char *d_greenBlurred, unsigned char *d_blueBlurred, const int filterWidth) { //TODO: Set reasonable block size (i.e., number of threads per block) const dim3 blockSize(32, 32, 1); //TODO: //Compute correct grid size (i.e., number of blocks per kernel launch) //from the image size and and block size. const dim3 gridSize((numCols + blockSize.x - 1) / blockSize.x, (numRows + blockSize.y - 1) / blockSize.y,1); //TODO: Launch a kernel for separating the RGBA image into different color channels /* unsigned char* d_red; unsigned char* d_green; unsigned char* d_blue; checkCudaErrors(cudaMalloc((void**)&d_red,numRows * numCols *sizeof(unsigned char))); checkCudaErrors(cudaMalloc((void**)&d_green,numRows * numCols *sizeof(unsigned char))); checkCudaErrors(cudaMalloc((void**)&d_blue,numRows * numCols *sizeof(unsigned char))); */ separateChannels<<< gridSize,blockSize>>>(d_inputImageRGBA, numRows, numCols, d_red, d_green, d_blue); // Call cudaDeviceSynchronize(), then call checkCudaErrors() immediately after // launching your kernel to make sure that you didn't make any mistakes. cudaDeviceSynchronize(); checkCudaErrors(cudaGetLastError()); //TODO: Call your convolution kernel here 3 times, once for each color channel. gaussian_blur<<<gridSize,blockSize>>>(d_red, d_redBlurred, numRows, numCols, d_filter, filterWidth); gaussian_blur<<<gridSize,blockSize>>>(d_blue, d_blueBlurred, numRows, numCols, d_filter, filterWidth); gaussian_blur<<<gridSize,blockSize>>>(d_green, d_greenBlurred, numRows, numCols, d_filter, filterWidth); // Again, call cudaDeviceSynchronize(), then call checkCudaErrors() immediately after // launching your kernel to make sure that you didn't make any mistakes. cudaDeviceSynchronize(); checkCudaErrors(cudaGetLastError()); // Now we recombine your results. We take care of launching this kernel for you. // // NOTE: This kernel launch depends on the gridSize and blockSize variables, // which you must set yourself. recombineChannels<<<gridSize, blockSize>>>(d_redBlurred, d_greenBlurred, d_blueBlurred, d_outputImageRGBA, numRows, numCols); cudaDeviceSynchronize(); checkCudaErrors(cudaGetLastError()); } //Free all the memory that we allocated //TODO: make sure you free any arrays that you allocated void cleanup() { checkCudaErrors(cudaFree(d_red)); checkCudaErrors(cudaFree(d_green)); checkCudaErrors(cudaFree(d_blue)); } Problem set 3 /* Udacity Homework 3 HDR Tone-mapping Background HDR ============== A High Dynamic Range (HDR) image contains a wider variation of intensity and color than is allowed by the RGB format with 1 byte per channel that we have used in the previous assignment. To store this extra information we use single precision floating point for each channel. This allows for an extremely wide range of intensity values. In the image for this assignment, the inside of church with light coming in through stained glass windows, the raw input floating point values for the channels range from 0 to 275. But the mean is .41 and 98% of the values are less than 3! This means that certain areas (the windows) are extremely bright compared to everywhere else. If we linearly map this [0-275] range into the [0-255] range that we have been using then most values will be mapped to zero! The only thing we will be able to see are the very brightest areas - the windows - everything else will appear pitch black. The problem is that although we have cameras capable of recording the wide range of intensity that exists in the real world our monitors are not capable of displaying them. Our eyes are also quite capable of observing a much wider range of intensities than our image formats / monitors are capable of displaying. Tone-mapping is a process that transforms the intensities in the image so that the brightest values aren't nearly so far away from the mean. That way when we transform the values into [0-255] we can actually see the entire image. There are many ways to perform this process and it is as much an art as a science - there is no single "right" answer. In this homework we will implement one possible technique. Background Chrominance-Luminance ================================ The RGB space that we have been using to represent images can be thought of as one possible set of axes spanning a three dimensional space of color. We sometimes choose other axes to represent this space because they make certain operations more convenient. Another possible way of representing a color image is to separate the color information (chromaticity) from the brightness information. There are multiple different methods for doing this - a common one during the analog television days was known as Chrominance-Luminance or YUV. We choose to represent the image in this way so that we can remap only the intensity channel and then recombine the new intensity values with the color information to form the final image. Old TV signals used to be transmitted in this way so that black & white televisions could display the luminance channel while color televisions would display all three of the channels. Tone-mapping ============ In this assignment we are going to transform the luminance channel (actually the log of the luminance, but this is unimportant for the parts of the algorithm that you will be implementing) by compressing its range to [0, 1]. To do this we need the cumulative distribution of the luminance values. Example ------- input : [2 4 3 3 1 7 4 5 7 0 9 4 3 2] min / max / range: 0 / 9 / 9 histo with 3 bins: [4 7 3] cdf : [4 11 14] Your task is to calculate this cumulative distribution by following these steps. */ #include "utils.h" __global__ void reduce_minimum(float * d_out, const float * const d_in, const size_t numItem) { // sdata is allocated in the kernel call: 3rd arg to <<<b, t, shmem>>> extern __shared__ float sdata[]; int myId = threadIdx.x + blockDim.x * blockIdx.x; int tid = threadIdx.x; // load shared mem from global mem sdata[tid] = 99999999999.0f; if (myId < numItem) sdata[tid] = d_in[myId]; __syncthreads(); // make sure entire block is loaded! // do reduction in shared mem for (unsigned int s = blockDim.x / 2; s > 0; s >>= 1) { if (tid < s) { sdata[tid] = min(sdata[tid], sdata[tid + s]); } __syncthreads(); // make sure all adds at one stage are done! } // only thread 0 writes result for this block back to global mem if (tid == 0) { d_out[blockIdx.x] = sdata[0]; } } __global__ void reduce_maximum(float * d_out, const float * const d_in, const size_t numItem) { // sdata is allocated in the kernel call: 3rd arg to <<<b, t, shmem>>> extern __shared__ float sdata[]; int myId = threadIdx.x + blockDim.x * blockIdx.x; int tid = threadIdx.x; // load shared mem from global mem sdata[tid] = -99999999999.0f; if (myId < numItem) sdata[tid] = d_in[myId]; __syncthreads(); // make sure entire block is loaded! // do reduction in shared mem for (unsigned int s = blockDim.x / 2; s > 0; s >>= 1) { if (tid < s) { sdata[tid] = max(sdata[tid], sdata[tid + s]); } __syncthreads(); // make sure all adds at one stage are done! } // only thread 0 writes result for this block back to global mem if (tid == 0) { d_out[blockIdx.x] = sdata[0]; } } __global__ void histogram(unsigned int *d_bins, const float * const d_in, const size_t numBins, const float min_logLum, const float range, const size_t numRows, const size_t numCols) { int myId = threadIdx.x + blockDim.x * blockIdx.x; if (myId >= (numRows * numCols)) return; float myItem = d_in[myId]; int myBin = (myItem - min_logLum) / range * numBins; atomicAdd(&(d_bins[myBin]), 1); } __global__ void scan(unsigned int *d_out, unsigned int *d_sums, const unsigned int * const d_in, const unsigned int numBins, const unsigned int numElems) { extern __shared__ float sdata[]; int myId = blockIdx.x * blockDim.x + threadIdx.x; int tid = threadIdx.x; int offset = 1; // load two items per thread into shared memory if ((2 * myId) < numBins) { sdata[2 * tid] = d_in[2 * myId]; } else { sdata[2 * tid] = 0; } if ((2 * myId + 1) < numBins) { sdata[2 * tid + 1] = d_in[2 * myId + 1]; } else { sdata[2 * tid + 1] = 0; } // Reduce for (unsigned int d = numElems >> 1; d > 0; d >>= 1) { if (tid < d) { int ai = offset * (2 * tid + 1) - 1; int bi = offset * (2 * tid + 2) - 1; sdata[bi] += sdata[ai]; } offset *= 2; __syncthreads(); } // clear the last element if (tid == 0) { d_sums[blockIdx.x] = sdata[numElems - 1]; sdata[numElems - 1] = 0; } // Down Sweep for (unsigned int d = 1; d < numElems; d *= 2) { offset >>= 1; if (tid < d) { int ai = offset * (2 * tid + 1) - 1; int bi = offset * (2 * tid + 2) - 1; float t = sdata[ai]; sdata[ai] = sdata[bi]; sdata[bi] += t; } __syncthreads(); } // write the output to global memory if ((2 * myId) < numBins) { d_out[2 * myId] = sdata[2 * tid]; } if ((2 * myId + 1) < numBins) { d_out[2 * myId + 1] = sdata[2 * tid + 1]; } } // This version only works for one single block! The size of the array of items __global__ void scan2(unsigned int *d_out, const unsigned int * const d_in, const unsigned int numBins, const unsigned int numElems) { extern __shared__ float sdata[]; int tid = threadIdx.x; int offset = 1; // load two items per thread into shared memory if ((2 * tid) < numBins) { sdata[2 * tid] = d_in[2 * tid]; } else { sdata[2 * tid] = 0; } if ((2 * tid + 1) < numBins) { sdata[2 * tid + 1] = d_in[2 * tid + 1]; } else { sdata[2 * tid + 1] = 0; } // Reduce for (unsigned int d = numElems >> 1; d > 0; d >>= 1) { if (tid < d) { int ai = offset * (2 * tid + 1) - 1; int bi = offset * (2 * tid + 2) - 1; sdata[bi] += sdata[ai]; } offset *= 2; __syncthreads(); } // clear the last element if (tid == 0) { sdata[numElems - 1] = 0; } // Down Sweep for (unsigned int d = 1; d < numElems; d *= 2) { offset >>= 1; if (tid < d) { int ai = offset * (2 * tid + 1) - 1; int bi = offset * (2 * tid + 2) - 1; float t = sdata[ai]; sdata[ai] = sdata[bi]; sdata[bi] += t; } __syncthreads(); } // write the output to global memory if ((2 * tid) < numBins) { d_out[2 * tid] = sdata[2 * tid]; } if ((2 * tid + 1) < numBins) { d_out[2 * tid + 1] = sdata[2 * tid + 1]; } } __global__ void add_scan(unsigned int *d_out, const unsigned int * const d_in, const unsigned int numBins) { if (blockIdx.x == 0) return; int myId = blockIdx.x * blockDim.x + threadIdx.x; unsigned int myOffset = d_in[blockIdx.x]; if ((2 * myId) < numBins) { d_out[2 * myId] += myOffset; } if ((2 * myId + 1) < numBins) { d_out[2 * myId + 1] += myOffset; } } void your_histogram_and_prefixsum(const float* const d_logLuminance, unsigned int* const d_cdf, float &min_logLum, float &max_logLum, const size_t numRows, const size_t numCols, const size_t numBins) { //TODO /*Here are the steps you need to implement 1) find the minimum and maximum value in the input logLuminance channel store in min_logLum and max_logLum 2) subtract them to find the range 3) generate a histogram of all the values in the logLuminance channel using the formula: bin = (lum[i] - lumMin) / lumRange * numBins 4) Perform an exclusive scan (prefix sum) on the histogram to get the cumulative distribution of luminance values (this should go in the incoming d_cdf pointer which already has been allocated for you) */ // Initialization unsigned int numItem = numRows * numCols; dim3 blockSize(256, 1, 1); dim3 gridSize(numItem / blockSize.x + 1, 1, 1); float * d_inter_min; float * d_inter_max; unsigned int * d_histogram; unsigned int * d_sums; unsigned int * d_incr; checkCudaErrors(cudaMalloc(&d_inter_min, sizeof(float) * gridSize.x)); checkCudaErrors(cudaMalloc(&d_inter_max, sizeof(float) * gridSize.x)); checkCudaErrors(cudaMalloc(&d_histogram, sizeof(unsigned int) * numBins)); checkCudaErrors(cudaMemset(d_histogram, 0, sizeof(unsigned int) * numBins)); // Step 1: Reduce (min and max). It could be done in one step only! reduce_minimum<<<gridSize, blockSize, sizeof(float) * blockSize.x>>>(d_inter_min, d_logLuminance, numItem); reduce_maximum<<<gridSize, blockSize, sizeof(float) * blockSize.x>>>(d_inter_max, d_logLuminance, numItem); numItem = gridSize.x; gridSize.x = numItem / blockSize.x + 1; while (numItem > 1) { reduce_minimum<<<gridSize, blockSize, sizeof(float) * blockSize.x>>>(d_inter_min, d_inter_min, numItem); reduce_maximum<<<gridSize, blockSize, sizeof(float) * blockSize.x>>>(d_inter_max, d_inter_max, numItem); numItem = gridSize.x; gridSize.x = numItem / blockSize.x + 1; } // Step 2: Range checkCudaErrors(cudaMemcpy(&min_logLum, d_inter_min, sizeof(float), cudaMemcpyDeviceToHost)); checkCudaErrors(cudaMemcpy(&max_logLum, d_inter_max, sizeof(float), cudaMemcpyDeviceToHost)); float range = max_logLum - min_logLum; // Step 3: Histogram gridSize.x = numRows * numCols / blockSize.x + 1; histogram<<<gridSize, blockSize>>>(d_histogram, d_logLuminance, numBins, min_logLum, range, numRows, numCols); // Step 4: Exclusive scan - Blelloch unsigned int numElems = 256; blockSize.x = numElems / 2; gridSize.x = numBins / numElems; if (numBins % numElems != 0) gridSize.x++; checkCudaErrors(cudaMalloc(&d_sums, sizeof(unsigned int) * gridSize.x)); checkCudaErrors(cudaMemset(d_sums, 0, sizeof(unsigned int) * gridSize.x)); // First-level scan to obtain the scanned blocks scan<<<gridSize, blockSize, sizeof(float) * numElems>>>(d_cdf, d_sums, d_histogram, numBins, numElems); // Second-level scan to obtain the scanned blocks sums numElems = gridSize.x; // Look for the next power of 2 (32 bits) unsigned int nextPow = numElems; nextPow--; nextPow = (nextPow >> 1) | nextPow; nextPow = (nextPow >> 2) | nextPow; nextPow = (nextPow >> 4) | nextPow; nextPow = (nextPow >> 8) | nextPow; nextPow = (nextPow >> 16) | nextPow; nextPow++; blockSize.x = nextPow / 2; gridSize.x = 1; checkCudaErrors(cudaMalloc(&d_incr, sizeof(unsigned int) * numElems)); checkCudaErrors(cudaMemset(d_incr, 0, sizeof(unsigned int) * numElems)); scan2<<<gridSize, blockSize, sizeof(float) * nextPow>>>(d_incr, d_sums, numElems, nextPow); // Add scanned block sum i to all values of scanned block i numElems = 256; blockSize.x = numElems / 2; gridSize.x = numBins / numElems; if (numBins % numElems != 0) gridSize.x++; add_scan<<<gridSize, blockSize>>>(d_cdf, d_incr, numBins); // Clean memory checkCudaErrors(cudaFree(d_inter_min)); checkCudaErrors(cudaFree(d_inter_max)); checkCudaErrors(cudaFree(d_histogram)); checkCudaErrors(cudaFree(d_sums)); checkCudaErrors(cudaFree(d_incr)); } Problem set 4 //Udacity HW 4 //Radix Sorting #include "utils.h" #include <thrust/host_vector.h> /* Red Eye Removal =============== For this assignment we are implementing red eye removal. This is accomplished by first creating a score for every pixel that tells us how likely it is to be a red eye pixel. We have already done this for you - you are receiving the scores and need to sort them in ascending order so that we know which pixels to alter to remove the red eye. Note: ascending order == smallest to largest Each score is associated with a position, when you sort the scores, you must also move the positions accordingly. Implementing Parallel Radix Sort with CUDA ========================================== The basic idea is to construct a histogram on each pass of how many of each "digit" there are. Then we scan this histogram so that we know where to put the output of each digit. For example, the first 1 must come after all the 0s so we have to know how many 0s there are to be able to start moving 1s into the correct position. 1) Histogram of the number of occurrences of each digit 2) Exclusive Prefix Sum of Histogram 3) Determine relative offset of each digit For example [0 0 1 1 0 0 1] -> [0 1 0 1 2 3 2] 4) Combine the results of steps 2 & 3 to determine the final output location for each element and move it there LSB Radix sort is an out-of-place sort and you will need to ping-pong values between the input and output buffers we have provided. Make sure the final sorted results end up in the output buffer! Hint: You may need to do a copy at the end. */ /* void your_sort(unsigned int* const d_inputVals, unsigned int* const d_inputPos, unsigned int* const d_outputVals, unsigned int* const d_outputPos, const size_t numElems) { //TODO //PUT YOUR SORT HERE const int numBits = 1; unsigned int* d_splitVals; checkCudaErrors(cudaMalloc((void**)&d_splitVals,numElems * sizeof(unsigned))); unsigned int* d_cdf; checkCudaErrors(cudaMalloc((void**)&d_cdf,numElems * sizeof(unsigned))); //d_scatterAddr keeps track of the scattered oringinal addresses at every pass unsigned int* d_scatterAddr; checkCudaErrors(cudaMalloc((void**)&d_scatterAddr,numElems * sizeof(unsigned))); checkCudaErrors(cudaMemcpy(d_scatterAddr,d_inputPos,numElems * sizeof(unsigned),cudaMemcpyDeviceToDevice)); } */ //Udacity HW 4 //Radix Sorting #include "reference_calc.cpp" #include "utils.h" #include <float.h> #include <math.h> #include <stdio.h> #include "utils.h" /* Red Eye Removal =============== For this assignment we are implementing red eye removal. This is accomplished by first creating a score for every pixel that tells us how likely it is to be a red eye pixel. We have already done this for you - you are receiving the scores and need to sort them in ascending order so that we know which pixels to alter to remove the red eye. Note: ascending order == smallest to largest Each score is associated with a position, when you sort the scores, you must also move the positions accordingly. Implementing Parallel Radix Sort with CUDA ========================================== The basic idea is to construct a histogram on each pass of how many of each "digit" there are. Then we scan this histogram so that we know where to put the output of each digit. For example, the first 1 must come after all the 0s so we have to know how many 0s there are to be able to start moving 1s into the correct position. 1) Histogram of the number of occurrences of each digit 2) Exclusive Prefix Sum of Histogram 3) Determine relative offset of each digit For example [0 0 1 1 0 0 1] -> [0 1 0 1 2 3 2] 4) Combine the results of steps 2 & 3 to determine the final output location for each element and move it there LSB Radix sort is an out-of-place sort and you will need to ping-pong values between the input and output buffers we have provided. Make sure the final sorted results end up in the output buffer! Hint: You may need to do a copy at the end. */ const int MAX_THREADS_PER_BLOCK = 512; /*---------------------------------------------------------------------------------*/ /////////////////////////////////////////////////////// //--------------------- KERNELS ---------------------// /////////////////////////////////////////////////////// __global__ void split_array(unsigned int* d_inputVals, unsigned int* d_splitVals, const size_t numElems, unsigned int mask, unsigned int ibit) { int array_idx = blockIdx.x*blockDim.x + threadIdx.x; if (array_idx >= numElems) return; // Split based on whether inputVals digit is 1 or 0: d_splitVals[array_idx] = !(d_inputVals[array_idx] & mask); } __global__ void blelloch_scan_single_block(unsigned int* d_in_array, const size_t numBins, unsigned normalization=0) /* Computes the blelloch exclusive scan for a cumulative distribution function of a histogram, one block at a time. \Params: * d_in_array - input array of histogram values in each bin. Gets converted to cdf by the end of the function. * numBins - number of bins in the histogram (Must be < 2*MAX_THREADS_PER_BLOCK) * normalization - constant value to add to all bins (when doing full exclusive sum scan over multiple blocks). */ { int thid = threadIdx.x; extern __shared__ float temp_array[]; // Make sure that we do not read from undefined part of array if it // is smaller than the number of threads that we gave defined. If // that is the case, the final values of the input array are // extended to zero. if (thid < numBins) temp_array[thid] = d_in_array[thid]; else temp_array[thid] = 0; if( (thid + numBins/2) < numBins) temp_array[thid + numBins/2] = d_in_array[thid + numBins/2]; else temp_array[thid + numBins/2] = 0; __syncthreads(); // Part 1: Up Sweep, reduction // Iterate log_2(numBins) times, and each element adds value 'stride' // elements away to its own value. int stride = 1; for (int d = numBins>>1; d > 0; d>>=1) { if (thid < d) { int neighbor = stride*(2*thid+1) - 1; int index = stride*(2*thid+2) - 1; temp_array[index] += temp_array[neighbor]; } stride *=2; __syncthreads(); } // Now set last element to identity: if (thid == 0) temp_array[numBins-1] = 0; // Part 2: Down sweep // Iterate log(n) times. Each thread adds value stride elements away to // its own value, and sets the value stride elements away to its own // previous value. for (int d=1; d<numBins; d *= 2) { stride >>= 1; __syncthreads(); if(thid < d) { int neighbor = stride*(2*thid+1) - 1; int index = stride*(2*thid+2) - 1; float t = temp_array[neighbor]; temp_array[neighbor] = temp_array[index]; temp_array[index] += t; } } __syncthreads(); if (thid < numBins) d_in_array[thid] = temp_array[thid] + normalization; if ((thid + numBins/2) < numBins) d_in_array[thid + numBins/2] = temp_array[thid + numBins/2] + normalization; } __global__ void compute_outputPos(const unsigned int* d_inputVals, unsigned int* d_outputVals, unsigned int* d_outputPos, unsigned int* d_tVals, const unsigned int* d_splitVals, const unsigned int* d_cdf, const unsigned int totalFalses, const unsigned int numElems) { int thid = threadIdx.x; int global_id = blockIdx.x*blockDim.x + thid; if (global_id >= numElems) return; d_tVals[global_id] = global_id - d_cdf[global_id] + totalFalses; unsigned int scatter = (!(d_splitVals[global_id]) ? d_tVals[global_id] : d_cdf[global_id] ); d_outputPos[global_id] = scatter; } __global__ void do_scatter(unsigned int* d_outputVals, const unsigned int* d_inputVals, unsigned int* d_outputPos, unsigned int* d_inputPos, unsigned int* d_scatterAddr, const unsigned int numElems) { int global_id = blockIdx.x*blockDim.x + threadIdx.x; if(global_id >= numElems) return; d_outputVals[d_outputPos[global_id]] = d_inputVals[global_id]; d_scatterAddr[d_outputPos[global_id]] = d_inputPos[global_id]; } /////////////////////////////////////////////////////////// //--------------------- END KERNELS ---------------------// /////////////////////////////////////////////////////////// void full_blelloch_exclusive_scan(unsigned int* d_binScan, const size_t totalNumElems) /* NOTE: blelloch_scan_single_block() does an exclusive sum scan over an array (balanced tree) of size 2*MAX_THREADS_PER_BLOCK, by performing the up and down sweep of the scan in shared memory (which is limited in size). In order to scan over an entire array of size > 2*MAX_THREADS_PER_BLOCK, we employ the following procedure: 1) Compute total number of blocks of size 2*MAX_THREADS_PER_BLOCK 2) Loop over each block and compute a partial array of number of bins: 2*MAX_THREADS_PER_BLOCK 3) Give this partial array to blelloch_scan_single_block() and let it return the sum scan. 4) Now, one has a full array of partial sum scans, and then we take the last element of the j-1 block and add it to each element of the jth block. \Params: * d_binScan - starts out as the "histogram" or in this case, the split_array that we will perform an exclusive scan over. * totalNumElems - total number of elements in the d_binScan array to perform an exclusive scan over. */ { int nthreads = MAX_THREADS_PER_BLOCK; int nblocksTotal = (totalNumElems/2 - 1) / nthreads + 1; int partialBins = 2*nthreads; int smSize = partialBins*sizeof(unsigned); // Need a balanced d_binScan array so that on final block, correct // values are given to d_partialBinScan. // 1. define balanced bin scan // 2. set all values to zero // 3. copy all of binScan into binScanBalanced. unsigned int* d_binScanBalanced; unsigned int balanced_size = nblocksTotal*partialBins*sizeof(unsigned); checkCudaErrors(cudaMalloc((void**)&d_binScanBalanced, balanced_size)); checkCudaErrors(cudaMemset(d_binScanBalanced, 0, balanced_size)); checkCudaErrors(cudaMemcpy(d_binScanBalanced, d_binScan, totalNumElems*sizeof(unsigned), cudaMemcpyDeviceToDevice)); unsigned int* d_partialBinScan; checkCudaErrors(cudaMalloc((void**)&d_partialBinScan, partialBins*sizeof(unsigned))); unsigned int* normalization = (unsigned*)malloc(sizeof(unsigned)); unsigned int* lastVal = (unsigned*)malloc(sizeof(unsigned)); for (unsigned iblock = 0; iblock < nblocksTotal; iblock++) { unsigned offset = iblock*partialBins; // Copy binScan Partition into partialBinScan checkCudaErrors(cudaMemcpy(d_partialBinScan, (d_binScanBalanced + offset), smSize, cudaMemcpyDeviceToDevice)); if (iblock > 0) { // get normalization - final value in last cdf bin + last value in original checkCudaErrors(cudaMemcpy(normalization, (d_binScanBalanced + (offset-1)), sizeof(unsigned), cudaMemcpyDeviceToHost)); checkCudaErrors(cudaMemcpy(lastVal, (d_binScan + (offset-1)), sizeof(unsigned), cudaMemcpyDeviceToHost)); *normalization += (*lastVal); } else *normalization = 0; blelloch_scan_single_block<<<1, nthreads, smSize>>>(d_partialBinScan, partialBins, *normalization); cudaDeviceSynchronize(); checkCudaErrors(cudaGetLastError()); // Copy partialBinScan back into binScanBalanced: checkCudaErrors(cudaMemcpy((d_binScanBalanced+offset), d_partialBinScan, smSize, cudaMemcpyDeviceToDevice)); } // ONE BLOCK WORKING HERE!!! // binScanBalanced now needs to be copied into d_binScan! checkCudaErrors(cudaMemcpy(d_binScan,d_binScanBalanced,totalNumElems*sizeof(unsigned), cudaMemcpyDeviceToDevice)); free(normalization); free(lastVal); checkCudaErrors(cudaFree(d_binScanBalanced)); checkCudaErrors(cudaFree(d_partialBinScan)); } void compute_scatter_addresses(const unsigned int* d_inputVals, unsigned int* d_outputVals, unsigned int* d_inputPos, unsigned int* d_outputPos, unsigned int* d_scatterAddr, const unsigned int* const d_splitVals, const unsigned int* const d_cdf, const unsigned totalFalses, const size_t numElems) /* Modifies d_outputVals and d_outputPos */ { unsigned int* d_tVals; checkCudaErrors(cudaMalloc((void**)&d_tVals, numElems*sizeof(unsigned))); int nthreads = MAX_THREADS_PER_BLOCK; int nblocks = (numElems - 1) / nthreads + 1; compute_outputPos<<<nblocks, nthreads>>>(d_inputVals, d_outputVals, d_outputPos, d_tVals, d_splitVals, d_cdf, totalFalses, numElems); // Testing purposes - REMOVE in production cudaDeviceSynchronize(); checkCudaErrors(cudaGetLastError()); do_scatter<<<nblocks, nthreads>>>(d_outputVals, d_inputVals, d_outputPos, d_inputPos, d_scatterAddr, numElems); // Testing purposes - REMOVE in production cudaDeviceSynchronize(); checkCudaErrors(cudaGetLastError()); checkCudaErrors(cudaFree(d_tVals)); } void your_sort(unsigned int* const d_inputVals, unsigned int* const d_inputPos, unsigned int* const d_outputVals, unsigned int* const d_outputPos, const size_t numElems) { //-----Set up----- const int numBits = 1; unsigned int* d_splitVals; checkCudaErrors(cudaMalloc((void**)&d_splitVals, numElems*sizeof(unsigned))); unsigned int* d_cdf; checkCudaErrors(cudaMalloc((void**)&d_cdf, numElems*sizeof(unsigned))); // d_scatterAddr keeps track of the scattered original addresses at every pass unsigned int* d_scatterAddr; checkCudaErrors(cudaMalloc((void**)&d_scatterAddr, numElems*sizeof(unsigned))); checkCudaErrors(cudaMemcpy(d_scatterAddr, d_inputPos, numElems*sizeof(unsigned), cudaMemcpyDeviceToDevice)); // Need a global device array for blelloch scan: const int nBlellochBins = 1 << unsigned(log((long double)numElems)/log((long double)2) + 0.5); unsigned int* d_blelloch; checkCudaErrors(cudaMalloc((void**)&d_blelloch, nBlellochBins*sizeof(unsigned))); //printf(" numElems: %lu, numBlellochBins: %d \n",numElems, nBlellochBins); unsigned int* d_inVals = d_inputVals; unsigned int* d_inPos = d_inputPos; unsigned int* d_outVals = d_outputVals; unsigned int* d_outPos = d_outputPos; // Testing purposes - also free'd at end unsigned int* h_splitVals = (unsigned*)malloc(numElems*sizeof(unsigned)); unsigned int* h_cdf = (unsigned*)malloc(numElems*sizeof(unsigned)); unsigned int* h_inVals = (unsigned*)malloc(numElems*sizeof(unsigned)); unsigned int* h_outVals = (unsigned*)malloc(numElems*sizeof(unsigned)); unsigned int* h_inPos = (unsigned*)malloc(numElems*sizeof(unsigned)); unsigned int* h_outPos = (unsigned*)malloc(numElems*sizeof(unsigned)); // Parallel radix sort - For each pass (each bit): // 1) Split values based on current bit // 2) Scan values of split array // 3) Compute scatter output position // 4) Scatter output values using inputVals and outputPos for(unsigned ibit = 0; ibit < 8 * sizeof(unsigned); ibit+=numBits) { checkCudaErrors(cudaMemset(d_splitVals, 0, numElems*sizeof(unsigned))); checkCudaErrors(cudaMemset(d_cdf,0,numElems*sizeof(unsigned))); checkCudaErrors(cudaMemset(d_blelloch,0,nBlellochBins*sizeof(unsigned))); // Step 1: Split values on True if bit matches 0 in the given bit // NOTE: mask = [1 2 4 8 ... 2147483648] // [2^0, 2^1,...2^31] unsigned int mask = 1 << ibit; int nthreads = MAX_THREADS_PER_BLOCK; int nblocks = (numElems - 1)/nthreads + 1; split_array<<<nblocks, nthreads>>>(d_inVals, d_splitVals, numElems, mask, ibit); // Testing purposes - REMOVE in production cudaDeviceSynchronize(); checkCudaErrors(cudaGetLastError()); checkCudaErrors(cudaMemcpy(d_cdf, d_splitVals, numElems*sizeof(unsigned), cudaMemcpyDeviceToDevice)); // Step 2: Scan values of split array: // Uses Blelloch exclusive scan full_blelloch_exclusive_scan(d_cdf, numElems); // STEP 2 --> WORKING!!! VERIFIED FOR ALL STEPS! // Step 3: compute scatter addresses // Get totalFalses: unsigned totalFalses = 0; checkCudaErrors(cudaMemcpy(h_splitVals, d_splitVals + (numElems-1), sizeof(unsigned), cudaMemcpyDeviceToHost)); checkCudaErrors(cudaMemcpy(h_cdf, d_cdf + (numElems -1), sizeof(unsigned), cudaMemcpyDeviceToHost)); totalFalses = h_splitVals[0] + h_cdf[0]; compute_scatter_addresses(d_inVals, d_outVals, d_inPos, d_outPos, d_scatterAddr, d_splitVals, d_cdf, totalFalses, numElems); // swap pointers: std::swap(d_inVals, d_outVals); std::swap(d_inPos, d_scatterAddr); } // Do we need this? checkCudaErrors(cudaMemcpy(d_outputVals, d_inputVals, numElems*sizeof(unsigned), cudaMemcpyDeviceToDevice)); checkCudaErrors(cudaMemcpy(d_outputPos, d_inputPos, numElems*sizeof(unsigned), cudaMemcpyDeviceToDevice)); // Put scatter addresses (->inPos) into d_outputVals; checkCudaErrors(cudaMemcpy(d_outputPos, d_inPos, numElems*sizeof(unsigned), cudaMemcpyDeviceToDevice)); checkCudaErrors(cudaFree(d_splitVals)); checkCudaErrors(cudaFree(d_cdf)); checkCudaErrors(cudaFree(d_blelloch)); free(h_splitVals); free(h_cdf); free(h_inVals); free(h_outVals); free(h_inPos); free(h_outPos); }
本周主要内容如下: 如何分析GPU算法的速度和效率 3个新的基本算法:归约、扫描和直方图(Reduce、Scan、Histogram) 一、评估标准 首先介绍用于评估GPU计算的两个标准: step :完成某特定计算所需时间--挖洞操作(Operation Hole Digging) work:工作总量 如下图示,第一种情况只有一个工人挖洞,他需要8小时才能完成,所以工作总量(Work)是8小时。第二种情况是有4个工人,它们2个小时就能完成挖洞任务,此时工作总量是8小时。第三种情况同理不加赘述。 另一种直观理解如下图示。该计算任务步骤复杂度是3,工作复杂度是7。而接下来的课程的目的则是学会如何优化GPU算法。 二、3个新的基本算法 2.1 Reduce 2.1.1 Reduce运算基本介绍 下图展示的是reduce运算。 reduce运算由两部分组成:输入值和 归约运算符(Reduction Operation)。 其中归约运算符需要满足下面两个条件: Binary 二元运算符:即对两个输入对象进行操作得到一个结果 Associative 结合性:直白解释就是运算符需要与顺序无关,例如加号就满足这一性质,而减号不满足。 2.1.2 串行方式实现归约和并行方式实现归约 串行方式实现归约 reduce运算和map运算有点类似,但不同的地方在于map是可以对多个元素做单独操作的,而reduce每次计算都需要依赖上一次运算的结果。示意图如下: 并行方式实现归约 并行方式与串行方式的不同点在于计算的方式不同,如果以穿行方式对a,b,c,d进行加法归约运算,那么计算方式则为\(((a+b)+c)+d\)。而如果以并行方式的话,则为\((a+b)+(c+d)\)。 两种方式的复杂度如下图示: 上面的例子貌似并不能很明显的看出串行和并行的差异,下面我们通过大致计算来更直观的看出不同: 首先假设使用的并行的方式计算,当输入对象有两个,那么step=1。当有4个输入对象,那么就有step=2。以此类推,当有N个输入对象,那么step=logN。而串行的step=N-1。因此当N一定大的时候,并行方式的优势就很明显了。 下面通过具体示例来看看使用 Global Memory和 Shared Memory进行并行归约运算的区别。 如下图示,一共有1024个线程块,其中每个线程块有1024个线程需要进行归约运算。运算分成两个步骤,首先块内运算得到1个值,之后再将1024个值再做归约运算。 下面给出了使用全局变量的代码。实现思路是将线程分成两份,然后等距相加。 具体来说就是首先将后512个线程值添加对应添加到前512个线程中去,之后再折半添加,即将前面512个线程拆成前256和后256个线程,以此类推,直到最后计算得到一个值。 下面给出了使用 Shared Memory的代码,它的思路与上面类似,但是因为使用共享内存,速度得到了提升 理论上来说使用Global Memory需要进行2n次读操作,n次写操作,而Shared Memory需要进行n次读操作,1次写操作,所以后者应该比前者快3倍左右。 但是因为该示例并没有使得计算饱和,所以最终测试结果并没有3倍,具体时间效果如下图。 2.2 Scan 2.2.1 Scan基本介绍 下图给出了Scan的例子,输入为1,2,3,4,运算符是+,输出结果是当前输入值与前面值的总和。 咋看貌似并不像是并行计算,但是Scan运算对于并行计算具有很大的作用。 下图给出了Scan的在实际生活中的例子,即银行存款账户余额情况,左边表示存钱,取钱数,右边表示余额。 2.2.2 Scan运算组成 Scan运算由输入向量,二元结合运算符(与Reduce运算类似)以及标识值组成。 基本上该课程中提到的运算符都需要具有Associative(结合性),这样更加符合并行计算的特点。 为方便说明,标识值(Identify element)用I表示,它需要满足\(I op a = a\),其中op表示二元结合运算符,a表示任意值。举例来说可能更好理解: 例如如果运算符是and,那么标识值就是1。如果运算符是or,那么标识值就是0。 2.2.3 Exclusive Scan & Inclusive Scan Scan运算分为两种:Exclusive和 Inclusive。区别是前者的输出不包含当前的输入值,后者则包括。下图给出了具体例子,使用的运算符是add。 下面以Inclusive Scan为例来计算Step复杂度和Work复杂度。 Step复杂度 它的Steps复杂度和Reduce运算相同都是\(n(logn)\),不再详细解释。 Work复杂度 Work复杂度是\(o(n^2)\)。如下图示,第一个输出值的计算量是0,第二个是1,第三个是2,以此类推,最终复杂度是\(0+1+2+...+(n-1)≈\frac{n*(n-1+0)}{2}=o(n^2)\) 2.2.4 改进Scan运算的实现方法 如果采用上面的实现方式,Work复杂度太大,在实际运用中消耗太多资源,为了改善这一问题,出现了如 Hillis Steele和 Blelloch Scan方法,下面分别进行介绍。 1. Hillis Steele Inclusive Scan 还是从1到8做scan运算。该方法的主要实现思路是对于step i,每个位置的输出值是当前值加上其\(2^i\)左边的值。 Step 0:每个输出值是当前值加上前一个值。(绿色) Step 1:每个输出值是当前值加上前两个值(蓝色)。 Step 2:每个输出值是当前值加上前4 (\(=2^2\)) 个值(红色)。 总结起来计算过程就是: step 0: out[i] = in[i] + in[i-\(2^0\)]; step 1: out[i] = in[i] + in[i-\(2^1\)]; step 2: out[i] = in[i] + in[i-\(2^2\)]; 通过下面完整的计算过程,我们可以明显看到Step复杂度依旧是\(logn\). Work复杂度不能很明显的算出来,我们可以把所有的计算过程看成一个矩阵,纵向长度是\(logn\),横向长度是\(n\),所以Work复杂度是\(nlogn\),该算法相对于之前的\(o(n^2)\)Work复杂度有了一定的提升。 2. Blelloch Scan 这是一种优化Work complexity的算法,比上面的要复杂一些。主要分为reduce和downsweep两步。 该算法由两部分组成:Reduce+Downsweep。 以exclusive scan加法运算为例,如下图示。 为更好说明用下图进行解释说明(图片来源:CUDA系列学习(五)GPU基础算法: Reduce, Scan, Histogram 如图示,上部分是Reduce,不再赘述。 下部分的Downsweep其实可以理解成Reduce的镜像操作,对于每一组输入in1 & in2有两个输出,左边输出out1 = in2,右边输出out2 = in1 op in2 (这里的op就是reduce部分的op),如图: 该算法有点绕,是否明白了呢?下面做个题来看看是不是真的明白了: 运算符是Max,将框中答案补充完整。 Ans: 介绍了这么多,那该方法的复杂度如何呢? 因为我们已经知道Reduce的Step复杂度是\(O(logn)\),Work复杂度是\(O(n)\)。 而Downsweep其实可以理解成Reduce的镜像运算,所以复杂度与之相同。 所以该算法整体的Step复杂度是\(O(2logn)\),Work复杂度是\(O(n)\)。这里对于Work复杂度存疑,不明白为什么不是\(O(2n)\),但是网上好几篇博文都说是\(O(n)\)) 2.2.5 如何选择合适的算法 上面已经分别介绍了 Hillis Steele和Blelloch Scan算法,下面将二者的复杂度总结如下: Method Step complexity Work Complexity type Hillis Steele \(O(logn)\) \(O(nlogn)\) SE (Step Effient) Blelloch \(O(2logn)\) \(O(n)\) WE (Work Efficient) 在实际中可能会遇到如下图示的情况,随着工作量的变化我们需要动态改变算法策略。 例如最开始可能任务中地Work要远多于已有的处理器数量,这种情况执行速度受到了处理器数量的限制,此时我们则需要选择 更具高效工作性的算法(如Blelloch)。 随着工作量不断完成,渐渐地可用处理器数量比工作更多,此时我们则可以选择 更高效步骤的算法实现(如Hillis Steele算法)。 下面来做个题看是否已经理解了如何选择合适的算法。 情况 Serial(串行) Hillis Steele Blelloch 128k的vector, 1个processor √ 512个元素的vector, 512个processor √ 一百万的vector, 512个processor √ 解析: 第一种情况只有一个处理器,难不成还能选择并行算法? 第二种情况处理器数量刚好合适,工作量也不大,所以可以选择Hillis Steele 第三种情况工作量远大于处理器数量,所以选择Blelloch算法来提高工作效率。 2.3 Histogram 2.3.1 Histogram是啥 顾名思义,统计直方图就是将一个统计量在直方图中显示出来。 2.3.2 Histogram 的 Serial 实现 分两部分:1. 初始化,2. 统计 for(i = 0; i < bin.count; i++) res[i] = 0; for(i = 0; i<nElements; i++) res[computeBin(i)] ++; // computeBin(i)函数是用来判断第i个元素属于哪一类 2.3.3 Histogram 的 Parallel 实现 直接实现: __global__ void naive_histo(int* d_bins, const int* d_in, const in BIN_COUNT){ int myID = threadIdx.x + blockDim.x * blockIdx.x; int myItem = d_in[myID]; int myBin = myItem % BIN_COUNT; d_bins[myBin]++; } 仔细看上面的代码,我们很容易看出d_bins[myBin]++;语句在并行计算过程中会出现read-modify-write(从全局内存中读取数据,修改数据,写回数据) 冲突问题,进而造成结果出错。 而serial implementation不会有这个问题,那么想实现parallel histogram计算有什么方法呢? 1. accumulate using atomics 下图给出了read-modify-write介绍,可以看出读取,修改,写入是3个独立的原子运算,但是如果我们将这3个操作整合成1个原子操作那么就可以很好地解决上述问题。而且现如今的GPU能够锁定特定的内存地址,因此其他的线程就无法访问该地址。 具体实现代码只需要将d_bins[myBin]++;修改成atomicAdd(&(d_bins[myBin]), 1);即可。 __global__ void simple_histo(int* d_bins, const int* d_in, const in BIN_COUNT){ int myID = threadIdx.x + blockDim.x * blockIdx.x; int myItem = d_in[myID]; int myBin = myItem % BIN_COUNT; atomicAdd(&(d_bins[myBin]), 1); } 但是对于atomics的方法而言,不管GPU多好,并行线程数都被限制到histogram个数N,也就是最多只有N个线程并行。 2. local memory + reduce 思路原理:设置n个并行线程,每个线程都有自己的local histogram(一个长为bin数的vector);即每个local histogram都被一个thread顺序访问,所以这样没有shared memory,即便没有用atomics也不会出现read-modify-write问题。然后,我们将这n个histogram进行合并(即加和),可以通过reduce实现。 举例: 如下图示,假设有128个item,有8个线程可以使用,然后需要分成3类。 这样每个线程理论上需要处理16个item,即将这16个item分成3类。 通过使用并行线程,我们无需使用上面那个方法将read-modify-write原子化,因为这8个线程使用的是自己的local memory,而不是shared memory。如此一来我们只需要在所有线程计算结束后,再使用Reduce运算将8个线程的计算结果对应相加即可。 3. sort then reduce by key 该方法的主要思路是采用键值对(类似Python中的字典)的方式来记录数据,之后对键值对按键的大小排序得到下图中蓝色键值对。 如此一来键相同的挨在一起,之后便可用Reduce运算将相同类别的值相加即可。 三、作业应用 Tone Mapping(色调映射) 是转换一组颜色到另一组颜色的过程。之所以要色调映射是因为真实世界中的光谱强度是非常广的,例如白天太阳的光亮程度到晚上月亮的光亮程度,这之间的范围十分大。而我们的设备,如电脑显示屏,手机所能显示的光亮程度远小于真实世界,所以我们需要进行色调映射。我们手机上拍照功能中的HDR模式就是色调映射的作用。 该过程如果没有处理好,那么得到的效果则要么变得过于暗淡,要么过于明亮。 下图很好地展示了这两种情况。 下图左曝光较低,很多细节丢失。下图右虽然细节保留更多,但是窗户部分因为曝光过多基本模糊掉了。两图片下面的直方图也很直观的呈现了像素分布情况。 而色调映射就是为了解决上述情况,以期望达到如下图的效果。 本次作业需要结合所学的三种运算Reduce,Scan,Histogram。 MARSGGBO原创 2018-7-9
一、基础模型 假设要翻译下面这句话: "简将要在9月访问中国" 正确的翻译结果应该是: "Jane is visiting China in September" 在这个例子中输入数据是10个中文汉字,输出为6个英文单词,\(T_x\)和\(T_y\)数量不一致,这就需要用到序列到序列的RNN模型。 类似的例子还有看图说话: 只需要将encoder部分用一个CNN模型替换就可以了,比如AlexNet,就可以得到“一只(可爱的)猫躺在楼梯上” 二、选择最优句子 下面将之前学习的语言模型和机器翻译模型做一个对比, P为概率 下图是语言模型,可以用在自动生成文章或者预测文字之类的应用中,即根据前一个字输出下一个字。 下图是机器翻译模型,可以看到后半部分(紫色)其实就是语言模型,吴大大称之为“条件语言模型”,即在语言模型之前有一个条件,也就是被翻译的句子。 用数学公式表示就是: \[P(y^{<1>},…,y^{<T_y>}|x^{<1>},…,x^{<T_x>})\] 但是我们知道翻译是有很多种方式的,同一句话可以翻译成很多不同的句子,那么我们如何判断那一个句子是最好的呢? 还是翻译上面那句话,有如下几种翻译结果: "Jane is visiting China in September." "Jane is going to visit China in September." "In September, Jane will visit China" "Jane's Chinese friend welcomed her in September." .... 与语言模型不同的是,机器模型在输出部分不再使用softmax随机分布的形式进行取样,因为很容易得到一 个不准确的翻译,取而代之的是使用Beam Search做最优化的选择。这个方法会在后下一小节介绍,在此之前先介绍一下贪婪搜索(Greedy Search)及其弊端,这样才能更好地了解Beam Search的优点。 得到最好的翻译结果,转换成数学公式就是 \[argmax P(y^{<1>},…,y^{<T_y>}|x^{<1>},…,x^{<T_x>})\] 那么贪婪搜索是什么呢? 通俗解释就是每次输出的那个都必须是最好的。还是以翻译那句话为例。 现在假设通过贪婪搜索已经确定最好的翻译的前两个单词是:"Jane is " 然后因为"going"这个单词出现频率较高和其它原因,所以根据贪婪算法得出此时第三个单词的最好结果是"going"。 所以根据贪婪算法最后的翻译结果可能是下图中的第二个句子,但是第一句可能会更好(不服气的话,我们就假设第一句更好hhhh)。 所以贪婪搜索的缺点是局部最优并不代表全局最优,就好像五黑,一队都是很牛逼的,但是各个都太优秀,就显得没那么优秀了,而另一队虽然说不是每个都是最优秀,但是凑在一起就是能carry全场。 更形象的理解可能就是贪婪搜索更加短视,看的不长远,而且也更加耗时。假设字典中共有10000个单词,如果使用贪婪搜索,那么可能的组合有\(10000^{10}\)种,所以还是挺恐怖的2333~~ 三、定向搜索(Beam Search) Beam Search是贪婪搜索的加强版,首先它需要设置beam width,下面设置为3。(如果设置为1,就是贪婪搜索) 步骤一: 如下图示,因为beam width=3,所以根据输入的需要翻译的句子选出3个\(y^{<1>}\)最可能的输出值,即选出\(P(y^{<1>|x})\)最大的前3个值。假设分别是"in","jane","september"。 步骤二: 以"in"为例进行说明,其他同理。 如下图示,在给定被翻译句子\(x\)和确定 \(y^{<1>}\)="in" 的条件下,下一个输出值的条件概率是\(P(y^{<2>}|x,"in")\)。此时需要从10000种可能中找出条件概率最高的前3个。 又由公式\(P(y^{<1>},y^{<2>}|x)=P(y^{<1>}|x)P(y^{<2>}|x,y^{<1>})\),我们此时已经得到了给定输入数据,前两个输出值的输出概率比较大的组合了。 另外2个单词也做同样的计算。 此时我们得到了9组\(P(y^{<1>},y^{<2>}|x)\),此时我们再从这9组中选出概率值最高的前3个。如下图示,假设是这3个: "in september" "jane is" "jane visits" 步骤3: 继续步骤2的过程,根据\(P(y^{<3>}|x,y^{<1>},y^{<2>})\)选出\(P(y^{<1>},y^{<2>},y^{<3>}|x)\)最大的前3个组合。 后面重复上述步骤得出结果。 总结一下上面的步骤就是: 第一步: 经过encoder以后,decoder给出最有可能的三个开头词依次为“in”, "jane", "september" --- \(P(y^{<1>}|x)\) 第二步: 经过将第一步得到的值输入到第二步中,最有可能的三个个翻译为“in september”, "jane is", "jane visits" ---\(P(y^{<2>}|x,y^{<1>})\) (这里,september开头的句子由于概率没有其他的可能性大,已经失去了作为开头词资格) 第三步: 继续这个过程... ---- \(P(y^{<3>}|x,y^{<1>},y^{<2>})\) 四、改进定向搜索 因为\[P(y^{<1>},….,P(y^{T_y})|x)=P(y^{<1>}|x)P(y^{<2>}|x,y^{<1>})…P(y^{<T_y>}|x,y^{<1>},…y^{<{T_y-1}>})\] 所以要满足\(argmax P(y^{<1>},….,P(y^{T_y})|x)\),也就等同于要满足 \[argmax \prod_{t=1}^{T_y}P(y^{<t>}|x,y^{<1>},…y^{<{t-1}>})\] 但是上面的公式存在一个问题,因为概率都是小于1的,累乘之后会越来越小,可能小到计算机无法精确存储,所以可以将其转变成log形式(因为log是单调递增的,所以对最终结果不会有影响),其公式如下: \[argmax \sum_{t=1}^{T_y}logP(y^{<t>}|x,y^{<1>},…y^{<{t-1}>})\] But!!!上述公式仍然存在bug,观察可以知道,概率值都是小于1的,那么log之后都是负数,所以为了使得最后的值最大,那么只要保证翻译的句子越短,那么值就越大,所以如果使用这个公式,那么最后翻译的句子通常都是比较短的句子,这显然不行。 所以我们可以通过归一化的方式来纠正,即保证平均到每个单词都能得到最大值。其公式如下: \[argmax \frac{1}{T_y}\sum_{t=1}^{T_y}logP(y^{<t>}|x,y^{<1>},…y^{<{t-1}>})\] 通过归一化的确能很好的解决上述问题,但是在实际运用中,会额外添加一个参数\(α\),其大小介于0和1之间,公式如下: \[argmax \frac{1}{T_y^α}\sum_{t=1}^{T_y}logP(y^{<t>}|x,y^{<1>},…y^{<{t-1}>})\] 五、定向搜索的误差分析 静下心来仔细想想beam search,我们会发现其实它是近似搜索,也就是说可能使用这种方法最终得到的结果并不是最好的。当然也有可能是因为使用的RNN模型有缺陷导致结果不是最好的。 所以我们如何判断误差是出在哪个地方呢? 还是以翻译这句话为例:“简在9月访问中国”。 假设按照人类的习惯翻译成英文是“Jane visits China in September.”,该结果用 \(y^*\) 表示。 假设通过算法得出的翻译结果是:“Jane visited China in September.”,该结果用\(\hat{y}\)表示。 要判断误差出在哪,只需要比较\(P(y^*|x)\)和\(P(\hat{y}|x)\)的大小即可。 下面分两种情况讨论: 1.\(P(y^*|x)>P(\hat{y}|x)\) 上面的不等式的含义是beam search最后选出的结果不如人类,也就是beam search并没有选出最好的结果,所以问题出在beam search。 2.\(P(y^*|x)≤P(\hat{y}|x)\) 上面不等式表示beam search最后选出的结果要比人类的更好,也就是说beam search已经选出了最好的结果,但是模型对各个组合的预测概率值并不符合人类的预期,所以这个锅需要模型背。 上面已经介绍了误差分析的方式,但时仅凭一次误差分析就判定谁该背锅肯定也不行,所以还需要进行多次误差分析多次。 如下图示已经进行了多次的误差分析,每次分析之后都判定了锅该谁背,最后计算出beam search和模型背锅的比例,根据比例作出相应的调整。 例如如果beam search更高,可以相应调整beam width。 如果模型背锅比例更高,那么可以考虑增加正则化,增加数据等操作。 六、Bleu得分(选修) 主要介绍了如何给机器翻译结果打分,因为是选修内容。。。so。。。emm 七、注意力模型直观理解 1.为什么要用注意力模型 之前介绍的RNN翻译模型存在一个很明显的问题就是机器翻译的翻译过程是首先将所有需要翻译的句子输入到Encoder中,之后再通过Decoder输出翻译语句。如下图示机器算法将法语翻译成英语的模型。 机器翻译与人类的翻译过程不太相同。因为人类翻译一般是逐句翻译,或者是讲一段很长的句子分解开来进行翻译。 所以上述模型的翻译结果的Bleu评分与被翻译句子的长短有很大关系,句子较短时,模型可能无法捕捉到关键信息,所以翻译结果不是很高;但是当句子过长时,模型又抓不到重点等原因使得结果也不是很高。 而如果机器能像人一样逐句或者每次将注意力只集中在一小部分进行翻译,那么翻译结果将不受句子长度的影响。下图中的绿色线即为使用了注意力模型后的翻译句子得分。 2.模型介绍 下图展示了普通的翻译模型双向RNN结构,该结构可根据输入\(x^{<t>}\)直接得到输出\(y^{<t>}\)。 注意力模型在此基础上做进一步处理。 为避免误解,使用另一个符号\(s\)来表示节点。 如下图示,根据下面一层的双向RNN计算结果可得到节点\(s^{<1>}\)与其他节点权重\(α^{<1,1>},α^{<1,2>},…\),通过这些权重可以知道该节点与其他节点的相关联程度,从而可以达到将注意力集中到部分区域的效果。 其他节点同理。整个注意力模型结构如下图示。 八、注意力模型 特别要区分\(a\) (字母a) 和\(α\) (alpha)。前者表示特征节点,后者表示注意力权重。 1.参数介绍 如下图示,注意力模型采用双向RNN结构,所以每个节点有两个值,用(\(\overrightarrow{a}^{<t'>},\overleftarrow{a}^{<t'>}\))表示,为了使公式更简化,令\(a^{<t'>}=(\overrightarrow{a}^{<t'>},\overleftarrow{a}^{<t'>})\)。其中\(t'\)表示输入数据的索引。 上一节已经介绍了注意力权重\(α^{<t,t'>}\),以第一个节点为例,它的权重值可以用\(α^{<1,t'>}\)表示,且所有权重值满足\(\sum{α^{<1,t'>}}=1\)。 所有权重与对应节点的线性之和用\(c^{<t'>}\)表示(为方便书写,用\(c\)表示),c表示context,即上下文变量。 还是以第一个节点为例,c的计算公式如下: \[c^{<1>}=\sum_{t'}α^{<1,t'>}a^{<t'>}\] 2.注意力权值计算公式 \[\alpha^{<t,t'>}=\frac{exp(e^{<t,t'>})}{\sum_{t''=1}^{T_x}{exp(e^{t,t''})}}\] 上面公式中的\(e^{<t,t'>}\)计算图如下: 其中\(s^{<t-1>}\)表示上一个状态的值,\(a^{<t'>}\)表示第t'个特征节点。 视频中吴大大并没有很详细的介绍上面的网络,只是一笔带过,说反向传播和梯度下降会自动学习,emmm。。。那就这样吧。 结合下图可以独自参考一下上面的公式是什么意思。 3.舶来品 下面的笔记是《大数据文摘》的笔记,感觉他写的清楚一些。 如图所示,这是一个双向的rnn,并且在普通rnn的基础上增加attention层,将阶段性的输入部分转化为输出,这样的方式也更符合人类的翻译过程。 让我们拿出细节部分仔细的理解一下,首先是attention层,也就是下图中\(context^{<t>}\),每一个attention单元接受 三个单词的输入所以也称作语境单元(context), \(α\)是每单个输入词在语境单元中占得权重。对每一个语境单元t 来说,因为\(α\)是通过softmax决定的,所以\(\sum_{i=1}^{T_x}α^{t,i}=1\)。这里决定终每一个单词占得语境权重仍然是通过一 个小型的神经网络来进行计算并且后得到的。 输出的\(context^{<t>}\)进入到下一层Post LSTM 这一步就和之前学习过的那样子,将前一步的输出与这一步经过重重分析的输入综合到一起产生这一步的输出。 让我们评估一下attention model: 由于结构的复杂,计算量与时间比普通的语言模型要多和慢许多。不过对于机 器翻译来说,由于每一句话并不会特别特比特的长,所以有的时候稍微慢一点也不是完全无法接受:p 一个很重要attention model的应用就是语音识别,人通过麦克风输入一句话让机器来翻译输入的内容,让我们来 看一下是如何实现的 九、语音辨识 一般语音识别过程是如下图示的,即首相将原音频(黑白的,纵轴表示振幅)转化成纵轴为频率的音谱图,并且通过人工预先设定的音素(phonemes)再来识别。 而引入注意力机制后的模型就表现优秀得多了 CTC(connectionist temporal classification)是之前较为常用的方法。 具体原理如下: 假设每秒音频可以提取出100个特征值,那么假设10秒的音频就有1000个特征值,那么输出值也有1000个,但是说出的话并没有这么多啊,那该怎么处理呢? 方法很简单,只需要把“_”进行压缩即可,注意需要将 "_"和空额区分开来,因为空格也是占一个字符的。 十、触发字检测 假设下图式训练集中的一段音频,其中包含了两次唤醒词 搭建一个attention model,在听到唤醒词之前一直输出的是0,在听到唤醒词以后输出1,但因为一个唤醒词会持 续半秒左右所以我们也不仅仅只输出一次1,而是将输出的1持续一段时间,通过这样的方式训练出的rnn就可以很 有效的检测到唤醒词了。 十一、结论和致谢 啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦了,终于学完了。虽然好像也并不能说明什么~~~2333333333 MARSGGBO原创 2018-6-3
问题背景 本文主要希望解决如下两个问题: 1.远程登录jupyter notebook 2.远程运行深度学习框架如何可视化loss和accuracy? 服务器一般都是字符界面的,但是有时我们需要可视化我们的结果,这样能够直观的看到我们的训练成果,这该怎么办呢? 没关系,因为服务器都会内置一个firefox浏览器,你只需要在命令窗口输入 >>> firefox 之后你的本地电脑会弹出一个Firefox的窗口,是不是很惊奇!!!(我使用的连接服务器的软件是MobaXterm)。正常的情况会出现这个界面(当然如果你是其他版本的系统可能会有所不同。) 但是很不幸我之前打开后就提示页面崩了,啥都打不开,而且后面会跟着这些错误提示: 连接浏览器 刚开始我还以为是我的电脑防火墙的问题,或者是学校服务器限制了,不过后来把错误信息谷歌一下(千万别用百度。。。),一下就找到了解决办法 你只需要在打开的Firefox浏览器地址栏输入about:config 之后回车进入这个页面,点击I accept this risk 之后再输入autostart找到图中两个设置项,将value设置为False。 之后再命令窗口关闭Firefox,然后重新打开浏览器就可以了。 远程连接Jupyter Notebook 浏览器的问题解决了,远程连接Jupyter Notebook还会远吗? emm,有一点点远,不过可以很快解决。 具体方法如下: 服务器端安装好ipython, Jupyter Notebook pip install ipython pip install Jupyter 生成配置文件 jupyter notebook --generate-config 生成密码 进入ipython In [1]: from notebook.auth import passwd In [2]: passwd() Enter password: Verify password: Out[2]: 'sha1:ce23d945972f:34769685a7ccd3d08c84a18c63968a41f1140274' 把生成的密文‘sha:ce…’复制下来 修改默认配置文件 vim ~/.jupyter/jupyter_notebook_config.py 按照下面进行修改 c.NotebookApp.ip='*' # 就是设置所有ip皆可访问 c.NotebookApp.password = u'sha:ce...刚才复制的那个密文' c.NotebookApp.open_browser = False # 禁止自动打开浏览器,如果服务器有浏览器也可以不修改这一项 c.NotebookApp.port =8888 #随便指定一个端口 温馨提示: vim快速搜索的快捷键是 “/ 搜索词” 例如如果想修改ip项,在进入vim编辑器后直接输入"/ip"回车即开始匹配,"n"是寻找下一个,"p"是寻找上一个。 启动jupyter notebook 可以不指定端口,也可以指定端口 jupyter notebook --port=9000 深度学习结果远程可视化 其实上面的jupyter 已经可以帮助我们实现可视化了,但是这里推荐一个比较好用的可视化模块,是百度出的visualDL模块。 具体安装和使用方法在这就不做介绍了,感兴趣的可以去官网查阅。 安装好之后启动visualDL服务器即可看到如下效果: 感谢 远程访问Jupyter Notebook Firefox tab crashes at startup MARSGGBO原创 2018-4-18
不间断更新。。。 增减layer 增加layer 增加layer很方便,可以使用model.add_module('layer name', layer)。 删减layer 删减layer很少用的到,之所以我会有这么一个需求,是因为我需要使用vgg做迁移学习,而且需要修改最后的输出。 而vgg由两个部分组成:features和classifier,这两个部分都是torch.nn.Sequential,所以不能单独对其中某一层做修改。 而如果对整个Sequential做修改,那么这个模型的参数会被初始化,而我又需要保留这些参数,所以才想到是否有办法把最后一层fc删掉,重新再填加一个不就行了?具体方法如下: 以vgg16为例,假设我们现在只需要对classifier的最后一层全连接层的输出做修改: model = models.vgg16(pretrained=True) 先看一下未做修改之前的classifier的参数: 截取要修改的layer之前的网络 removed = list(model.classifier.children())[:-1] model.classifier = torch.nn.Sequential(*removed) 添加fc层 model.add_module('fc', torch.nn.Linear(4096, out_num)) # out_num是你希望输出的数量 此时我们看一下model以及classifier的参数有什么变化: 这达到了我预期的效果。 MARSGGBO原创 2018-4-10
Augmentor 使用介绍 原图 1.random_distortion(probability, grid_height, grid_width, magnitude) 最终选择参数为 p.random_distortion(probability=0.8, grid_height=3, grid_width=3, magnitude=6) 其他参数效果: magnitude和grid_width,grid_height越大,扭曲程度越大 p.random_distortion(probability=0.6, grid_height=6, grid_width=6, magnitude=5) p.random_distortion(probability=0.6, grid_height=6, grid_width=6, magnitude=9) p.random_distortion(probability=0.6, grid_height=10, grid_width=10, magnitude=5) 2.random_erasing(probability, rectangle_area) rectangle_area表示覆盖区域的比例,值越大比例越大。但是设置为1的时候并不是全覆盖,不知道为什么,反正也没必要弄清楚 p.random_erasing(1,1) 3.zoom_random(probability, percentage_area) 放大图片,然后按照percenta_area的比例对图片进行crop。 p.zoom_random(probability=1, percentage_area=0.2) p.zoom_random(probability=1, percentage_area=0.8) 4.zoom(probability, min_factor, max_factor) p.zoom(probability=1, min_factor=1.1, max_factor=1.5) p.zoom(probability=1, min_factor=2, max_factor=2) 组合操作 p.rotate_random_90(probability=0.8) p.random_distortion(probability=0.8, grid_height=3, grid_width=3, magnitude=6) p.random_erasing(0.3, 0.2) p.zoom(probability=0.4, min_factor=1.1, max_factor=1.5) p.sample(6) MARSGGBO原创 2018-4-1
想直接看公式的可跳至第三节 3.公式修正 一、为什么需要SPP 首先需要知道为什么会需要SPP。 我们都知道卷积神经网络(CNN)由卷积层和全连接层组成,其中卷积层对于输入数据的大小并没有要求,唯一对数据大小有要求的则是第一个全连接层,因此基本上所有的CNN都要求输入数据固定大小,例如著名的VGG模型则要求输入数据大小是 (224*224) 。 固定输入数据大小有两个问题: 1.很多场景所得到数据并不是固定大小的,例如街景文字基本上其高宽比是不固定的,如下图示红色框出的文字。 2.可能你会说可以对图片进行切割,但是切割的话很可能会丢失到重要信息。 综上,SPP的提出就是为了解决CNN输入图像大小必须固定的问题,从而可以使得输入图像高宽比和大小任意。 二、SPP原理 更加具体的原理可查阅原论文:Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition 上图是原文中给出的示意图,需要从下往上看: 首先是输入层(input image),其大小可以是任意的 进行卷积运算,到最后一个卷积层(图中是\(conv_5\))输出得到该层的特征映射(feature maps),其大小也是任意的 下面进入SPP层 我们先看最左边有16个蓝色小格子的图,它的意思是将从\(conv_5\)得到的特征映射分成16份,另外16X256中的256表示的是channel,即SPP对每一层都分成16份(不一定是等比分,原因看后面的内容就能理解了)。 中间的4个绿色小格子和右边1个紫色大格子也同理,即将特征映射分别分成4X256和1X256份 那么将特征映射分成若干等分是做什么用的呢? 我们看SPP的名字就是到了,是做池化操作,一般选择MAX Pooling,即对每一份进行最大池化。 我们看上图,通过SPP层,特征映射被转化成了16X256+4X256+1X256 = 21X256的矩阵,在送入全连接时可以扩展成一维矩阵,即1X10752,所以第一个全连接层的参数就可以设置成10752了,这样也就解决了输入数据大小任意的问题了。 注意上面划分成多少份是可以自己是情况设置的,例如我们也可以设置成3X3等,但一般建议还是按照论文中说的的进行划分。 三、SPP公式 理论应该理解了,那么如何实现呢?下面将介绍论文中给出的计算公式,但是在这之前先要介绍两种计算符号以及池化后矩阵大小的计算公式: 1.预先知识 取整符号: ⌊⌋:向下取整符号 ⌊59/60⌋=0,有时也用 floor() 表示 ⌈⌉:向上取整符号 ⌈59/60⌉=1, 有时也用ceil() 表示 池化后矩阵大小计算公式: 没有步长(Stride):\((h+2p-f+1)*(w+2p-f+1)\) 有步长(Stride):⌊\(\frac{h+2p-f}{s}\)+1⌋*⌊\(\frac{w+2p-f}{s}\)+1⌋ 2.公式 假设 输入数据大小是\((c, h_{in}, w_{in})\),分别表示通道数,高度,宽度 池化数量:\((n,n)\) 那么则有 核(Kernel)大小: \(⌈\frac{h_{in}}{n},\frac{w_{in}}{n}⌉=ceil(\frac{h_{in}}{n},\frac{w_{in}}{n})\) 步长(Stride)大小: \(⌊\frac{h_{in}}{n},\frac{w_{in}}{n}⌋=floor(\frac{h_{in}}{n},\frac{w_{in}}{n})\) 我们可以验证一下,假设输入数据大小是\((10, 7, 11)\), 池化数量\((2, 2)\): 那么核大小为\((4,6)\), 步长大小为\((3,5)\), 得到池化后的矩阵大小的确是\(2*2\)。 3.公式修正 是的,论文中给出的公式的确有些疏漏,我们还是以举例子的方式来说明 假设输入数据大小和上面一样是\((10, 7, 11)\), 但是池化数量改为\((4,4)\): 此时核大小为\((2,3)\), 步长大小为\((1,2)\),得到池化后的矩阵大小的确是\(6*5\) ←[简单的计算矩阵大小的方法:(7=2+1*5, 11=3+2*4)],而不是\(4*4\)。 那么问题出在哪呢? 我们忽略了padding的存在(我在原论文中没有看到关于padding的计算公式,如果有的话。。。那就是我看走眼了,麻烦提示我一下在哪个位置写过,谢谢)。 仔细看前面的计算公式我们很容易发现并没有给出padding的公式,在经过N次使用SPP计算得到的结果与预期不一样以及查找各种网上资料(尽管少得可怜)后,现将加入padding后的计算公式总结如下。 \(K_h = ⌈\frac{h_{in}}{n}⌉=ceil(\frac{h_{in}}{n})\)\(S_h = ⌈\frac{h_{in}}{n}⌉=ceil(\frac{h_{in}}{n})\)\(p_h = ⌊\frac{k_h*n-h_{in}+1}{2}⌋=floor(\frac{k_h*n-h_{in}+1}{2})\)\(h_{new} = 2*p_h +h_{in}\)\(K_w = ⌈\frac{w_{in}}{n}⌉=ceil(\frac{w_{in}}{n})\)\(S_w = ⌈\frac{w_{in}}{n}⌉=ceil(\frac{w_{in}}{n})\)\(p_w = ⌊\frac{k_w*n-w_{in}+1}{2}⌋=floor(\frac{k_w*n-w_{in}+1}{2})\)\(w_{new} = 2*p_w +w_{in}\) \(k_h\): 表示核的高度 \(S_h\): 表示高度方向的步长 \(p_h\): 表示高度方向的填充数量,需要乘以2 注意核和步长的计算公式都使用的是ceil(),即向上取整,而padding使用的是floor(),即向下取整。 现在再来检验一下: 假设输入数据大小和上面一样是\((10, 7, 11)\), 池化数量为\((4,4)\): Kernel大小为\((2,3)\),Stride大小为\((2,3)\),所以Padding为\((1,1)\)。 利用矩阵大小计算公式:⌊\(\frac{h+2p-f}{s}\)+1⌋*⌊\(\frac{w+2p-f}{s}\)+1⌋得到池化后的矩阵大小为:\(4*4\)。 四、代码实现(Python) 这里我使用的是PyTorch深度学习框架,构建了一个SPP层,代码如下: #coding=utf-8 import math import torch import torch.nn.functional as F # 构建SPP层(空间金字塔池化层) class SPPLayer(torch.nn.Module): def __init__(self, num_levels, pool_type='max_pool'): super(SPPLayer, self).__init__() self.num_levels = num_levels self.pool_type = pool_type def forward(self, x): num, c, h, w = x.size() # num:样本数量 c:通道数 h:高 w:宽 for i in range(self.num_levels): level = i+1 kernel_size = (math.ceil(h / level), math.ceil(w / level)) stride = (math.ceil(h / level), math.ceil(w / level)) pooling = (math.floor((kernel_size[0]*level-h+1)/2), math.floor((kernel_size[1]*level-w+1)/2)) # 选择池化方式 if self.pool_type == 'max_pool': tensor = F.max_pool2d(x, kernel_size=kernel_size, stride=stride, padding=pooling).view(num, -1) else: tensor = F.avg_pool2d(x, kernel_size=kernel_size, stride=stride, padding=pooling).view(num, -1) # 展开、拼接 if (i == 0): x_flatten = tensor.view(num, -1) else: x_flatten = torch.cat((x_flatten, tensor.view(num, -1)), 1) return x_flatten 上述代码参考: sppnet-pytorch 为防止原作者将代码删除,我已经Fork了,也可以通过如下地址访问代码:marsggbo/sppnet-pytorch MARSGGBO原创 2018-3-15
做件好事,考四级的兄弟们一起共勉~~~ 链接:https://pan.baidu.com/s/1im4BDVZofZbT9f5PZtsxdA 密码:9i5h 支持一下呗
以下方法同样适用于其他场景的批量下载。 最近在学习Coursera退出的深度学习课程,我希望把课程提供的作业下载下来以备以后复习,但是课程有很多文件,比如说脸部识别一课中的参数就多达226个csv文件,如果单纯靠鼠标点击下载简直要疯掉,所以给出如下方法: 等不及的可以跳过方法一,直接看方法二 方法一:提取出链接,然后批量下载 1.按F12查看网页代码,找到链接位置,如下图示 2.将对应位置代码复制保存成html文件,然后再文件末尾加上如下代码并保存: <script type="text/javascript"> var obj = document.getElementsByTagName('a'); for(var i=0,j;j=obj[i];i++){ document.write(j.href+'<br>'); }; </script> 3.打开保存的html文件,可以看到链接被提取出来了: 4.使用chrome下载一个扩展工具:chrono下载管理器 5.打开chrono下载管理器进入下载管理界面: 6.点击 + 号,将上面的提取出的所有链接(不用一个一个的)复制到输入框中,点击开始即可开始批量下载 7.下载效果 方法二:Chrono下载管理器 下载Chrono下载管理器后,直接进入嗅探器模式 你看,所有的文件链接都自动提取出来了,并分好类了,直接选择csv文件即可得到我所需要的参数文件了。 MARSGGBO原创 2018-3-7
检查一下信息: 1.iTunes是否安装 2.数据线是否完好 3.检查下图中的两个设备是否开启 4.最后一步是最恶心的:是否关闭了防火墙!!!! 操作步骤如下图示 我就是因为打开了防火墙,所以一直连接不上我自己的电脑,但是却能连上别人的电脑。
故障如下图: 绕了好一大圈才发现是goupi防火墙搞的鬼,弄得我一些软件一直运行不了!!!!! 废话不多说,关了防火墙就行了:操作步骤如下图示 关闭之后,美滋滋:
一、为什么选择序列模型 序列模型可以用于很多领域,如语音识别,撰写文章等等。总之很多优点。。。 二、数学符号 为了后面方便说明,先将会用到的数学符号进行介绍。 以下图为例,假如我们需要定位一句话中人名出现的位置。 红色框中的为输入、输出值。可以看到人名输出用1表示,反之用0表示; 绿色框中的\(x^{<t>},y^{<t>}\)表示对应红色框中的输入输出值的数学表示,注意从1开始。 灰色框中的\(T_x,T_y\)分别表示输入输出序列的长度,在该例中,\(T_x=9,T_y=9\) 黄色框中\(X^{(i)<t>}\)上的表示第i个输入样本的第t个输入值,\(T_x^{(i)}\)则表示第i个输入样本的长度。输出y也同理。 输入值中每个单词使用One-shot来表示。即首先会构建一个字典(Dictionary),假设该例中的字典维度是10000*1(如图示)。第一个单词"Harry"的数学表示形式即为[0,0,0,……,1 (在第4075位) ,0,……,0],其他单词同理。 但是如果某一个单词并没有被包含在字典中怎么办呢?此时我们可以添加一个新的标记,也就是一个叫做Unknown Word的伪造单词,用 <UNK> 表示。具体的细节会在后面介绍。 三、循环神经网络模型 1.为什么不用标准网络 在介绍RNN之前,首先解释一下为什么之前的标准网络不再适用了。因为它有两个缺点: 输入和输出的长度不尽相同 无法共享从其他位置学来的特征。例如上一节中的Harry这个词是用\(x^{<1>}\)表示的,网络从该位置学习了它是一个人名。但是我们希望无论Harry在哪个位置出现网络都能识别出这是一个人名的一部分,而标准网络无法做到这一点。 2.RNN结构 还是以识别人名为例,第一个单词\(x^{<1>}\)输入神经网络得到输出\(y^{<1>}\) 同理由\(x^{<2>}\)将得到\(y^{<2>}\),以此类推。但是这就是传统网络存在的问题,即单词之间没有联系 为了将单词之间关联起来,所以将前一层的结果也作为下一层的输入数据。如下图示 整体的RNN结构有两种表示形式,如下图示 左边是完整的表达形式,注意第一层的\(a^{<0>}\)一般设置为0向量。 右边的示意图是RNN的简写示意图。 介绍完结构之后,我们还需要知道网络中参数的表达方式及其含义。如下图示,\(x^{<i>}\)到网络的参数用\(W_{ax}\)表示,\(a^{<i>}\)到网络的参数用\(W_{aa}\)表示,\(y^{<i>}\)到网络的参数用\(W_{ya}\)表示,具体含义将在下面进行说明。 如下图示,\(x^{<1>}\)通过网络可以传递到\(y^{<3>}\),但是这存在一个问题,即每个输出只与前面的输入有关,而与后面的无关。这个问题会在后续内容中进行改进。 3.RNN前向传播 如图示,\(a^{<0>}=\vec{0}\) 激活函数:\(g_1\)一般为tanh函数或者是Relu函数,\(g_2\)一般是Sigmod函数。 \(a^{<1>}=g_1(W_{aa}a^{<0>}+W_{ax}x^{<1>}+b_a)\) \(y^{<1>}=g_2(W_{ya}a^{<1>}+b_y)\) \(a^{<t>}=g_1(W_{aa}a^{<t-1>}+W_{ax}x^{<t>}+b_a)\) \(y^{<t>}=g_2(W_{ya}a^{<t>}+b_y)\) 注意参数的下标是有顺序含义的,如\(W_{ax}\)下标的第一个参数表示要计算的量的类型,即要计算\(a\)矢量,第二个参数表示要进行乘法运算的数据类型,即需要与\(x\)矢量做运算。如 \(W_{ax} x^{t}\rightarrow{a}\) 4.简化RNN公式 下面将对如下公式进行化简: 1. 简化\(a^{<t>}\) \[\begin{align} a^{<t>}&= g(W_{aa}a^{<t-1>}+W_{ax}x^{<t>}+b_a) \notag \\ &= g(W_a [a^{<t-1>},x^{<t>}]^{T}+b_a) \notag \end{align}\] 注意,公式中使用了两个矩阵进行化简,分别是 \(W_a\)和\([a^{<t-1>},x^{<t>}]^T\)(使用转置符号更易理解),下面分别进行说明: \(W_a = [ W_{aa}, W_{ax}]\),假设\(W_{aa}\)是(100,100)的矩阵,\(W_{ax}\)是(100,10000)的矩阵,那么\(W\)则是(100,10100)的矩阵。 \([a^{<t-1>},x^{<t>}]^T\)是下图示意。 故\(W_a [a^{<t-1>},x^{<t>}]^{T}\)矩阵计算如下图示 2.简化\(y^{<t>}\) 该节PPT内容: 四、通过时间的反向传播 下面将会对反向传播进行灰常灰常详细的介绍,跟着下面一张一张的图片走起来~ 1.整体感受 首先再回顾一下RNN的整体结构 要进行反向传播,首先需要前向传播,传播方向如蓝色箭头所示,其次再按照红色箭头进行反向传播。 2.前向传播 首先给出所有输入数据,即从\(x^{<1>}\)到\(x^{<T_x>}\),\(T_x\)表示输入数据的数量。 初始化参数\(W_a,b_a\),将输入数据输入网络得到对应的\(a^{<t>}\) 再通过与初始化参数\(W_y,b_y\)得到\(y^{<t>}\) 3.损失函数定义 要进行反向传播,必须得有损失函数嘛,所以我们将损失函数定义如下: 每个节点的损失函数 \(L^{<t>}(\hat{y}^{<t>},y^{<t>})=y^{<t>}log(y^{<t>})-(1-y^{<t>})log(1-\hat{y}^{<t>})\) 整个网络的损失函数 \(L(\hat{y}^{<t>},y^{<t>)}) = \sum_{t=1}^{T_y}L^{<t>}(\hat{y}^{<t>},y^{<t>})\) 4.反向传播 计算出损失值后再通过梯度下降进行反向传播 5.整个流程图 五、不同类型的循环神经网络 本节主要介绍了其他更多类型的RNN结构,下图参考大数据文摘 六、语言模型和序列生成 1.什么是语言模型 凡事开头举个栗子,一切都好说: 假设一个语音识别系统听一句话得到了如下两种选择,作为正常人肯定会选择第二种。但是机器才如何做判断呢? 此时就需要通过语言模型来预测每句话的概率: 2.如何使用RNN构建语言模型 首先我们需要一个很大的语料库(Corpus) 将每个单词字符化(Tokenize,即使用One-shot编码)得到词典,,假设有10000个单词 还需要添加两个特殊的单词 : end of sentence. 终止符,表示句子结束. : UNknown, 之前的笔记已介绍过. 3.构建语言模型示例 假设要对这句话进行建模:Cats average 15 hours of sleep a day. <EOS> 1.初始化 这一步比较特殊,即\(x^{<1>}\)和\(a^{<0>}\)都需要初始化为\(\vec{0}\)。 此时\(\hat{y}^{<1>}\)将会对第一个字可能出现的每一个可能进行概率的判断,即\(\hat{y}^{<1>}=[p(a),…,p(cats),…]\)。 当然在最开始的时候没有任何的依据,可能得到的是完全不相干的字,因为只是根据初始的值和激活函数做出的取样。 2.将真实值作为输入值 之所以将真实值作为输入值很好理解,如果我们一直传错误的值,将永远也无法得到字与字之间的关系。 如下图示,将\(y^{<1>}\)所表示的真实值Cats作为输入,即\(x^{<2>}=y^{<1>}\)得到\(\hat{y}^{<2>}\)。 此时的\(\hat{y}^{<2>}=[p(a|cats),…,p(average|cats),…]\)。 同理有\(\hat{y}^{<3>}=[p(a|cats\, average),…,p(average|cats\,average),…]\) 另外输入值满足: \(x^{<t>}=y^{<t-1>}\) 3.计算出损失值 下图给出了构建模型的过程以及损失值计算公式。 七、对新序列采样 当我们训练得到了一个模型之后,如果我们想知道这个模型学到了些什么,一个非正式的方法就是对新序列进行采样。具体方法如下: 在每一步输出\(\hat{y}\)时,通常使用 softmax 作为激活函数,然后根据输出的分布,随机选择一个值,也就是对应的一个字或者英文单词。 然后将这个值作为下一个单元的x输入进去(即\(x^{<t>}=\hat{y}^{<t-1>}\)), 直到我们输出了终结符,或者输出长度超过了提前的预设值n才停止采样。. 上述步骤具体如图示: 下图给出了采样之后得到的效果: 左边是对训练得到新闻信息模型进行采样得到的内容; 右边是莎士比亚模型采样得到的内容。 八、带有神经网络的梯度消失 1.RNN的梯度消失、爆炸问题 梯度值在RNN中也可能因为反向传播的层次太多导致过小或者过大。 当梯度值过小的时候,神经网络将无法有效地调整自己的权重矩阵导致训练效果不佳,称之为“梯度消失问题”(gradient vanishing problem); 过大时可能直接影响到程序的运作因为程序已经无法存储那么大的值,直接返回 NaN ,称之为“梯度爆炸问题”(gradient exploding problem)。 当梯度值过大的时候有一个比较简便的解决方法,每次将返回的梯度值进行检查,如果超出了预定的范围,则手动设置为范围的边界值。 if (gradient > max) { gradient = max } 但梯度值过小的解决方案要稍微复杂一点,比如下面两句话: “The cat,which already ate apple,yogurt,banana,..., was full.” “The cats,which already ate apple,yogurt,banana,..., were full.” 重点标出的 cat(s)和be动词(was,were) 是有很重要的关联的,但是中间隔了一个which引导的定语从句,对于前面所介绍的基础的RNN网络很难学习到这个信息,尤其是当出现梯度消失时,而且这种情况很容易发生。 我们知道一旦神经网络层次很多时,反向传播很难影响前面层次的参数。所以为了解决梯度消失问题,提出了GRU单元,下面一节具体介绍。 九、GRU单元 GRU(Gated Recurrent Unit)是一种用来解决梯度值过小的方法,首先来看下在一个时刻下的RNN单元,激活函数为 tanh 1.首先回顾一下普通RNN单元的结构示意图 如图示,输入数据为\(a^{<t-1>}\)和\(x^{<t>}\),与参数\(W_a\)进行线性运算后再使用tanh函数转化得到\(a^{<t>}\). 当然再使用softmax函数处理可以得到预测值。 2.GRU结构 记忆细胞 在GRU中会用到 “记忆细胞(Memory cell)” 这个概念,我们用变量\(c\)表示。这个记忆细胞提供了记忆功能,例如它能够帮助记住 cat对应was,cats对应were。 而在t时刻,记忆细胞所包含的值其实就是激活函数值,即\(c^{<t>}=a^{<t>}\)。 注意:在这里两个变量的值虽然一样,但是含义不同。另外在下节将介绍的LSTM中,二者值的大小有可能是不一样的,所以有必要使用这两种变量进行区分。 为了更新记忆细胞的值,我们引入\(\tilde{c}\)来作为候选值从而来更新\(c^{<t>}\),其公式为: \[\tilde{c}=tanh(W_c [c^{<t-1>}, x^{<t>}]+b_c)\] 更新门(update gate) 更新门是GRU的核心概念,它的作用是用于判断是否需要进行更新。 更新门用\(\Gamma_u\)表示,其公式为: \[\Gamma_u=σ(W_u [c^{<t-1>}, x^{<t>}]+b_u)\] 如上图示,\(\Gamma_u\)值的大小大多分布在0或者1,所以可以将其值的大小粗略的视为0或者1。这就是为什么我们就可以将其理解为一扇门,如果\(\Gamma_u=1\),就表示此时需要更新值,反之不用。 t时刻记忆细胞 有了更新门公式后,我们则可以给出t时刻记忆细胞的值的计算公式了: \[c^{<t>}=\Gamma_u*\tilde{c}+(1-\Gamma_u)*c^{<t-1>}\] 注意:上面公式中的 * 表示元素之间进行乘法运算,而其他公式是矩阵运算。 公式很好理解,如果\(\Gamma_u=1\),那么t时刻记忆细胞的值就等于候选值\(\tilde{c}\),反之等于前一时刻记忆细胞的值。 下图给出了该公式很直观的解释: 在读到“cat”的时候 ,其他时候一直为0,知道要输出“was”的时刻我们仍然知道“cat”的存在,也就知道它为单数了。 GRU结构示意图 3.完整版GRU 上述是简化了的GRU,在完整版中还存在另一个符号 ,这个符号的意义是控制\(\tilde{c}\)和\(c^{<t-1>}\)之间的联系强弱,完整版公式如下: 注意,完整公式中多出了一个\(\Gamma_r\),这个符号的作用是控制\(\tilde{c}^{<t>}\)与\(c^{<t>}\)之间联系的强弱。 十、长短期记忆 介绍完GRU后,再介绍LSTM会更加容易理解。下图是二者公式对比: GRU只有两个门,而LSTM有三个门,分别是更新门\(\Gamma_u\)(是否需要更新为\(\tilde{c}^{<t>}\)),遗忘门\(\Gamma_f\)(是否需要丢弃上一个时刻的值),输出门\(\Gamma_o\)(是否需要输出本时刻的值) 虽然LSTM比GRU更复杂,但是它比GRU更早提出哦。另外一般而言LSTM的表现要更好,但是计算量更大,毕竟多了一个门嘛。而GRU实际上是对LSTM的简化,它的表现也不错,能够更好地扩展到深层网络。所以二者各有优势。 下图是LSTM的结构示意图: 十一、双向递归神经网络 前面介绍的都是单向的RNN结构,在处理某些问题上得到的效果不尽人意 如下面两句话,我们要从中标出人名: He said, "Teddy Roosevelt was a great President". He said, "Teddy bears are on sale". 第一句中的Teddy Roosevelt是人名,但第二句中的Teddy bears是泰迪熊,同样都是单词Teddy对应的输出在第一句中应该是1,第二句中应该是0。 像这样的例子如果想让我们的序列模型明白就需要借助不同的结构比如 - 双向递归神经网络(Bidirectional RNN)。该神经网络首先从正面理解一遍这句话,再从反方向理解一遍。 双向递归神经网络结构如下: 下图摘自大数据文摘整理 十二、深层循环神经网络 深层,顾名思义就是层次增加。如下图是深层循环神经网络的示意图 横向表示时间展开,纵向则是层次展开。 注意激活值的表达形式有所改变,以\(a^{[1]<0>}\)为例进行解释: [1]表示第一层 <0>表示第一个激活值 另外各个激活值的计算公式也略有不同,以\(a^{[2]<3>}\)为例,其计算公式如下: MARSGGBO原创 2018-1-17
一、什么是人脸识别 老实说这一节中的人脸识别技术的演示的确很牛bi,但是演技好尴尬,233333 啥是人脸识别就不用介绍了,下面笔记会介绍如何实现人脸识别。 二、One-shot(一次)学习 假设我们发财了,开了一家公司。然后作为老板的我们希望与时俱进,所以想使用人脸识别技术来实现打卡。 假如我们公司只有4个员工,按照之前的思路我们训练的神经网络模型应该如下: 如图示,输入一张图像,经过CNN,最后再通过Softmax输出5个可能值的大小(4个员工中的一个,或者都不是,所以一一共5种可能性)。 看起来好像没什么毛病,但是我们要相信我们的公司会越来越好的啊,所以难道公司每增加一个人就要重新训练CNN以及最后一层的输出数量吗? 这显然有问题,所以有人提出了一次学习(one-shot),更具体地说是通过一个函数来求出输入图像与数据库中的图像的差异度,用\(d(img1,img2)\)表示。 如上图示,如果两个图像之间的差异度不大于某一个阈值 τ,那么则认为两张图像是同一个人。反之,亦然。 下一小节介绍了如何计算差值。 三、Siamese网络 注意:下图中两个网络参数是一样的。 先看上面的网络。记输入图像为\(x^{(1)}\),经过卷积层,池化层和全连接层后得到了箭头所指位置的数据(一般后面还会接上softmax层,但在这里暂时不用管),假设有128个节点,该层用\(f(x^{(1)})\)表示,可以理解为输入\(x^{(1)}\)的编码。 那么下一个网络同理,不再赘述。 因此上一节中所说的差异度函数即为 \(d(x^{(1)},x^{(2)})=||f(x^{(1)})-f(x^{(2)})||^2\) 问题看起来好像解决了,但总感觉还漏了点什么。。。 没错!!! 你没错!!! 神经网络的参数咋确定啊?也就是说\(f(x^{(i)})\)的参数怎么计算呢? 首先我们可以很明确的是如果两个图像是同一个人,那么所得到的参数应该使得\(||f(x^{(1)})-f(x^{(2)})||^2\)的值较小,反之较大。(如下图示) 沿着这个思路我们继续看下一小节内容~~~ 四、Triple损失 1. Learning Objective 这里首先介绍一个三元组,即 (Anchor, Positive, Negative),简写为(A,P,N) Anchor: 可以理解为用于识别的图像 Positive: 表示是这个人 Negative: 表示不是同一个人 由上一节中的思路,我们可以得到如下不等式: \(d(A,P)\leqq d(A,N)\),即\(||f(A)-f(P)||^2-||f(A)-f(N)||^2\leqq0\) (如下图示) 但是这样存在一个问题,即如果神经网络什么都没学到,返回的值是0,也就是说如果\(f(x)=\vec{0}\)的话,那么这个不等式是始终成立的。(如下图示) 为了避免上述特殊情况,而且左边值必须小于0,所以在右边减去一个变量\(α\),但是按照惯例是加上一个值,所以将\(α\)加在左边。 综上,所得到的参数需要满足如下不等式 \(||f(A)-f(P)||^2-||f(A)-f(N)||^2+α\leqq0\) 2. Lost function 介绍完三元组后,我们可以对单个图像定义如下的损失函数(如下图示) \(L(A,P,N)=max(||f(A)-f(P)||^2-||f(A)-f(N)||^2+α,0)\) 解释一下为什么用max函数,因为如果只要满足\(||f(A)-f(P)||^2-||f(A)-f(N)||^2+α\leqq0\),我们就认为已经正确识别出了图像中的人,所以对于该图像的损失值是0. 所以总的损失函数是 : \(J=\sum{L(A^{(i)},P^{(i)},N^{(i)})}\) 要注意的是使用这种方法要保证每一个人不止有一张图像,否则无法训练。另外要注意与前面的One-shot区分开来,这里是在训练模型,所以训练集的数量要多一些,每个人要有多张照片。而One-shot是进行测试了,所以只需一张用于输入的照片即可。 3. Choosing the triplets(A,P,N) 还有一个很重要的问题就是如何选择三元组(A,P,N)。因为实际上要满足不等式\(d(A,P)+α\leqq d(A,N)\)是比较简单的,即只要将Negative选择的比较极端便可,比如anchor是一个小女孩,而Negative选择一个老大爷。 所以还应该尽量满足\(d(A,N)\approx{d(A,N)}\) 五、面部验证与二分类 通过以上内容,我们可以确定下图中的网络的参数了,那么现在开始进行面部验证了。 上面的是测试图,下面的是数据库中的一张照片。 和之前一样假设\(f(x^{(i)})\)有128个节点,之后这两个数据作为输入数据输入到后面的逻辑回归模型中去,即 \(\hat{y}=σ(\sum_{k=1}^{128}w_i|f(x^{(i)})_k-f(x^{(j)})_k|+b_i)\) 若\(\hat{y}=1\),为同一人。反之,不是。 如下图示,绿色下划线部分可以用其他公式替换,即有 \(\hat{y}=σ(\sum_{k=1}^{128}w_i \frac{(f(x^{(i)})_k-f(x^{(j)})_k)^2}{f(x^{(i)})_k+f(x^{(j)})_k}+b_i)\) 当然数据库中的图像不用每次来一张需要验证的图像都重新计算,其实可以提前计算好,将结果保存起来,这样就可以加快运算的速度了。 六、什么是神经风格转换 如下图示,不多赘述。 七、深度卷积网络在学什么? 八、代价函数 如下图示: 左上角的包含Content的图片简称为C,右上角包含Style的简称S,二者融合后得到的图片简称为G。 我们都知道计算问题必须是有限的,所以融合的标准是什么?也就是说Content的保留程度和Style的运用程度如何取舍呢? 此时引入损失函数,并对其进行最优化,这样便可得到最优解。 \(J(G)=αJ_{Content}(C,G)+βJ_{Style}(S,G)\) \(J_{Content}(C,G)\)表示图像C和图像G之间的差异,\(J_{Style}(S,G)\)同理。 计算过程示例: 随机初始化图像G,假设为100*100*3 (如下图右边四个图像最上面那个所示) 使用梯度下降不断优化J(G)。 (优化过程如下图右边下面3个图像所示) 下面一小节将具体介绍损失函数的计算。 九、内容代价函数 首先假设我们使用第\(l\)层隐藏层来计算\(J_{Content}(C,G)\),注意这里的\(l\)一般取在中间层,而不是最前面的层,或者最后层。 原因如下: 假如取第1层,那么得到的G图像将会与图像C像素级别的相似,这显然不行。 假如取很深层,那么该层已经提取出了比较重要的特征,例如图像C中有一条狗,那么得到的图像G会过度的保留这个特征。 然后使用预先训练好的卷积神经网络,如VGG网络。这样我们就可以得到图像C和图像G在第\(l\)层的激活函数值,分别记为\(a^{[l][C]},a^{[l][G]}\) 内容损失函数 \(J_{Content}(C,G)=\frac{1}{2}||a^{[l][C]}-a^{[l][G]}||^2\) 十、风格损失函数 1.什么是“风格” 要计算风格损失函数,我们首先需要知道“风格(Style)”是什么。 我们使用\(l\)层的激活来度量“Style”,将“Style”定义为通道间激活值之间的相关系数。(Define style as correlation between activation across channels) 那么我们如何计算这个所谓的相关系数呢? 下图是我们从上图中所标识的第\(l\)层,为方便说明,假设只有5层通道。 如上图示,红色通道和黄色通道对应位置都有激活项,而我们要求的便是它们之间的相关系数。 但是为什么这么求出来是有效的呢?为什么它们能够反映出风格呢? 继续往下看↓ 2.图像风格的直观理解 如图风格图像有5层通道,且该图像的可视化特征如左下角图所示。 其中红色通道可视化特征如图中箭头所指是垂直条纹,而黄色通道的特征则是橘色背景。 那么通过计算这两层通道的相关系数有什么用呢? 其实很好理解,如果二者相关系数性强,那么如果出现橘色背景,那么就应该很大概率出现垂直条纹。反之,亦然。 3.风格相关系数矩阵 令\(a_{i,j,k}^{[l]}\)表示(i,j,k)的激活项,其中i,j,k分别表示高度值(H),宽度值(W)以及所在通道层次(C)。 风格矩阵(也称为“Gram Matrix”)用\(G^{[l]}\)表示,其大小为\(n_c^{l]}*n_c^{l]}\). 因此风格图像的风格矩阵为: \[G_{kk'}^{[l](S)}=\sum_{i=1}^{n_H^{[l]}}\sum_{j=1}^{n_W^{[l]}}a_{i,j,k}^{[l](S)}a_{i,j,k'}^{[l](S)}\] 生成图像的相关系数矩阵 \[G_{kk'}^{[l](G)}=\sum_{i=1}^{n_H^{[l]}}\sum_{j=1}^{n_W^{[l]}}a_{i,j,k}^{[l](G)}a_{i,j,k'}^{[l](G)}\] 4.风格损失函数 第\(l\)层的风格损失函数为: \[J_{Style}^{[l]}=\frac{1}{(2n_H^{[l]}n_W^{[l]}n_C^{[l]})^2}\sum_{k}\sum_{k'}(G_{kk'}^{[l](S)}-G_{kk'}^{[l](G)})\] 总的风格损失函数: \[J_{Style}(S,G)=\sum_{l}λ^{[l]}J_{Style}^{[l]}(S,G)\] MARSGGBO原创 2018-2-13
此文记录我可爱的侄女-紫琪同学~ 太可爱太懂事了,有些瞬间需要用文字记录下来 2017.09.30 从武汉回到长沙,一到家琪琪就“fufu(叔叔)”的叫我,也不知道她是故意的还是真的还不能正确发音,于是我就逗着说:"你叫错了啦~是叔叔的fu"。琪琪居然仰头大笑,我当时一脸懵逼,这小丫头片子居然能懂我的冷幽默~~像我有出息,唔哈哈哈。 而后,拿出她的书包,取出“一堆”书(也就3,4本教材),接着选了一本故事书得意洋洋的用稚嫩的声音向我复述书上图画描绘的故事剧情。不得不说,真的被惊讶到了,居然说的有头有尾的(/ω\)。看她那么得意,我显然不能让她得逞,所以拿了她的一本英语书,指着“good morning”问她怎么读,是什么意思?她故做沉思,我还以为她知道,只不过在回忆,结果她回到:“你不是读过书吗还问我?” 一万吨反伤刺甲有没有,居然这么巧妙的化解了尴尬(/ω\)。 2017.10.17 今天跟我妈微信视频。视频里我妈开心的说起她和琪琪之间的对话,简直不要太暖(我妈在长沙,琪琪在老家) 我妈:琪琪,你想奶奶了吗? 琪琪:奶奶,我好想你的 琪琪:奶奶,你那边有饭吃,有水喝吗? 我妈:感动无比中.. 我妈:琪琪,爷爷今天有没有批评你啊 琪琪:没有啊 我妈:那爷爷有没有批评爸爸呀? 琪琪:(爸爸)是爷爷的仔仔,为什么要批评呀 好暖有没有~~~~~~~
注意!!!该方法针对Windows用户,亲测有效。 1.用管理员权限记事本打开host文件 2.将如下内容复制到文件末尾 52.84.246.90 d3c33hcgiwev3.cloudfront.net 52.84.246.252 d3c33hcgiwev3.cloudfront.net 52.84.246.144 d3c33hcgiwev3.cloudfront.net 52.84.246.72 d3c33hcgiwev3.cloudfront.net 52.84.246.106 d3c33hcgiwev3.cloudfront.net 52.84.246.135 d3c33hcgiwev3.cloudfront.net 52.84.246.114 d3c33hcgiwev3.cloudfront.net 52.84.246.90 d3c33hcgiwev3.cloudfront.net 52.84.246.227 d3c33hcgiwev3.cloudfront.net 3.打开命令行,输入如下命令 ipconfig/flushdns 4.刷新页面即可 要是觉得有效,去给我的博客点个赞可好~~~marsggbo的博客园 MARSGGBO原创 2017-10-4
强烈建议安装anaconda之后再来安装这个pytorch,具体怎么安装百度搜索就知道了。 温馨提示,在安装anaconda的时候记得将“添加到环境变量”(安装的时候是英文的)这一选项选上。 下面假设你已经安装好anaconda了: 1.第一步下载pytorch的安装包: 链接: https://pan.baidu.com/s/1mh6U01i 密码: 8yty 2.打开命令行进入上面所下载的安装包所在的目录,然后输入如下命令: conda install --offline pytorch-0.2.1-py36he6bf560_0.2.1cu80.tar.bz2 3.接下来就是查看是否安装成功 进入python命令行交互界面输入如下 ipython Python 3.6.2 |Anaconda custom (64-bit)| (default, Sep 19 2017, 08:03:39) [MSC v.1900 64 bit (AMD64)] Type 'copyright', 'credits' or 'license' for more information IPython 6.1.0 -- An enhanced Interactive Python. Type '?' for help. In [1]: import torch In [2]: 若没报错则表示安装成功 4.查看CUDA和cudnn是否能正常工作 In [6]: x = torch.Tensor([666]) In [7]: x_cuda = x.cuda() In [8]: print(x_cuda) 666 [torch.cuda.FloatTensor of size 1 (GPU 0)] In [9]: print(cudnn.is_acceptable(x_cuda)) True 这样表示cuda和cudnn可以正常使用。 如果cudnn报错,则使用如下代码将其关闭 cudnn.enabled = False MARSGGBO原创 2017-10-2
1. Mini-batch梯度下降法 介绍 假设我们的数据量非常多,达到了500万以上,那么此时如果按照传统的梯度下降算法,那么训练模型所花费的时间将非常巨大,所以我们对数据做如下处理: 如图所示,我们以1000为单位,将数据进行划分,令\(x^{\{1\}}=\{x^{(1)},x^{(2)}……x^{(5000)}\}\), 一般地用\(x^{\{t\}},y^{\{t\}}\)来表示划分后的mini-batch。 注意区分该系列教学视频的符号标记: 小括号() 表示具体的某一个元素,指一个具体的值,例如\(x^{(i)}\) 中括号[] 表示神经网络中的某一层,例如\(Z^{[l]}\) 大括号{} 表示将数据细分后的一个集合,例如\(x^{\{1\}}=\{x^{(1)},x^{(2)}……x^{(5000)}\}\) 算法步骤 假设我们有5,000,000个数据,每1000作为一个集合,计入上面所提到的\(x^{\{1\}}=\{x^{(1)},x^{(2)}……x^{(5000)}\},……\) 1)所以需要迭代运行5000次神经网络运算。 for i in range(5000): 2)每一次迭代其实与之前笔记中所提到的计算过程一样,首先是前向传播,但是每次计算的数量是1000 3)计算损失函数,如果有正则化,则记得加上正则项 4)反向传播 注意,mini-batch相比于之前一次性计算所有数据不仅速度快,而且反向传播需要计算5000次,所以效果也更好。 2. 理解mini-batch梯度下降法 如上面所提到的,我们以1000位单位对数据进行划分,但是这只是为了更方便说明问题才这样划分的,那么我们在实际操作中应该如何划分呢? 首先考虑两个极端情况: mini-batch size = m 此时即为Batch gradient descent,\((x^{\{t\}},y^{\{t\}})=(X,Y)\) mini-batch size = 1 此时即为Stochastic gradient descent, \((x^{\{t\}},y^{\{t\}})=(x^{(i)},y^{(i)})\) 如图示,蓝色收敛曲线表示mini-batch size=m,比较耗时,但是最后能够收敛到最小值;而紫色收敛曲线表示mini-batch size=1,虽然速度可能较快,但是收敛曲线十分曲折,并且最终不会收敛到最小点,而是在其附近来回波动。 说了这么多,那么mini-batch size该如何选择呢?以下是选择的原则: 如果数据量比较小(m<2000),可以使用batch gradient descent。一般来说mini-batch size取2的次方比较好,例如64,128,256,512等,因为这样与计算机内存设置相似,运算起来会更快一些。 3. 指数加权平均 为了理解后面会提到的各种优化算法,我们需要用到指数加权平均,在统计学中也叫做指数加权移动平均(Exponentially Weighted Moving Averages)。 首先我们假设有一年的温度数据,如下图所示 我们现在需要计算出一个温度趋势曲线,计算方法如下: \(V_0=0\) \(V_1=β*V_0+(1-β)θ_1\) \(……\) \(V_t=β*V_{t-1}+(1-β)θ_t\) 上面的\(θ_t\)表示第t天的温度,β是可调节的参数,\(V_t\)表示\(\frac{1}{1-β}\)天的每日温度。 当\(β=0.9\)时,表示平均了过去十天的温度,且温度趋势曲线如图中红线所示 当\(β=0.98\)时,表示平均了过去50天的温度,温度趋势曲线如图中绿线所示。此时绿线相比较红线要平滑一些,是因为对过去温度的权重更大,所以当天天气温度的影响降低,在温度变化时,适应得更缓慢一些。 当\(β=0.5\)时,温度趋势曲线如图中黄线所示 4. 理解指数加权平均 我们将上面的公式\(V_t=β*V_{t-1}+(1-β)θ_t\)展开可以得到 (假设β=0.9) \[V_t=0.1θ_t+0.1*0.9θ_{t-1}+0.1*0.9^2θ_{t-2}+…\] 可以看到在计算第t天的加权温度时,也将之前的温度考虑进来,但是都有一个衰减因子β,并且随着天数的增加,衰减幅度也不断增加。(有点类似于卷积计算) 5. 指数加权平均的偏差修正 为什么需要修正呢?我们仔细分析一下就知道了 首先我们假设的是\(β=0.98, V_0=0\),然后由\(V_t=βV_{t-1}+(1-β)θ_t\)可知 \(V_1=0.98V_0+0.02θ_1=0.02θ_1\) \(V_2=0.98V_1+0.02θ_2=0.0196θ_1+0.02θ_2\) 假设\(θ_1=40℃\),那么\(V_1=0.02*40=0.8℃\),这显然相差太大,同理对于后面的温度的计算也只会是变差越来越大。所以我们需要进行偏差修正,具体方法如下: \[V_t=\frac{βV_{t-1}+(1-β)θ_t}{1-β^t}\] 注意!!!上面公式中的 \(V_{t-1}\)是未修正的值。 为方便说明,令\(β=0.98,θ_1=40℃,θ_2=39℃\),则 当\(t=1,θ_1=40℃\)时,\(V_1=\frac{0.02*40}{1-0.98}=40\),哇哦~有没有很巧的感觉,再看 当\(t=2,θ_2=39℃\)时,\(V_2=\frac{0.98*V_{t-1}+0.02*θ_2}{1-0.98^2}=\frac{0.98*(0.02*θ_1)+0.02*39}{1-0.98^2}=39.49\) 所以,记住你如果直接用修正后的\(V_{t-1}\)值代入计算就大错特错了 6. 动量梯度下降法 首先介绍一下一般的梯度算法收敛情况是这样的 可以看到,在前进的道路上十分曲折,走了不少弯路,在纵向我们希望走得慢一点,横向则希望走得快一点,所以才有了动量梯度下降算法。 Momentum算法的第t次迭代: 计算出dw,db 这个计算式子与上一届提到的指数加权平均有点类似,即\(V_{dw}=βV_{dw}+(1-β)dw\)\(V_{db}=βV_{db}+(1-β)db\) \(W=W-αV_{dw},b=b-αV_{db}\) 最终得到收敛的效果如下图的红色曲线所示。 该算法中涉及到的超参数有两个,分别是 \(α,β\),其中一般\(β=0.9\)是比较常取的值。 7. RMSprop 该算法全称叫Root Mean Square Prop(均方根传播) 这一节和上一节讲的都比较概括,不是很深入,所以就直接把算法记录下来吧。 在第t次迭代: 计算该次mini-batch的dw,db \(S_{dw}=βS_{dw}+(1-β)dw^2\)\(S_{db}=βS_{db}+(1-β)db^2\) \(w:=w-α\frac{dw}{\sqrt{S_{dw}}}\)\(b:=b-α\frac{db}{\sqrt{S_{db}}}\) 收敛效果(原谅色) 8. Adam优化算法 Adam其实是Momentum和RMSprop两个算法的结合,具体算法如下: 初始化\(V_{dw}=0,V_{db}=0,S_{dw}=0,S_{dw}=0\) 在第t次迭代 计算出dw,db \(V_{dw}=β_1V_{dw}+(1-β_1)dw\),\(V_{db}=β_1V_{db}+(1-β_1)db\)\(S_{dw}=β_2S_{dw}+(1-β_2)dw^2\),\(S_{db}=β_2S_{db}+(1-β_2)db^2\) \(V_{dw}^{corrected}=\frac{V_{dw}}{1-β_1^t}\),\(V_{db}^{corrected}=\frac{V_{db}}{1-β_1^t}\)\(S_{dw}^{corrected}=\frac{S_{dw}}{1-β_2^t}\),\(S_{db}^{corrected}=\frac{S_{db}}{1-β_2^t}\) \(W=W-α\frac{V_{dw}^{corrected}}{\sqrt{S_{dw}^{corrected}}+ε}\),\(b=b-α\frac{V_{db}^{corrected}}{\sqrt{S_{db}^{corrected}}+ε}\) 该算法中的超参数有\(α,β_1,β_2,ε\),一般来说\(β_1=0.9,β_2=0.999,ε=10^{-8}\) 9. 学习率衰减 之前算法中提到的学习率α都是一个常数,这样有可能会一个问题,就是刚开始收敛速度刚刚好,可是在后面收敛过程中学习率偏大,导致不能完全收敛,而是在最低点来回波动。所以为了解决这个问题,需要让学习率能够随着迭代次数的增加进行衰减,常见的计算公式有如下几种: Learning rate decay \[α=\frac{1}{1+decay_rate*epoch_num}α_0\] decay_rate:衰减率epoch_num: 迭代次数 举个栗子: 假设\(α_0\)初始化为0.2,decay_rate=1,则α的衰减过程如下: Epoch α 1 0.1 2 0.067 3 0.05 …… …… 其他衰减算法 指数衰减:\(α=0.9^{epoch_num}α_0\) \(α=\frac{K}{\sqrt{epoch_num}}α_0\)或\(α=\frac{k}{t}α_0\)(这个t表示mini-batch的第t组数据) 离散衰减,每次迭代后变为上一次迭代的一半。 10. 局部最优问题 图左中有很多局部最优点。 图右用青色标记出来的点称为鞍点(saddle point),因为和马鞍相似,所以称为鞍点。 鞍点相比于局部最优点要更加棘手,因为从横向上看似乎是最低点,但是纵向上看却不是最低点,所以收敛过程有点缓慢,原因如下: 横向收敛只能沿着红线方向收敛,直到鞍点,而到了鞍点后才能往两边收敛,所以收敛的比较缓慢。 但是momentum和Adam等算法因为能够加速学习,所以收敛速率更快,能够更快地收敛。
第一章 神经网络与深度学习(Neural Network & Deeplearning) DeepLearning.ai学习笔记(一)神经网络和深度学习--Week3浅层神经网络 DeepLearning.ai学习笔记(一)神经网络和深度学习--Week4深层神经网络 第二章 改善深层神经网络 DeepLearning.ai学习笔记(二)改善深层神经网络:超参数调试、正则化以及优化--Week1深度学习的实用层面 DeepLearning.ai学习笔记(二)改善深层神经网络:超参数调试、正则化以及优化--Week2优化算法 DeepLearning.ai学习笔记(二)改善深层神经网络:超参数调试、正则化以及优化--week3 超参数调试、Batch正则化和程序框架 第三章 结构化机器学习项目 DeepLearning.ai学习笔记(三)结构化机器学习项目--week1 机器学习策略 DeepLearning.ai学习笔记(三)结构化机器学习项目--week2机器学习策略(2) 第四章 卷积神经网络 DeepLearning.ai学习笔记(四)卷积神经网络 -- week1 卷积神经网络基础知识介绍 DeepLearning.ai学习笔记(四)卷积神经网络 -- week2深度卷积神经网络 实例探究 DeepLearning.ai学习笔记(四)卷积神经网络 -- week3 目标检测 DeepLearning.ai学习笔记(四)卷积神经网络 -- week4 特殊应用:人力脸识别和神经风格转换 第五章 序列模型 DeepLearning.ai学习笔记(五)序列模型 -- week1 循环序列模型 DeepLearning.ai学习笔记(五)序列模型 -- week2 自然语言处理与词嵌入 后续持续更新中。。 MARSGGBO原创 2017-10-08
参考通俗理解决策树算法中的信息增益 说到决策树就要知道如下概念: 熵:表示一个随机变量的复杂性或者不确定性。 假如双十一我要剁手买一件衣服,但是我一直犹豫着要不要买,我决定买这件事的不确定性(熵)为2.6。 条件熵:表示在直到某一条件后,某一随机变量的复杂性或不确定性。 我在看了这件衣服的评价后,我决定买衣服这件事的不确定性是1.2。 我在线下实体店试穿衣服后,我决定买衣服这件事的不确定性是0.9。 信息增益:表示在知道某一条件后,某一随机变量的不确定性的减少量。 上面条件熵给出了两个: 一个是看了网上的评价,此时的信息增益是\(Gain_1 =2.6-1.2=1.4\)。 另一个是线下试穿了衣服,此时的信息增益\(Gain_2=2.6-0.9=1.7\)。 很显然我在线下试穿衣服之后对于决定买这件衣服的不确定度下降更多,更通俗的说就是我试穿衣服之后买这件衣服的可能性更大了。所以如果有看买家评价和线下试穿两个属性,首先应该选择线下试穿来构建内部节点。 信息熵计算公式 符号\(x_i\)所具备的信息为: \[I(x_i) = -log_2p(x_i)\] 所有类别所具有的信息熵(information entropy):\[H(X) = -\sum_{i=1}^{n}p(x_i)log_2p(x_i)\] MARSGGBO原创 2017-8-24
KNN实现手写数字识别 博客上显示这个没有Jupyter的好看,想看Jupyter Notebook的请戳KNN实现手写数字识别.ipynb 1 - 导入模块 import numpy as np import matplotlib.pyplot as plt from PIL import Image from ld_mnist import load_digits %matplotlib inline 2 - 导入数据及数据预处理 import tensorflow as tf # Import MNIST data from tensorflow.examples.tutorials.mnist import input_data def load_digits(): mnist = input_data.read_data_sets("path/", one_hot=True) return mnist mnist = load_digits() Extracting C:/Users/marsggbo/Documents/Code/ML/TF Tutorial/data/MNIST_data\train-images-idx3-ubyte.gz Extracting C:/Users/marsggbo/Documents/Code/ML/TF Tutorial/data/MNIST_data\train-labels-idx1-ubyte.gz Extracting C:/Users/marsggbo/Documents/Code/ML/TF Tutorial/data/MNIST_data\t10k-images-idx3-ubyte.gz Extracting C:/Users/marsggbo/Documents/Code/ML/TF Tutorial/data/MNIST_data\t10k-labels-idx1-ubyte.gz 数据维度 print("Train: "+ str(mnist.train.images.shape)) print("Train: "+ str(mnist.train.labels.shape)) print("Test: "+ str(mnist.test.images.shape)) print("Test: "+ str(mnist.test.labels.shape)) Train: (55000, 784) Train: (55000, 10) Test: (10000, 784) Test: (10000, 10) mnist数据采用的是TensorFlow的一个函数进行读取的,由上面的结果可以知道训练集数据X_train有55000个,每个X的数据长度是784(28*28)。 x_train, y_train, x_test, y_test = mnist.train.images, mnist.train.labels, mnist.test.images, mnist.test.labels 展示手写数字 nums = 6 for i in range(1,nums+1): plt.subplot(1,nums,i) plt.imshow(x_train[i].reshape(28,28), cmap="gray") 3 - 构建模型 class Knn(): def __init__(self,k): self.k = k self.distance = {} def topKDistance(self, x_train, x_test): ''' 计算距离,这里采用欧氏距离 ''' print("计算距离...") distance = {} for i in range(x_test.shape[0]): dis1 = x_train - x_test[i] dis2 = np.sqrt(np.sum(dis1*dis1, axis=1)) distance[str(i)] = np.argsort(dis2)[:self.k] if i%1000==0: print(distance[str(i)]) return distance def predict(self, x_train, y_train, x_test): ''' 预测 ''' self.distance = self.topKDistance(x_train, x_test) y_hat = [] print("选出每项最佳预测结果") for i in range(x_test.shape[0]): classes = {} for j in range(self.k): num = np.argmax(y_train[self.distance[str(i)][j]]) classes[num] = classes.get(num, 0) + 1 sortClasses = sorted(classes.items(), key= lambda x:x[1], reverse=True) y_hat.append(sortClasses[0][0]) y_hat = np.array(y_hat).reshape(-1,1) return y_hat def fit(self, x_train, y_train, x_test, y_test): ''' 计算准确率 ''' print("预测...") y_hat = self.predict(x_train, y_train, x_test) # index_hat =np.argmax(y_hat , axis=1) print("计算准确率...") index_test = np.argmax(y_test, axis=1).reshape(1,-1) accuracy = np.sum(y_hat.reshape(index_test.shape) == index_test)*1.0/y_test.shape[0] return accuracy, y_hat clf = Knn(10) accuracy, y_hat = clf.fit(x_train,y_train,x_test,y_test) print(accuracy) 预测... 计算距离... [48843 33620 11186 22059 42003 9563 39566 10260 35368 31395] [54214 4002 11005 15264 49069 8791 38147 47304 51494 11053] [46624 10708 22134 20108 48606 19774 7855 43740 51345 9308] [ 8758 47844 50994 45610 1930 3312 30140 17618 910 51918] [14953 1156 50024 26833 26006 38112 31080 9066 32112 41846] [45824 14234 48282 28432 50966 22786 40902 52264 38552 44080] [24878 4655 20258 36065 30755 15075 35584 12152 4683 43255] [48891 20744 47822 53511 54545 27392 10240 3970 25721 30357] [ 673 17747 33803 20960 25463 35723 969 50577 36714 35719] [ 8255 42067 53282 14383 14073 52083 7233 8199 8963 12617] 选出每项最佳预测结果 计算准确率... 0.9672 准确率略高。 MARSGGBO原创 2017-8-21
一、深层神经网络 深层神经网络的符号与浅层的不同,记录如下: 用\(L\)表示层数,该神经网络\(L=4\) \(n^{[l]}\)表示第\(l\)层的神经元的数量,例如\(n^{[1]}=n^{[2]}=5,n^{[3]}=3,n^{[4]}=1\) \(a^{[l]}\)表示第\(l\)层中的激活函数,\(a^{[l]}=g^{[l]}(z^{[l]})\) 二、前向和反向传播 1. 第\(l\)层的前向传播 输入为 \(a^{[l-1]}\) 输出为 \(a^{[l]}\), cache(\(z^{[l]}\)) 矢量化表示:\[Z^{[l]}=W^{[l]}·A^{[l-1]}+b^{[l]}\]\[A^{[l]}=g^{[l]}(Z^{[l]})\] 2. 第\(l\)层的反向传播 输入为 \(da^{[l]}\) 输出为 \(da^{[l-1]},dW^{[l]},db^{[l]}\) 计算细节:\[dz^{[l]}=da^{[l]}*g^{[l]'}(z^{[l]})\]\[dw^{[l]}=dz^{[l]}*a^{[l-1]}\]\[db^{[l]}=dz^{[l]}\]\[da^{[l-1]}=w^{[l]^T}·dz^{[l]}\]\[dz^{[l]}=w^{[l+1]^T}dz^{[l+1]}*g^{[l]'}(z^{[l]})\] 矢量化表示:\[dZ^{[l]}=dA^{[l]}*g^{[l]'}(z^{[l]})\]\[dw^{[l]}=\frac{1}{m}dz^{[l]}·A^{[l-1]^T}\]\[db^{[l]}=\frac{1}{m}np.sum(dz^{[l]},axis=1,keepdim=True)\]\[dA^{[l-1]}=w^{[l]^T}·dz^{[l]}\] 3. 总结 前向传播示例 反向传播 更清晰的表示: 三、深层网络中的前向传播 四、核对矩阵的维数 这节的内容主要是告诉我们如何知道自己在设计神经网络模型的时候各个参数的维度是否正确的方法。其实我自己在写代码的时候都得这样做才能有信心继续往下敲键盘,2333。 还是以这个神经网络为例,各层神经网络节点数为\(n^{[0]}=3,n^{[1]}=n^{[2]}=5,n^{[3]}=3,n^{[4]}=1\)。 先确定\(W^{[1]}\)的维度: 已知\(Z^{[1]}=W^{[1]}·X+b^{[1]}\),很容易知道\(Z^{[1]}∈R^{5×1},X∈R^{3×1}\),\(b^{[1]}\)其实不用计算就知道其维度与\(Z\)是相同的,即\(b^{[1]}∈R^{5×1}\)。根据矩阵内积计算公式可以确定\(W^{[1]}∈R^{5×3}\)。 其他层同理,不再赘述。 五、为什么使用深层表示 为什么要使用深层表示? 下面就从直观上来理解深层神经网络。 如上图所示是一个人脸识别的过程,具体的实现步骤如下: 1.通过深层神经网络首先会选取一些边缘信息,例如脸形,眼框,总之是一些边框之类的信息(我自己的理解是之所以先找出边缘信息是为了将要观察的事物与周围环境分割开来),这也就是第一层的作用。 2.找到边缘信息后,开始放大,将信息聚合在一起。例如找到眼睛轮廓信息后,通过往上一层汇聚从而得到眼睛的信息;同理通过汇聚脸的轮廓信息得到脸颊信息等等 3.在第二步的基础上将各个局部信息(眼睛、眉毛……)汇聚成一张人脸,最终达到人脸识别的效果。 六、搭建深层神经网络块 上图表示单个神经元的前向和反向传播算法过程。 前向 输入\(a^{[l-1]}\),经过计算\(g^{[l]}(w^{[l]}·a^{[l-1]}+b^{[l]})\)得到\(a^{[l]}\) 反向 计算\(da^{[l]}\),然后反向作为输入,经过一系列微分运算得到\(dw^{[l]},db^{[l]}\)(用来更新权重和偏差),以及上一层的\(da^{[l-1]}\)。 推广到整个深层神经网络就如下图所示: 祭上神图: 七、参数 vs 超参数 参数 常见的参数即为\(W^{[1]},b^{[1]},W^{[2]},b^{[2]}……\) 超参数 learning_rate: \(α\) iterations(迭代次数) hidden layer (隐藏层数量\(L\)) hidden units (隐藏层神经元数量\(n^{[l]}\)) 激活函数的选择 minibatch size 几种正则化的方法 momentum(动力、动量)后面会提到 八、这和大脑有什么关系 主要就是说神经网络和人的大脑运行机理貌似很相似,blabla。。。 MARSGGBO原创 2017-9-2
博客园自定义皮肤设计 效果抢先看 眼见为实!!!戳戳戳》》》marsggbo的博客园 1. 博客皮肤 首先将博客皮肤选为BlackLowKey 2. 代码设置 1) 页面定制CSS代码 body{ background: url(http://dik.img.lgdsy.com/pic/38/26542/7d7f902577c15f1e_1440x900.jpg) fixed center center; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; } #navList a:link, #navList a:visited, #navList a:active{ text-shadow: none; } #navList a{ text-shadow: none; } #navList a:hover{ font-weight: bold; color:rgb(10,186,157); text-shadow: none; text-decoration: none; } .postTitle a:link, .postTitle a:visited, .postTitle a:active { color: rgb(7,113,126); } .postTitle { border-left: 5px solid rgb(10,186,157); } #Header1_HeaderTitle{ font-size: 350%; } #Header1_HeaderTitle:hover{ text-decoration: none; color:rgb(10,186,157); } #blogTitle h2{ margin-left: 2em; font-size: 1.5em; padding-bottom: 20px } #back-to-top { background-color: rgb(10,186,157); bottom: 0; box-shadow: 0 0 6px rgb(10,186,157); color:white; padding: 10px 10px; position: fixed; right: 50px; cursor: pointer; width:20px; height:20px; border-radius:20px; text-align:center; } #back-to-top a{ width:16px; height: 16px; text-decoration: none; font-weight:bold; color:white; font-size:18px; } #back-to-top a:hover{ color:rgb(255,202,189); } #sidebar_shortcut{ display:none; } #div_digg{ position:fixed; bottom:5px; width:120px; left:0px; padding:5px; background-color:#fff; border-radius:5px 5px 5px 5px !important; box-shadow:0 0 0 1px #5F5A4B, 1px 1px 6px 1px rgba(10, 10, 0, 0.5); } 2)博客侧边栏公告 <div id="myTime"> <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0" width="180" height="80" id="honehoneclock" align="middle"> <param name="allowScriptAccess" value="always"> <param name="movie" value="http://chabudai.sakura.ne.jp/blogparts/honehoneclock/honehone_clock_wh.swf"> <param name="quality" value="high"> <param name="bgcolor" value="#ffffff"> <param name="wmode" value="transparent"> <embed wmode="transparent" src="http://chabudai.sakura.ne.jp/blogparts/honehoneclock/honehone_clock_wh.swf" quality="high" bgcolor="#ffffff" width="180" height="80" name="honehoneclock" align="middle" allowscriptaccess="always" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer"> </object> </div> <div align="left"> <b>您是第</b> <a href="http://www.amazingcounters.com"><img border="0" src="http://cc.amazingcounters.com/counter.php?i=3216805&c=9650728" alt="AmazingCounters.com"></a> <b>位访客</b> </div> <div id="sidebar_friendLinks" class="sidebar-block"> <div class="friendLinks"> <hr><br> <b class="friendLinksTitle">友情链接</b> <ul> <li><a href="http://blog.csdn.net/marsggbo" title="CSDN博客">marsggbo的CSDN博客</a></li> <li><a href="http://blog.163.com/hexin_mars_blog/" title="网易博客">火星教</a></li> <li><a href="http://marsggbo.github.io/blog/" title="github博客">github博客</a></li> <li><a href="http://www.cnblogs.com/marsggbo/p/" title="我的博客的评论列表">我的随笔</a></li> </ul> <div id="itemListLin_con" style="display:none;"> <ul> </ul> </div> </div> <hr><br> 3)页首Html代码 github地址 <a href="https://github.com/marsggbo"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://github-camo.global.ssl.fastly.net/652c5b9acfaddf3a9c326fa6bde407b87f7be0f4/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f6f72616e67655f6666373630302e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_orange_ff7600.png"></a> 4)页脚Html代码 <script type="text/javascript">var cnzz_protocol = (("https:" == document.location.protocol) ? " https://" : " http://");document.write(unescape("%3Cspan id='cnzz_stat_icon_1263920205'%3E%3C/span%3E%3Cscript src='" + cnzz_protocol + "s13.cnzz.com/z_stat.php%3Fid%3D1263920205%26show%3Dpic' type='text/javascript'%3E%3C/script%3E"));</script> <script src="http://cdn.bootcss.com/bootstrap/3.2.0/js/bootstrap.min.js"></script> <link href="https://files.cnblogs.com/files/marsggbo/category.css" rel="stylesheet"> <script type="text/javascript" src="https://files.cnblogs.com/files/marsggbo/category.js"></script> <span id="back-to-top"><a href="#top">↑</a></span> <br><br><hr> <footer style="color: white; background-color: rgb(24,24,24); padding: 10px"> <h3 style="text-align: center; color: tomato; font-size: 16px"> <b>MARSGGBO</b><b style="color: white"><span style="font-size: 25px"></span>原创</b> <br> <br> <b style="color: white"> </b> </h3> </footer> 页脚引入的文件链接: 博客园自定义设计
之前看Andrew大神的视频有介绍到神经网络权重需要随机初始化而不是全初始化为0的问题,其真正深层次的含义没有弄明白,所以结合一些资料(cs231n课程)希望能让自己之后再想到这个问题的时候能够快速地明白过来。 另外这篇文章其实是一篇译文,所以翻译不是很确定的地方也将原文中的英文语句复制在句后,如果有更合适的翻译也请留言告知一下,谢谢! 参考文献: CS231n Convolutional Neural Networks for Visual Recognition 权重初始化 我们已经知道了如何构建神经网络结构,也知道了怎么预处理数据。在我们真正开始训练网络之前,我们必须要知道如何去初始化神经网络的参数。 陷阱: 都初始化为0。 首先介绍一下我们不应该做的事情(即初始化为0)。需要注意的是我们并不知道在训练神经网络中每一个权重最后的值,但是如果进行了恰当的数据归一化后,我们可以有理由认为有一半的权重是正的,另一半是负的。令所有权重都初始化为0这个一个听起来还蛮合理的想法也许是一个我们假设中最好的一个假设了。但结果正确是一个错误(的想法),因为如果神经网络计算出来的输出值都一个样,那么反向传播算法计算出来的梯度值一样,并且参数更新值也一样(\(w=w-α*dw\))。更一般地说,如果权重初始化为同一个值,网络就不可能不对称(即是对称的)。 为什么不能是对称的? 答案参考【知乎:为什么神经网络在考虑梯度下降的时候,网络参数的初始值不能设定为全0,而是要采用随机初始化思想?】 设想你在爬山,但身处直线形的山谷中,两边是对称的山峰。 由于对称性,你所在之处的梯度只能沿着山谷的方向,不会指向山峰;你走了一步之后,情况依然不变。 结果就是你只能收敛到山谷中的一个极大值,而走不到山峰上去。 初始化为小的随机数 既然不能都初始化为0,那么很自然的我们会想到将权重初始化为非常接近0的小数(正如我们上面所讨论的不能等于0)。将权重初始化为很小的数字是一个普遍的打破网络对称性的解决办法。这个想法是,神经元在一开始都是随机的、独一无二的,所以它们会计算出不同的更新,并将自己整合到整个网络的各个部分。一个权重矩阵的实现可能看起来像\(W=0.01 * np.random.randn(D,H)\),其中randn是从均值为0的单位标准高斯分布进行取样。通过这个公式(函数),每个神经元的权重向量初始化为一个从多维高斯分布取样的随机向量,所以神经元在输入空间中指向随机的方向(so the neurons point in random direction in the input space.应该是指输入空间对于随机方向有影响)。其实也可以从均匀分布中来随机选取小数,但是在实际操作中看起来似乎对最后的表现并没有太大的影响。 警告:并不是数字越小就会表现的越好。比如,如果一个神经网络层的权重非常小,那么在反向传播算法就会计算出很小的梯度(因为梯度gradient是与权重成正比的)。在网络不断的反向传播过程中将极大地减少“梯度信号”,并可能成为深层网络的一个需要注意的问题。 用1/sqrt(n)校准方差 上述建议的一个问题是,随机初始化神经元的输出的分布有一个随输入量增加而变化的方差。结果证明,我们可以通过将其权重向量按其输入的平方根(即输入的数量)进行缩放,从而将每个神经元的输出的方差标准化到1。也就是说推荐的启发式方法(heuristic)是将每个神经元的权重向量按下面的方法进行初始化:\(w=np.random.randn(n)/sqrt(n)\),其中\(n\)表示输入的数量。这保证了网络中所有的神经元最初的输出分布大致相同,并在经验上提高了收敛速度。 \(w=np.random.randn(n)/sqrt(n)\)推导过程大致如下: 令权重\(w\)和输入\(x\)的内积表达式为:\(s=\sum_{i}^{n}w_ix_i\),这就使得神经元在非线性之前得到了原始的激活。 计算\(s\)的方差: \[ \begin{align} Var(s) &= Var(\sum_{i}^{n}w_ix_i) \\ &= \sum_{i}^{n}Var(w_ix_i) \\ &= \sum_{i}^{n}{[E(w_i)]^2}Var(x_i) + E{[x_i]}^2Var(w_i) + Var(x_i)Var(w_i) \\ &= \sum_{i}^{n}Var(x_i)Var(w_i) \\ &= (nVar(w))Var(x) \end{align} \] 在最开始的两步我们使用了方差的性质。 在第三步我们我们假设输入值和权重均值为0,所以\(E[x_i]=E[w_i]=0\)。注意到这不是一般的情况:比如ReLU的单元会有一个正的均值。 在最后一步我们假设所有的\(w_i,x_i\)都是同分布的(即\(w_1,w_2...\)是同分布,\(x_1,x_2,...\)是同分布,但是\(w\)和\(x\)不是同分布)。 从这个推导中我们可以看到如果我们想让\(s\)和所有的输入都有相同的方差,那么需要保证在初始化的时候每个权重\(w\)的方差是\(\frac{1}{n}\)。并且因为\(Var(aX)=a^2Var(X)\)(\(a\)是一个标量,\(X\)是一个随机变量),这就意味着我们可以从单位高斯分布中取样,然后通过\(a=\sqrt{\frac{1}{n}}\)进行缩放来使得权重的方差为\(\frac{1}{n}\)。也就是得到了最开始所介绍的初始化方法:\(w = np.random.randn(n) / sqrt(n)\) Glorot等人所写的文章Understanding the difficulty of training deep feedforward neural networks中有类似的分析。在这篇论文中,作者的结论是建议初始化的形式是\(Var(w)=\frac{2}{(n_{in}+n_{out})}\),其中\(n_in,n_out\)分别是上一层和下一层神经元的数量。这是基于一个折中的选择和对反向传播梯度等价分析后具有积极作用的办法。(This is motivated by based on a compromise and an equivalent analysis of the backpropagated gradients)。关于这个话题的最新论文,由He等人所写的Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification推导了ReLU神经元的权重初始化问题,得出的结论是神经元的方差需要是\(\frac{2.0}{n}\),即\(w = np.random.randn(n) / sqrt(2.0/n)\),这是目前在神经网络中使用相关神经网络的建议。 稀疏初始化(Sparse Initialazation) 另一种解决未校准方差问题的方法是把所有的权重矩阵都设为零,但是为了打破对称性,每个神经元都是随机连接地(从如上面所介绍的一个小的高斯分布中抽取权重)到它下面的一个固定数量的神经元。一个典型的神经元连接的数目可能是小到10个。 初始化偏差 将偏差初始化为零是可能的,也是很常见的,因为非对称性破坏是由权重的小随机数导致的。因为ReLU具有非线性特点,所以有些人喜欢使用将所有的偏差设定为小的常数值如0.01,因为这样可以确保所有的ReLU单元在最开始就激活触发(fire)并因此能够获得和传播一些梯度值。然而,这是否能够提供持续的改善还不太清楚(实际上一些结果表明这样做反而使得性能更加糟糕),所以更通常的做法是简单地将偏差初始化为0. 实际操作 通常的建议是使用ReLU单元以及 He等人 推荐的公式\(w = np.random.randn(n) * sqrt(2.0/n)\) 批量标准化 loffe和Ioffe最近开发的一项技术,称为“Batch Normalization”,在训练开始的时候,通过显式地迫使网络上的激活函数让整个网络上采用高斯分布来初始化神经网络,从而缓解了许多头痛的问题。(A recently developed technique by Ioffe and Szegedy called Batch Normalization alleviates a lot of headaches with properly initializing neural networks by explicitly forcing the activations throughout a network to take on a unit gaussian distribution at the beginning of the training.)。通过核心观察证明这是可能的,因为标准化是一个简单的可微分的操作(The core observation is that this is possible because normalization is a simple differentiable operation.)。在实际操作中,运用这项技术相当于在全连接层(或者卷积层,我们很快将会看到)后面嵌入BatchNorm层,并嵌在非线性(层)前。这里我们不会展开来讲解这项技术,因为它已经在上面那提供链接的论文中详细的介绍了,但是请注意,在神经网络中使用批量标准化已经成为一种非常常见的做法。在实践中,使用批量标准化的网络对糟糕的初始化更加健壮。还要需要提到的是,批量标准化可以解释为在网络的每一层进行预处理,但它以可微分的方式整合到网络中。完美! MARSGGBO原创 2017-9-1