├── README.md ├── README.txt ├── SRC ├── cnn_ts_ts.py ├── cnn_ts_tt.py ├── cnn_tst_tt.py ├── cnn_tt_tt.py ├── generateMNIST_SandMNIST_T.py ├── generate_data.py └── temp.py └── result ├── ts_ts.txt ├── ts_tt.txt ├── tst_tt1.txt └── tt_tt.txt /README.md: -------------------------------------------------------------------------------- 1 | # domain_adaptation 2 | this project is the code of domain adaptation referenced by unsupervised domain adaptation by backpropagation(http://machinelearning.wustl.edu/mlpapers/paper_files/icml2015_ganin15.pdf).And i realized it on mnist. 3 | 4 | domain adaptation实现 5 | ============================= 6 | 7 | 项目在cnn网络上实现了domain adaptation 8 | 项目大体上遵照论文:unsupervised domain adaptation by backpropagation : 9 | 下载地址:http://machinelearning.wustl.edu/mlpapers/paper_files/icml2015_ganin15.pdf 10 | 11 | ============================= 12 | 实现是基于cnn网络 13 | CNN代码解读博文:http://blog.csdn.net/u012162613/article/details/43225445 14 | CNN代码来源:https://github.com/wepe/MachineLearning/blob/master/DeepLearning%20Tutorials/cnn_LeNet/convolutional_mlp_commentate.py 15 | 16 | ============================= 17 | 项目文件下只有四个文件夹:SRC,data,result,reference 18 | 19 | ============================= 20 | SRC存储了项目所有的源代码: 21 | 运行顺序:generate_data.py -> generateMNIST_SandMNIST_T.py -> cnn_~~~.py 22 | 23 | generate_data.py:根据mnist.pkl.gz生成目标域数据集:target~.pkl和st~.pkl(存在data/下) 24 | 可以在generate_data.py中修改theta(源域和目标域图片做差时乘的比例,防止两个域的差距过大) 25 | 26 | generateMNIST_SandMNIST_T.py:根据source.pkl、target~.pkl和st~.pkl生成部分图片,用来做展示 27 | 只生成了一部分,train,valid,test各生成10个图片。 28 | 29 | cnn_ts_ts.py:在源域上训练,在源域上测试 30 | 31 | cnn_ts_tt.py:在源域上训练,在目标域上测试(验证集为目标域数据) 32 | 33 | cnn_tt_tt.py:在目标域上训练,在目标域上测试 34 | 35 | cnn_tst_ts.py:在源域和目标域上训练,在目标域上测试(也就是使用领域自适应机制)(验证集合为目标域数据) 36 | 可以在其中调节lmbda(论文中公式4的lambda) 37 | 38 | 上述4个文件将结果输出在txt文件中,存在result/下 39 | 40 | ============================= 41 | data下存了项目需要存的数据: 42 | 在运行程序前要具有以下文件:BSR文件夹,imageMNIST_T文件夹,imageMNIST_S文件夹,mnist.pkl.gz, source.pkl 43 | 44 | 其中BSR文件夹: 45 | 下载来源:http://www.eecs.berkeley.edu/Research/Projects/CS/vision/grouping/BSR/BSR_bsds500.tgz 46 | 数据集介绍:https://www2.eecs.berkeley.edu/Research/Projects/CS/vision/grouping/resources.html#bsds500 47 | 下载完成之后将BSR_bsds500.tgz解压,包含一个BSR文件夹,把这个BSR文件夹放到data下就行 48 | 49 | imageMNIST_S和imageMNIST_T:两个文件夹存储generateMNIST_SandMNIST_T.py生成的图片, 50 | imageMNIST_S存储源域的图片 51 | imageMNIST_T存储目标域图片 52 | 运行前为空即可 53 | imageMNIST_S和imageMNIST_T中名字相同的图片相对应 54 | 55 | mnist.pkl.gz:是mnist数据集,运行前要下载好 56 | 数据来源:http://www.iro.umontreal.ca/~lisa/deep/data/mnist/mnist.pkl.gz 57 | 58 | source.pkl:是mnist.pkl.gz的解压文件,运行前要解压好 59 | 60 | 61 | 运行后会生成st~.pkl和target~.pkl存在data下: 62 | 其中~部分表示theta的取值 63 | 64 | ============================= 65 | result下存了运行结果 66 | 运行结果文件命名方式和代码文件命名方式相同 67 | 68 | ============================= 69 | reference下存了项目论文 70 | unsupervised domain adaptation by backpropagation 71 | 72 | ============================= 73 | 项目调节的主要参数: 74 | theta:源域和噪声域图片同像素点灰度值做差时,噪声域乘的比例。 75 | 论文中4.1Results--MNIST->MNIST-M中的图片生成公式,我在I1和I2做差时对I2乘了一个theta,控制源域和目标域的差距。 76 | theta在0到1之间,越小差距越小,越大差距越大 77 | 78 | lmbda:论文中公式4的lambda。控制在反向传播过程中,域分类器的损失对特征提取器的参数的修改程度。 79 | lmbda在0到1之间,lmbda越小,域分类器的损失对特征提取器的参数的修改程度越小。 80 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shucunt/domain_adaptation/f4f283bdf731b2c507532dd7f7bde5f920551434/README.txt -------------------------------------------------------------------------------- /SRC/cnn_ts_ts.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | @author:wepon 4 | @blog:http://blog.csdn.net/u012162613/article/details/43225445 5 | 6 | 本程序基于LeNet5,但是有所简化,具体如下: 7 | -没有实现location-specific gain and bias parameters 8 | -用的是maxpooling,而不是average_pooling 9 | -分类器用的是softmax,LeNet5用的是rbf 10 | -LeNet5第二层并不是全连接的,本程序实现的是全连接 11 | 具体参考: 12 | - Y. LeCun, L. Bottou, Y. Bengio and P. Haffner: 13 | Gradient-Based Learning Applied to Document 14 | Recognition, Proceedings of the IEEE, 86(11):2278-2324, November 1998. 15 | http://yann.lecun.com/exdb/publis/pdf/lecun-98.pdf 16 | 17 | """ 18 | import cPickle 19 | import gzip 20 | import os 21 | import sys 22 | import time 23 | 24 | import numpy 25 | 26 | import theano 27 | import theano.tensor as T 28 | from theano.tensor.signal import downsample 29 | from theano.tensor.nnet import conv 30 | 31 | 32 | resultSource = os.path.join(os.path.abspath('..'), 'result/ts_ts.txt') 33 | file_w = open(resultSource, 'a') 34 | try: 35 | file_w.write("modify the time result") 36 | finally: 37 | file_w.close() 38 | 39 | """ 40 | 卷积+下采样合成一个层LeNetConvPoolLayer 41 | W是系数矩阵,也就是卷积核============================?和filter同形状 42 | rng:随机数生成器,用于初始化W 43 | input:4维的向量,theano.tensor.dtensor4 44 | filter_shape:(number of filters, num input feature maps,filter height, filter width)滤波器,例如5*5的滤波器 45 | num imput feature maps:表示输入的特征的个数 46 | number of filters代表滤波器的个数,也就是输出的特征的个数 47 | image_shape:(batch size, num input feature maps,image height, image width),图像的大小 48 | batch size代表是第几个batch 49 | poolsize: (#rows, #cols),下采样时对pool中的数求和 50 | num input feature maps:是上一层到卷基层过程中,代表可训练参数的个数,也就是滤波器个数*(滤波矩阵中点的个数+b的个数) 51 | 例如有6个滤波器,为5*5的矩阵,那么就是6*(5*5+1) 52 | num output feature maps:和num input feature maps一样,不过是从卷基层到下采样层的过程。 53 | """ 54 | class LeNetConvPoolLayer(object): 55 | def __init__(self, rng, input, filter_shape, image_shape, poolsize=(2, 2)): 56 | 57 | #assert condition,condition为True,则继续往下执行,condition为False,中断程序 58 | #image_shape[1]和filter_shape[1]都是num input feature maps,它们必须是一样的。 59 | assert image_shape[1] == filter_shape[1] 60 | self.input = input 61 | 62 | #每个隐层神经元(即像素)与上一层的连接数为num input feature maps * filter height * filter width。 63 | #可以用numpy.prod(filter_shape[1:])来求得,prod是连乘操作。 64 | #卷基层和上一层的连接个数 65 | fan_in = numpy.prod(filter_shape[1:]) 66 | 67 | #lower layer上每个神经元获得的梯度来自于:"num output feature maps * filter height * filter width" /pooling size 68 | #卷基层和下采样层的连接个数 69 | fan_out = (filter_shape[0] * numpy.prod(filter_shape[2:]) / 70 | numpy.prod(poolsize)) #=======================?应该乘以2,因为一组滤波器只有一个w和一个b 71 | 72 | #以上求得fan_in、fan_out ,将它们代入公式,以此来随机初始化W,W就是线性卷积核 73 | #初始化为一个极小值,似乎有公式,W的初始化有个规则:如果使用tanh函数,则在-sqrt(6./(n_in+n_hidden))到sqrt(6./(n_in+n_hidden))之间均匀 74 | #numpy.random.uniform(low,high,size)左闭右开,返回size形状的随机数组 75 | W_bound = numpy.sqrt(6. / (fan_in + fan_out)) 76 | self.W = theano.shared( 77 | numpy.asarray( 78 | rng.uniform(low=-W_bound, high=W_bound, size=filter_shape), 79 | dtype=theano.config.floatX 80 | ), 81 | borrow=True 82 | ) 83 | 84 | # the bias is a 1D tensor -- one bias per output feature map 85 | #偏置b是一维向量,每个输出图的特征图都对应一个偏置,初始化为0向量 86 | #而输出的特征图的个数由filter个数决定,因此用filter_shape[0]即number of filters来初始化 87 | b_values = numpy.zeros((filter_shape[0],), dtype=theano.config.floatX) 88 | self.b = theano.shared(value=b_values, borrow=True) 89 | 90 | #将输入图像与filter卷积,conv.conv2d函数 91 | #卷积完没有加b再通过sigmoid,这里是一处简化。 92 | conv_out = conv.conv2d( 93 | input=input, 94 | filters=self.W, 95 | filter_shape=filter_shape, 96 | image_shape=image_shape 97 | ) 98 | 99 | #maxpooling,最大子采样过程 100 | pooled_out = downsample.max_pool_2d( 101 | input=conv_out, 102 | ds=poolsize, 103 | ignore_border=True 104 | ) 105 | 106 | #加偏置,再通过tanh映射,得到卷积+子采样层的最终输出 107 | #因为b是一维向量,这里用维度转换函数dimshuffle将其reshape。比如b是(10,), 108 | #则b.dimshuffle('x', 0, 'x', 'x'))将其reshape为(1,10,1,1) 109 | self.output = T.tanh(pooled_out + self.b.dimshuffle('x', 0, 'x', 'x')) 110 | #卷积+采样层的参数 111 | self.params = [self.W, self.b] 112 | 113 | 114 | 115 | 116 | 117 | """ 118 | 注释: 119 | 这是定义隐藏层的类,首先明确:隐藏层的输入即input,输出即隐藏层的神经元个数。输入层与隐藏层是全连接的。 120 | 假设输入是n_in维的向量(也可以说时n_in个神经元),隐藏层有n_out个神经元,则因为是全连接, 121 | 一共有n_in*n_out个权重,故W大小时(n_in,n_out),n_in行n_out列,每一列对应隐藏层的每一个神经元的连接权重。 122 | b是偏置,隐藏层有n_out个神经元,故b时n_out维向量。 123 | rng即随机数生成器,numpy.random.RandomState,用于初始化W。 124 | input训练模型所用到的所有输入,并不是MLP的输入层,MLP的输入层的神经元个数时n_in, 125 | 而这里的参数input大小是(n_example,n_in),每一行一个样本,即每一行作为MLP的输入层。 126 | activation:激活函数,这里定义为函数tanh 127 | """ 128 | class HiddenLayer(object): 129 | def __init__(self, rng, input, n_in, n_out, W=None, b=None, activation=T.tanh): 130 | self.input = input #类HiddenLayer的input即所传递进来的input 131 | 132 | """ 133 | 注释: 134 | 代码要兼容GPU,则必须使用 dtype=theano.config.floatX,并且定义为theano.shared 135 | 另外,W的初始化有个规则:如果使用tanh函数,则在-sqrt(6./(n_in+n_hidden))到sqrt(6./(n_in+n_hidden))之间均匀 136 | 抽取数值来初始化W,若时sigmoid函数,则以上再乘4倍。 137 | """ 138 | #如果W未初始化,则根据上述方法初始化。 139 | #加入这个判断的原因是:有时候我们可以用训练好的参数来初始化W,见我的上一篇文章。 140 | if W is None: 141 | W_values = numpy.asarray( 142 | rng.uniform( 143 | low=-numpy.sqrt(6. / (n_in + n_out)), 144 | high=numpy.sqrt(6. / (n_in + n_out)), 145 | size=(n_in, n_out) 146 | ), 147 | dtype=theano.config.floatX 148 | ) 149 | if activation == theano.tensor.nnet.sigmoid: 150 | W_values *= 4 151 | W = theano.shared(value=W_values, name='W', borrow=True) 152 | 153 | if b is None: 154 | b_values = numpy.zeros((n_out,), dtype=theano.config.floatX) 155 | b = theano.shared(value=b_values, name='b', borrow=True) 156 | 157 | #用上面定义的W、b来初始化类HiddenLayer的W、b 158 | self.W = W 159 | self.b = b 160 | 161 | #隐含层的输出 162 | lin_output = T.dot(input, self.W) + self.b 163 | self.output = ( 164 | lin_output if activation is None 165 | else activation(lin_output) 166 | ) 167 | 168 | #隐含层的参数 169 | self.params = [self.W, self.b] 170 | 171 | 172 | 173 | """ 174 | 定义分类层LogisticRegression,也即Softmax回归 175 | 在deeplearning tutorial中,直接将LogisticRegression视为Softmax, 176 | 而我们所认识的二类别的逻辑回归就是当n_out=2时的LogisticRegression 177 | """ 178 | #参数说明: 179 | #input,大小就是(n_example,n_in),其中n_example是一个batch的大小, 180 | #因为我们训练时用的是Minibatch SGD,因此input这样定义 181 | #n_in,即上一层(隐含层)的输出 182 | #n_out,输出的类别数 183 | class LogisticRegression(object): 184 | def __init__(self, input, n_in, n_out): 185 | 186 | #W大小是n_in行n_out列,b为n_out维向量。即:每个输出对应W的一列以及b的一个元素。 187 | self.W = theano.shared( 188 | value=numpy.zeros( 189 | (n_in, n_out), 190 | dtype=theano.config.floatX 191 | ), 192 | name='W', 193 | borrow=True 194 | ) 195 | 196 | self.b = theano.shared( 197 | value=numpy.zeros( 198 | (n_out,), 199 | dtype=theano.config.floatX 200 | ), 201 | name='b', 202 | borrow=True 203 | ) 204 | 205 | #input是(n_example,n_in),W是(n_in,n_out),点乘得到(n_example,n_out),加上偏置b, 206 | #再作为T.nnet.softmax的输入,得到p_y_given_x 207 | #故p_y_given_x每一行代表每一个样本被估计为各类别的概率 208 | #PS:b是n_out维向量,与(n_example,n_out)矩阵相加,内部其实是先复制n_example个b, 209 | #然后(n_example,n_out)矩阵的每一行都加b 210 | self.p_y_given_x = T.nnet.softmax(T.dot(input, self.W) + self.b) 211 | 212 | #argmax返回最大值下标,因为本例数据集是MNIST,下标刚好就是类别。axis=1表示按行操作。================? 213 | self.y_pred = T.argmax(self.p_y_given_x, axis=1) 214 | 215 | #params,LogisticRegression的参数 216 | self.params = [self.W, self.b] 217 | 218 | def negative_log_likelihood(self, y): #==============================? 219 | return -T.mean(T.log(self.p_y_given_x)[T.arange(y.shape[0]), y]) 220 | 221 | def errors(self, y): 222 | if y.ndim != self.y_pred.ndim: 223 | raise TypeError( 224 | 'y should have the same shape as self.y_pred', 225 | ('y', y.type, 'y_pred', self.y_pred.type) 226 | ) 227 | if y.dtype.startswith('int'): 228 | return T.mean(T.neq(self.y_pred, y)) 229 | else: 230 | raise NotImplementedError() 231 | 232 | 233 | """ 234 | 加载MNIST数据集load_data() 235 | mnist.pkl将60000个训练数据分成了50000个训练数据和10000校正数据集; 236 | 每个数组由两部分内容组成,一个图片数组和一个标签数组,图片数组的每一行代表一个图片的像素,有784个元素(28×28) 237 | """ 238 | def load_data(dataset): 239 | # dataset是数据集的路径,程序首先检测该路径下有没有MNIST数据集,没有的话就下载MNIST数据集 240 | #这一部分就不解释了,与softmax回归算法无关。 241 | data_dir, data_file = os.path.split(dataset) 242 | if data_dir == "" and not os.path.isfile(dataset): 243 | # Check if dataset is in the data directory. 244 | new_path = os.path.join( 245 | os.path.split(__file__)[0], 246 | "..", 247 | "data", 248 | dataset 249 | ) 250 | if os.path.isfile(new_path) or data_file == 'mnist.pkl.gz': 251 | dataset = new_path 252 | 253 | if (not os.path.isfile(dataset)) and data_file == 'mnist.pkl.gz': 254 | import urllib 255 | origin = ( 256 | 'http://www.iro.umontreal.ca/~lisa/deep/data/mnist/mnist.pkl.gz' 257 | ) 258 | print 'Downloading data from %s' % origin 259 | 260 | urllib.urlretrieve(origin, dataset) 261 | 262 | print '... loading data' 263 | global resultSource 264 | file_write = open(resultSource, 'a') 265 | try: 266 | file_write.write("... loading data\n" ) 267 | finally : 268 | file_write.close() 269 | 270 | #以上是检测并下载数据集mnist.pkl.gz,不是本文重点。下面才是load_data的开始 271 | 272 | #从"mnist.pkl.gz"里加载train_set, valid_set, test_set,它们都是包括label的 273 | #主要用到python里的gzip.open()函数,以及 cPickle.load()。 274 | #‘rb’表示以二进制可读的方式打开文件 275 | f = gzip.open(dataset, 'rb') 276 | train_set, valid_set, test_set = cPickle.load(f) 277 | f.close() 278 | 279 | 280 | #将数据设置成shared variables,主要时为了GPU加速,只有shared variables才能存到GPU memory中 281 | #GPU里数据类型只能是float。而data_y是类别,所以最后又转换为int返回 282 | def shared_dataset(data_xy, borrow=True): 283 | data_x, data_y = data_xy 284 | shared_x = theano.shared(numpy.asarray(data_x, 285 | dtype=theano.config.floatX), 286 | borrow=borrow) 287 | shared_y = theano.shared(numpy.asarray(data_y, 288 | dtype=theano.config.floatX), 289 | borrow=borrow) 290 | return shared_x, T.cast(shared_y, 'int32') 291 | 292 | 293 | test_set_x, test_set_y = shared_dataset(test_set) 294 | valid_set_x, valid_set_y = shared_dataset(valid_set) 295 | train_set_x, train_set_y = shared_dataset(train_set) 296 | 297 | rval = [(train_set_x, train_set_y), (valid_set_x, valid_set_y), 298 | (test_set_x, test_set_y)] 299 | return rval 300 | 301 | 302 | """ 303 | 实现LeNet5 304 | LeNet5有两个卷积层,第一个卷积层有20个卷积核,第二个卷积层有50个卷积核 305 | """ 306 | def evaluate_lenet5(learning_rate=0.1, n_epochs=200, 307 | dataset='/home/zhangshu/faq/shucunt/temp/domainAdaption/data/mnist.pkl.gz', 308 | nkerns=[20, 50], batch_size=500): 309 | """ 310 | learning_rate:学习速率,随机梯度前的系数。 311 | n_epochs训练步数,每一步都会遍历所有batch,即所有样本 312 | batch_size,这里设置为500,即每遍历完500个样本,才计算梯度并更新参数 313 | nkerns=[20, 50],每一个LeNetConvPoolLayer卷积核的个数,第一个LeNetConvPoolLayer有 314 | 20个卷积核,第二个有50个 315 | """ 316 | 317 | rng = numpy.random.RandomState(23455) 318 | 319 | #加载数据 320 | datasets = load_data(dataset) 321 | train_set_x, train_set_y = datasets[0] 322 | valid_set_x, valid_set_y = datasets[1] 323 | test_set_x, test_set_y = datasets[2] 324 | 325 | # 计算batch的个数 326 | n_train_batches = train_set_x.get_value(borrow=True).shape[0] 327 | n_valid_batches = valid_set_x.get_value(borrow=True).shape[0] 328 | n_test_batches = test_set_x.get_value(borrow=True).shape[0] 329 | n_train_batches /= batch_size 330 | n_valid_batches /= batch_size 331 | n_test_batches /= batch_size 332 | 333 | #定义几个变量,index表示batch下标,x表示输入的训练数据,y对应其标签 334 | index = T.lscalar() 335 | x = T.matrix('x') 336 | y = T.ivector('y') 337 | 338 | ###################### 339 | # BUILD ACTUAL MODEL # 340 | ###################### 341 | print '... building the model' 342 | global resultSource 343 | file_write = open(resultSource, 'a') 344 | try: 345 | file_write.write("... building the model\n" ) 346 | finally : 347 | file_write.close() 348 | 349 | 350 | #我们加载进来的batch大小的数据是(batch_size, 28 * 28),但是LeNetConvPoolLayer的输入是四维的,所以要reshape 351 | layer0_input = x.reshape((batch_size, 1, 28, 28)) 352 | 353 | # layer0即第一个LeNetConvPoolLayer层 354 | #输入的单张图片(28,28),经过conv得到(28-5+1 , 28-5+1) = (24, 24), 355 | #经过maxpooling得到(24/2, 24/2) = (12, 12) 356 | #因为每个batch有batch_size张图,第一个LeNetConvPoolLayer层有nkerns[0]个卷积核, 357 | #故layer0输出为(batch_size, nkerns[0], 12, 12) 358 | layer0 = LeNetConvPoolLayer( 359 | rng, 360 | input=layer0_input, 361 | image_shape=(batch_size, 1, 28, 28), 362 | filter_shape=(nkerns[0], 1, 5, 5), 363 | poolsize=(2, 2) 364 | ) 365 | 366 | 367 | #layer1即第二个LeNetConvPoolLayer层 368 | #输入是layer0的输出,每张特征图为(12,12),经过conv得到(12-5+1, 12-5+1) = (8, 8), 369 | #经过maxpooling得到(8/2, 8/2) = (4, 4) 370 | #因为每个batch有batch_size张图(特征图),第二个LeNetConvPoolLayer层有nkerns[1]个卷积核 371 | #,故layer1输出为(batch_size, nkerns[1], 4, 4) 372 | layer1 = LeNetConvPoolLayer( 373 | rng, 374 | input=layer0.output, 375 | image_shape=(batch_size, nkerns[0], 12, 12),#输入nkerns[0]个特征,即layer0输出nkerns[0]个特征 376 | filter_shape=(nkerns[1], nkerns[0], 5, 5), 377 | poolsize=(2, 2) 378 | ) 379 | #到此,实际上将原来的一张图片的一个特征压成了一个4*4的图片,但是一张图片对应nkerns[1]个特征图片 380 | #因此在全连接层,可以认为一个图片变为nkerns[1]*4*4大小的图片----the input of hiddenLayer 381 | 382 | #前面定义好了两个LeNetConvPoolLayer(layer0和layer1),layer1后面接layer2,这是一个全连接层,相当于MLP里面的隐含层 383 | #故可以用MLP中定义的HiddenLayer来初始化layer2,layer2的输入是二维的(batch_size, num_pixels) , 384 | #故要将上层中同一张图经不同卷积核卷积出来的特征图合并为一维向量, 385 | #也就是将layer1的输出(batch_size, nkerns[1], 4, 4)flatten为(batch_size, nkerns[1]*4*4)=(500,800),作为layer2的输入。 386 | #而隐藏层的w为800*500(nin*nout) 387 | #(500,800)表示有500个样本,每一行代表一个样本。layer2的输出大小是(batch_size,n_out)=(500,500) 388 | layer2_input = layer1.output.flatten(2) 389 | layer2 = HiddenLayer( 390 | rng, 391 | input=layer2_input, 392 | n_in=nkerns[1] * 4 * 4, 393 | n_out=500, 394 | activation=T.tanh 395 | ) 396 | 397 | #最后一层layer3是分类层,用的是逻辑回归中定义的LogisticRegression, 398 | #layer3的输入是layer2的输出(500,500),layer3的输出就是(batch_size,n_out)=(500,10) 399 | layer3 = LogisticRegression(input=layer2.output, n_in=500, n_out=10) 400 | 401 | #代价函数NLL 402 | cost = layer3.negative_log_likelihood(y) 403 | 404 | # test_model计算测试误差,x、y根据给定的index具体化,然后调用layer3, 405 | #layer3又会逐层地调用layer2、layer1、layer0,故test_model其实就是整个CNN结构, 406 | #test_model的输入是x、y,输出是layer3.errors(y)的输出,即误差。 407 | test_model = theano.function( 408 | [index], 409 | layer3.errors(y), 410 | givens={ 411 | x: test_set_x[index * batch_size: (index + 1) * batch_size], 412 | y: test_set_y[index * batch_size: (index + 1) * batch_size] 413 | } 414 | ) 415 | #validate_model,验证模型,分析同上。 416 | validate_model = theano.function( 417 | [index], 418 | layer3.errors(y), 419 | givens={ 420 | x: valid_set_x[index * batch_size: (index + 1) * batch_size], 421 | y: valid_set_y[index * batch_size: (index + 1) * batch_size] 422 | } 423 | ) 424 | 425 | #下面是train_model,涉及到优化算法即SGD,需要计算梯度、更新参数 426 | #参数集 427 | params = layer3.params + layer2.params + layer1.params + layer0.params 428 | 429 | #对各个参数的梯度 430 | grads = T.grad(cost, params) 431 | 432 | #因为参数太多,在updates规则里面一个一个具体地写出来是很麻烦的,所以下面用了一个for..in..,自动生成规则对(param_i, param_i - learning_rate * grad_i) 433 | updates = [ 434 | (param_i, param_i - learning_rate * grad_i) 435 | for param_i, grad_i in zip(params, grads) 436 | ] 437 | 438 | #train_model,代码分析同test_model。train_model里比test_model、validation_model多出updates规则 439 | train_model = theano.function( 440 | [index], 441 | cost, 442 | updates=updates, 443 | givens={ 444 | x: train_set_x[index * batch_size: (index + 1) * batch_size], 445 | y: train_set_y[index * batch_size: (index + 1) * batch_size] 446 | } 447 | ) 448 | 449 | 450 | ############### 451 | # 开始训练 # 452 | ############### 453 | print '... training' 454 | global resultSource 455 | file_write = open(resultSource, 'a') 456 | try: 457 | file_write.write("... training\n" ) 458 | finally : 459 | file_write.close() 460 | patience = 10000 461 | patience_increase = 2 462 | improvement_threshold = 0.995 463 | 464 | validation_frequency = min(n_train_batches, patience / 2) 465 | #这样设置validation_frequency可以保证每一次epoch都会在验证集上测试。 466 | 467 | best_validation_loss = numpy.inf #最好的验证集上的loss,最好即最小 468 | best_iter = 0 #最好的迭代次数,以batch为单位。比如best_iter=10000,说明在训练完第10000个batch时,达到best_validation_loss 469 | test_score = 0. 470 | start_time = time.time() 471 | 472 | epoch = 0 473 | done_looping = False 474 | 475 | #下面就是训练过程了,while循环控制的时步数epoch,一个epoch会遍历所有的batch,即所有的图片。 476 | #for循环是遍历一个个batch,一次一个batch地训练。for循环体里会用train_model(minibatch_index)去训练模型, 477 | #train_model里面的updatas会更新各个参数。 478 | #for循环里面会累加训练过的batch数iter,当iter是validation_frequency倍数时则会在验证集上测试, 479 | #如果验证集的损失this_validation_loss小于之前最佳的损失best_validation_loss, 480 | #则更新best_validation_loss和best_iter,同时在testset上测试。 481 | #如果验证集的损失this_validation_loss小于best_validation_loss*improvement_threshold时则更新patience。 482 | #当达到最大步数n_epoch时,或者patience