CNN求导

来源:互联网 发布:雅思6.5知乎 编辑:程序博客网 时间:2024/05/12 12:37

Deep learning:五十一(CNN的反向求导及练习)

 

  前言:

  CNN作为DL中最成功的模型之一,有必要对其更进一步研究它。虽然在前面的博文Stacked CNN简单介绍中有大概介绍过CNN的使用,不过那是有个前提的:CNN中的参数必须已提前学习好。而本文的主要目的是介绍CNN参数在使用bp算法时该怎么训练,毕竟CNN中有卷积层和下采样层,虽然和MLP的bp算法本质上相同,但形式上还是有些区别的,很显然在完成CNN反向传播前了解bp算法是必须的。本文的实验部分是参考斯坦福UFLDL新教程UFLDL:Exercise: Convolutional Neural Network里面的内容。完成的是对MNIST数字的识别,采用有监督的CNN网络,当然了,实验很多参数结构都按照教程上给的,这里并没有去调整。

 

  实验基础:

  CNN反向传播求导时的具体过程可以参考论文Notes on Convolutional Neural Networks, Jake Bouvrie,该论文讲得很全面,比如它考虑了pooling层也加入了权值、偏置值及非线性激发(因为这2种值也需要learn),对该论文的解读可参考zouxy09的博文CNN卷积神经网络推导和实现。除了bp算法外,本人认为理解了下面4个子问题,基本上就可以弄懂CNN的求导了(bp算法这里就不多做介绍,网上资料实在是太多了),另外为了讲清楚一些细节过程,本文中举的例子都是简化了一些条件的,且linux下文本和公式编辑太难弄了,文中难免有些地方会出错,大家原谅下。

  首先我们来看看CNN系统的目标函数,设有样本(xi, yi)共m个,CNN网络共有L层,中间包含若干个卷积层和pooling层,最后一层的输出为f(xi),则系统的loss表达式为(对权值进行了惩罚,一般分类都采用交叉熵形式):

   

 

  问题一:求输出层的误差敏感项。

  现在只考虑个一个输入样本(x, y)的情形,loss函数和上面的公式类似是用交叉熵来表示的,暂时不考虑权值规则项,样本标签采用one-hot编码,CNN网络的最后一层采用softmax全连接(多分类时输出层一般用softmax),样本(x,y)经过CNN网络后的最终的输出用f(x)表示,则对应该样本的loss值为:

   

  其中f(x)的下标c的含义见公式:

   

  因为x通过CNN后得到的输出f(x)是一个向量,该向量的元素值都是概率值,分别代表着x被分到各个类中的概率,而f(x)中下标c的意思就是输出向量中取对应c那个类的概率值。

  采用上面的符号,可以求得此时loss值对输出层的误差敏感性表达式为:

   

  其中e(y)表示的是样本x标签值的one-hot表示,其中只有一个元素为1,其它都为0.

  其推导过程如下(先求出对输出层某个节点c的误差敏感性,参考Larochelle关于DL的课件:Output layer gradient),求出输出层中c节点的误差敏感值:

   

  由上面公式可知,如果输出层采用sfotmax,且loss用交叉熵形式,则最后一层的误差敏感值就等于CNN网络输出值f(x)减样本标签值e(y),即f(x)-e(y),其形式非常简单,这个公式是不是很眼熟?很多情况下如果model采用MSE的loss,即loss=1/2*(e(y)-f(x))^2,那么loss对最终的输出f(x)求导时其结果就是f(x)-e(y),虽然和上面的结果一样,但是大家不要搞混淆了,这2个含义是不同的,一个是对输出层节点输入值的导数(softmax激发函数),一个是对输出层节点输出值的导数(任意激发函数)。而在使用MSE的loss表达式时,输出层的误差敏感项为(f(x)-e(y)).*f(x)’,两者只相差一个因子。

  这样就可以求出第L层的权值W的偏导数:

   

  输出层偏置的偏导数:

   

  上面2个公式的e(y)和f(x)是一个矩阵,已经把所有m个训练样本考虑进去了,每一列代表一个样本。

 

  问题二:当接在卷积层的下一层为pooling层时,求卷积层的误差敏感项。

  假设第l(小写的l,不要看成数字’1’了)层为卷积层,第l+1层为pooling层,且pooling层的误差敏感项为:   ,卷积层的误差敏感项为:  , 则两者的关系表达式为:

   

  这里符号●表示的是矩阵的点积操作,即对应元素的乘积。卷积层和unsample()后的pooling层节点是一一对应的,所以下标都是用j表示。后面的符号表示的是第l层第j个节点处激发函数的导数(对节点输入的导数)。

  其中的函数unsample()为上采样过程,其具体的操作得看是采用的什么pooling方法了。但unsample的大概思想为:pooling层的每个节点是由卷积层中多个节点(一般为一个矩形区域)共同计算得到,所以pooling层每个节点的误差敏感值也是由卷积层中多个节点的误差敏感值共同产生的,只需满足两层见各自的误差敏感值相等,下面以mean-pooling和max-pooling为例来说明。

  假设卷积层的矩形大小为4×4, pooling区域大小为2×2, 很容易知道pooling后得到的矩形大小也为2*2(本文默认pooling过程是没有重叠的,卷积过程是每次移动一个像素,即是有重叠的,后续不再声明),如果此时pooling后的矩形误差敏感值如下:

   

  则按照mean-pooling,首先得到的卷积层应该是4×4大小,其值分布为(等值复制):

   

  因为得满足反向传播时各层间误差敏感总和不变,所以卷积层对应每个值需要平摊(除以pooling区域大小即可,这里pooling层大小为2×2=4)),最后的卷积层值

分布为:

   

  mean-pooling时的unsample操作可以使用matalb中的函数kron()来实现,因为是采用的矩阵Kronecker乘积。C=kron(A, B)表示的是矩阵B分别与矩阵A中每个元素相乘,然后将相乘的结果放在C中对应的位置。比如:

    

  如果是max-pooling,则需要记录前向传播过程中pooling区域中最大值的位置,这里假设pooling层值1,3,2,4对应的pooling区域位置分别为右下、右上、左上、左下。则此时对应卷积层误差敏感值分布为:

   

  当然了,上面2种结果还需要点乘卷积层激发函数对应位置的导数值了,这里省略掉。

 

  问题三:当接在pooling层的下一层为卷积层时,求该pooling层的误差敏感项。

  假设第l层(pooling层)有N个通道,即有N张特征图,第l+1层(卷积层)有M个特征,l层中每个通道图都对应有自己的误差敏感值,其计算依据为第l+1层所有特征核的贡献之和。下面是第l+1层中第j个核对第l层第i个通道的误差敏感值计算方法:

  

  符号★表示的是矩阵的卷积操作,这是真正意义上的离散卷积,不同于卷积层前向传播时的相关操作,因为严格意义上来讲,卷积神经网络中的卷积操作本质是一个相关操作,并不是卷积操作,只不过它可以用卷积的方法去实现才这样叫。而求第i个通道的误差敏感项时需要将l+1层的所有核都计算一遍,然后求和。另外因为这里默认pooling层是线性激发函数,所以后面没有乘相应节点的导数。

  举个简单的例子,假设拿出第l层某个通道图,大小为3×3,第l+1层有2个特征核,核大小为2×2,则在前向传播卷积时第l+1层会有2个大小为2×2的卷积图。如果2个特征核分别为:

       

  反向传播求误差敏感项时,假设已经知道第l+1层2个卷积图的误差敏感值:

    

  离散卷积函数conv2()的实现相关子操作时需先将核旋转180度(即左右翻转后上下翻转),但这里实现的是严格意义上的卷积,所以在用conv2()时,对应的参数核不需要翻转(在有些toolbox里面,求这个问题时用了旋转,那是因为它们已经把所有的卷积核都旋转过,这样在前向传播时的相关操作就不用旋转了。并不矛盾)。且这时候该函数需要采用’full’模式,所以最终得到的矩阵大小为3×3,(其中3=2+2-1),刚好符第l层通道图的大小。采用’full’模式需先将第l+1层2个卷积图扩充,周围填0,padding后如下:

          

  扩充后的矩阵和对应的核进行卷积的结果如下情况:

   

   

  可以通过手动去验证上面的结果,因为是离散卷积操作,而离散卷积等价于将核旋转后再进行相关操作。而第l层那个通道的误差敏感项为上面2者的和,呼应问题三,最终答案为:

   

  那么这样问题3这样解的依据是什么呢?其实很简单,本质上还是bp算法,即第l层的误差敏感值等于第l+1层的误差敏感值乘以两者之间的权值,只不过这里由于是用了卷积,且是有重叠的,l层中某个点会对l+1层中的多个点有影响。比如说最终的结果矩阵中最中间那个0.3是怎么来的呢?在用2×2的核对3×3的输入矩阵进行卷积时,一共进行了4次移动,而3×3矩阵最中间那个值在4次移动中均对输出结果有影响,且4次的影响分别在右下角、左下角、右上角、左上角。所以它的值为2×0.2+1×0.1+1×0.1-1×0.3=0.3, 建议大家用笔去算一下,那样就可以明白为什么这里可以采用带’full’类型的conv2()实现。

 

  问题四:求与卷积层相连那层的权值、偏置值导数。

  前面3个问题分别求得了输出层的误差敏感值、从pooling层推断出卷积层的误差敏感值、从卷积层推断出pooling层的误差敏感值。下面需要利用这些误差敏感值模型中参数的导数。这里没有考虑pooling层的非线性激发,因此pooling层前面是没有权值的,也就没有所谓的权值的导数了。现在将主要精力放在卷积层前面权值的求导上(也就是问题四)。

  假设现在需要求第l层的第i个通道,与第l+1层的第j个通道之间的权值和偏置的导数,则计算公式如下:

   

  其中符号⊙表示矩阵的相关操作,可以采用conv2()函数实现。在使用该函数时,需将第l+1层第j个误差敏感值翻转。

  例如,如果第l层某个通道矩阵i大小为4×4,如下:

   

  第l+1层第j个特征的误差敏感值矩阵大小为3×3,如下:

   

  很明显,这时候的特征Kij导数的大小为2×2的,且其结果为:

   

  而此时偏置值bj的导数为1.2 ,将j区域的误差敏感值相加即可(0.8+0.1-0.6+0.3+0.5+0.7-0.4-0.2=1.2),因为b对j中的每个节点都有贡献,按照多项式的求导规则(和的导数等于导数的和)很容易得到。

  为什么采用矩阵的相关操作就可以实现这个功能呢?由bp算法可知,l层i和l+1层j之间的权值等于l+1层j处误差敏感值乘以l层i处的输入,而j中某个节点因为是由i+1中一个区域与权值卷积后所得,所以j处该节点的误差敏感值对权值中所有元素都有贡献,由此可见,将j中每个元素对权值的贡献(尺寸和核大小相同)相加,就得到了权值的偏导数了(这个例子的结果是由9个2×2大小的矩阵之和),同样,如果大家动笔去推算一下,就会明白这时候为什么可以用带’valid’的conv2()完成此功能。

 

  实验总结:

  1. 卷积层过后,可以先跟pooling层,再通过非线性传播函数。也可以是先通过非线性传播函数再经过pooling层。
  2. CNN的结构本身就是一种规则项。
  3. 实际上每个权值的学习率不同时优化会更好。
  4. 发现Ng以前的ufldl中教程里面softmax并没有包含偏置值参数,至少他给的start code里面没有包含,严格来说是错误的。
  5. 当输入样本为多个时,bp算法中的误差敏感性也是一个矩阵。每一个样本都对应有自己每层的误差敏感性。
  6. 血的教训啊,以后循环变量名不能与终止名太相似,一不小心引用下标是就弄错,比如for filterNum = 1:numFilters 时一不小心把下标用numFilters去代替了,花了大半天去查这个debug.

  7.  matlab中conv2()函数在卷积过程中默认是每次移动一个像素,即重叠度最高的卷积。

 

  实验结果:

  按照网页教程UFLDL:Exercise: Convolutional Neural Network和UFLDL:Optimization: Stochastic Gradient Descent,对MNIST数据库进行识别,完成练习中YOU CODE HERE部分后,该CNN网络的识别率为:

  95.76%

  只采用了一个卷积层+一个pooling层+一个softmax层。卷积层的特征个数为20,卷积核大小为9×9, pooling区域大小为2×2.

  在进行实验前,需下载好本实验的标准代码:https://github.com/amaas/stanford_dl_ex。

  然后在common文件夹放入下载好的MNIST数据库,见http://yann.lecun.com/exdb/mnist/.注意MNIST文件名需要和代码中的保持一致。

 

  实验代码:

cnnTrain.m: 

复制代码
%% Convolution Neural Network Exercise%  Instructions%  ------------% %  This file contains code that helps you get started in building a single.%  layer convolutional nerual network. In this exercise, you will only%  need to modify cnnCost.m and cnnminFuncSGD.m. You will not need to %  modify this file.%%======================================================================%% STEP 0: Initialize Parameters and Load Data%  Here we initialize some parameters used for the exercise.% ConfigurationimageDim = 28;numClasses = 10;  % Number of classes (MNIST images fall into 10 classes)filterDim = 9;    % Filter size for conv layer,9*9numFilters = 20;   % Number of filters for conv layerpoolDim = 2;      % Pooling dimension, (should divide imageDim-filterDim+1)% Load MNIST Trainaddpath ./common/; images = loadMNISTImages('./common/train-images-idx3-ubyte');images = reshape(images,imageDim,imageDim,[]);labels = loadMNISTLabels('./common/train-labels-idx1-ubyte');labels(labels==0) = 10; % Remap 0 to 10% Initialize Parameters,theta=(2165,1),2165=9*9*20+20+100*20*10+10theta = cnnInitParams(imageDim,filterDim,numFilters,poolDim,numClasses);%%======================================================================%% STEP 1: Implement convNet Objective%  Implement the function cnnCost.m.%%======================================================================%% STEP 2: Gradient Check%  Use the file computeNumericalGradient.m to check the gradient%  calculation for your cnnCost.m function.  You may need to add the%  appropriate path or copy the file to this directory.DEBUG=false;  % set this to true to check gradient%DEBUG = true;if DEBUG    % To speed up gradient checking, we will use a reduced network and    % a debugging data set    db_numFilters = 2;    db_filterDim = 9;    db_poolDim = 5;    db_images = images(:,:,1:10);    db_labels = labels(1:10);    db_theta = cnnInitParams(imageDim,db_filterDim,db_numFilters,...                db_poolDim,numClasses);    [cost grad] = cnnCost(db_theta,db_images,db_labels,numClasses,...                                db_filterDim,db_numFilters,db_poolDim);    % Check gradients    numGrad = computeNumericalGradient( @(x) cnnCost(x,db_images,...                                db_labels,numClasses,db_filterDim,...                                db_numFilters,db_poolDim), db_theta);    % Use this to visually compare the gradients side by side    disp([numGrad grad]);    diff = norm(numGrad-grad)/norm(numGrad+grad);    % Should be small. In our implementation, these values are usually     % less than 1e-9.    disp(diff);     assert(diff < 1e-9,...        'Difference too large. Check your gradient computation again');end;%%======================================================================%% STEP 3: Learn Parameters%  Implement minFuncSGD.m, then train the model.% 因为是采用的mini-batch梯度下降法,所以总共对样本的循环次数次数比标准梯度下降法要少% 很多,因为每次循环中权值已经迭代多次了options.epochs = 3; options.minibatch = 256;options.alpha = 1e-1;options.momentum = .95;opttheta = minFuncSGD(@(x,y,z) cnnCost(x,y,z,numClasses,filterDim,...                      numFilters,poolDim),theta,images,labels,options);save('theta.mat','opttheta');             %%======================================================================%% STEP 4: Test%  Test the performance of the trained model using the MNIST test set. Your%  accuracy should be above 97% after 3 epochs of trainingtestImages = loadMNISTImages('./common/t10k-images-idx3-ubyte');testImages = reshape(testImages,imageDim,imageDim,[]);testLabels = loadMNISTLabels('./common/t10k-labels-idx1-ubyte');testLabels(testLabels==0) = 10; % Remap 0 to 10[~,cost,preds]=cnnCost(opttheta,testImages,testLabels,numClasses,...                filterDim,numFilters,poolDim,true);acc = sum(preds==testLabels)/length(preds);% Accuracy should be around 97.4% after 3 epochsfprintf('Accuracy is %f\n',acc);
复制代码

 

cnnConvolve.m: 

复制代码
function convolvedFeatures = cnnConvolve(filterDim, numFilters, images, W, b)%cnnConvolve Returns the convolution of the features given by W and b with%the given images%% Parameters:%  filterDim - filter (feature) dimension%  numFilters - number of feature maps%  images - large images to convolve with, matrix in the form%           images(r, c, image number)%  W, b - W, b for features from the sparse autoencoder,传进来的w已经是矩阵的形式%         W is of shape (filterDim,filterDim,numFilters)%         b is of shape (numFilters,1)%% Returns:%  convolvedFeatures - matrix of convolved features in the form%                      convolvedFeatures(imageRow, imageCol, featureNum, imageNum)numImages = size(images, 3);imageDim = size(images, 1);convDim = imageDim - filterDim + 1;convolvedFeatures = zeros(convDim, convDim, numFilters, numImages);% Instructions:%   Convolve every filter with every image here to produce the %   (imageDim - filterDim + 1) x (imageDim - filterDim + 1) x numFeatures x numImages%   matrix convolvedFeatures, such that %   convolvedFeatures(imageRow, imageCol, featureNum, imageNum) is the%   value of the convolved featureNum feature for the imageNum image over%   the region (imageRow, imageCol) to (imageRow + filterDim - 1, imageCol + filterDim - 1)%% Expected running times: %   Convolving with 100 images should take less than 30 seconds %   Convolving with 5000 images should take around 2 minutes%   (So to save time when testing, you should convolve with less images, as%   described earlier)for imageNum = 1:numImages  for filterNum = 1:numFilters    % convolution of image with feature matrix    convolvedImage = zeros(convDim, convDim);    % Obtain the feature (filterDim x filterDim) needed during the convolution    %%% YOUR CODE HERE %%%    filter = W(:,:,filterNum);    bc = b(filterNum);    % Flip the feature matrix because of the definition of convolution, as explained later    filter = rot90(squeeze(filter),2);    % Obtain the image    im = squeeze(images(:, :, imageNum));    % Convolve "filter" with "im", adding the result to convolvedImage    % be sure to do a 'valid' convolution    %%% YOUR CODE HERE %%%    convolvedImage = conv2(im, filter, 'valid');    % Add the bias unit    % Then, apply the sigmoid function to get the hidden activation    %%% YOUR CODE HERE %%%    convolvedImage = sigmoid(convolvedImage+bc);    convolvedFeatures(:, :, filterNum, imageNum) = convolvedImage;  endendendfunction sigm = sigmoid(x)    sigm = 1./(1+exp(-x));end
复制代码

 

cnnPool.m: 

复制代码
function pooledFeatures = cnnPool(poolDim, convolvedFeatures)%cnnPool Pools the given convolved features%% Parameters:%  poolDim - dimension of pooling region%  convolvedFeatures - convolved features to pool (as given by cnnConvolve)%                      convolvedFeatures(imageRow, imageCol, featureNum, imageNum)%% Returns:%  pooledFeatures - matrix of pooled features in the form%                   pooledFeatures(poolRow, poolCol, featureNum, imageNum)%     numImages = size(convolvedFeatures, 4);numFilters = size(convolvedFeatures, 3);convolvedDim = size(convolvedFeatures, 1);pooledFeatures = zeros(convolvedDim / poolDim, ...        convolvedDim / poolDim, numFilters, numImages);% Instructions:%   Now pool the convolved features in regions of poolDim x poolDim,%   to obtain the %   (convolvedDim/poolDim) x (convolvedDim/poolDim) x numFeatures x numImages %   matrix pooledFeatures, such that%   pooledFeatures(poolRow, poolCol, featureNum, imageNum) is the %   value of the featureNum feature for the imageNum image pooled over the%   corresponding (poolRow, poolCol) pooling region. %   %   Use mean pooling here.%%% YOUR CODE HERE %%%    % convolvedFeatures(imageRow, imageCol, featureNum, imageNum)    % pooledFeatures(poolRow, poolCol, featureNum, imageNum)    for numImage = 1:numImages        for numFeature = 1:numFilters            for poolRow = 1:convolvedDim / poolDim                offsetRow = 1+(poolRow-1)*poolDim;                for poolCol = 1:convolvedDim / poolDim                    offsetCol = 1+(poolCol-1)*poolDim;                    patch = convolvedFeatures(offsetRow:offsetRow+poolDim-1, ...                        offsetCol:offsetCol+poolDim-1,numFeature,numImage); %取出一个patch                    pooledFeatures(poolRow,poolCol,numFeature,numImage) = mean(patch(:));                end            end                    end    endend
复制代码

 

cnnCost.m:

复制代码
function [cost, grad, preds] = cnnCost(theta,images,labels,numClasses,...                                filterDim,numFilters,poolDim,pred)% Calcualte cost and gradient for a single layer convolutional% neural network followed by a softmax layer with cross entropy% objective.%                            % Parameters:%  theta      -  unrolled parameter vector%  images     -  stores images in imageDim x imageDim x numImges%                array%  numClasses -  number of classes to predict%  filterDim  -  dimension of convolutional filter                            %  numFilters -  number of convolutional filters%  poolDim    -  dimension of pooling area%  pred       -  boolean only forward propagate and return%                predictions%%% Returns:%  cost       -  cross entropy cost%  grad       -  gradient with respect to theta (if pred==False)%  preds      -  list of predictions for each example (if pred==True)if ~exist('pred','var')    pred = false;end;imageDim = size(images,1); % height/width of imagenumImages = size(images,3); % number of imageslambda = 3e-3; % weight decay parameter     %% Reshape parameters and setup gradient matrices% Wc is filterDim x filterDim x numFilters parameter matrix% bc is the corresponding bias% Wd is numClasses x hiddenSize parameter matrix where hiddenSize% is the number of output units from the convolutional layer% bd is corresponding bias[Wc, Wd, bc, bd] = cnnParamsToStack(theta,imageDim,filterDim,numFilters,...                        poolDim,numClasses); %the theta vector cosists wc,wd,bc,bd in order% Same sizes as Wc,Wd,bc,bd. Used to hold gradient w.r.t above params.Wc_grad = zeros(size(Wc));Wd_grad = zeros(size(Wd));bc_grad = zeros(size(bc));bd_grad = zeros(size(bd));%%======================================================================%% STEP 1a: Forward Propagation%  In this step you will forward propagate the input through the%  convolutional and subsampling (mean pooling) layers.  You will then use%  the responses from the convolution and pooling layer as the input to a%  standard softmax layer.%% Convolutional Layer%  For each image and each filter, convolve the image with the filter, add%  the bias and apply the sigmoid nonlinearity.  Then subsample the %  convolved activations with mean pooling.  Store the results of the%  convolution in activations and the results of the pooling in%  activationsPooled.  You will need to save the convolved activations for%  backpropagation.convDim = imageDim-filterDim+1; % dimension of convolved outputoutputDim = (convDim)/poolDim; % dimension of subsampled output% convDim x convDim x numFilters x numImages tensor for storing activationsactivations = zeros(convDim,convDim,numFilters,numImages);% outputDim x outputDim x numFilters x numImages tensor for storing% subsampled activationsactivationsPooled = zeros(outputDim,outputDim,numFilters,numImages);%%% YOUR CODE HERE %%%convolvedFeatures = cnnConvolve(filterDim, numFilters, images, Wc, bc); %前向传播,已经经过了激发函数activationsPooled = cnnPool(poolDim, convolvedFeatures);% Reshape activations into 2-d matrix, hiddenSize x numImages,% for Softmax layeractivationsPooled = reshape(activationsPooled,[],numImages);%% Softmax Layer%  Forward propagate the pooled activations calculated above into a%  standard softmax layer. For your convenience we have reshaped%  activationPooled into a hiddenSize x numImages matrix.  Store the%  results in probs.% numClasses x numImages for storing probability that each image belongs to% each class.probs = zeros(numClasses,numImages);%%% YOUR CODE HERE %%%%Wd=(numClasses,hiddenSize),probs的每一列代表一个输出M = Wd*activationsPooled+repmat(bd,[1,numImages]); M = bsxfun(@minus,M,max(M,[],1));M = exp(M);probs = bsxfun(@rdivide, M, sum(M)); %why rdivide?%%======================================================================%% STEP 1b: Calculate Cost%  In this step you will use the labels given as input and the probs%  calculate above to evaluate the cross entropy objective.  Store your%  results in cost.cost = 0; % save objective into cost%%% YOUR CODE HERE %%%% cost = -1/numImages*labels(:)'*log(probs(:));% 首先需要把labels弄成one-hot编码groundTruth = full(sparse(labels, 1:numImages, 1));cost = -1./numImages*groundTruth(:)'*log(probs(:))+(lambda/2.)*(sum(Wd(:).^2)+sum(Wc(:).^2)); %加入一个惩罚项% cost = -1./numImages*groundTruth(:)'*log(probs(:));% Makes predictions given probs and returns without backproagating errors.if pred    [~,preds] = max(probs,[],1);    preds = preds';    grad = 0;    return;end;%% 将c步和d步合成在一起了%======================================================================% STEP 1c: Backpropagation%  Backpropagate errors through the softmax and convolutional/subsampling%  layers.  Store the errors for the next step to calculate the gradient.%  Backpropagating the error w.r.t the softmax layer is as usual.  To%  backpropagate through the pooling layer, you will need to upsample the%  error with respect to the pooling layer for each filter and each image.  %  Use the kron function and a matrix of ones to do this upsampling %  quickly.% STEP 1d: Gradient Calculation%  After backpropagating the errors above, we can use them to calculate the%  gradient with respect to all the parameters.  The gradient w.r.t the%  softmax layer is calculated as usual.  To calculate the gradient w.r.t.%  a filter in the convolutional layer, convolve the backpropagated error%  for that filter with each image and aggregate over images.%%% YOUR CODE HERE %%%%%% YOUR CODE HERE %%%% 网络结构: images--> convolvedFeatures--> activationsPooled--> probs% Wd = (numClasses,hiddenSize)% bd = (hiddenSize,1)% Wc = (filterDim,filterDim,numFilters)% bc = (numFilters,1)% activationsPooled = zeros(outputDim,outputDim,numFilters,numImages);% convolvedFeatures = (convDim,convDim,numFilters,numImages)% images(imageDim,imageDim,numImges)delta_d = -(groundTruth-probs); % softmax layer's preactivation,每一个样本都对应有自己每层的误差敏感性。Wd_grad = (1./numImages)*delta_d*activationsPooled'+lambda*Wd;bd_grad = (1./numImages)*sum(delta_d,2); %注意这里是要求和delta_s = Wd'*delta_d; %the pooling/sample layer's preactivationdelta_s = reshape(delta_s,outputDim,outputDim,numFilters,numImages);%进行unsampling操作,由于kron函数只能对二维矩阵操作,所以还得分开弄%delta_c = convolvedFeatures.*(1-convolvedFeatures).*(1./poolDim^2)*kron(delta_s, ones(poolDim)); delta_c = zeros(convDim,convDim,numFilters,numImages);for i=1:numImages    for j=1:numFilters        delta_c(:,:,j,i) = (1./poolDim^2)*kron(squeeze(delta_s(:,:,j,i)), ones(poolDim));    endenddelta_c = convolvedFeatures.*(1-convolvedFeatures).*delta_c;% Wc_grad = convn(images,rot90(delta_c,2,'valid'))+ lambda*Wc;for i=1:numFilters    Wc_i = zeros(filterDim,filterDim);    for j=1:numImages          Wc_i = Wc_i+conv2(squeeze(images(:,:,j)),rot90(squeeze(delta_c(:,:,i,j)),2),'valid');    end   % Wc_i = convn(images,rot180(squeeze(delta_c(:,:,i,:))),'valid');    % add penalize    Wc_grad(:,:,i) = (1./numImages)*Wc_i+lambda*Wc(:,:,i);    bc_i = delta_c(:,:,i,:);    bc_i = bc_i(:);    bc_grad(i) = sum(bc_i)/numImages;end%% Unroll gradient into grad vector for minFuncgrad = [Wc_grad(:) ; Wd_grad(:) ; bc_grad(:) ; bd_grad(:)];endfunction X = rot180(X)    X = flipdim(flipdim(X, 1), 2);end
复制代码

 

minFuncSGD.m: 

复制代码
function [opttheta] = minFuncSGD(funObj,theta,data,labels,...                        options)% Runs stochastic gradient descent with momentum to optimize the% parameters for the given objective.%% Parameters:%  funObj     -  function handle which accepts as input theta,%                data, labels and returns cost and gradient w.r.t%                to theta.%  theta      -  unrolled parameter vector%  data       -  stores data in m x n x numExamples tensor%  labels     -  corresponding labels in numExamples x 1 vector%  options    -  struct to store specific options for optimization%% Returns:%  opttheta   -  optimized parameter vector%% Options (* required)%  epochs*     - number of epochs through data%  alpha*      - initial learning rate%  minibatch*  - size of minibatch%  momentum    - momentum constant, defualts to 0.9%%======================================================================%% Setupassert(all(isfield(options,{'epochs','alpha','minibatch'})),...        'Some options not defined');if ~isfield(options,'momentum')    options.momentum = 0.9;end;epochs = options.epochs;alpha = options.alpha;minibatch = options.minibatch;m = length(labels); % training set size% Setup for momentummom = 0.5;momIncrease = 20;velocity = zeros(size(theta));%%======================================================================%% SGD loopit = 0;for e = 1:epochs    % randomly permute indices of data for quick minibatch sampling    rp = randperm(m);    for s=1:minibatch:(m-minibatch+1)        it = it + 1;        % increase momentum after momIncrease iterations        if it == momIncrease            mom = options.momentum;        end;        % get next randomly selected minibatch        mb_data = data(:,:,rp(s:s+minibatch-1)); % 取出当前的mini-batch的训练样本        mb_labels = labels(rp(s:s+minibatch-1));        % evaluate the objective function on the next minibatch        [cost grad] = funObj(theta,mb_data,mb_labels);        % Instructions: Add in the weighted velocity vector to the        % gradient evaluated above scaled by the learning rate.        % Then update the current weights theta according to the        % sgd update rule        %%% YOUR CODE HERE %%%        velocity = mom*velocity+alpha*grad; % 见ufldl教程Optimization: Stochastic Gradient Descent        theta = theta-velocity;        fprintf('Epoch %d: Cost on iteration %d is %f\n',e,it,cost);    end;    % aneal learning rate by factor of two after each epoch    alpha = alpha/2.0;end;opttheta = theta;end
复制代码

 

computeNumericalGradient.m: 

复制代码
function numgrad = computeNumericalGradient(J, theta)% numgrad = computeNumericalGradient(J, theta)% theta: a vector of parameters% J: a function that outputs a real-number. Calling y = J(theta) will return the% function value at theta. % Initialize numgrad with zerosnumgrad = zeros(size(theta));%% ---------- YOUR CODE HERE --------------------------------------% Instructions: % Implement numerical gradient checking, and return the result in numgrad.  % (See Section 2.3 of the lecture notes.)% You should write code so that numgrad(i) is (the numerical approximation to) the % partial derivative of J with respect to the i-th input argument, evaluated at theta.  % I.e., numgrad(i) should be the (approximately) the partial derivative of J with % respect to theta(i).%                % Hint: You will probably want to compute the elements of numgrad one at a time. epsilon = 1e-4;for i =1:length(numgrad)    oldT = theta(i);    theta(i)=oldT+epsilon;    pos = J(theta);    theta(i)=oldT-epsilon;    neg = J(theta);    numgrad(i) = (pos-neg)/(2*epsilon);    theta(i)=oldT;    if mod(i,100)==0       fprintf('Done with %d\n',i);    end;end;%% ---------------------------------------------------------------end
复制代码

 

cnnInitParams.m: 

复制代码
function theta = cnnInitParams(imageDim,filterDim,numFilters,...                                poolDim,numClasses)% Initialize parameters for a single layer convolutional neural% network followed by a softmax layer.%                            % Parameters:%  imageDim   -  height/width of image%  filterDim  -  dimension of convolutional filter                            %  numFilters -  number of convolutional filters%  poolDim    -  dimension of pooling area%  numClasses -  number of classes to predict%%% Returns:%  theta      -  unrolled parameter vector with initialized weights%% Initialize parameters randomly based on layer sizes.assert(filterDim < imageDim,'filterDim must be less that imageDim');Wc = 1e-1*randn(filterDim,filterDim,numFilters);outDim = imageDim - filterDim + 1; % dimension of convolved image% assume outDim is multiple of poolDimassert(mod(outDim,poolDim)==0,...       'poolDim must divide imageDim - filterDim + 1');outDim = outDim/poolDim;hiddenSize = outDim^2*numFilters;% we'll choose weights uniformly from the interval [-r, r]r  = sqrt(6) / sqrt(numClasses+hiddenSize+1);Wd = rand(numClasses, hiddenSize) * 2 * r - r;bc = zeros(numFilters, 1); %初始化为0bd = zeros(numClasses, 1);% Convert weights and bias gradients to the vector form.% This step will "unroll" (flatten and concatenate together) all % your parameters into a vector, which can then be used with minFunc. theta = [Wc(:) ; Wd(:) ; bc(:) ; bd(:)];end
复制代码

 

cnnParamsToStack.m: 

复制代码
function [Wc, Wd, bc, bd] = cnnParamsToStack(theta,imageDim,filterDim,...                                 numFilters,poolDim,numClasses)% Converts unrolled parameters for a single layer convolutional neural% network followed by a softmax layer into structured weight% tensors/matrices and corresponding biases%                            % Parameters:%  theta      -  unrolled parameter vectore%  imageDim   -  height/width of image%  filterDim  -  dimension of convolutional filter                            %  numFilters -  number of convolutional filters%  poolDim    -  dimension of pooling area%  numClasses -  number of classes to predict%%% Returns:%  Wc      -  filterDim x filterDim x numFilters parameter matrix%  Wd      -  numClasses x hiddenSize parameter matrix, hiddenSize is%             calculated as numFilters*((imageDim-filterDim+1)/poolDim)^2 %  bc      -  bias for convolution layer of size numFilters x 1%  bd      -  bias for dense layer of size hiddenSize x 1outDim = (imageDim - filterDim + 1)/poolDim;hiddenSize = outDim^2*numFilters;%% Reshape thetaindS = 1;indE = filterDim^2*numFilters;Wc = reshape(theta(indS:indE),filterDim,filterDim,numFilters);indS = indE+1;indE = indE+hiddenSize*numClasses;Wd = reshape(theta(indS:indE),numClasses,hiddenSize);indS = indE+1;indE = indE+numFilters;bc = theta(indS:indE);bd = theta(indE+1:end);end
复制代码

 

   2013.12.30:

  微博网友@路遥_机器学习利用matlab自带的优化函数conv2,实现的mean-pooling,可以大大加快速度,大家可以参考。cnnPool.m文件里面:

tmp = conv2(convolvedFeatures(:,:,numFeature,numImage), ones(poolDim),'valid'); 
pooledFeatures(:,:,numFeature,numImage) =1./(poolDim^2)*tmp(1:poolDim:end,1:poolDim:end);

 

 

  参考资料:

       Deep learning:三十八(Stacked CNN简单介绍)

       UFLDL:Convolutional Neural Network

       UFLDL:Exercise: Convolutional Neural Network

       UFLDL:Optimization: Stochastic Gradient Descent

       zouxy09博文:Deep Learning论文笔记之(四)CNN卷积神经网络推导和实现

       论文Notes on Convolutional Neural Networks, Jake Bouvrie

       Larochelle关于DL的课件:Output layer gradient

       github.com/rasmusbergpalm/DeepLearnToolbox/blob/master/CNN/cnnbp.m

     https://github.com/amaas/stanford_dl_ex

   http://yann.lecun.com/exdb/mnist/

 

 

 

 

 

 

作者:tornadomeet

出处:http://www.cnblogs.com/tornadomeet

欢迎转载或分享,但请务必声明文章出处。 (新浪微博:tornadomeet,欢迎交流!)

分类: Deep Learning,机器学习
标签: 机器学习, Deep Learning
好文要顶 关注我 收藏该文
tornadomeet
关注 - 46
粉丝 - 3723
+加关注
6
2

« 上一篇:Deep learning:五十(Deconvolution Network简单理解)
» 下一篇:机器学习&数据挖掘笔记_18(PGM练习二:贝叶斯网络在遗传图谱在的应用)
<div class="postDesc">posted on <span id="post-date">2013-12-10 23:36</span> <a href="http://www.cnblogs.com/tornadomeet/">tornadomeet</a> 阅读(<span id="post_view_count">89192</span>) 评论(<span id="post_comment_count">49</span>)  <a href="https://i.cnblogs.com/EditPosts.aspx?postid=3468450" rel="nofollow">编辑</a> <a href="#" onclick="AddToWz(3468450);return false;">收藏</a></div>


评论:
#1楼 2013-12-19 23:05 | cc_jony  
LZ很强大哦
支持(0)反对(0)
  
#2楼 2014-01-20 06:54 | ribbons  
@tornadomeet
请问楼主,在pooling层的下一层为卷积层时,求该pooling层的误差敏感项的时候。如果卷积图的误差敏感值矩阵的大小为3X3 以上时,那是怎么扩充呢?是在边界填0,还是其他方式?
支持(1)反对(0)
  
#3楼[楼主] 2014-01-21 17:01 | tornadomeet  
@ribbons
这个还和卷积核大小有关。假设核大小为n*n,则在卷积误差层4个方向向外扩充n-1大小。
支持(0)反对(0)
  
#4楼 2014-02-07 15:58 | leesusu  
cnnCost.m 中的一句注释
% bd = (hiddenSize,1)
应该是
% bd = (numClasses,1)
支持(0)反对(0)
http://pic.cnblogs.com/face/568368/20131016155901.png  
#5楼 2014-02-11 16:48 | P.O.S.–Power Output Stream  
大赞LZ!
有个问题想请教一下,为什么在问题三中,敏感度求解中,变成了卷积?
就是说前向传播时是相关,反向时就要是卷积?这段感觉很难理解。
希望能得到您的解答。
支持(0)反对(0)
  
#6楼[楼主] 2014-02-12 17:19 | tornadomeet  
@P.O.S.–Power Output Stream
最好的方法是举个例,自己手动测试下,动手算一遍就会懂了,我在博客中已经说过
支持(0)反对(1)
  
#7楼 2014-02-12 17:31 | P.O.S.–Power Output Stream  
好的,谢谢了。
等我静下心来,慢慢推导,现在太浮躁了,推一下就烦了。
支持(0)反对(0)
  
#8楼 2014-02-24 17:19 | lqforsym  
请问楼主,对于深度的CNN,用啥方法进行pre-training啊?
支持(0)反对(0)
  
#9楼 2014-03-19 15:44 | 波小妞  
这个误差敏感项有什么作用吗?是来优化权值和阈值的吗?
支持(0)反对(0)
  
#10楼 2014-04-21 10:12 | bzjia  
楼主,cnnCost里这行代码:
delta_c = convolvedFeatures.*(1-convolvedFeatures).*delta_c;求delta的时候是不是少乘了个权值啊?记得每层的残差值公式是:
delt(L) = W(L)*delt(L+1)*a(L)*(1-a(L));
这里感觉少了个W(L),不太明白是不是这样,还请指教啊
支持(0)反对(0)
  
#11楼[楼主] 2014-04-21 18:24 | tornadomeet  
@lqforsym
Convolution Auto-encoder
支持(0)反对(0)
  
#12楼[楼主] 2014-04-21 18:25 | tornadomeet  
@波小妞
计算目标函数梯度的中间变量,因为优化时需要梯度值
支持(0)反对(0)
  
#13楼[楼主] 2014-04-21 18:26 | tornadomeet  
@bzjia
整个代码一起看
支持(0)反对(0)
  
#14楼 2014-05-14 09:22 | 波小妞  
这里如果用max-pooling ,unsample操作应该怎么做啊 怎么记录pooling区域的最大值位置
支持(0)反对(0)
  
#15楼 2014-05-14 14:49 | 波小妞  
为什么我在梯度确认的时候一直出不来呢 会有哪些原因啊
支持(0)反对(0)
  
#16楼 2014-05-20 21:15 | 波小妞  

能帮我看一下这个代码吗 在梯度确认是只达到了1e-5,实在不知道错在哪了!
支持(0)反对(0)
  
#17楼 2014-05-26 15:25 | lqforsym  
最近在找一些dl的代码读读,请问你那里有多层CNN的matlab代码吗,一直不清楚层与层之间该如何连接比较好
支持(0)反对(0)
  
#18楼 2014-06-25 16:15 | 小王旺  
@leesusu
恩 是的
支持(0)反对(0)
  
#19楼 2014-06-27 11:22 | 小王旺  
@tornadomeet
楼主,用Convolutional autoencoder来预训练CNN,具体怎么弄?是指在CNN框架中随机初始化W,b改为用CAE来求解?
支持(0)反对(0)
  
#20楼 2014-07-01 10:16 | 小王旺  
楼主,我看到有的文章中有卷积后并没跟池化的情况,也就是说BP时,存在当前层为卷积层,求上一层为卷积的敏感误差项情况,这个怎么求?
支持(0)反对(0)
  
#21楼 2014-08-04 20:37 | Tsien  
@波小妞
引用这里如果用max-pooling ,unsample操作应该怎么做啊 怎么记录pooling区域的最大值位置
在前向传播max-Pooling时,记录最大值位置。
LeCun好多论文里提到过用switch变量记录位置,来近似到达unpool
支持(0)反对(0)
  
#22楼 2014-08-13 10:47 | LoveLanLan  
@波小妞
郁闷,我也是通不过梯度检验。。。
支持(0)反对(0)
  
#23楼 2014-09-06 11:04 | stifl  
问题三种 前向时候l层经过卷积操作的到了l+1层,但是反向的时候却默认是l+1层的敏感度进行卷积操作得到l层的敏感度,这不反过来了么?是不是错了
支持(0)反对(0)
  
#24楼 2014-10-16 09:33 | leftorright  
%% Use only the first 8 images for testing
convImages = images(:, :, 1:8); %取8副图片做卷积

% NOTE: Implement cnnConvolve in cnnConvolve.m first!
convolvedFeatures = cnnConvolve(filterDim, numFilters, convImages, W, b);
这里不才取了8副图片么,为什么到cnnConvolve函数中执行numImages = size(images, 3);却是60000呢?
支持(0)反对(0)
  
#25楼 2014-10-18 11:37 | leftorright  
代码中说到Accuracy should be around 97.4% after 3 epochs
但是博主你的程序没有这么高精度,是不是哪里有错误?
支持(1)反对(0)
  
#26楼 2014-11-17 20:37 | codding  
楼主你好,我对minFuncSGD中的参数更新方式有点疑惑。就是 velocity = mom*velocity+alpha*grad;
theta = theta-velocity;
这个部分,我去ufldl上似乎没找到这个更新方式,楼主可以贴个连接给我么?
支持(0)反对(0)
  
#27楼 2014-12-16 16:27 | a_kun  
楼主你好,对cnnPool有个疑问,在pooling层不需要激发函数吗?sigmond
支持(0)反对(0)
  
#28楼 2015-01-31 10:43 | arsenicer  
我也根据教程写了类似的代码,可以运行,精度在96.5%左右,达不到原来代码里所说的97.4%左右。也不知道问题在哪里?是否是nn常见的局部最优值问题?不过教程里倒是说在96%以上。
支持(1)反对(0)
  
#29楼 2015-05-17 13:41 | OleNet  
楼主感觉你的第6个图(“这样就可以求出第L层的权值W的偏导数”下面的图),貌似有点错误,:不应该是f`(x)吧, 应该是f(x)吧,cnnCost.m 里面求W导数也是这么写的:Wd_grad = (1./numImages)*delta_d*activationsPooled’+lambda*Wd;而不是乘以activationsPooled的导数。
支持(0)反对(0)
http://pic.cnblogs.com/face/500039/20150318170618.png  
#30楼 2015-05-19 15:32 | DoMagic  
楼主 你好 留个qq563524748 有几个问题想请教一 谢谢!!!
支持(0)反对(0)
  
#31楼 2015-07-02 15:15 | trantor  
发现Ng以前的ufldl中教程里面softmax并没有包含偏置值参数,至少他给的start code里面没有包含,严格来说是错误的。

softmax 定义就没有偏置项吧?
支持(1)反对(0)
  
#32楼 2015-07-23 15:19 | samrtape  
很喜欢博主的文章,刚刚用豆约翰博客备份专家备份了您的全部博文。
支持(0)反对(0)
  
#33楼 2015-11-21 13:56 | 懒得想名字  
看到实验总结的第6点,简直不能更赞同,我也犯了同样的错误[大哭],要是在当时看到这篇博文就好了。
支持(0)反对(0)
http://pic.cnblogs.com/face/787638/20160121172438.png  
#34楼 2015-11-25 21:35 | zxwzyw  
很喜欢博主的文章,刚刚用豆约翰博客备份专家备份了您的全部博文。
支持(0)反对(0)
  
#35楼 2015-12-29 20:36 | JoesRain  
欢迎加入机器学习研究QQ群445858879,可以跟悉尼科技大学博导徐亦达教授亲切交流,不过最好使用英语进行学术交流。谢谢!
支持(0)反对(0)
  
#36楼 2016-02-07 02:40 | 我是夏天的一滴雨水  
真心跪求 你博客里 说的 卷积 是严格卷积,不需要旋转的意思,感谢99999年

QQ 83656313
支持(0)反对(0)
  
#37楼 2016-04-07 10:43 | junedwx  
我刚接触CNN,在运行上面的代码时,数据怎么都加载不进去,求问怎么解决
支持(0)反对(0)
  
#38楼 2016-05-30 17:47 | 执着的博客  
@leftorright
如果代价函数中只对输出层softmax的权值进行衰减,而不加入卷积层权值的衰减,可以达到96%以上(我试验的96.87%)。
在老版本的Exercise: Implement deep networks for digit classification中有提到这样的处理。
Note: When adding the weight decay term to the cost, you should regularize only the softmax weights (do not regularize the weights that compute the hidden layer activations).
支持(0)反对(0)
  
#39楼 2016-05-30 17:54 | 执着的博客  
@arsenicer
这个练习好像没有用到自编码提取的特征作为卷积权值W。不知道先用自编码获得的Feature Map来做会不会效果更好。
老版Exercise: Implement deep networks for digit classification中,我试验的准确率达到了98%,这个练习却只有96+%。
支持(0)反对(0)
  
#40楼 2016-06-05 12:16 | 脚踏实地,不忘初心  
请问有大神知道问题四中的第l+1层的误差敏感项是如何翻转的,从而得到权值矩阵【20.4 2.8 4.9 12.7】的吗?我算了好几种方式,都不对。
支持(0)反对(0)
  
#41楼 2016-08-14 15:33 | 工长山  
@脚踏实地,不忘初心
16*0.8+2*0.1-3*0.6+5*0.3+11*0.5+10*0.7-9*0.4+0-6*0.2=20.4
支持(0)反对(0)
  
#42楼 2016-08-14 15:36 | 工长山  
@我是夏天的一滴雨水
在forward和backpropagation求残差敏感值时对卷积核都旋转或者都不旋转。
支持(0)反对(0)
  
#43楼 2016-10-17 18:36 | susanwq  
@tornadomeet
您好,我想请问一下,怎么换成自己的数据,我想看看效果,希望能指点,谢谢!
支持(0)反对(0)
  
#44楼 2016-11-16 16:29 | kimir17  
楼主你好 ufldl上说在更新参数的时候要在20分钟之内 我的程序跑了40分钟 请问有什么最值得优化的地方 或者说最耗时的地方
支持(0)反对(0)
  
#45楼 2017-06-20 16:51 | mrzhouxixi  
不知道我理解的对不对,总感觉Pooling层前向传播是对特征图进行downsample,但反向传播是却是对敏感项进行upsample,是吗?
支持(0)反对(0)
  
#46楼 2017-06-23 19:55 | mrzhouxixi  
还有cnnPool.m里pooledFeatures = zeros(convolvedDim / poolDim, convolvedDim / poolDim, numFilters, numImages); 这里的convolvedDim / poolDim不一定整除诶,难道默认向下取整??
支持(0)反对(0)
  
#47楼 2017-08-10 13:08 | larryluo619  
博主的代码我试跑了下只能跑到80%-90%的acc,查了一遍代码发现是cnnCost.m里的M = bsxfun(@minus,M,max(M,[],1));这句的问题,这里应该是参照了softmax那章减去一个常数防止溢出,但会导致梯度检验无法通过,拿掉以后就能跑到97%以上了。另外如果cnnPool的处理不向量化是跑不到作业要求的20mins以内的,必须用向量化,否则实在太慢了。
支持(0)反对(0)
  
#48楼 2017-08-10 13:10 | larryluo619  
@mrzhouxixi
poolDim大小必须选择为能被卷积层输出尺寸整除的数,教程里有要求的。
支持(0)反对(0)
  
#49楼37546622017/8/10 13:14:19 2017-08-10 13:14 | larryluo619  
@mrzhouxixi
反向传播传播的就是delta,然后通过delta和前一层的激活输出值之间的关系来求出相应梯度。
支持(0)反对(0)
  
刷新评论刷新页面返回顶部
【推荐】50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
【力荐】普惠云计算 0门槛体验 30+云产品免费半年
【推荐】可嵌入您系统的“在线Excel”!SpreadJS 纯前端表格控件
【推荐】阿里云“全民云计算”优惠升级
美团云0907
最新IT新闻:
· 舆论浪尖上的暴风集团:CEO冯鑫今年来股权质押12次
· Google正在北京组建AI团队,开始AI人才争夺大战
· 美国运营商AT&T不再独享 三防版三星S8国际版现身
· 你在京东退掉的商品,去了哪儿?
· 沃尔玛欲借助英伟达GPU和AI对抗亚马逊
» 更多新闻…
极光0908
最新知识库文章:
· 做到这一点,你也可以成为优秀的程序员
· 写给立志做码农的大学生
· 架构腐化之谜
· 学会思考,而不只是编程
· 编写Shell脚本的最佳实践
» 更多知识库文章…
fixPostBody(); setTimeout(function () { incrementViewCount(cb_entryId); }, 50); deliverAdT2(); deliverAdC1(); deliverAdC2(); loadNewsAndKb(); loadBlogSignature(); LoadPostInfoBlock(cb_blogId, cb_entryId, cb_blogApp, cb_blogUserGuid); GetPrevNextPost(cb_entryId, cb_blogId, cb_entryCreatedDate); loadOptUnderPost(); GetHistoryToday(cb_blogId, cb_blogApp, cb_entryCreatedDate);

原创粉丝点击