这是我参与11月更文挑战的第13天
卷积网络
之前使用的神经网络为全连接层网络,即:⽹络中的神经元与相邻的层上的每个神经元都有连接。这种网络架构不考虑图像的空间结构,对待相距很远和彼此接近的输⼊像素的方法是一样的。而卷积神经网络则不一样,它设法利用图像的空间结构的架构来学习,采用了三种基本概念:局部接收视野(local receptive fields),参数共享(shared weights),和池化(pooling)。
局部接收视野
把输⼊图像进⾏⼩的,局部区域的连接,更确切的说就是第一个隐藏层中的每个神经元会连接到⼀个输⼊神经元的⼀个⼩区域如下所示:
这个输⼊图像的区域被称为隐藏神经元的局部接收视野。
参数共享
每个隐藏神经元都有⼀个偏置和连接到它的局部感受野的权重,对每一个隐含神经元使⽤相同的权重和偏置。假设局部接收视野的大小为 像素,则对第 个隐含神经元其输出为:
是神经元激活函数, 是偏置, 为 大小的数组, 是位置为 的输入的激活值。
通过这个方法,第一层隐含层的作用实际上是在输入图像的不同位置检测完全相同的特征(把权重和偏置设想成隐藏神经元可以挑选的东西,例如,在⼀个特定的局部接收视野检测是否存在垂直边缘)。因此卷积⽹络能很好地适应图像的平移不变性:稍微移动⼀幅猫的图像,它仍然是⼀幅猫的图像。
从输⼊层到隐藏层的映射称为特征映射,定义特征映射的权重称为共享权重,以这种⽅式定义特征映射的偏置称为共享偏置,共享权重和偏置经常被称为⼀个卷积核或者滤波器。,上述都是不同论文中可能使用的术语。
为了完成图像识别需要超过⼀个的特征映射,⼀个完整的卷积层由⼏个不同的特征映射组成:
下面20 幅图像对应于 20 个不同的特征映射(也可以叫滤波器或卷积核),每个映射由一块 大小的图像表示,对应于局部接收视野中 的权重。⽩⾊块意味着⼀个⼩(典型的,更⼩的负数)权重,更暗的块意味着⼀个更⼤的权重。这些特征许多有清晰的亮和暗的⼦区域,表⽰⽹络实际上正在学习和空间结构相关的东西。
卷积(convolutional)的概念:这⼀名称源⾃⽅程中的操作符,写作 , 表示出自一个特征映射的输出激活值集合, 表示输入激活值集合, 称为一个卷积操作。
池化
除了刚刚描述的卷积层,卷积神经⽹络也包含池化层(pooling layers),它紧接着在卷积层之后使⽤,主要作用是简化从卷积层输出的信息,即:⼀个池化层取得从卷积层输出的每⼀个特征映射并且做⼀个凝缩的特征映射。
最大值池化
池化层的每个单元可能概括了前⼀层的⼀个(⽐如) 的区域,常⻅的有最⼤值池化(max-pooling),这种方法中⼀个池化单元简单地输出其 输⼊区域的最⼤激活值:
通过这种方法可以把卷积层 的输出压缩成 个神经元输出:
L2池化
这种方法取 的区域激活值的平方根(而不是最大值)。
代码实现
import pickle
import gzip
import numpy as np
import theano
import theano.tensor as T
from theano.tensor.nnet import conv
from theano.tensor.nnet import softmax
from theano.tensor import shared_randomstreams
from theano.tensor.signal.pool import pool_2d
# 神经元激活函数
def linear(z): return z
def ReLU(z): return T.maximum(0.0, z)
from theano.tensor.nnet import sigmoid
from theano.tensor import tanh
# 常量
GPU = True
if GPU:
print ("Trying to run under a GPU. If this is not desired, then modify "+\
"network3.py\nto set the GPU flag to False.")
try: theano.config.device = 'gpu'
except: pass # it's already set
theano.config.floatX = 'float32'
else:
print ("Running with a CPU. If this is not desired, then the modify "+\
"network3.py to set\nthe GPU flag to True.")
# 加载MINST数据集
def load_data_shared(filename="mnist.pkl.gz"):
f = gzip.open(filename, 'rb')
training_data, validation_data, test_data = pickle.load(f)
f.close()
def shared(data):
#将数据放入共享变量中,允许Theano复制数据到GPU,当GPU可用时
shared_x = theano.shared(
np.asarray(data[0], dtype=theano.config.floatX), borrow=True)
shared_y = theano.shared(
np.asarray(data[1], dtype=theano.config.floatX), borrow=True)
return shared_x, T.cast(shared_y, "int32")
return [shared(training_data), shared(validation_data), shared(test_data)]
# 用于构造和训练网络的类
class Network:
# 获取一个“层”列表,描述网络架构,以及在随机梯度下降训练期间使用的“mini_batch_size”值
def __init__(self, layers, mini_batch_size):
self.layers = layers
self.mini_batch_size = mini_batch_size
# 此⾏代码将每层的参数放到⼀个列表中
# Network.SGD ⽅法使⽤ self.params 来确定 Network 中哪些变量需要学习
self.params = [param for layer in self.layers for param in layer.params]
# theano.tensor 高维数组
# matrix 矩阵
# ivector 向量
# 定义了 Theano 符号变量 x 和 y,⽤来表⽰输⼊和⽹络得到的输出
self.x = T.matrix("x")
self.y = T.ivector("y")
init_layer = self.layers[0]
# 设置初始层的输⼊
# 输⼊ self.x 传了两次:这是因为我们可能会以两种⽅式(有dropout 和⽆ dropout)使⽤⽹络
init_layer.set_inpt(self.x, self.x, self.mini_batch_size)
for j in range(1, len(self.layers)):
prev_layer, layer = self.layers[j - 1], self.layers[j]
layer.set_inpt(prev_layer.output, prev_layer.output_dropout, self.mini_batch_size)
self.output = self.layers[-1].output
self.output_dropout = self.layers[-1].output_dropout
def SGD(self, training_data, epochs, mini_batch_size, eta,
validation_data, test_data, lmbda=0.0):
# 将数据集分解成 x 和 y 两部分,并计算在每个数据集中⼩批量数据的数量
# 使用小批量随机梯度下降训练网络
training_x, training_y = training_data
validation_x, validation_y = validation_data
test_x, test_y = test_data
# 计算用于训练、验证和测试的小批数量
num_training_batches = size(training_data) / mini_batch_size
num_validation_batches = size(validation_data) / mini_batch_size
num_test_batches = size(test_data) / mini_batch_size
# 定义(正则化的)代价函数、符号渐变和更新
# 符号化地给出了规范化的对数似然代价函数,在梯度函数中计算了对应的导数,以及对应参数的更新⽅式
l2_norm_squared = sum([(layer.w ** 2).sum() for layer in self.layers])
cost = self.layers[-1].cost(self) + \
0.5 * lmbda * l2_norm_squared / num_training_batches
grads = T.grad(cost, self.params)
updates = [(param, param - eta * grad)
for param, grad in zip(self.params, grads)]
# 定义函数来训练一个小批处理,并计算验证和测试小批的准确性
i = T.lscalar() # mini-batch index
# Theano 符号函数在给定 minibatch 索引的情况下使⽤ updates 来更新 Network 的参数
train_mb = theano.function(
[i], cost, updates=updates,
givens={
self.x:
training_x[i * self.mini_batch_size: (i + 1) * self.mini_batch_size],
self.y:
training_y[i * self.mini_batch_size: (i + 1) * self.mini_batch_size]
})
validate_mb_accuracy = theano.function(
[i], self.layers[-1].accuracy(self.y),
givens={
self.x:
validation_x[i * self.mini_batch_size: (i + 1) * self.mini_batch_size],
self.y:
validation_y[i * self.mini_batch_size: (i + 1) * self.mini_batch_size]
})
test_mb_accuracy = theano.function(
[i], self.layers[-1].accuracy(self.y),
givens={
self.x:
test_x[i * self.mini_batch_size: (i + 1) * self.mini_batch_size],
self.y:
test_y[i * self.mini_batch_size: (i + 1) * self.mini_batch_size]
})
self.test_mb_predictions = theano.function(
[i], self.layers[-1].y_out,
givens={
self.x:
test_x[i * self.mini_batch_size: (i + 1) * self.mini_batch_size]
})
# 做训练
best_validation_accuracy = 0.0
for epoch in range(epochs):
for minibatch_index in range(num_training_batches):
iteration = num_training_batches * epoch + minibatch_index
if iteration % 1000 == 0:
print("Training mini-batch number {0}".format(iteration))
cost_ij = train_mb(minibatch_index)
if (iteration + 1) % num_training_batches == 0:
validation_accuracy = np.mean(
[validate_mb_accuracy(j) for j in range(num_validation_batches)])
print("Epoch {0}: validation accuracy {1:.2%}".format(
epoch, validation_accuracy))
if validation_accuracy >= best_validation_accuracy:
print("This is the best validation accuracy to date.")
best_validation_accuracy = validation_accuracy
best_iteration = iteration
if test_data:
test_accuracy = np.mean(
[test_mb_accuracy(j) for j in range(num_test_batches)])
print('The corresponding test accuracy is {0:.2%}'.format(
test_accuracy))
print("Finished training network.")
print("Best validation accuracy of {0:.2%} obtained at iteration {1}".format(best_validation_accuracy, best_iteration))
print("Corresponding test accuracy of {0:.2%}".format(test_accuracy))
# 定义层的类型
# 用于创建卷积和最大池化层的组合,更复杂的实现会将二者分开,但是为简化问题这里将二者合并
class ConvPoolLayer:
def __init__(self, filter_shape, image_shape, poolsize=(2, 2),activation_fn=sigmoid):
# filter_shape为一个长度为4的元组,其中的实体分别为:
# 过滤器的数量、输入特征映射的数量、过滤器的高度和过滤器的宽度
self.filter_shape = filter_shape
# image_shape为一个长度为4的元组,其中的实体分别为:
# 小批量大小、输入特征映射的数量、图像高度和图像宽度
self.image_shape = image_shape
# poolsize为一个长度为2的元组,其中的实体分别为:
# y和x 池化大小
self.poolsize = poolsize
self.activation_fn = activation_fn
# 初始化权重和偏差
# theano.shared 载⼊权重和偏差到 Theano 中的共享变量中,这样可以确保这些变量可在 GPU 中进⾏处理
n_out = (filter_shape[0] * np.prod(filter_shape[2:]) / np.prod(poolsize))
self.w = theano.shared(
np.asarray(
np.random.normal(loc=0, scale=np.sqrt(1.0 / n_out), size=filter_shape),
dtype=theano.config.floatX),
borrow=True)
self.b = theano.shared(
np.asarray(
np.random.normal(loc=0, scale=1.0, size=(filter_shape[0],)),
dtype=theano.config.floatX),
borrow=True)
# self.params存储参数
self.params = [self.w, self.b]
# set_inpt ⽅法⽤来设置该层的输⼊,并计算相应的输出
def set_inpt(self, inpt, inpt_dropout, mini_batch_size):
self.inpt = inpt.reshape(self.image_shape)
conv_out = conv.conv2d(
input=self.inpt, filters=self.w, filter_shape=self.filter_shape,
image_shape=self.image_shape)
pooled_out = pool_2d(
input=conv_out, ds=self.poolsize, ignore_border=True)
self.output = self.activation_fn(
pooled_out + self.b.dimshuffle('x', 0, 'x', 'x'))
self.output_dropout = self.output # 在卷积层中不做dropout操作
# 全连接层
class FullyConnectedLayer:
def __init__(self, n_in, n_out, activation_fn=sigmoid, p_dropout=0.0):
self.n_in = n_in
self.n_out = n_out
self.activation_fn = activation_fn
self.p_dropout = p_dropout
# 初始化权重和偏差
self.w = theano.shared(
np.asarray(
np.random.normal(
loc=0.0, scale=np.sqrt(1.0 / n_out), size=(n_in, n_out)),
dtype=theano.config.floatX),
name='w', borrow=True)
self.b = theano.shared(
np.asarray(np.random.normal(loc=0.0, scale=1.0, size=(n_out,)),
dtype=theano.config.floatX),
name='b', borrow=True)
self.params = [self.w, self.b]
def set_inpt(self, inpt, inpt_dropout, mini_batch_size):
self.inpt = inpt.reshape((mini_batch_size, self.n_in))
self.output = self.activation_fn(
(1 - self.p_dropout) * T.dot(self.inpt, self.w) + self.b)
self.y_out = T.argmax(self.output, axis=1)
# 训练时我们可能要使⽤ dropout。如果使⽤ dropout,就需要设置对应丢弃的概率
self.inpt_dropout = dropout_layer(
inpt_dropout.reshape((mini_batch_size, self.n_in)), self.p_dropout)
self.output_dropout = self.activation_fn(
T.dot(self.inpt_dropout, self.w) + self.b)
# 返回小批量的准确率
def accuracy(self, y):
return T.mean(T.eq(y, self.y_out))
# softmax层
class SoftmaxLayer:
def __init__(self, n_in, n_out, p_dropout=0.0):
self.n_in = n_in
self.n_out = n_out
self.p_dropout = p_dropout
# 初始化参数
self.w = theano.shared(
np.zeros((n_in, n_out), dtype=theano.config.floatX),
name='w', borrow=True)
self.b = theano.shared(
np.zeros((n_out,), dtype=theano.config.floatX),
name='b', borrow=True)
self.params = [self.w, self.b]
def set_inpt(self, inpt, inpt_dropout, mini_batch_size):
self.inpt = inpt.reshape((mini_batch_size, self.n_in))
self.output = softmax((1 - self.p_dropout) * T.dot(self.inpt, self.w) + self.b)
self.y_out = T.argmax(self.output, axis=1)
self.inpt_dropout = dropout_layer(
inpt_dropout.reshape((mini_batch_size, self.n_in)), self.p_dropout)
self.output_dropout = softmax(T.dot(self.inpt_dropout, self.w) + self.b)
def cost(self, net):
# 返回log-likelihood 损失
return -T.mean(T.log(self.output_dropout)[T.arange(net.y.shape[0]), net.y])
def accuracy(self, y):
# 返回准确率
return T.mean(T.eq(y, self.y_out))
#### Miscellanea
def size(data):
# 返回dataset的size
return data[0].get_value(borrow=True).shape[0]
def dropout_layer(layer, p_dropout):
srng = shared_randomstreams.RandomStreams(
np.random.RandomState(0).randint(999999))
mask = srng.binomial(n=1, p=1 - p_dropout, size=layer.shape)
return layer * T.cast(mask, theano.config.floatX)
复制代码