cnn训练时每次的cnn batchsize怎么取的

后使用快捷导航没有帐号?
查看: 13409|回复: 10
用Tensorflow实现CNN文本分类 zz
金牌会员, 积分 1849, 距离下一级还需 1151 积分
论坛徽章:17
Paper:
&a href=&http://www.people.fas.harvard.edu/~yoonkim/data/emnlp_2014.pdf&&http://www.people.fas.harvard.edu/~yoonkim/data/emnlp_2014.pdf&/a&复制代码
&p&Code: &a href=&/2015/12/implementing-a-cnn-for-text-classification-in-tensorflow/&&&font color=&#0066cc&&/2015/12/implementing-a-cnn-for-text-classification-in-tensorflow/&/font&&/a&&/p&&p&&a href=&/dennybritz/cnn-text-classification-tf&&/dennybritz/cnn-text-classification-tf&/a&&/p&复制代码tensorflow之路-解读textCNN& && && && && && && && && && &
发表于& && && &&&& && && && && && && && && && && && && && && && && && && && && && && && && &|& && && && && &&&& && && && && && && && && && && && &&&
Ox00: Motivation最近在研究的一篇经典之作,这篇文章可以说是cnn模型用于文本分类的开山之作(其实第一个用的不是他,但是Kim提出了几个variants,并有详细的调参)
对这篇paper有一个tensorflow的实现,具体参见。其实blog已经写的很详细了,但是对于刚入手tensorflow的新人来说代码可能仍存在一些细节不太容易理解,我也是初学,就简单总结下自己的理解,如果对读者有帮助那将是极好的。
Ox01: Start!我主要对TextCNN这个类进行解读,具体代码在。
研究别人代码时,时常问自己几个问题,由问题切入,在读的过程中找答案,这种方式我个人认为是最efficient的
1 这个class的主要作用是什么?TextCNN类搭建了一个最basic的CNN模型,有input layer,convolutional layer,max-pooling layer和最后输出的softmax layer。
但是又因为整个模型是用于文本的(而非CNN的传统处理对象:图像),因此在cnn的操作上相对应地做了一些小调整:
对于文本任务,输入层自然使用了word embedding来做input data representation。接下来是卷积层,大家在图像处理中经常看到的卷积核都是正方形的,比如4*4,然后在整张image上沿宽和高逐步移动进行卷积操作。但是nlp中输入的“image”是一个词矩阵,比如n个words,每个word用200维的vector表示的话,这个”image”就是n*200的矩阵,卷积核只在高度上已经滑动,在宽度上和word vector的维度一致(=200),也就是说每次窗口滑动过的位置都是完整的单词,不会将几个单词的一部分“vector”进行卷积,这也保证了word作为语言中最小粒度的合理性。(当然,如果研究的粒度是character-level而不是word-level,需要另外的方式处理)由于卷积核和word embedding的宽度一致,一个卷积核对于一个sentence,卷积后得到的结果是一个vector, shape=(sentence_len - filter_window + 1, 1),那么,在max-pooling后得到的就是一个r。所以,这点也是和图像卷积的不同之处,需要注意一下。正是由于max-pooling后只是得到一个scalar,在nlp中,会实施多个filter_window_size(比如3,4,5个words的宽度分别作为卷积的窗口大小),每个window_size又有num_filters个(比如64个)卷积核。一个卷积核得到的只是一个scalar太孤单了,智慧的人们就将相同window_size卷积出来的num_filter个scalar组合在一起,组成这个window_size下的feature_vector。最后再将所有window_size下的feature_vector也组合成一个single vector,作为最后一层softmax的输入。
重要的事情说三遍:一个卷积核对于一个句子,convolution后得到的是一个vector;max-pooling后,得到的是一个scalar。
如果对上述讲解还有什么不理解的地方,请移步wildml的另一篇,包教包会。
说了这么多,总结一下这个类的作用就是:搭建一个用于文本数据的CNN模型!
2 一些参数既然TextCNN类是基于YoonKim的思路搭建的,那么我们接下来一个很重要的步骤就是将paper中提到的各种参数设置都整理出来,有一些参数是关于模型的,有一些参数是关于training的,比如epoch等,这类参数就和模型本身无关,以此来确定我们的TextCNN类需要传递哪些参数来初始化。
赶紧把打开,来仔细找找参数吧。
3.1节Hyperparameters and Training部分讲到一些,还有一部分在Table1中:
关于modelfilter windows: [3,4,5]filter maps: 100 for each filter windowdropout rate: 0.5l2 constraint: 3randomly select 10% of training data as dev set(early stopping)word2vec(google news) as initial input, dim = 300sentence of length: n, padding where necessarynumber of target classesdataset sizevocabulary size
关于trainingmini batch size: 50shuffuled mini batchAdadelta update rule: similar results to Adagrad but required fewer epochsTest method: standard train/test split ot CV
3 Dropout注意事项正则是解决过拟合的问题,在最后一层softmax的时候是full-connected layer,因此容易产生过拟合。
策略就是在:
在训练阶段,对max-pooling layer的输出实行一些dropout,以概率p激活,激活的部分传递给softmax层。
在测试阶段,w已经学好了,但是不能直接用于unseen sentences,要乘以p之后再用,这个阶段没有dropout了全部输出给softmax层。
4 Embedding Layer1
# Embedding layer
with tf.device('/cpu:0'), tf.name_scope(&embedding&):
& & W = tf.Variable(
& && &&&tf.random_uniform([vocab_size, embedding_size], -1.0, 1.0),
& && &&&name=&W&)
& & self.embedded_chars = tf.nn.embedding_lookup(W, self.input_x)
& & self.embedded_chars_expanded = tf.expand_dims(self.embedded_chars, -1)
存储全部word vector的矩阵&span tabindex=&0& class=&MathJax& id=&MathJax-Element-1-Frame& role=&presentation& style=&display: inline- position:& data-mathml='W'&W W初始化时是随机random出来的,也就是paper中的第一种模型CNN-rand
训练过程中并不是每次都会使用全部的vocabulary,而只是产生一个batch(batch中都是sentence,每个sentence标记了出现哪些word(较大长度为sequence_length),因此batch相当于一个二维列表),这个batch就是input_x。
self.input_x = tf.placeholder(tf.int32, [None, sequence_length], name=&input_x&)
tf.nn.embedding_lookup:查找input_x中所有的ids,获取它们的word vector。batch中的每个sentence的每个word都要查找。所以得到的embedded_chars的shape应该是[None, sequence_length, embedding_size](1)
但是,输入的word vectors得到之后,下一步就是输入到卷积层,用到tf.nn.conv2d函数,
再看看conv2d的参数列表:
input: [batch, in_height, in_width, in_channels](2)
filter: [filter_height, filter_width, in_channels, out_channels](3)
对比(1)(2)可以发现,就差一个in_channels了,而最simple的版本也就只有1通道(Yoon的第四个模型用到了multichannel)
因此需要expand dim来适应conv2d的input要求,万能的tensorflow已经提供了这样的功能:
This operation is useful if you want to add a batch dimension to a single element. For example, if you have a single image of shape [height, width, channels], you can make it a batch of 1 image with expand_dims(image, 0), which will make the shape [1, height, width, channels].
# ‘t’ is a tensor of shape [2]
shape(expand_dims(t, -1)) ==& [2, 1]
因此只需要
tf.expand_dims(self.embedded_chars, -1)
就能在embedded_chars后面加一个in_channels=1
5 Conv and Max-pooling1
# Create a convolution + maxpool layer for each filter size
pooled_outputs = []
for i, filter_size in enumerate(filter_sizes):
& & with tf.name_scope(&conv-maxpool-%s& % filter_size):
& && &&&# Convolution Layer
& && &&&filter_shape = [filter_size, embedding_size, 1, num_filters]
& && &&&W = tf.Variable(tf.truncated_normal(filter_shape, stddev=0.1), name=&W&)
& && &&&b = tf.Variable(tf.constant(0.1, shape=[num_filters]), name=&b&)
& && &&&conv = tf.nn.conv2d(
& && && && &self.embedded_chars_expanded,
& && && && &W,
& && && && &strides=[1, 1, 1, 1],
& && && && &padding=&VALID&,
& && && && &name=&conv&)
& && &&&# Apply nonlinearity
& && &&&h = tf.nn.relu(tf.nn.bias_add(conv, b), name=&relu&)
& && &&&# Maxpooling over the outputs
& && &&&pooled = tf.nn.max_pool(
& && && && &h,
& && && && &ksize=[1, sequence_length - filter_size + 1, 1, 1],
& && && && &strides=[1, 1, 1, 1],
& && && && &padding='VALID',
& && && && &name=&pool&)
& && &&&pooled_outputs.append(pooled)
# Combine all the pooled features
num_filters_total = num_filters * len(filter_sizes)
self.h_pool = tf.concat(3, pooled_outputs)
self.h_pool_flat = tf.reshape(self.h_pool, [-1, num_filters_total])
首先,对filter_sizes中的每一个filter_window_size都要进行卷积(每一种size都要产生num_filters那么多个filter maps),所以外层就是一个大的for循环。
继续,看到了一个比较陌生的函数tf.name_scope('xxx')
这个函数的作用参见
由于在for循环内部,filter_size是固定了的,因此可以结合(3):[filter_height, filter_width, in_channels, out_channels]得到,filter_shape = [filter_size, embedding_size, 1, num_filters]
之所以要弄清楚filter shape是因为要对filter的权重矩阵w进行初始化:
W = tf.Variable(tf.truncated_normal(filter_shape, stddev=0.1), name=&W&)
这里为什么要用tf.truncated_normal()函数呢?
答:tensorflow中提供了两个normal函数:
tf.random_normal(shape, mean=0.0, stddev=1.0, dtype=tf.float32, seed=None, name=None)tf.truncated_normal(shape, mean=0.0, stddev=1.0, dtype=tf.float32, seed=None, name=None)
对比了一下,这两个函数的参数列表完全相同,不同之处我就直接引用文档中的说明,讲解的很清楚,
Outputs random values from a truncated normal distribution.
The generated values follow a normal distribution with specified mean and standard deviation, except that values whose magnitude is more than 2 standard deviations from the mean are dropped and re-picked.
也就是说random出来的值的范围都在[mean - 2
standard_deviations, mean + 2
standard_deviations]内。
下图可以告诉你这个范围在哪,
conv2d得到的其实是下图中的&span tabindex=&0& class=&MathJax& id=&MathJax-Element-2-Frame& role=&presentation& style=&display: inline- position:& data-mathml='w⋅x'&w⋅x w⋅x的部分,
还要加上bias项tf.nn.bias_add(conv, b),并且通过relu:tf.nn.relu才最终得到卷积层的输出&span tabindex=&0& class=&MathJax& id=&MathJax-Element-3-Frame& role=&presentation& style=&display: inline- position:& data-mathml='h'&h h。
那究竟卷积层的输出的shape是什么样呢?
官方文档中有一段话解释了卷积后得到的输出结果:
第三部进行了right-multiply之后得到的结果就是[batch, out_height, out_width, output_channels],但是还是不清楚这里的out_height和out_width到底是什么。
那就看看wildml中怎么说的吧“VALID” padding means that we slide the filter over our sentence without padding the edges, performing a narrow convolution that gives us an output of shape [1, sequence_length - filter_size + 1, 1, 1].
哦,这句话的意思是说out_height和out_width其实和padding的方式有关系,这里选择了”VALID”的方式,也就是不在边缘加padding,得到的out_height=sequence_length - filter_size + 1,out_width=1
因此,综合上面的两个解释,我们知道conv2d-加bias-relu之后得到的&span tabindex=&0& class=&MathJax& id=&MathJax-Element-4-Frame& role=&presentation& style=&display: inline- position:& data-mathml='h'&h h的shape=[batch, sequence_length - filter_size + 1, 1, num_filters]接下来的工作就是max-pooling了,来看一下tensorflow中给出的函数:
tf.nn.max_pool(value, ksize, strides, padding, data_format='NHWC', name=None)
其中最重要的两个参数是value和ksize。
value相当于是max pooling层的输入,在整个网络中就是刚才我们得到的&span tabindex=&0& class=&MathJax& id=&MathJax-Element-5-Frame& role=&presentation& style=&display: inline- position:& data-mathml='h'&h h,check了一下它俩的shape是一致的,说明可以直接传递到下一层。
另一个参数是ksize,官方解释说是input tensor每一维度上的window size。仔细想一下,其实就是想定义多大的范围来进行max-pooling,比如在图像中常见的2*2的小正方形区域对整个h得到feature map进行pooling,但是在nlp中,刚才说到了每一个feature map现在是[batch, sequence_length - filter_size + 1, 1, num_filters]维度的,我们想知道每个output_channels(每个channel是一个vector)的较大值,也就是最重要的feature是哪一个,那么就是在第二个维度上设定window=sequence_length - filter_size + 1【这里感觉没解释通,待后续探索】
根据ksize的设置,和value的shape,可以得到pooled的shape=[batch, 1, 1, num_filters],
这是一个filter_size的结果(比如filter_size = 3),pooled存储的是当前filter_size下每个sentence最重要的num_filters个features,结果append到pooled_outputs列表中存起来,再对下一个filter_size进行相同的操作。等到for循环结束时,也就是所有的filter_size全部进行了卷积和max-pooling之后,首先需要把相同filter_size的所有pooled结果concat起来,再将不同的filter_size之间的结果concat起来,最后的到的应该类似于二维数组,[batch, all_pooled_result]
all_pooled_result一共有num_filters\(100)*len(filter_sizes)(3)个,比如300个
连接的过程需要使用,官方给出的例子很容易理解。
最后得到的h_pool_flat也就是[batch, 300]维的tensor。
6 Dropout1
# Add dropout
with tf.name_scope(&dropout&):
& & self.h_drop = tf.nn.dropout(self.h_pool_flat, self.dropout_keep_prob)
前面在“dropout注意事项”中讲到了,dropout仅对hiddenlayer的输出层进行drop,使得有些结点的值不输出给softmax层。
# Final (unnormalized) scores and predictions
with tf.name_scope(&output&):
& & W = tf.get_variable(
& && &&&&W&,
& && &&&shape=[num_filters_total, num_classes],
& && &&&initializer=tf.contrib.layers.xavier_initializer())
& & b = tf.Variable(tf.constant(0.1, shape=[num_classes]), name=&b&)
& & l2_loss += tf.nn.l2_loss(W)
& & l2_loss += tf.nn.l2_loss(b)
& & self.scores = tf.nn.xw_plus_b(self.h_drop, W, b, name=&scores&)
& & self.predictions = tf.argmax(self.scores, 1, name=&predictions&)
输出层其实是个softmax分类器,没什么可讲的,但是要注意l2正则(虽然有paper说l2加不加并没有什么区别)
但是我还有一个疑问是为什么对b也要进行正则约束?
另外,tf.nn.xw_plus_b()在open api中并没有提供,参考github上的某个
因此可以改为tf.matmul(self.h_drop, W) + b但是不好的地方是无法设置name了。。(用xw_plus_b也不会报错不改也可以)
还有一个奇怪的地方是,这一层按道理说应该是一个softmax layer,但是并没有使用到softmax函数,在Yoon的文章中也是直接得到输出的,
因此,我们也按照这种方式写代码,得到所有类别的score,并且选出较大值的那个类别(argmax)
y的shape为[batch, num_classes],因此argmax的时候是选取每行的max,dimention=1
因此,最后scores的shape为[batch, 1]
8 Loss function得到了整个网络的输出之后,也就是我们得到了y_prediction,但还需要和真实的y label进行比较,以此来确定预测好坏。
# CalculateMean cross-entropy loss
with tf.name_scope(&loss&):
& & losses = tf.nn.softmax_cross_entropy_with_logits(self.scores, self.input_y)
& & self.loss = tf.reduce_mean(losses) + l2_reg_lambda * l2_loss
还是使用常规的cross_entropy作为loss function。最后一层是全连接层,为了防止过拟合,最后还要在loss func中加入l2正则项,即l2_loss。l2_reg_lambda来确定惩罚的力度。
9 Accuracy1
# Accuracy
with tf.name_scope(&accuracy&):
& & correct_predictions = tf.equal(self.predictions, tf.argmax(self.input_y, 1))
& & self.accuracy = tf.reduce_mean(tf.cast(correct_predictions, &float&), name=&accuracy&)
tf.equal(x, y)返回的是一个bool tensor,如果xy对应位置的值相等就是true,否则false。得到的tensor是[batch, 1]的。
tf.cast(x, dtype)将bool tensor转化成float类型的tensor,方便计算
tf.reduce_mean()本身输入的就是一个float类型的vector(元素要么是0.0,要么是1.0),直接对这样的vector计算mean得到的就是accuracy,不需要指定reduction_indices
0x02: Conclusion后续可能还会对其他部分进行解读,敬请期待。
中级会员, 积分 477, 距离下一级还需 23 积分
论坛徽章:7
先学习一下,好东东,谢谢
金牌会员, 积分 1849, 距离下一级还需 1151 积分
论坛徽章:17
不用谢,一起完成作业。
金牌会员, 积分 1269, 距离下一级还需 1731 积分
论坛徽章:13
多谢分享。有空研究下。连参考文献都写这么仔细。赞。
高级会员, 积分 644, 距离下一级还需 356 积分
论坛徽章:9
有人研究google开源的机器学习框架了,赞一个
新手上路, 积分 1, 距离下一级还需 49 积分
论坛徽章:0
楼主很6啊,厉害厉害,感谢感谢.
中级会员, 积分 313, 距离下一级还需 187 积分
论坛徽章:9
好好学习天天向上。。。。
中级会员, 积分 415, 距离下一级还需 85 积分
论坛徽章:7
谢谢分享!!!!!!!!!!!!!!
中级会员, 积分 415, 距离下一级还需 85 积分
论坛徽章:7
先收藏,谢谢分享& && && && && && && &
中级会员, 积分 236, 距离下一级还需 264 积分
论坛徽章:11
学习一下& && && && && && && && &CNN代码理解
我的图书馆
CNN代码理解
下面是自己对代码的注释:
cnnexamples.m
addpath('../data');
addpath('../util');
load mnist_uint8;
train_x = double(reshape(train_x',28,28,60000))/255;
test_x = double(reshape(test_x',28,28,10000))/255;
train_y = double(train_y');
test_y = double(test_y');
%will run 1 epoch in about 200 second and get around 11% error.
%With 100 epochs you'll get around 1.2% error
cnn.layers = {
&&& struct('type', 'i') %input layer
&&& struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer
&&& struct('type', 's', 'scale', 2) %sub
sampling layer
&&& struct('type', 'c', 'outputmaps', 12,
'kernelsize', 5) %convolution layer
&&& struct('type', 's', 'scale', 2)
%subsampling layer
% 这里把cnn的设置给cnnsetup,它会据此构建一个完整的CNN网络,并返回
cnn = cnnsetup(cnn, train_x, train_y);
opts.alpha = 1;
% 每次挑出一个batchsize的batch来训练,也就是每用batchsize个样本就调整一次权值,而不是
% 把所有样本都输入了,计算所有样本的误差了才调整一次权值
opts.batchsize = 50;
% 训练次数,用同样的样本集。我训练的时候:
% 1的时候 11.41% error
% 5的时候 4.2% error
% 10的时候 2.73% error
opts.numepochs = 10;
% 然后开始把训练样本给它,开始训练这个CNN网络
cnn = cnntrain(cnn, train_x, train_y, opts);
% 然后就用测试样本来测试
[er, bad] = cnntest(cnn, test_x, test_y);
%plot mean squared error
plot(cnn.rL);
%show test error
disp([num2str(er*100) '% error']);
cnnsetup.m
function net = cnnsetup(net, x, y)
&&& inputmaps = 1;
& % B=squeeze(A) 返回和矩阵A相同元素但所有单一维都移除的矩阵B,单一维是满足size(A,dim)=1的维。
& % train_x中图像的存放方式是三维的reshape(train_x',28,28,60000),前面两维表示图像的行与列,
& % 第三维就表示有多少个图像。这样squeeze(x(:, :, 1))就相当于取第一个图像样本后,再把第三维
& % 移除,就变成了28x28的矩阵,也就是得到一幅图像,再size一下就得到了训练样本图像的行数与列数了
&&& mapsize =
size(squeeze(x(:, :, 1)));
& % 下面通过传入net这个结构体来逐层构建CNN网络
& % n = numel(A)返回数组A中元素个数
& % net.layers中有五个struct类型的元素,实际上就表示CNN共有五层,这里范围的是5
&&& for l = 1 :
numel(net.layers)&& %& layer
&&&&&&& if
strcmp(net.layers{l}.type, 's') % 如果这层是 子采样层
&&&&&&&&&&& %
subsampling层的mapsize,最开始mapsize是每张图的大小28*28
&&&&& % 这里除以scale=2,就是pooling之后图的大小,pooling域之间没有重叠,所以pooling后的图像为14*14
&&&&& % 注意这里的右边的mapsize保存的都是上一层每张特征map的大小,它会随着循环进行不断更新
&&&&& mapsize =
floor(mapsize / net.layers{l}.scale);
&&&&&&&&&&& for j =
1 : inputmaps % inputmap就是上一层有多少张特征图
&&&&&&&&&&&&&&&
net.layers{l}.b{j} = 0; % 将偏置初始化为0
&&&&&&&&&&& end
&&&&&&& end
&&&&&&& if
strcmp(net.layers{l}.type, 'c') % 如果这层是 卷积层
&&&&&&&&&& &% 旧的mapsize保存的是上一层的特征map的大小,那么如果卷积核的移动步长是1,那用
kernelsize*kernelsize大小的卷积核卷积上一层的特征map后,得到的新的map的大小就是下面这样
&&&&& mapsize =
mapsize - net.layers{l}.kernelsize + 1;
&&&&& % 该层需要学习的参数个数。每张特征map是一个(后层特征图数量)*(用来卷积的patch图的大小)
&&&&& % 因为是通过用一个核窗口在上一个特征map层中移动(核窗口每次移动1个像素),遍历上一个特征map
&&&&& % 层的每个神经元。核窗口由kernelsize*kernelsize个元素组成,每个元素是一个独立的权值,所以
&&&&& % 就有kernelsize*kernelsize个需要学习的权值,再加一个偏置值。另外,由于是权值共享,也就是
&&&&& % 说同一个特征map层是用同一个具有相同权值元素的kernelsize*kernelsize的核窗口去感受输入上一
&&&&& % 个特征map层的每个神经元得到的,所以同一个特征map,它的权值是一样的,共享的,权值只取决于
&&&&& % 核窗口。然后,不同的特征map提取输入上一个特征map层不同的特征,所以采用的核窗口不一样,也
&&&&& % 就是权值不一样,所以outputmaps个特征map就有(kernelsize*kernelsize+1)* outputmaps那么多的权值了
&&&&& % 但这里fan_out只保存卷积核的权值W,偏置b在下面独立保存
&&&&&&&&&&& fan_out
= net.layers{l}.outputmaps * net.layers{l}.kernelsize ^ 2;
&&&&&&&&&&& for j =
1 : net.layers{l}.outputmaps& %& output map
&&&&&&&&&&&&&&& %
fan_out保存的是对于上一层的一张特征map,我在这一层需要对这一张特征map提取outputmaps种特征,
&&&&&&& % 提取每种特征用到的卷积核不同,所以fan_out保存的是这一层输出新的特征需要学习的参数个数
&&&&&&& % 而,fan_in保存的是,我在这一层,要连接到上一层中所有的特征map,然后用fan_out保存的提取特征
&&&&&&& % 的权值来提取他们的特征。也即是对于每一个当前层特征图,有多少个参数链到前层
&&&&&&& fan_in =
inputmaps * net.layers{l}.kernelsize ^ 2;
&&&&&&&&&&&&&&& for
i = 1 : inputmaps& %& input map
&&&&&&&&& % 随机初始化权值,也就是共有outputmaps个卷积核,对上层的每个特征map,都需要用这么多个卷积核
&&&&&&&&& % 去卷积提取特征。
&&&&&&&&& % rand(n)是产生n×n的 0-1之间均匀取值的数值的矩阵,再减去0.5就相当于产生-0.5到0.5之间的随机数
&&&&&&&&& % 再 *2 就放大到 [-1, 1]。然后再乘以后面那一数,why?
&&&&&&&&& % 反正就是将卷积核每个元素初始化为[-sqrt(6 / (fan_in + fan_out)), sqrt(6 / (fan_in +
fan_out))]
&&&&&&&&& % 之间的随机数。因为这里是权值共享的,也就是对于一张特征map,所有感受野位置的卷积核都是一样的
&&&&&&&&& % 所以只需要保存的是 inputmaps * outputmaps 个卷积核。
&&&&&&&&&&&&&&&&&&&
net.layers{l}.k{i}{j} = (rand(net.layers{l}.kernelsize) - 0.5) * 2 *
sqrt(6 / (fan_in + fan_out));
&&&&&&&&&&&&&&& end
&&&&&&&&&&&&&&&
net.layers{l}.b{j} = 0; % 将偏置初始化为0
&&&&&&&&&&& end
&&&&& % 只有在卷积层的时候才会改变特征map的个数,pooling的时候不会改变个数。这层输出的特征map个数就是
&&&&& % 输入到下一层的特征map个数
&&&&&&&&&&&
inputmaps = net.layers{l}.
&&&&&&& end
& % fvnum 是输出层的前面一层的神经元个数。
& % 这一层的上一层是经过pooling后的层,包含有inputmaps个特征map。每个特征map的大小是mapsize。
& % 所以,该层的神经元个数是 inputmaps * (每个特征map的大小)
& % prod: Product
of elements.
& % For vectors,
prod(X) is the product of the elements of X
& % 在这里 mapsize = [特征map的行数 特征map的列数],所以prod后就是 特征map的行*列
&&& fvnum =
prod(mapsize) *
& % onum 是标签的个数,也就是输出层神经元的个数。你要分多少个类,自然就有多少个输出神经元
&&& onum = size(y,
& % 这里是最后一层神经网络的设定
& % ffb 是输出层每个神经元对应的基biases
&&& net.ffb =
zeros(onum, 1);
& % ffW 输出层前一层 与 输出层 连接的权值,这两层之间是全连接的
&&& net.ffW =
(rand(onum, fvnum) - 0.5) * 2 * sqrt(6 / (onum + fvnum));
cnntrain.m
function net = cnntrain(net, x, y, opts)
&&& m = size(x, 3); % m 保存的是 训练样本个数
&&& numbatches = m
& % rem: Remainder after division. rem(x,y) is x -
n.*y 相当于求余
& % rem(numbatches,
1) 就相当于取其小数部分,如果为0,就是整数
rem(numbatches, 1) ~= 0
&&&&&&& error('numbatches not integer');
&&& net.rL = [];
&&& for i = 1 : opts.numepochs
&&& % disp(X) 打印数组元素。如果X是个字符串,那就打印这个字符串
&&&&&&& disp(['epoch ' num2str(i) '/' num2str(opts.numepochs)]);
&&&&&&& % tic 和 toc 是用来计时的,计算这两条语句之间所耗的时间
randperm(N) 返回[1, N]之间所有整数的一个随机的序列,例如
&&& % randperm(6) 可能会返回 [2 4 5 6 1 3]
&&& % 这样就相当于把原来的样本排列打乱,再挑出一些样本来训练
&&&&&&& kk =
randperm(m);
&&&&&&& for
l = 1 : numbatches
&&&&& % 取出打乱顺序后的batchsize个样本和对应的标签
&&&&&&&&&&& batch_x
= x(:, :, kk((l - 1) *
opts.batchsize + 1 : l * opts.batchsize));
&&&&&&&&&&& batch_y
kk((l - 1) * opts.batchsize + 1 : l * opts.batchsize));
&&&&& % 在当前的网络权值和网络输入下计算网络的输出
&&&&&&&&&&& net =
cnnff(net, batch_x); % Feedforward
&&&&& % 得到上面的网络输出后,通过对应的样本标签用bp算法来得到误差对网络权值
&&&&& %(也就是那些卷积核的元素)的导数
&&&&&&&&&&& net =
cnnbp(net, batch_y); % Backpropagation
&&&&& % 得到误差对权值的导数后,就通过权值更新方法去更新权值
&&&&&&&&&&& net =
cnnapplygrads(net, opts);
&&&&&&&&&&& if
isempty(net.rL)
&&&&&&&&&&&&&&&
net.rL(1) = net.L; % 代价函数值,也就是误差值
&&&&&&&&&&& end
&&&&&&&&&&& net.rL(end
+ 1) = 0.99 * net.rL(end)
+ 0.01 * net.L; % 保存历史的误差值,以便画图分析
&&&&&&& end
function net = cnnff(net, x)
numel(net.layers); % 层数
&&& net.layers{1}.a{1} = x; % 网络的第一层就是输入,但这里的输入包含了多个训练图像
&&& inputmaps = 1; % 输入层只有一个特征map,也就是原始的输入图像
&&& for l = 2 : n&&
%& for each layer
&&&&&&& if
strcmp(net.layers{l}.type, 'c') % 卷积层
&&&&&&&&&&& %& !!below can probably be handled by insane
matrix operations
&&&&& % 对每一个输入map,或者说我们需要用outputmaps个不同的卷积核去卷积图像
&&&&&&&&&&& for
j = 1 : net.layers{l}.outputmaps&& %& for
each output map
&&&&&&&&&&&&&&&
%& create temp output map
&&&&&&& % 对上一层的每一张特征map,卷积后的特征map的大小就是
&&&&&&& % (输入map宽 - 卷积核的宽 + 1)* (输入map高 - 卷积核高 + 1)
&&&&&&& % 对于这里的层,因为每层都包含多张特征map,对应的索引保存在每层map的第三维
&&&&&&& % 所以,这里的z保存的就是该层中所有的特征map了
&&&&&&&&&&&&&&& z =
zeros(size(net.layers{l - 1}.a{1}) - [net.layers{l}.kernelsize - 1 net.layers{l}.kernelsize - 1 0]);
&&&&&&&&&&&&&&& for
i = 1 : inputmaps&& %& for
each input map
&&&&&&&&&&&&&&&&&&&
%& convolve with corresponding
kernel and add to temp output map
&&&&&&&&& % 将上一层的每一个特征map(也就是这层的输入map)与该层的卷积核进行卷积
&&&&&&&&& % 然后将对上一层特征map的所有结果加起来。也就是说,当前层的一张特征map,是
&&&&&&&&& % 用一种卷积核去卷积上一层中所有的特征map,然后所有特征map对应位置的卷积值的和
&& &&&&&&&% 另外,有些论文或者实际应用中,并不是与全部的特征map链接的,有可能只与其中的某几个连接
&&&&&&&&&&&&&&&&&&&
z = z + convn(net.layers{l - 1}.a{i}, net.layers{l}.k{i}{j}, 'valid');
&&&&&&&&&&&&&&& end
&&&&&&&&&&&&&&&
%& add bias, pass through
nonlinearity
&&&&&&& % 加上对应位置的基b,然后再用sigmoid函数算出特征map中每个位置的激活值,作为该层输出特征map
&&&&&&&&&&&&&&&
net.layers{l}.a{j} = sigm(z + net.layers{l}.b{j});
&&&&&&&&&&& end
&&&&&&&&&&& %& set number of input maps to this layers
number of outputmaps
&&&&&&&&&&&
inputmaps = net.layers{l}.
&&&&&&& elseif strcmp(net.layers{l}.type, 's') % 下采样层
&&&&&&&&&&& %& downsample
&&&&&&&&&&& for
j = 1 : inputmaps
&&&&&&&&&&&&&&&
%& !! replace with variable
&&&&&&& % 例如我们要在scale=2的域上面执行mean pooling,那么可以卷积大小为2*2,每个元素都是1/4的卷积核
&&&&&&& z =
convn(net.layers{l - 1}.a{j}, ones(net.layers{l}.scale) / (net.layers{l}.scale ^ 2), 'valid');
&&&&&&& % 因为convn函数的默认卷积步长为1,而pooling操作的域是没有重叠的,所以对于上面的卷积结果
&&&&&&& % 最终pooling的结果需要从上面得到的卷积结果中以scale=2为步长,跳着把mean pooling的值读出来
&&&&&&&&&&&&&&&
net.layers{l}.a{j} = z(1 : net.layers{l}.scale : end, 1 : net.layers{l}.scale : end, :);
&&&&&&&&&&& end
&&&&&&& end
&&& %& concatenate all end layer feature maps into
& % 把最后一层得到的特征map拉成一条向量,作为最终提取到的特征向量
&&& net.fv = [];
&&& for j = 1 : numel(net.layers{n}.a) % 最后一层的特征map的个数
&& &&&&&sa = size(net.layers{n}.a{j}); % 第j个特征map的大小
&&& % 将所有的特征map拉成一条列向量。还有一维就是对应的样本索引。每个样本一列,每列为对应的特征向量
&&&&&&& net.fv =
[net. reshape(net.layers{n}.a{j}, sa(1) * sa(2), sa(3))];
&&& %& feedforward into output perceptrons
& % 计算网络的最终输出值。sigmoid(W*X + b),注意是同时计算了batchsize个样本的输出值
&&& net.o =
sigm(net.ffW * net.fv + repmat(net.ffb, 1, size(net.fv, 2)));
function net = cnnbp(net, y)
numel(net.layers); % 网络层数
&&& %& error
&&& net.e = net.o -
&&& %& loss function
& % 代价函数是 均方误差
&&& net.L = 1/2* sum(net.e(:) .^ 2) / size(net.e, 2);
backprop deltas
& % 这里可以参考 UFLDL 的 反向传导算法 的说明
& % 输出层的 灵敏度 或者 残差
&&& net.od = net.e
.* (net.o .* (1 - net.o));&& %&
output delta
& % 残差 反向传播回 前一层
&&& net.fvd =
(net.ffW' *
net.od);&&&&&&& &&&&&&%&
feature vector delta
strcmp(net.layers{n}.type, 'c')&&&&&&&& %&
only conv layers has sigm function
&&&&&&& net.fvd =
net.fvd .* (net.fv .* (1 - net.fv));
&&& %& reshape feature vector deltas into output map
size(net.layers{n}.a{1}); % 最后一层特征map的大小。这里的最后一层都是指输出层的前一层
&&& fvnum = sa(1) *
sa(2); % 因为是将最后一层特征map拉成一条向量,所以对于一个样本来说,特征维数是这样
&&& for j = 1 :
numel(net.layers{n}.a) % 最后一层的特征map的个数
&&& % 在fvd里面保存的是所有样本的特征向量(在cnnff.m函数中用特征map拉成的),所以这里需要重新
&&& % 变换回来特征map的形式。d 保存的是 delta,也就是 灵敏度 或者 残差
net.layers{n}.d{j} = reshape(net.fvd(((j - 1) * fvnum + 1) : j * fvnum,
:), sa(1), sa(2), sa(3));
& % 对于 输出层前面的层(与输出层计算残差的方式不同)
&&& for l = (n - 1)
&&&&&&& if
strcmp(net.layers{l}.type, 'c')
&&&&&&&&&&& for j = 1 : numel(net.layers{l}.a) %
该层特征map的个数
&&&&&&&&&&&&&&& %
net.layers{l}.d{j} 保存的是 第l层 的 第j个 map 的 灵敏度map。 也就是每个神经元节点的delta的值
&&&&&&& % expand的操作相当于对l+1层的灵敏度map进行上采样。然后前面的操作相当于对该层的输入a进行sigmoid求导
&&&&&&& % 这条公式请参考 Notes on Convolutional Neural Networks
&&&&&&& % for k =
1:size(net.layers{l + 1}.d{j}, 3)
&&&&&&&&& %
net.layers{l}.d{j}(:,:,k) = net.layers{l}.a{j}(:,:,k) .* (1 -
net.layers{l}.a{j}(:,:,k)) .*&
kron(net.layers{l + 1}.d{j}(:,:,k), ones(net.layers{l + 1}.scale)) /
net.layers{l + 1}.scale ^ 2;
&&&&&&& % end
net.layers{l}.d{j} = net.layers{l}.a{j} .* (1 - net.layers{l}.a{j}) .*
(expand(net.layers{l + 1}.d{j}, [net.layers{l + 1}.scale net.layers{l +
1}.scale 1]) / net.layers{l + 1}.scale ^ 2);
&&&&&&&&&&& end
&&&&&&& elseif strcmp(net.layers{l}.type,
&&&&&&&&&&& for i =
1 : numel(net.layers{l}.a) % 第l层特征map的个数
&&&&&&&&&&&&&&& z =
zeros(size(net.layers{l}.a{1}));
&&&&&&&&&&&&&&& for
j = 1 : numel(net.layers{l + 1}.a) % 第l+1层特征map的个数
&&&&&&&&&&&&&&&&&&&&
z = z + convn(net.layers{l + 1}.d{j}, rot180(net.layers{l + 1}.k{i}{j}),
&&&&&&&&&&&&&&& end
&&&&&&&&&&&&&&&
net.layers{l}.d{i} =
&&&&&&&&&&& end
&&&&&&& end
&&& %%& calc gradients
& % 这里与 Notes on Convolutional Neural Networks 中不同,这里的 子采样 层没有参数,也没有
& % 激活函数,所以在子采样层是没有需要求解的参数的
&&& for l = 2 : n
&&&&&&& if
strcmp(net.layers{l}.type, 'c')
&&&&&&&&&&& for j =
1 : numel(net.layers{l}.a)
&&&&&&&&&&&&&&& for
i = 1 : numel(net.layers{l - 1}.a)
&&&&&&&&& % dk 保存的是 误差对卷积核 的导数
&&&&&&&&&&&&&&&&&&&
net.layers{l}.dk{i}{j} = convn(flipall(net.layers{l - 1}.a{i}),
net.layers{l}.d{j}, 'valid') /
size(net.layers{l}.d{j}, 3);
&&&&&&&&&&&&&&& end
&&&&&&& % db 保存的是 误差对于bias基 的导数
&&&&&&&&&&&&&&&
net.layers{l}.db{j} = sum(net.layers{l}.d{j}(:)) /
size(net.layers{l}.d{j}, 3);
&&&&&&&&&&& end
&&&&&&& end
& % 最后一层perceptron的gradient的计算
&&& net.dffW =
net.od * (net.fv)' /
size(net.od, 2);
&&& net.dffb =
mean(net.od, 2);
&&& function X =
&&&&&&& X =
flipdim(flipdim(X, 1), 2);
cnnapplygrads.m
function net = cnnapplygrads(net, opts)
&&& for l = 2 : numel(net.layers)
&&&&&&& if
strcmp(net.layers{l}.type, 'c')
&&&&&&&&&&& for
j = 1 : numel(net.layers{l}.a)
&&&&&&&&&&&&&&& for
ii = 1 : numel(net.layers{l - 1}.a)
&&&&&&&&& % 这里没什么好说的,就是普通的权值更新的公式:W_new = W_old - alpha * de/dW(误差对权值导数)
&&&&&&&&&&&&&&&&&&&
net.layers{l}.k{ii}{j} = net.layers{l}.k{ii}{j} - opts.alpha *
net.layers{l}.dk{ii}{j};
&&&&&&&&&&&&&&& end
&&&&&&&&&&& end
&&&&&&&&&&&
net.layers{l}.b{j} = net.layers{l}.b{j} - opts.alpha *
net.layers{l}.db{j};
&&&&&&& end
&&& net.ffW =
net.ffW - opts.alpha * net.dffW;
&&& net.ffb =
net.ffb - opts.alpha * net.
function [er, bad] = cnntest(net, x, y)
&&& %& feedforward
cnnff(net, x); % 前向传播得到输出
& % [Y,I] = max(X)
returns the indices of the maximum values in vector I
&&& [~, h] =
max(net.o); % 找到最大的输出对应的标签
&&& [~, a] =
max(y); &% 找到最大的期望输出对应的索引
&&& bad = find(h ~=
a);& % 找到他们不相同的个数,也就是错误的次数
&&& er = numel(bad)
/ size(y, 2); % 计算错误率
喜欢该文的人也喜欢

我要回帖

更多关于 cnn batch size 的文章

 

随机推荐