利用TensorFlow搭建CNN,DNN网络实现图像手写识别,总结。

摘要

本文章将对比用Tensorflow搭建的神经网络和卷积网络实现手写数字识别的过程,并最后对俩进行对比,最后总结Tensorflow搭建深度学习的主要步骤。

一、神经网络与卷积网络的对比

1.数据处理

数据处理的整个模块代码:

x_train_root,y_train_root,x_test_root,y_test_root,classes=tf_utils.load_dataset()
x_train_re=x_train_root.reshape(x_train_root.shape[0],-1).T
print(x_train_re.shape,x_train_root.shape)
x_test_re=x_test_root.reshape(x_test_root.shape[0],-1).T
#归一化数据
x_train=x_train_re/255
x_test=x_test_re/255
#转换为One_hot矩阵
y_train_oh=tf_utils.convert_to_one_hot(y_train_root,6)
y_test_oh=tf_utils.convert_to_one_hot(y_test_root,6)

1.加载数据集,首先要能读取到h5文件:h5py.File(),返回的结果是一个字典类变量a
2.将a通过索引后把里面的x值和y值拿出来同时转换为ndarray类型的,如果不转换的话,从字典里面拿出来的是list类型的变量。
记录一个问题:如何去读取一个H5文件的主键?

#HDF5的读取:
f = h5py.File('HDF5_FILE.h5','r')  
#打开h5文件
# 可以查看所有的主键
for key in f.keys():    
	print(f[key].name)   	
	print(f[key].shape)    
	print(f[key].value)

我们将会得到:

/list_classes
(6,)
[0 1 2 3 4 5]
/train_set_x
(1080, 64, 64, 3)

…#此处省略的了x的内容
/train_set_y
(1080,)
[5 0 2 …, 2 4 5]

3.最后将获得的标签值y转换为一行m列,防止格式出错以及转化为ndarray类型的变量

y.reshape((1, train_set_y_orig.shape[0]) 

4.这一步我觉得放在上面一起做处理比较好,但是看了吴恩达的思路就是 训练集x的格式要放在函数外面进行处理,`我想放在这里可能是为了对数据加载和处理做一个统一的函数吧,就是不论是CNN,还是DNN,对数据的处理到这一步都是一样的。

x_train_re=x_train_root.reshape(x_train_root.shape[0],-1).T

对这里做个解释,先打印出shape:

print(x_train_re.shape,x_train_root.shape)

显示结果:
(12288, 1080) (1080, 64, 64, 3)
其实这里就开始和卷积神经网络的输入层的数据格式不一样了
可以看到,这里是将输入的图片的矩阵信息转化为了一个2darray类型的了,其中(12288,1080)的转换意义是:将64643的矩阵拉成一列,这一列中顺序的放着1080个图片中的一张图片的所有信息。
但是卷积神经网络不是这样的,卷积神经在这块是直接输入图片的矩阵信息,即输入的变量类型就是4darray类型的。

2.对获取到的数据进行归一化和独热编码

为什么要进行归一化:
答:这是为了在程序显示时,将范围变成[0,1]之间,因为图片信息的数值矩阵类型为unit8型,在0~255范围内,而在数据处理时使用float型,并且将其局限在[0,1]之间的话数据波动会很小。
为什么要进行独热编码(one_hot矩阵):
答:因为在做图像手写识别的时候我们最后会有个softmax分类器,在这个项目中我们的softmax层就是和输出层一起的,拥有6个输出单元:0~5之间的数字,这6个输出单元会输出属于每一个数字的概率值,也就是最后我们得到的y hat(预测值),至于softmax层的具体实现原理我将放在后面再补上。
One-hot()的函数是这样的:

def convert_to_one_hot(Y, C):
    Y = np.eye(C)[Y.reshape(-1)].T
    return Y

其中np.eye©是生成c行c列的对角矩阵,就是左对角全为1的矩阵,之后紧跟着list类型的变量是将每行的1移动到每行的索引值index处,Y.reshape(-1)的将(1,1080)大小的Y转化为1行1080列,其实在这个项目中没有处理这个的必要。
注意我们移动的套路是将每行的1移动到list表内的index处,所以np.eye©[Y.reshape(-1)]生成的是一个1080行6列的矩阵,但是我们要的是6行1080列的数,这样才是独热编码的标准形态,所以我们取其转置。
注意np.eye©[]形式可以改变最终生成的矩阵形状,一开始可能是c*c列的对角矩阵,但是最终的大小还是看[]里面确定的,很灵活,看个反向的例子:

>>np.eye(3)
array([[ 1.,  0.,  0.],
   [ 0.,  1.,  0.],
   [ 0.,  0.,  1.]])
>>> np.eye(3)[1]
array([ 0.,  1.,  0.])

我们可以看到这里原先的3*3的矩阵通过[1]的索引后,就只剩下一行。
现在看个正向的例子:

b=np.eye(3)[np.array([1, 0, 2, 1, 0, 1, 0, 2])]
print(b)

我们看下输出结果:


[[ 0.  1.  0.]
 [ 1.  0.  0.]
 [ 0.  0.  1.]
 [ 0.  1.  0.]
 [ 1.  0.  0.]
 [ 0.  1.  0.]
 [ 1.  0.  0.]
 [ 0.  0.  1.]]

但是一定要注意index不要超过了列的最大值,因为列数是不能扩展和减少的。
介绍完上面的细节之后,我们来看下处理后的结果:

y_train_oh=tf_utils.convert_to_one_hot(y_train_root,6)
print(y_train_root.shape,y_train_oh.shape)

我们查看下打印的结果:
(1, 1080) (6, 1080)

不出我们意料,结果是符合我们预期的形状的。

二、开始我们的tensorflow神经网络搭建

1.创建placeholder

为什么要创建placeholder?
placeholder就是占位符的意思我们看下怎么搭建的:

def create_placeholder(n_x,n_y):
    x=tf.placeholder(tf.float32,[n_x,None],name="x")
    y=tf.placeholder(tf.float32,[n_y,None],name="y")
    print("placeholder:",x,y)
    return x,y

[n_x,None]中None代表着喂入的样本数待定,但是喂入的格式是[n_x,None]形式,但是卷积神经网络中是这样的:

def create_placeholders(n_H0, n_W0, n_C0, n_y):
    X = tf.placeholder(tf.float32,[None, n_H0, n_W0, n_C0])
    Y = tf.placeholder(tf.float32,[None, n_y])
    return X,Y
x,Y=create_placeholder(n_x,n_y)

为什么是[None, n_H0, n_W0, n_C0])形式呢? 因为我们的输入层的格式就是这样的,他是一个[1080,64,64,3]这种格式输入进去的,所以我们创建的placeholder就要是这样的格式。
注意这里的x,Y就是我们需要喂入的索引,我们在后面通过feed_dict={x:minibatch_x,Y:minibatch_y}进行投喂。x,Y必须保持一致。

2.初始化各层权重(w,b)

在DNN中,我们的操作是这样的:

def initialize_parameters():
	tf.set_random_seed(1)
	w1=tf.get_variable("w1",[25,12288],initializer=tf.contrib.layers.xavier_initializer(seed=1))
   	b1=tf.get_variable("b1",[25,1],initializer=tf.zeros_initializer())
         w2=tf.get_variable("w2",[12,25],initializer=tf.contrib.layers.xavier_initializer(seed=1))
    	b2=tf.get_variable("b2",[12,1],initializer=tf.zeros_initializer())
    	w3=tf.get_variable("w3",[6,12],initializer=tf.contrib.layers.xavier_initializer(seed=1))
   	b3=tf.get_variable("b3",[6,1],initializer=tf.zeros_initializer())
   	parameters={"W1":w1,
                "b1":b1,
                "W2":w2,
                "b2":b2,
                "W3":w3,
                "b3":b3,
            }

也就是说我们的每层的权重形状是长这样子的:


    '''
        W1 : [25, 12288]
        b1 : [25, 1]
        W2 : [12, 25]
        b2 : [12, 1]
        W3 : [6, 12]
        b3 : [6, 1]
        返回:带有w,b参数的字典形式
        '''

我们现在主要梳理一下每层的权重为什么要是这个大小,这个很关键,因为这个是我们手动设计的:

神经网络模型至于这里为什么每层有那么多神经元我们不去解释,因为我一直在思考这个问题,可能是参考案例的,拿别人的模型来做的吧。
我们的input输入层大小是(12288,1080),我们在forward_propagation前向传播计算的时候是用w1x,所以我们得到的layer1层的输出大小是layer1.shape :=(25,1080)。
我们在第二层的时候用w2
layer1,得到的输出大小是layer2.shape :=(12,1080)。
在第三层的时候用w3*layer2,得到的输出大小是layer3.shape:=(6,1080)。
这种形状确实是我们想要的,6行是6个softmax输出层的神经元。

在CNN中,我们的初始化权重是这样的:

def initialize_parameters():
	tf.set_random_seed(1)
	W1 = tf.get_variable("W1",[4,4,3,8],initializer=tf.contrib.layers.xavier_initializer(seed=0))
    	W2 = tf.get_variable("W2",[2,2,8,16],initializer=tf.contrib.layers.xavier_initializer(seed=0))
    	parameters = {"W1": W1,
         "W2": W2}

initializer=tf.contrib.layers.xavier_initializer(seed=1)是定义一层的权重值,是随机的数,大小是第二个参数的大小。

其实DNN和CNN这一步都是基本一样的,这是这里没有初始化偏移量b。

3.前向传播(forwardpropagation)

DNN中:

def forward_propagation(train_x,parameters):
	w1=parameters["W1"]
   	b1=parameters["b1"]
    	w2=parameters["W2"]
    	b2=parameters["b2"]
    	w3=parameters["W3"]
    	b3=parameters["b3"]
    	z1=tf.add(tf.matmul(w1,train_x),b1)
    	a1=tf.nn.relu(z1)
    	z2=tf.add(tf.matmul(w2,a1),b2)
    	a2=tf.nn.relu(z2)
    	z3=tf.add(tf.matmul(w3,a2),b3)
        print("z3.shape=",z3.shape)
        return z3

CNN中:

def forward_propagation(X,parameters):
	    W1 = parameters['W1']
    	    W2 = parameters['W2']
    	    Z1 = tf.nn.conv2d(X,W1,strides=[1,1,1,1],padding="SAME")
    	    A1 = tf.nn.relu(Z1)
    	    P1 = tf.nn.max_pool(A1,ksize=[1,8,8,1],strides=[1,8,8,1],padding="SAME")
    	    Z2 = tf.nn.conv2d(P1,W2,strides=[1,1,1,1],padding="SAME")
    	    A2 = tf.nn.relu(Z2)
    	    P2 = tf.nn.max_pool(A2,ksize=[1,4,4,1],strides=[1,4,4,1],padding="SAME")
    	    P = tf.contrib.layers.flatten(P2)
    	    Z3 = tf.contrib.layers.fully_connected(P,6,activation_fn=None)
    	    return Z3

可以看到几点:
1.DNN与CNN在构建前向传播的过程中用到了函数不一样
2.两者在处理思路上大致是一样的,不过CNN后面加上了

 P = tf.contrib.layers.flatten(P2)
 Z3 = tf.contrib.layers.fully_connected(P,6,activation_fn=None)

DNN采用全连接的方式去搭建整个网络,这个过程我们好理解,但是CNN采用了卷积池化的操作,最后还有个flatten的操作,我们似乎也看到了fully_connected()全连接的字样,那么两者在这块到底有何不同?
答:在回答这个前,我先屡一下CNN网络每层输出的大小:

CNN网络结构

注意在第一层池化的时候我们计算的P是1.5,所以我们可以在上方和左方填充一个,右方和下方填充两个,这个整体就多了3个长度。
下面举个例子,并不是所有的池化操作在进行SAME和无填充Valid的时候形状都和刚开始输入进来的时候一样的,可以看下面的例子:


池化:max_pool

I'll give an example to make it clearer:

x: input image of shape [2, 3], 1 channel
valid_pad: max pool with 2x2 kernel, stride 2 and VALID padding.
same_pad: max pool with 2x2 kernel, stride 2 and SAME padding (this is the classic way to go)

The output shapes are:
valid_pad: here, no padding so the output shape is [1, 1]
same_pad: here, we pad the image to the shape [2, 4] (with-inf and then apply max pool), so the output shape is [1, 2]
#SAME 向上取整

#VALID 向下取整

我们为了确认CNN中每一层的形状是否正确,执行语句:

print(Z1.shape,P1.shape,Z2.shape,P2.shape,P.shape,Z3.shape)

我们打印结果:

(?, 64, 64, 8) (?, 8, 8, 8) (?, 8, 8, 16) (?, 2, 2, 16) (?, 64) (?, 6)

我们可以看到Z1就是第一层卷积的结果,其大小是与输入的维度一致的,说明在padding的时候P=1.5,因为是SAME填充。看下layer1的池化层大小:(?,8,8,8) 。
在layer2中我们的通过公式中,参数:f=2,s=1,p=same,卷积之后的结果还是(?,8,8,8)的大小。
layer2池化层大小,参数:f=4,s=4,p=same,池化之后的大小P2(?,2,2,16),此时P值为0
上述黑体字的原因我也没搞懂,为什么same的时候p=0。

现在我们前向传播的时候输入的样本数是1080个,所以我们一步步下来的大小依次是:

在这里插入图片描述
文字转成图像的流程就是上面这张图的形式,这个过程让我也明白了每一层的神经元个数是如何慢慢确定下来的
假定我们现在在做一个卷积神经网络,目标是确定手写数字识别(假定只识别6个数字),现在老师让我去设计一个CNN网络结构。
我首先要思考的应该是这个网络结构大致是什么样子的了:
1.我最后肯定会在最后一层用softmax分类出6个数字可能的概率,这就需要我有6个输出层的神经元。
2.那么现在样本是1080个,图片分辨率都是64x64的RGB格式的,也就是(1080,64,64,3)的格式,我们需要卷积池化后倒数第二层的激活值大小是(1080,a)的大小的,这个大小是我们在最后一次池化的时候将其flatten(拉长操作)。
a是一个不确定的数值,因为卷积池化后的大小需要根据自己选择的kernel的大小在卷积和池化上的应用。
3.在我们得到了(1080,a)倒数第二层的大小的时候,我们再通过:

tf.contrib.layers.fully_connected(P,6,activation_fn=None)

将输出层的神经元就确定下来了,从而我们得到的是一个(a,6)的输出层权值,最后我们将(1080,a)*(a,6)操作,也就是output=w * x的计算就可以得到输出层的值了,这个输出层的大小是(1080,6)的大小,
这里的w是多少我也不知道,我不知道是不是我的理解错了,按道理应该是在这里也随机化一个权重大小的,然后反向传播的时候再更新这里的权重。
针对上述黑体字,我刚想明白了:
我看了tf.contrib.layers.fully_connected()的参数说明,如下:

tf.contrib.layers.fully_connected(
    inputs,
    num_outputs,
    activation_fn=tf.nn.relu,#默认是relu函数作为激活,但是这里我们不用relu,用softmax激活,由于softmax激活在tensorflow中不是这一步,所以这里填None。
    normalizer_fn=None,
    normalizer_params=None,
    weights_initializer=initializers.xavier_initializer(), #他是自动初始化权值w的,w的大小根据num_outputs神经元的数目决定
    weights_regularizer=None,
    biases_initializer=tf.zeros_initializer(),#自动初始化权值b=0的
    biases_regularizer=None,
    reuse=None,
    variables_collections=None,
    outputs_collections=None,
    trainable=True,
    scope=None
)

所以我们在这一步已经得到了Z3的大小了,就是(1080,6),那么激活函数放在了计算损失值一起的:

tf.nn.softmax_cross_entropy_with_logits(logits=Z3,labels=Y)

完整的代码如下:

def compute_cost(Z3,Y):
	cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=Z3,labels=Y))
	return cost

4.我们在看下softmax激活函数的本质:
Softmax图解
需要的文字说明都在图片中。
这里我们需要注意的一点就是我们用什么损失函数来训练这个网络?

看到上图中我最后一句话说道概率最大的就说明是属于哪一类,这是我们人为的感受,在计算机中,由于我们需要迭代n轮训练一个模型,所以我们需要记录一个偏差值,这个偏差值不是和方差对应的偏差的意思,而是我们在得到预测值的时候,在n轮迭代中与标签值对比后,累加偏差运算的的结果然后求均值意思。这个每一个迭代中的偏差就是我们的损失函数的值,我们定义如下:

softmax损失函数
看到这个后你可能会联想到Logistics回归的损失函数,即二分类的损失函数,但是这里的softmax分类器的损失函数定义和二分类的不一样,,现在解释下为什么会是上面这个函数:
损失函数解释

现就是说:我们在反向传播的时候用梯度下降是降低损失函数的,我们就要使预测值尽可能的大,因为它大了就说明和标签的相似性越大,所以就会导致损失值变小。

至此,前向传播就告一段落了。
总结:DNN前向传播的时候采用全连接的方式,神经元与神经元进行线性运算和非线性运算,而CNN的前向传播采用卷积池化的操作,最后采用全连接的方式,本质上一致的,都是都输入进来的数据进行线性非线性运算,然后通过softmax分类,只是运算的过程是不一样的,DNN中是权值,CNN中是Filter的滤波器进行卷积运算,后者更注重原数据的特征,先保留了数据的特征后开始运算的。

4.损失函数的定义

在DNN与CNN中,损失函数的定义基本一致:
DNN:

def compute_cost(z3,y):
	logits=tf.transpose(z3)
	labels=tf.transpose(y)
	cost=tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=logits,labels=labels))
    	return cost

CNN:

def compute_cost(Z3,Y):
	cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=Z3,labels=Y))
	return cost

注意这里输入到tf.nn.softmax_cross_entropy_with_logits()中参数的形状,DNN中将最后的Z3与标签值y做了转置的操作,
我们从一开始DNN的记录中可以看到Z3的大小就是:layer3.shape:=(6,1080)
CNN中的Z3大小:(1080,6)
完美,可以看到tf.nn.softmax_cross_entropy_with_logits()的要求就是CNN中Z3的格式,即对于1080个样本来说,其中一个样本的6个Z3值是在一行平铺的形式输入进去的,我打印出了Y的形状:
Y.shape is: (?, 6)
和Z3是一样的,都是6列,这也充分说明了我们的数据格式到计算损失函数值这里是没有错的了,这下我们的前向传播与损失值定义是完美了。

5.定义Model(模型),将上述函数封装到我们的模型里面

DNN中:

def model(x_train,y_train,x_test,y_test,learning_rate=0.0001,num_epochs=200,minibatch_size=32,print_cost=True,is_plot=True):
    ops.reset_default_graph()
    tf.set_random_seed(1)
    seed=3
    (n_x,m)=x_train.shape
    print("here is ",n_x,m)
    n_y=y_train.shape[0]
    print("n_y:",n_y)
    costs=[]
    x,Y=create_placeholder(n_x,n_y)#将输入与输出节点数量传入作为placeholder的size
    #初始化参数W,b
    parameters=initialize_parameters()
    #前向传播
    a3=forward_propagation(x,parameters)
    #计算损失函数
    cost=compute_cost(a3,Y)
    #反向传播,使用Adam优化
    optimizer=tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)
    #初始化所有的全局变量
    init=tf.global_variables_initializer()
     #开始会话:创建session和迭代
    with tf.Session() as session:
        session.run(init)
        for epoch  in range(num_epochs):
            epoch_cost=0
            num_minibatches=int(m/minibatch_size)#minibatch的总数量
            seed=seed+1
            minibatches=tf_utils.random_mini_batches(x_train,y_train,minibatch_size,seed)
            for minibatche in minibatches:
                #选择一个minibatch
                (minibatch_x,minibatch_y)=minibatche
                #数据已经准备好了,开始运行session
                _,minibatch_cost=session.run([optimizer,cost],feed_dict={x:minibatch_x,Y:minibatch_y})
                #minibatch如何在一次迭代中计算误差?
                epoch_cost=epoch_cost+minibatch_cost/num_minibatches
            if epoch %5==0:
                costs.append(epoch_cost)
                if print_cost and epoch %100 ==0:
                    print("epch=",epoch,"epoch_cost=",epoch_cost)
        if is_plot:
            plt.plot(np.squeeze(costs))
            plt.ylabel("cost")
            plt.xlabel("iteration")
            plt.title("learing_rate="+str(learning_rate))
            plt.show()
            #保存学习后的参数
            parameters=session.run(parameters)
            print("参数已经保存到session")#?
            #计算当前的预测结果
            correct_prediction=tf.equal(tf.argmax(a3),tf.argmax(Y))
            #计算准确率
            accuracy=tf.reduce_mean(tf.cast(correct_prediction,"float"))
            print("训练集的准确率:",accuracy.eval({x:x_train,Y:y_train}))
            print("测试集的准确率:",accuracy.eval({x:x_test,Y:y_test}))   
            return parameters

CNN中:

def model(X_train, Y_train, X_test, Y_test, learning_rate=0.009, 
         num_epochs=100,minibatch_size=64,print_cost=True,isPlot=True):
         ops.reset_default_graph()  #能够重新运行模型而不覆盖tf变量
   	 tf.set_random_seed(1)    
    	 seed = 3                
    	 (m , n_H0, n_W0, n_C0) = X_train.shape
    	 n_y = Y_train.shape[1]
    	 costs = []
    	 X , Y = create_placeholders(n_H0, n_W0, n_C0, n_y)
    	 parameters = initialize_parameters()
    	 Z3 = forward_propagation(X,parameters)
    	 cost = compute_cost(Z3,Y)
    	 optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)
    	 init = tf.global_variables_initializer()
    	 with tf.Session() as sess:
    	 	sess.run(init)
    	 	for epoch in range(num_epochs):
            		minibatch_cost = 0
            		num_minibatches = int(m / minibatch_size)
            		seed = seed + 1
            		minibatches = cnn_utils.random_mini_batches(X_train,Y_train,minibatch_size,seed)
            		for minibatch in minibatches:
            			(minibatch_X,minibatch_Y) = minibatch
            		 	_ , temp_cost = sess.run([optimizer,cost],feed_dict={X:minibatch_X, Y:minibatch_Y})
            		 	minibatch_cost += temp_cost / num_minibatches
            			if print_cost:
            				if epoch % 5 == 0:
                   		 		print("当前是第 " + str(epoch) + " 代,成本值为:" + str(minibatch_cost))
                   			if epoch % 1 == 0:
                		 			costs.append(minibatch_cost)
                 #cost值记录后选择是否绘图
                 if isPlot:
            		plt.plot(np.squeeze(costs))
            		plt.ylabel('cost')
            		plt.xlabel('iterations (per tens)')
            		plt.title("Learning rate =" + str(learning_rate))
            		plt.show()
            		predict_op = tf.arg_max(Z3,1)#axis=1 按行为单位,获得每行中最大值的索引值
        		corrent_prediction = tf.equal(predict_op , tf.arg_max(Y,1))#tf.equal:对比两个矩阵或者向量的相等的元		#素,如果是相等的那就返回True,反正返回False,返回的值的矩阵维度和A是一样的
        		##计算准确度
        		accuracy = tf.reduce_mean(tf.cast(corrent_prediction,"float"))#tf.cast,将前者转化为后者的变量类型
       		 	print("corrent_prediction accuracy= " + str(accuracy))
       		 	train_accuracy = accuracy.eval({X: X_train, Y: Y_train})#eval:等效于run,不过run可以同时运行多个tensor
       			test_accuary = accuracy.eval({X: X_test, Y: Y_test})
       			print("训练集准确度:" + str(train_accuracy))
       			print("测试集准确度:" + str(test_accuary))
       			return (train_accuracy,test_accuary,parameters)
       		 

对比结果:
1.ops.reset_default_graph()
2.n_y=y_train.shape[0]与n_y = Y_train.shape[1]
3.计算当前的预测情况
可以发现大致上就这三处不一样,现在按顺序解释:
1.
ops.reset_default_graph()是干什么用的,为什么DNN与CNN上都需要这步处理?
答:#能够重新运行模型而不覆盖tf变量
如果你不写这个代码的时候,由于训练完一个模型后,模型的参数将会保存parameters中,即一开始创建的placeholder中"w1"“w2”,如果你再次加载这个模型的时候而不加ops.reset_default_graph()Tensorflow将会提示我们已经拥有了权值了,模型就不会运行并且给出错误:

ValueError: Variable W1 already exists, disallowed. Did you mean to set reuse=True in VarScope? Originally defined at:.....

所以我们加上在session启动前给出ops.reset_default_graph()是重新给出一个存储权值的空间,这个空间不是默认的空间,而是我们新给出来的,里面将会存储模型运行后接下来的权值。
2.
为什么DNN中的n_y=y_train.shape[0]?与n_y = Y_train.shape[1]?
答:分析下两者的标签值
在前面我已经在DNN中打印出了y_train_oh.shape的大小为:
(6,1080)
现在我打印下CNN中:

print(Y_train.shape)

输出结果为:

(1080, 6)

也就是说在介绍创建placeholder的时候我没有补充这个,其实这两种处理都是一样的,在DNN神经网络中,先从数据集中取出了标签值Y后没有对其转置。但是在CNN中对其进行了转置,随之在创建Placeholder前对其转置了。
两者的目的都是创建一个输出层为[n_y,None]大小。即:

y=tf.placeholder(tf.float32,[n_y,None],name="y")

3.
为什么需要有计算当前的预测情况的代码段?
答:在计算准确的时候,预测集数据就是训练集的数据,所以在预测的时候我们需要重新运行模型,并且将我们的训练集数据灌入进去。预测程序思路大致上和训练模型的思路是一致的,但是预测程序仅仅只有前向传播,并没有计算softmax的非线性激活值、损失值、以及反向传播。
预测程序代码:

##计算准确度
	 ## 计算当前的预测情况
       	   predict_op = tf.arg_max(Z3,1)#axis=1 按行为单位,获得每行中最大值的索引值
           corrent_prediction = tf.equal(predict_op , tf.arg_max(Y,1))#tf.equal:对比两个矩阵或者向量的相等的元素,如果是相等的那就返回True,反正返回False,返回的值的矩阵维度和A是一样的
           accuracy = tf.reduce_mean(tf.cast(corrent_prediction,"float"))#tf.cast,将前者转化为后者的变量类型
           print("corrent_prediction accuracy= " + str(accuracy))
           train_accuracy = accuracy.eval({X: X_train, Y: Y_train})#eval:等效于run,不过run可以同时运行多个tensor
           test_accuary = accuracy.eval({X: X_test, Y: Y_test})
           print("训练集准确度:" + str(train_accuracy))
           print("测试集准确度:" + str(test_accuary))
           return (train_accuracy,test_accuary,parameters)
   	   train_accuracy = accuracy.eval({X: X_train, Y: Y_train})#eval:等效于run,不过run可以同时运行多个tensor
	   test_accuary = accuracy.eval({X: X_test, Y: Y_test}) 
	   

accuracy.eval()的作用和session.run()类似,可以重复运行模型的前向传播而不使用ops.reset_default_graph()。
至于预测程序的代码解释不多说,思路就是:
1.取最后一层中线性运算后的值大小(没有经过softmax非线性运算),取最大的一个数(也就是最大概率)在那一行的索引号————>
2.然后取标签值数据中每行最大的索引号(这也是前面为什么说道在CNN中一开始对从数据集中的标签值进行转置操作的原因)——————>
3.通过 tf.equa()去对比这两个索引号的值是否相等,如果相等说明预测正确,返回TRUE
——————>
4.接着通过tf.cast()将bool型转换为float型 也就是转化为1.,0. 通过reduce_mean()对所有的样本取均值,计算为1的个数占总样本的比例——————>
5.将上面的框架搭建好了之后开始“Session.run()”,也就是 accuracy.eval({X:,Y:}),这里的placeholder就是训练集训练模型的时候创建的placeholder。————————>
6.最后我们打印accuracy.eval({X:,Y:})返回的结果,分别对训练集和测试集的准确率进行预测。

我们可以去计算下程序一共运行的时间,这个不管在哪个地方都是通用的,引入了time模块的clock()方法,返回的是以秒为单位的时间:

start_time=time.clock()
_, _, parameters = model(X_train, Y_train, X_test, Y_test,num_epochs=150)
end_time=time.clock()
print("cpu运行时间:",end_time-start_time,"(s)")

到此为止,比对就完毕了。下面说一下训练好的模型是如何保存的,万一我们想在另外一个python中调用他呢?总不能还得重新训练吧。

三、模型保存与调用

模型保存:
步骤是:在初始化全局变量后,使用tf.train.Saver()构造一个Saver对象(类),再调用该对象(类)下的save方法,调用这个save方法保存模型的时候肯定是在迭代完一次之后,即前向传播和反向传播之后保存,由于在tensorflow中,变量是存在于Session环境中,只有在Session环境下变量值才开始被读取,因此,保存模型时需要传入session。
注意save_path=saver.save(sess,’./model.ckpt’)是在Session.run()之后添加的。

saver=tf.train.Saver()

with tf.Session() as sess::
	_,_,cost=sess.run(...)
	save_path=saver.save(sess,'./Model/new_model.ckpt',global_step=10)返回的是字符串变量
	print('Model saveed in file: ',save_path)

可以通过设置save里面的参数global_step来指定多少次保存一次模型,这样即使在一个for epoch in epochs中 每次都执行save()方法时也不会每次都保存,因为我们设置的step可以不是1,可以是其他值如100:save_path=saver.save(sess,'./Model/new_model.ckpt',global_step=10),
这样就省去了在循环中判断当前epoch是多少的操作了。详细的可以看我文章最后方的参考1。

在Python中调用封装好的模型

上面我们已经训练好了一个模型存放在了Model文件夹下面,现在我们要做的就是新建一个py文件然后加载模型,之后读取模型中的变量:

import tensorflow as tf
import numpy as np
#load the parameters
with tf.Session() as sess:   
    #step1:Create the network
    new_saver=tf.train.import_meta_graph('./Model/new_model.ckpt-10.meta')
    #step2:load the parameters
    new_saver.restore(sess,tf.train.latest_checkpoint('./Model'))
    print(sess.run('W1:0'))

我们打印出的结果如下所示:
截图

可以看到参考1中说道:
a) Create the network:
You can create the network by writing python code to create each and every layer manually as the original model. However, if you think about it, we had saved the network in .meta file which we can use to recreate the network using tf.train.import() function like this: saver = tf.train.import_meta_graph(‘my_test_model-1000.meta’)
Remember, import_meta_graph appends the network defined in .meta file to the current graph. So, this will create the graph/network for you but we still need to load the value of the parameters that we had trained on this graph.
b) Load the parameters:
We can restore the parameters of the network by calling restore on this saver which is an instance of tf.train.Saver() class.

现在我们读取到了训练好的模型的参数了,但是我们利用这个模型去训练数据呢?
我们可以加载训练好的模型的 .meta 文件记录的graph网络结构信息,以及weights、biases、gradients等变量存放在了 .data-00000-of-00001.index 两个文件里。我们利用这三个去做点什么事情吧!

加载模型然后预测

参考1中有句话:Note that when the network is saved, values of the placeholders are not saved.
所以我们需要重新建立一个placeholder:
关于调用模型还是不要着急,试了下不行,这块我将作为一个学习模块详细学习。
08:50,在model模块将训练屏蔽后,加载了模型,可以,详细问题见predict_op
在这里插入图片描述

在AndroidStudio上加载模型

在.NETFrameWork上加载模型

四、未来计划

参考1:TensorFlow模型的保存与读取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值