Neural Networks and Deep Learning(四)图解神经网络为什么能拟合任意函数

我们应该都听说过神经网络强大到能拟合任意一个函数,但细究起来很少有人能论证这个观点,这一章就用通俗易懂的图解方式来证明神经网络为什么能拟合任意一个函数。 开始介绍之前,有两点需要注意: 并不是说神经网络可以精确计算任意一个函数\(f(x)\),而是说当隐藏层神经元增加时,可以无限逼近\(f(x)\),比如对于任何一个输入\(x\),网络的输出\(g(x)\)和正确值\(f(x)\)的差小于某个阈值,\(|g(x) – f(x)| < \epsilon\); 神经网络拟合的是连续函数,而不是那种不连续、离散、急剧变化的函数。 假设给定一个下图的连续函数,函数形式未知,本章将用图解的方式来证明,一个单隐层的神经网络就可以很好的拟合这个未知函数。 首先,假设我们的隐藏层只有两个神经元,激活函数使用Sigmoid,并且我们暂时只关注上面那个神经元的参数和输出。则通过调整该神经元的\(w\)和\(b\),可以得到不同形状的Sigmoid函数形式。 极端情况下,如果\(w\)很大而\(b\)很小,则可以用Sigmoid函数模拟阶梯函数: 如果令\(s = -b/w\),则只用一个\(s\)就可以确定Sigmoid的函数图像: 如果把隐藏层下面那个神经元也考虑进来,并且令隐藏层的两个神经元和输出层的神经元的连接权重互为相反数,则输出层未激活值\(z=w_1 a_1 + w_2 a_2\)的函数图像变成了一个神奇的鼓包,这个鼓包就是我们后续拟合任意函数的基本单元。根据严格的函数形式,还可以知道\(w_1\)和\(w_2\)的绝对值控制着鼓包的高度,\(s_1\)和\(s_2\)的值控制着鼓包的位置和宽度。大家可以去原始网页上体验一下作者给出的可交互版本,很有意思。 有了这个基本单元之后,我们可以通过增加隐藏层神经元的个数来增加鼓包的个数,比如再增加一对隐层神经元,可增加一个鼓包。虽然下图的例子中两个鼓包相互独立,但通过调整4个\(s\),可以让两个鼓包相连甚至交错,大家可以去原网页试一试。 继续增加隐层神经元个数,则可以继续增加鼓包的数量,如下图所示。 到这里想必大家马上知道了为什么神经网络能拟合任何一个函数了,如果隐层神经元足够多,则右图的小鼓包可以足够密,通过调整每个鼓包的高度,则无穷多个鼓包的顶点连线可以拟合任意一个函数。这和我们求函数积分(函数下方面积)时使用多个小矩形近似是一个道理! 所以对于本章开头的未知函数,我们通过调整不同鼓包的高度,可以使得小矩形面积之和与真实积分的差在\( \epsilon=0.4\)以内。如果无限增加隐层神经元个数,则可以无限逼近真实值。这就说明神经网络确实可以拟合任意一个函数。 上述推导稍微需要注意的一点是,右图的输出是未激活函数值\(\sum_j w_j a_j\),而网络真正的输出是激活值\(\sigma(\sum_j w_j a_j + b)\)。这没有太大的关系,因为上面已经说明未激活输出能拟合任意函数,激活函数也是一个函数。增加激活函数就要求右图需要拟合激活函数和真实函数的嵌套函数。既然未激活输出能拟合任意函数,肯定能拟合这个嵌套函数\(\sigma^{-1} \circ f(x)\),再用激活函数作用一下\(\sigma\circ\sigma^{-1} \circ f(x)\),激活函数抵消了,正好得到\(f(x)\)。 如果输入是多维,或者输出是多维,都是类似的道理。这就说明神经网络确实可以拟合任意函数,真的很强大哦。

April 7, 2019 · 1 min

Neural Networks and Deep Learning(三·三)权重初始化及其他

权重初始化 在之前的章节中,我们都是用一个标准正态分布\(N(0,1^2)\)初始化所有的参数\(w\)和\(b\),但是当神经元数量比较多时,会出现意想不到的问题。 假设一个神经网络的输入层有1000个神经元,且某个样本的1000维输入中,恰好有500维是0,另500维是1。我们目前考察隐藏层的第一个神经元,则该神经元为激活的输出为\(z = \sum_j w_j x_j+b\),因为输入中的500维是0,所以\(z\)相当于有501个来自\(N(0,1^2)\)的随机变量相加。因为\(w_j\)和\(b\)的初始化都是独立同分布的,所以\(z\)也是一个正态分布,均值为0,但方差变成了\(\sqrt{501} \approx 22.4\),即\(z\sim N(0,\sqrt{501}^2)\)。我们知道对于正态分布,如果方差越小,则分布的形状是高廋型的;如果方差越大,则分布的形状是矮胖型的。所以\(z\)有很大的概率取值会远大于1或远小于-1。又因为激活函数是sigmoid,当\(z\)远大于1或远小于-1时,\(\sigma (z)\)趋近于1或者0,且导数趋于0,变化缓慢,导致梯度消失。 请注意,这里的梯度消失和之前介绍得梯度消失稍有不同,之前是说在误差反向传播过程中,损失函数对权重的导数中包含梯度消失项,所以可以通过更换损失函数来解决。但是这里的梯度消失并不是在误差反向传播过程中产生的,而是在正向传播产生的,跟损失函数没关系。 解决这个问题的方法很简单,根据上面的分析,如果输入\(x_j\)全为1,\(w\)和\(b\)都来自\(N(0,1^2)\),则\(z\sim N(0, \sqrt{n+1}^2)\),其中\(n\)为输入样本的维度。要减小\(z\)的方差,减小\(w\)和\(b\)的方差就可以了。因为\(b\)只有一个,对整体的影响不大,可以不修改\(b\)的分布,\(b\)依然来自\(N(0,1^2)\)。把\(w_j\)的分布修改为\(N(0, (\frac{1}{\sqrt{n}})^2)\),此时\(z\sim N(0, \sqrt{2}^2)\),\(\sqrt{2}=1.414\)就非常接近1了,\(z\)的分布也变成了一个高廋型的,梯度消失问题也就不存在了。 如果是开头的例子,输入维度为1000,其中500为0,500为1,\(w_j\sim N(0, (\frac{1}{\sqrt{1000}})^2)\),\(b\sim N(0,1^2)\),则\(z\sim N(0, \sqrt{\frac{3}{2}}^2)\),\(\sqrt{3/2} = 1.22\ldots\)也是高廋型的,不会有梯度消失的问题。 由下图可知,在新的权重初始化策略下,网络很快就收敛了,比之前的方法快很多。 怎样选择超参数 大原则:在网络优化的前期,尽量使网络结构、问题简单,以便快速得实验结果,不断尝试超参数取值,当找到正确的优化方向后,再慢慢把网络和问题变复杂,精细调整超参数。比如MNIST问题,开始可以减少训练数据,只取0和1的图片,做二分类;同时可以减少网络层数,验证集大小等,以便快速得到网络输出,判断网络性能变化。这样可以快速尝试新的超参数。 学习率\(\eta\) 在误差反向传播中,学习率太大,虽然可以加速学习,但在后期可能导致网络震荡,无法收敛;学习率太小,导致学习速度太慢,训练时间过长。 确定学习率的方法是:首先随便选定一个值,比如0.01,然后不断增大10倍:0.1, 1, 10, 100…如果发现cost曲线在震荡,说明选大了,要降低,直到找到一个比较合适的值。这个过程只要找到合适的数量级就可以了,不一定要非常精确。比如发现0.1是比较合适的,那么可以再尝试0.2,0.3…,如果发现0.5不错,可以设学习率为0.5的一半0.25,这样可以使得在后续epoch中,不容易发生震荡。最好的方法是可变学习率,即前期学习率稍大(0.5),后期学习率稍小(0.1)之类的。 epoch no-improvement-in-ten rule,就是说如果模型在最近的10个epoch中,验证集的accuracy都没有提高,则可以stop了。在早期实验中可以这么做,后续精细优化时可以改变ten,比如no-improvement-in-20/30等。 正则化参数\(\lambda\) 首先不要正则(\(\lambda=0\)),使用上面提到的方法确定学习率\(\eta\),在确定的学习率情况下,正则\(\lambda=1\)开始进行优化,比如每次乘以10或者除以10,观察验证集上的accuracy指标,找到正则化所在的合适的数量级,然后再fine-tune。 Mini-batch size 太小了,无法利用现有软件包的矩阵操作的优势,速度会很慢。极端情况下,如果mini-batch size=1,就是说每次只用一个sample做BP,则100次mini-batch=1会比一次mini-batch=100操作慢很多,因为很多软件包对矩阵操作有优化,而没有对for循环优化。太大了,则一次BP要很久,参数更新的次数也比较少。 其他技术 随机梯度下降SGD的变种 海森矩阵法 SGD优化的目标就是最小化损失函数\(C\),\(C\)是所有参数\(w = w_1, w_2, \ldots\)的函数,即\(C=C(w)\)。希望能够通过改变\(w\),不断最小化\(C\),即找一个\(\Delta w\),使得\(C(w+\Delta w)\)最小化。把\(C(w+\Delta w)\)泰勒展开得到: $$\begin{eqnarray}C(w+\Delta w) & = & C(w) + \sum_j \frac{\partial C}{\partial w_j} \Delta w_j\nonumber \\ & & + \frac{1}{2} \sum_{jk} \Delta w_j \frac{\partial^2 C}{\partial w_j\partial w_k} \Delta w_k + \ldots\tag{1}\end{eqnarray}$$写成矩阵形式就是: ...

April 6, 2019 · 1 min

Neural Networks and Deep Learning(三·二)过拟合与正则化

过拟合介绍 首先介绍一下神经网络中不同数据集的功能,包括训练集、验证集和测试集。 训练集是用来训练网络参数的。当觉得在训练集上训练得差不多时,就可以在验证集上进行测试,如果验证集上的性能不好,则需要调整网络结构或者超参数,重新在训练集上训练。所以本质上验证集指导训练过程,也参与了训练和调参。为了防止网络对验证集过拟合,当网络在训练集和验证集上表现都不错时,就可以在测试集上进行测试了。测试集上的性能代表了模型的最终性能。 当然如果发现网络在测试集上性能不好,可能还会反过来去优化网络,重新训练和验证,这么说测试集最终也变相参与了调优。如果一直这么推下去的话,就没完没了了,所以一般还是认为用验证集对模型进行优化,用测试集对模型性能进行测试。 过拟合的含义就是网络在训练集上性能很好,但是在验证集(或者测试集)上的性能较差,这说明网络在训练集上训练过头了,对训练集产生了过拟合。为了便于叙述,本文没有验证集,直接使用测试集作为验证集对模型进行调优,所以主要考察网络在训练集和测试集上的性能表现。 判断网络是否过拟合的方法就是观察网络在训练集和测试集上的accuracy和loss的变化曲线。对于accuracy,如果训练集的accuracy很高接近100%且收敛了,但测试集上的accuracy和训练集上的accuracy相差较大也收敛了(如下图收敛到82%左右),说明网络过拟合了。对于loss,如果训练集的loss一直在下降,但测试集的loss先下降后又上升,也说明网络过拟合了。这两种现象,虽然指标不同,但含义是一样的,即网络在训练集上的性能一直在提高甚至到完美水平,但在测试集上的性能提高到一定水平后不再变化甚至下降了。 不过下面几张图反应的过拟合epoch时间可能不一样,比如对于测试集上的accuracy,可能在280左右过拟合,但是对于测试集上的loss,在15和280左右都可以认为是过拟合了,尤其是15,loss最低,之后loss反升,可以认为是一个合理的过拟合的点。具体哪个epoch之后过拟合,取决于问题本身关注哪个指标,比如MNIST分类问题,可能关注分类accuracy,所以可重点关注测试集上的accuracy那个图,认为是280左右过拟合,因为200~280的accuracy还一直有提升,虽然提升很有限。 应对过拟合最好的方法就是增加训练数据,如果能把所有可能的数据都收集到,对所有数据产生过拟合,那相当于对所有数据都能预测得很好,那问题本质上已经解决了。 但是,在实际应用场景中,不可能收集到所有数据,而且数据往往是严重不足的,此时,应对过拟合主要有三种方法:正则化、Dropout和数据增强,下面分别介绍这三个部分。 正则化 正则化的思路就是修改损失函数,使损失函数考虑模型复杂度。考虑正则化的损失函数的通用公式如下: $$\begin{eqnarray} C = C_0(w,x,y) + \lambda\Omega(w)\tag{1}\end{eqnarray}$$其中\(C_0\)为原始的没有正则化项的损失函数,比如MSE或者交叉熵损失等,\(\Omega(w)\)表示正则化项,即用来惩罚模型复杂度的,\(\lambda\)表示正则化参数,用来平衡\(C_0\)和\(\Omega(w)\)的重要性。 正则化又分为L2正则和L1正则,它们很类似,先详细介绍下L2正则。 举个例子,L2正则化后的损失函数如下: $$\begin{eqnarray} C = C_0 + \frac{\lambda}{2n}\sum_w w^2,\tag{2}\end{eqnarray}$$前半部分就是普通的损失函数(比如MSE或者交叉熵损失),后半部分就是L2正则。L2正则是对网络中的所有权重\(w\)求平方和(\(\vec w\)的L2范数,所以叫L2正则),然后除以\(2n\),其中\(n\)是训练样本数,除以2应该是为了后面求导方便。 (2)式的直观含义是,\(\min C\)的过程中,我不但希望损失函数本身\(C_0\)足够小,还希望网络的权重\(w\)也比较小,最好不要出现很大的\(w\)。如果\(\lambda\)越大,表示正则化越厉害,对大的\(w\)惩罚越严重。 加入L2正则后的梯度也很容易计算,如下: $$\begin{eqnarray}\frac{\partial C}{\partial w} & = & \frac{\partial C_0}{\partial w} + \frac{\lambda}{n} w \tag{3}\\ \frac{\partial C}{\partial b} & = & \frac{\partial C_0}{\partial b}.\tag{4}\end{eqnarray}$$对应的参数更新公式如下: $$\begin{eqnarray}b & \rightarrow & b -\eta \frac{\partial C_0}{\partial b}.\tag{5}\end{eqnarray}$$$$\begin{eqnarray} w & \rightarrow & w-\eta \frac{\partial C_0}{\partial w}-\frac{\eta \lambda}{n} w \tag{6}\\ & = & \left(1-\frac{\eta \lambda}{n}\right) w -\eta \frac{\partial C_0}{\partial w}. \tag{7}\end{eqnarray}$$由(5)可知,偏移量\(b\)的梯度更新和没有正则化时是一样的,因为正则化并没有惩罚\(b\),这个后面会解释为什么。由(7)可知,对\(w\)的梯度更新和没有正则化时很类似,只不过需要先对\(w\)进行缩放,缩放因子为\(1-\frac{\eta\lambda}{n}\),因为训练样本\(n\)往往很大,所以缩放因子在(0,1),即先对\(w\)进行缩小,然后正常梯度下降,这种操作也被称为权值衰减。\(\lambda\)最好根据\(n\)的大小进行调整,如果\(n\)非常大的话,\(\lambda\)最好也大一些,否则权值衰减因子就会很小,正则化效果就不明显。 ...

March 24, 2019 · 1 min

Neural Networks and Deep Learning(三·一)梯度消失

原文的第三章内容较多,本博客将分三个部分进行介绍:梯度消失、过拟合与正则化、权重初始化及其他,首先介绍梯度消失问题。 为简单起见,假设网络只包含一个输入和一个神经元,网络的损失是均方误差损失MSE,激活函数是Sigmoid函数。则该网络的参数只包含权重\(w\)和偏移量\(b\)。我们想训练这个网络,使得当输入为1时,输出0。 假设我们随机初始化\(w_0=0.6\),\(b_0=0.9\),则网络的损失随着训练的epoch变化曲线如下,看起来挺好的,一开始损失下降很快,随着epoch增加,损失下降逐渐平缓,直至收敛。 但是,如果随机初始化\(w_0=2.0\),\(b_0=2.0\),则网络的损失一开始下降得很缓慢,要训练到快200个epoch时,损失才快速下降。可以看到同样是300个epoch,由于初始化权重的差别,损失下降的趋势完全不一样,而且对于下面这种情况,到300个epoch时,损失还有下降的空间,所以期望的output不如上面的接近目标值0。 为什么同样的网络,只是因为初始化权重的差异,损失的变化曲线却相差这么多呢,这和我们选择的损失函数与激活函数有关。 回顾一下,我们在上一讲的末尾介绍到如果损失函数是MSE且激活函数是Sigmoid时,有\(\delta^L = (a^L-y) \odot \{\sigma(z^L)(1-\sigma(z^L))\}\),又因为网络只有一个神经元,所以梯度如下: $$\begin{eqnarray}\frac{\partial C}{\partial w} & = & (a-y)\sigma'(z) x = a \sigma'(z),\tag{1}\\\frac{\partial C}{\partial b} & = & (a-y)\sigma'(z) = a \sigma'(z)\tag{2}\end{eqnarray}$$其中第二个等号是把\(x=1\)和\(y=0\)带入得到的。由此可见,误差对两个参数\(w\)和\(b\)的梯度都和激活函数的导数有关,因为激活函数是Sigmoid,当神经元的输出接近0或1时,梯度几乎为0,误差反向传播就会非常慢,导致上图出现损失下降非常慢的现象。这就是梯度消失的原因。 为了解决这个问题,我们可以采取两种策略,一是替换损失函数,一是替换激活函数。 第一种方法是将MSE的损失函数替换为交叉熵损失函数,激活函数依然是Sigmoid。我们考虑一个比本文开头更复杂的网络,仍然是一个输出神经元,但包含多个输入神经元。 此时,交叉熵损失函数定义如下,其中的\(n\)表示训练样本数,\(\frac{1}{n}\sum_x\)表示对所有输入样本\(x\)的交叉熵损失求均值。 $$\begin{eqnarray}C = -\frac{1}{n} \sum_x \left[y \ln a + (1-y ) \ln (1-a) \right]\tag{3}\end{eqnarray}$$我们首先考察为什么(3)可以是一个损失函数,损失函数需要满足如下两个条件: 非负; 当网络输出和目标答案越接近,损失越小;反之损失越大。 简单代入几组不同的样本很容易验证交叉熵满足上述两个条件 ,所以交叉熵可以作为一个损失函数。 下面我们再考察一下为什么交叉熵损失函数+Sigmoid激活函数可以解决梯度消失的问题。首先推导交叉熵损失\(C\)对权重\(w_j\)和\(b\)的梯度: $$\begin{eqnarray}\frac{\partial C}{\partial w_j} & = & -\frac{1}{n} \sum_x \left(\frac{y }{\sigma(z)} -\frac{(1-y)}{1-\sigma(z)} \right)\frac{\partial \sigma}{\partial w_j} \tag{4}\\& = & -\frac{1}{n} \sum_x \left(\frac{y}{\sigma(z)}-\frac{(1-y)}{1-\sigma(z)} \right)\sigma'(z) x_j\tag{5}\\& = & \frac{1}{n}\sum_x \frac{\sigma'(z) x_j}{\sigma(z) (1-\sigma(z))}(\sigma(z)-y).\tag{6}\end{eqnarray}$$上式分子Sigmoid的导数正好可以和分母抵消,得到: ...

March 18, 2019 · 2 min

Neural Networks and Deep Learning(二)BP网络

这一讲介绍误差反向传播(backpropagation)网络,简称BP网络。 以上一讲介绍的MNIST手写数字图片分类问题为研究对象,首先明确输入输出:输入就是一张28×28的手写数字图片,展开后可以表示成一个长度为784的向量;输出可以表示为一个长度为10的one-hot向量,比如输入是一张“3”的图片,则输出向量为(0,0,0,1,0,0,0,0,0,0,0)。 然后构造一个如下的三层全连接网络。第一层为输入层,包含784个神经元,正好对应输入的一张28×28的图片。第二层为隐藏层,假设隐藏层有15个神经元。第三层为输出层,正好10个神经元,对应该图片的one-hot结果。 全连接网络表示上一层的每个神经元都和下一层的每个神经元有连接,即每个神经元的输入来自上一层所有神经元的输出,每个神经元的输出连接到下一层的所有神经元。每条连边上都有一个权重w。 每个神经元执行的操作非常简单,就是把跟它连接的每个输入乘以边上的权重,然后累加起来。 比如上面的一个神经元,它的输出就是: $$\begin{eqnarray}\mbox{output} = \left\{ \begin{array}{ll}0 & \mbox{if} \sum_j w_j x_j \leq \mbox{ threshold} \\1 & \mbox{if} \sum_j w_j x_j > \mbox{threshold}\end{array}\right.\tag{1}\end{eqnarray}$$其中的threshold就是该神经元激活的阈值,如果累加值超过threshold,则该神经元被激活,输出为1,否则为0。这就是最原始的感知机网络。感知机网络也可以写成如下的向量形式,用激活阈值b代替threshold,然后移到左边。神经网络中,每条边具有权重w,每个神经元具有激活阈值b。 $$\begin{eqnarray}\mbox{output} = \left\{ \begin{array}{ll} 0 & \mbox{if } w\cdot x + b \leq 0 \\1 & \mbox{if } w\cdot x + b > 0\end{array}\right.\tag{2}\end{eqnarray}$$ 但是感知机网络的这种激活方式不够灵活,它在threshold左右有一个突变,如果输入或者某个边上的权重稍微有一点变化,输出结果可能就千差万别了。于是后来人们提出了用sigmoid函数来当激活函数,它在0附近的斜率较大,在两边的斜率较小,能达到和阶梯函数类似的效果,而且函数光滑可导。sigmoid的函数形式如下,其中\(z\equiv w \cdot x + b\)为神经元激活之前的值。 $$\begin{eqnarray} \sigma(z) \equiv \frac{1}{1+e^{-z}}\tag{3}\end{eqnarray}$$sigmmoid函数还有一个优点就是它的导数很好计算,可以用它本身来表示: $$\begin{eqnarray}\sigma'(z)=\sigma(z)(1-\sigma(z))\tag{4}\end{eqnarray}$$BP网络的参数就是所有连线上的权重w和所有神经元中的激活阈值b,如果知道这些参数,给定一个输入x,则可以很容易的通过正向传播(feedforward)的方法计算到输出,即不断的执行\(w \cdot x + b\)操作,然后用sigmoid激活,再把上一层的输出传递给下一层作为输入,直到最后一层。 1 2 3 4 5 def feedforward(self, a): """Return the output of the network if ``a`` is input.""" for b, w in zip(self.biases, self.weights): a = sigmoid(np.dot(w, a)+b) return a 同时,网络的误差可以用均方误差(mean squared error, MSE)表示,即网络在最后一层的激活值(即网络的输出值)\(a\)和对应训练集输入\(x\)的正确答案\(y(x)\)的差的平方。有\(n\)个输入则误差取平均,\(\dfrac{1}{2}\)是为了后续求导方便。 ...

December 14, 2018 · 2 min

Neural Networks and Deep Learning(一)MNIST数据集介绍

最近开始学习神经网络和深度学习,使用的是网上教程:http://neuralnetworksanddeeplearning.com/,这是学习心得第一讲,介绍经典的MNIST手写数字图片数据集。 MNIST(Modified National Institute of Standards and Technology database)数据集改编自美国国家标准与技术研究所收集的更大的NIST数据集,该数据集来自250个不同人手写的数字图片,一半是人口普查局的工作人员,一半是高中生。该数据集包括60000张训练集图片和10000张测试集图片,训练集和测试集都提供了正确答案。每张图片都是28×28=784大小的灰度图片,也就是一个28×28的矩阵,里面每个值是一个像素点,值在[0,1]之间,0表示白色,1表示黑色,(0,1)之间表示不同的灰度。下面是该数据集中的一些手写数字图片,可以有一个感性的认识。 MNIST数据集可以在Yann LeCun的网站上下载到:http://yann.lecun.com/exdb/mnist/,但是他提供的MNIST数据集格式比较复杂,需要自己写代码进行解析。目前很多深度学习框架都自带了MNIST数据集,比较流行的是转换为pkl格式的版本:http://deeplearning.net/data/mnist/mnist.pkl.gz,该版本把原始的60000张训练集进一步划分成了50000张小训练集和10000张验证集,下面以这个版本为例进行介绍。 pkl是python内置的一种格式,可以将python的各种数据结构序列化存储到磁盘中,需要时又可以读取并反序列化到内存中。mnist.pkl.gz做了两次操作,先pkl序列化,再gz压缩存储,所以要读取该文件,需要先解压再反序列化,在python3中,读取mnist.pkl.gz的方式如下: 1 2 3 4 5 import pickle import gzip f = gzip.open(‘../data/mnist.pkl.gz’, ‘rb’) training_data, validation_data, test_data = pickle.load(f, encoding=’bytes’) f.close() 这样就得到了训练集、验证集和测试集。将数据集序列化到文件中的方法也很简单,需要注意的是pickle在序列化和反序列化时有不同的协议,可以用protocol参数进行设置。 1 2 3 4 dataset=[training_data, validation_data, test_data] f=gzip.open(‘../data/mnist3.pkl.gz’,’wb’) pickle.dump(dataset,f,protocol=3) f.close() 我们从mnist.pkl.gz读取到的training_data, validation_data, test_data这三个数据的结构是一样的,每个都是一个二维的tuple。以training_data为例,training_data[0]是训练样本,是一个50000×784的矩阵,表示有50000个训练样本,每个训练样本是一个784的一维数组,784就是把一张28×28的图片展开reshape成的一维数组;training_data[1]是训练样本对应的类标号,大小为50000的一维数组,每个值为0~9中的某个数,表示对应样本的数字标号。 ...

November 25, 2018 · 1 min

Ubuntu下使用VSCode连接Github

VSCode是微软开源的一个很强大的IDE,可以支持几乎所有编程语言,而且是跨平台的,Linux用户终于可以用上宇宙最强IDE了。我最近在使用VSCode编写调试Python项目,其调试功能很强大,和VS上调试C++的感觉是一样的,强烈推荐。 VSCode还可以连接Github,进行版本控制。下面以我最近学习的深度学习项目为例,介绍下怎样在Ubuntu下使用VSCode连接Github。以我fork的repo为例:https://github.com/01joy/neural-networks-and-deep-learning。 连接Github有两种方式,一种是HTTPS,另一种是SSH,在每个repo页面的右边,有一个Clone or download按钮,可以获取到这两种连接方式的地址。HTTPS方式和网址类似,以HTTPS开头;SSH方式以git@githu.com开头。使用HTTPS连接比较简单,但是每次push的时候需要输入用户名和密码,比较麻烦,如果想记住密码,需要把用户名和密码以明文的形式保存到一个文件中,个人感觉不方便且不安全。下面以SSH连接为例进行介绍。 首先设置Github提交时的用户名和密码,一般设置成全局的:https://help.github.com/articles/setting-your-username-in-git/、https://help.github.com/articles/setting-your-commit-email-address-in-git/ 生成一对新的SSH公钥和私钥,并添加到ssh-agent中。注意生成的时候需要输入passphrase,这个passphrase不是Github的密码,自己随便取一个记住就好。https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/ 把SSH的公钥添加到Github账号中:https://help.github.com/articles/adding-a-new-ssh-key-to-your-github-account/ 测试SSH连接是否成功:https://help.github.com/articles/testing-your-ssh-connection/ (可选)修改SSH密码,即第1步设置的passphrase:https://help.github.com/articles/working-with-ssh-key-passphrases/ 到这里,本级就能通过SSH连接Github了。 如果没有安装VSCode,可以直接通过Ubuntu的终端连接Github,步骤如下: 在本地创建一个和远程repo名称一样的空文件夹 终端cd到该文件夹内 git init # 在该文件夹内初始化 git remote add origin git@github.com:01joy/notes-on-writing.git # 使用repo的SSH地址 git pull origin master # 把远程代码拉到本地 修改代码 git add . # 在根目录执行,添加所有修改 git commit -m ‘comments’ # commit第7步添加的修改 git push origin master # 把第8步发布到远程 如果安装了VSCode,其实和直接用终端是一样的,在菜单栏的Terminal下新建一个终端,在这个终端内执行上述代码,如果在第4步出现”Enter password to unlock the private key”时,输入创建SSH时第2步的密码即可,只需一次,下次就不用再输入密码了。点击File的Open Folder打开本地repo文件夹。点击VSCode左边栏的Explorer可以在编辑器下修改代码。切换到左边栏的Source Control可以进行Git相关操作,修改的文件右边会出现一个M,点击这个M会出现diff视图;Source Control左边的右上角有三个点,点击这个按钮会出现很多Git操作,包括commit、push等,其实相当于调用上述代码,效果是一样的。 VSCode快捷键Ctrl+Shift+P会出现命令窗口,在里面输入commit、push等会出现相关操作的,能起到一定的加速效果,当然也可以自定义快捷键。 Have Fun!

November 13, 2018 · 1 min

Word2016批注行距太大的问题

最近导师给我批注文章,说我的Word文档的批注行距极大,从入学到现在一直都是如此,对他造成了极大的困扰,希望我能解决这个问题。 但是我自己用Word2016查看导师的批注,看不出行距极大的问题,显示完全是正常的。后来猜测导师用的是旧版的Word2010,于是在虚拟机中安装了Word2010,进行测试。 经过长时间的Debug,终于发现问题所在。Word针对批注有一个默认的样式,为“批注文字(使用前隐藏)”,可以点击样式右下角的箭头,或者直接按快捷键Ctrl+Shift+Alt+S调出样式窗口。然后点击底部的管理样式就可以看到所有的样式了。有意思的是,批注文字样式默认是隐藏的,所以在下图的样式列表中是找不到这个样式的。 找到批注文字样式,点击修改,在弹出的窗口中点击左下角的格式,选择段落,就可以看到批注的默认格式了。段落格式看不出什么异常,Word2016和Word2010的批注段落格式都是一样的,其中“如果定义了文档网格,则对齐到网格”都是默认选中的。 有意思的是,Word2016和Word2010对网格的默认设置却不一样。在布局、页面设置中点击右下角的箭头,打开页面设置对话框。切换到文档网格选项卡。 Word2016默认指定了行网格,而Word2010默认却是无网格。因为批注文字样式中选中了“如果定义了文档网格,则对齐到网格”这个选项,Word2016默认指定了行网格,所以批注文字会对齐到行网格,导致行间距太大,Word2010默认没有指定任何网格,所以其批注文字的行间距是正常。 Word2016文档网格,默认指定了行网格 Word2010文档网格,默认无网格 解决办法就是,修改Word2016的设置,选中“无网格”,并设置为默认值,这样以后新建的Word文档默认都是无网格,批注的行间距也就正常了。

September 1, 2018 · 1 min

2017年终总结

2017年是目前为止最折腾的一年。 科研工作 今年的科研任务包括两个方面,一方面是pLink2软件的完善和发布,另一方面是完成pLink2文章初稿。软件方面,上半年忙着修改完善算法,下半年重写界面代码,修改各种bug并反复测试。最终在12月31日晚通过邮件正式发布。文章方面,元旦软件发布之后,一月份抽两周时间完成了初稿,算是自己的第一篇全英文初稿,正文加附录接近一万词。不过因为有一个实验结果不太好,还需要接着完善算法,文字也很稚嫩,需要反复修改。希望能在2018年上半年投出去。 个人提高 上半年忙着找工作,一直在刷题看书攒面经,经过自己的不懈努力,收获了微软、百度、头条、Face++等心仪的Offer。在确定自己找工作没问题之后,被李沐的博客“忽悠”,下半年华丽丽的转博了,赶在了2015级最后一次转博前夕。 因为找工作,看了7本专业书;因为有Kindle,以及忙里偷闲,竟也看了9本非专业书。元旦完成软件发布任务之后,奖励自己去电影院连看了一整天的电影,这种休假方式也是蛮奇葩的,今年累计去电影院看的电影数已经达到16了。上半年和欣欣看了一场“OFO轻睐演唱会”,第一次参加演唱会,现场的感觉和看视频不一样,气氛很热烈,大家都很兴奋,会情不自禁跟着一起唱。国庆第一次一个人远行,去了郑州、登封和杭州,加上12月份参加厦门质谱会议,今年去的第三个城市已经达到了4个。 运动方面。在两个师兄的帮助以及室友的陪练下,真的学会了蛙泳,今年8月份还拿到了深水证,为此贺老师每个月奖励我100块钱,简单粗暴又有效的奖励机制:-)。临近年底的时候,心血来潮,准备提高乒乓球技术,混入了所里的乒乓球圈子,拜师王老师门下,经过训练以及看视频学习,竟偶尔能赢浩哥了,今年再接再厉。 今年约好了和哥一起回家过年,给家里买了一台55寸的乐视超级电视,给父母的红包也涨了不少。总体来说,家里在一年一年变好。 对照年初定的目标, 发表pLink 2文章。只完成了初稿。 至少完成毕业工作的80%。转博了。 刷完LeetCode所有简单题和中等题,找工作之前最好刷完两遍。完成。 找到一个满意的工作。完成,具体请看https://bitjoy.net/posts/2018-02-04-2018-campus-recruiting/。 读10本书。完成16:《编程珠玑》、《C++ Primer》、《程序员面试笔试宝典》、《STL源码剖析》、《剑指Offer》、《深度探索C++对象模型》、《编程之美》、《人间失格》、《枪炮、病菌与钢铁》、《杀死一只知更鸟》、《别闹了,费曼先生》、《月亮与六便士》、《突破极限》、《解忧杂货店》、《北京折叠》、《以色列,一个国家的诞生123》。 去电影院看10场电影。完成16:《爱乐之城》、《摔跤吧!爸爸》、《银河护卫队2》、《战狼2》、《敦刻尔克》、《羞羞的铁拳》、《看不见的客人》、《东方快车谋杀案》、《寻梦环游记》、《帕丁顿熊2》、《芳华》、《解忧杂货店》、《前任3:再见前任》、《无问西东》、《南极之恋》、《太空救援》。 看一场话剧(音乐会、歌剧等都可以)。完成。2017年7月2日,北京工人体育场,OFO轻睐演唱会。 学会游泳。完成,学会蛙泳和踩水,年中拿到深水证。 去第三个城市。完成,国庆去了郑州、登封、杭州,12月份第一次坐飞机去了厦门。 总体来说,2017年的目标都完成了,而且好几项是超额完成。2018年目标如下: 发表pLink2文章,科研的重中之重。 开展新课题,或SUMO或深度学习。 完成博士课程的学习。 读10本书。 去电影院看10场电影。 去北京公园年票范围中的19家公园。 看一场话剧(音乐会、歌剧等都可以)。 学会自由泳。 乒乓球稳赢。 去第三个城市。 机动目标,高温假带父母来北京玩。 突然发现每年的目标都差不多,读万卷书和行万里路是每年都有的保留项目。 找工作,分手,转博,软件发布,文章写作构成了我2017年的365天,喜忧参半,在跌跌撞撞中前行。2018年要保持一如既往的冲劲,打赢转博之后的第一仗!

February 18, 2018 · 1 min

一念成博

证明你能做一件事的最好方法就是做成这件事! 从保研开始,我根本就没打算读博士,心里想的是,好好学习,认真刷题,顺利毕业,高薪就业。 2016年底,也就是进实验室半年之后,贺老师开始“怂恿”我读博:“贫寒人家子弟,有个高学历,在这个拼爹的时代,更容易出人头地。年轻时多读点儿难读的书,也会更好地在未来人工智能时代生存。”我不为所动。 2017年春节在家,疯狂刷题看书,为开学后的实习面试以及半年之后的校招面试准备着。 2017年2月份,开年工作计划会,老师说只要我愿意读博,博士的三个课题都帮我规划好了,要知道我们实验室没有哪个博士生是在三年级之前就确定方向的,大家都是摸着石头过河。 我还是不为所动,疯狂刷题看书,攒实习面经。 到了8月,老师最后来信希望我能认真考虑一下读博的事情:“pFind+NIBS是难得的良性成长环境,换一个新环境未见得能成长像现在这么快”。甚至把我的博士女朋友都搬出来了。我跟欣欣聊了聊,思想开始有点动摇了。但是那段时间忙于找工作,没空想太多。 9月10日教师节,我和欣欣分手,与工作无关,与硕士博士无关。 后来有一天,当我在对比百度凤巢和微软的Offer时,偶然看到凤巢前辈李沐博士写的一篇博客:《博士这五年》。李沐是上海交大ACM班的,毕业之后去了百度凤巢,但是后来毅然辞职去CMU攻读博士学位。博士五年期间,他不但发表了多篇很牛的paper,而且亲手写了一个类似TensorFlow的深度学习平台MXNet,MXNet现已加入Apache家族,并被Amazon选为官方深度学习框架。他博士答辩的评委有来自Google, Amazon, Apple的AI负责人,阵容非常强大。最后,李沐光荣毕业,加入Amazon。 这篇博客的结尾在谈到如何选择工作和读博时有一段话,令我印象深刻:“不过我觉得还是会选择读博。赚钱以后还有大把时间可以,但是能花几年时间在某个领域从入门到精通甚至到推动这个领域发展的机会就一次……更重要的是理想和情怀。人一生要工作五十年,为什么不花五年来追求下理想和情怀呢?”。 看完这篇博客之后,那一整天,我都没心思上班。校招至今,拿了一堆Offer,不能说轻而易举,但是我现在知道拿Offer就跟考试一样,只要准备好,不会差到哪里去。甚至可以说,校招面试比回答贺老师的问题要简单多了。未来可以工作的时间还很长,何不花几年时间来挑战自己呢。当你在面对两难选择时,选择更难的那个,日后你会感谢当初自己的选择。 人拼了命的工作,到底是为了什么,除了更早的成为工厂里的螺丝钉,成为房奴、孩奴,你还能得到什么。是的,提前三年工作,你也许能赚到100万,能积累更丰富的工作经验和更广阔的人脉。但是,这又怎样,这些东西该来的肯定会来,博士毕业之后,我同样能得到,只是比大多数人晚了一点。 与其早早毕业,成为一个nobody,还不如再潜心修炼几年,成为somebody。博士这几年,我能得到更加系统全面的训练,pFind+NIBS的良性科研环境,也不可多得。曾经在知乎上看到有个人回答为什么选择读博:“在一个很安全的环境里,父母健在,自己不用操心赚钱养家;有老板给你提供指导、资金;你可以安心研究自己感兴趣的问题;发表的文章也将署上自己的名字,流传后世;习得的技能也将转化为自己的能力;获得的博士学位也将是自己的荣誉…”,这么好的事情,为什么不去做呢? 那一天之后,我内心几乎就决定要转博了。 然而,话虽如此,想到要放弃到手的大Offer,继续在实验室里待至少三年;想到免不了要经历大多数博士师兄师姐们经历过的痛苦日子;想到彼时同学们都已经年入x万,有房有车,说不定我日后的面试官就是现在的同学;想到我最亲密的女朋友对我的不信任;想到自己的苦衷无处倾诉… 那段日子过得很艰难,也许是我到目前为止最低谷的时期。左手是各大互联网公司的Offer,立即可以实现我很多的愿望;右手是若干年未知的博士磨砺。虽然内心知道往右走是正确的,但还是下不了这个狠心。脑海中的两个小人,吵个不停。 期间和很多在读的、已毕业的博士师兄师姐们聊过,也和很多公司的面试官聊过,当然也和老师父母聊过。得到的回答无外乎三种:读、不读,根据自己的情况决定。这些谈话更像是换了种方式的倾述,我已经记不清具体的内容了,只知道,我做选择的决心越来越坚定了。 之后恰逢十一长假,给自己放了一个长长的假。规划去了郑州、登封、杭州。了却夙愿,重新开始。 10月9日,长假结束。我给老师发了一封邮件:“贺老师,您好。非常感谢您的信任和等待,我决定读博了!这将是我人生二十多年来所作的第一个重大决定,我接受挑战!” 人这一生,说长也长,说短也短,去做你认为对的事情吧,去追求你想要的生活。愿我们都能绽放美丽,不负芳华!

February 14, 2018 · 1 min