美文网首页
手势识别:用tensorflow搭建一个三层的神经网络

手势识别:用tensorflow搭建一个三层的神经网络

作者: 霞客环肥 | 来源:发表于2019-02-11 21:50 被阅读0次
    hands.png

    1.0 SIGN Dataset

    每当我们要解决一个机器学习的问题时,需要先考虑数据集,因为数据集是一切学习的基础。

    手势识别的数据集(各种手势)来自NG的deeplearning.ai课程,其实就是他们实验室的大家【好像是个病句呢】手动在同一白板前拍的照片。

    训练集有1080张手势图片,像素为64乘64,分表代表从0到5.

    测试集有120张手势图片,像素同为64乘64,分表代表从0到5.

    其实这仅是SIGN Dataset的一个子集,完整数据集要更大一些。

    2.0 导入tensorflow等模块

    import numpy as np
    import tensorflow as tf
    import h5py
    import matplotlib.pyplot as plt
    from tensorflow.python.framework import ops
    

    这里说明一下h5py模块,这个模块主要是为了处理h5py文件,因为这次的手势数据的存储形式是h5py文件。

    3.0 导入数据集

    在我个人的电脑上


    image.png

    存储数据的文件夹叫'datasets',与我的神经网络文件在同一个文件夹下,故

    train_dataset = h5py.File('datasets/train_signs.h5', "r") 中的文件位置是一个相对路径。

    文件夹'datasets'下有2个h5文件,分别是训练集和测试集。


    image.png

    导入数据集的function的代码:

    def load_dataset():
        #以只读的形式读入训练集文件rain_signs.h5
        train_dataset = h5py.File('datasets/train_signs.h5', "r")
        train_set_x_orig = np.array(train_dataset["train_set_x"][:]) # 训练集特征
        train_set_y_orig = np.array(train_dataset["train_set_y"][:]) # 训练集标签
        
        #以只读的形式读入测试集文件rain_signs.h5
        test_dataset = h5py.File('datasets/test_signs.h5', "r")
        test_set_x_orig = np.array(test_dataset["test_set_x"][:]) # 测试集特征
        test_set_y_orig = np.array(test_dataset["test_set_y"][:]) # 测试集标签
    
        classes = np.array(test_dataset["list_classes"][:]) # 类别的列表
        
        #对训练集和测试集的标签做了一个统一处理,使得它们的shape都是(1,样本数)
        train_set_y_orig = train_set_y_orig.reshape((1, train_set_y_orig.shape[0]))
        test_set_y_orig = test_set_y_orig.reshape((1, test_set_y_orig.shape[0]))
        
        return train_set_x_orig, train_set_y_orig, test_set_x_orig, test_set_y_orig, classes
    

    那么自然,在定义了一个载入数据的函数之后,我们要引用这个函数来载入数据。

    X_train_orig, Y_train_orig, X_test_orig, Y_test_orig, classes = load_dataset()
    

    当然如果想查看一下数据到底长什么样(为了心里有个X数),我们可以选择某一个样本来可视化。

    可视化用matplotlib.pyplot来完成。

    index = 0
    plt.imshow(X_train_orig[index])
    print ("y = " + str(np.squeeze(Y_train_orig[:, index])))
    
    image.png

    其中,

    np.squeeze(Y_train_orig[:, index]))
    

    是为了保证Y_train_orig[:, index])的秩为1,简单来说就是保证它是一个数。

    对于数据,我们还经常对其做归一化处理。在图像处理中,图像的每个像素点可以看作一种特征。

    这里我们要做的是

    • 先把图片的像素点(这里每张图片的像素点有64*64*3=12288个)展开。
    • 再除以255,因为每个像素点的最大值就是255,这样可以保证每个像素点大小的范围都控制在(0, 1)之间。
    X_train = X_train_orig.reshape(X_train_orig.shape[0], -1).T/255
    X_test = X_test_orig.reshape(X_test_orig.shape[0], -1).T/255
    

    以上是对特征的处理,接下来介绍对标签的处理。

    这里引入一个新的概念 独热编码(One hot),又称一位有效编码。

    接下来涉及数电的知识,选读.

    One-hot编码主要采取N位状态寄存器来对N个状态进行编码,每个状态都由他独立的寄存器位,并且在任意时候只有一位有效。

    简单理解就是将一个我们日常用的十进制的数(1, 2, 3,...)转化为一个列向量,且只在对应位置置1,其他位置皆置0.

    One-hot编码的代码实现

    有2种方式,numpy或tensorflow(其实还有很多,如sklearn里也有集成,我这里只介绍这两种)。

    1. numpy实现,也是本次代码用到的。非常简单,强推

    优点: 除了简单之外,还有一点很重要,就是不会改变Y的秩

    之所以会提到shape是因为与下面要介绍的tensorflow里集成的tf.one_hot()语句相对比。

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

    简单说明:

    1. np.eye()返回一个单位矩阵。

    2. np.reshape(-1)的用法。

    • 最简单的方法np.reshape(-1)--将矩阵捋成一串。注意这一串既不是列向量也是行向量,是秩为1的数组。(不是向量或矩阵,处理数据经常报错,要避免)
    • np.reshape(a,-1),a为指定的数,表达的意思是:我不管这个矩阵到底是什么样子的,我就要这个矩阵是a行的,剩下的你计算机自己算完捋成一列就成,不要烦我。
    • np.reshape(-1,a),a为指定的数,表达的意思是:我不管这个矩阵到底是什么样子的,我就要这个矩阵是a列的,剩下的你计算机自己算完捋成一行就成,不要烦我。

      总结:-1是模糊控制。

    这样当引用convert_to_one_hot(Y, C)会返回Y的one_hot编码。

    简单结果展示:

    labels = np.array([1, 2, 0, 1, 3])
    one_hot = convert_to_one_hot(labels, 4)
    print(one_hot)
    

    结果是

    image.png

    此时我们可以对标签进行处理:

    Y_train = convert_to_one_hot(Y=Y_train_orig, C=6)
    Y_test = convert_to_one_hot(Y=Y_test_orig, C=6)
    
    2. tensorflow实现(选读)。

    实际上,tensorflow实现更为简单,毕竟只有一句的tf.one_hot()就可以完成。

    我们先来看一下的tf.one_hot()的参数。

    one_hot(
        indices,        #输入的tensor,在深度学习中一般是给定的labels,通常是数字列表,属于一维输入,也可以是多维。
        depth,          #一个标量,用于定义一个 one hot 维度的深度
        on_value=None,  #定义在 indices[j] = i 时填充输出的值的标量,默认为1
        off_value=None, #定义在 indices[j] != i 时填充输出的值的标量,默认为0
        axis=None,      #要填充的轴,默认为-1,即一个新的最内层轴
        dtype=None,     
        name=None
    )
    

    我们来看几个例子。

    • 输入为1维,即rank为1的数组。
    import tensorflow as tf
    
    labels = np.array([1, 2, 0, 1, 3])
    one_hot = tf.one_hot(indices=labels,depth=4,axis=-1)
    
    with tf.Session() as sess:
        print(sess.run(one_hot))
    

    结果是:横向转换


    image.png

    我们再细究一下label的shape,以及输出的shape。

    print("Labels'shape: ",labels.shape)
    print("one_hot'shape: ",one_hot.shape)
    
    image.png

    可见label是一个数组,维度是一维,秩(rank)为1,

    而输出one_hot是一个矩阵,维度为二维,秩(rank)为2.

    即如果输入的indices的秩为 N,则输出的秩为 N+1。上例中当axis = -1时,维度变化情况为(5,)->(5 , 4)。

    • 输入为2维。
    import tensorflow as tf
    import numpy as np
    
    labels = np.array([[1, 2, 0, 1, 3]])
    one_hot = tf.one_hot(indices=labels,depth=4,axis=-1)
    
    with tf.Session() as sess:
        print(sess.run(one_hot))
    

    输出:横向转换


    image.png

    我们再细究一下label的shape,以及输出的shape。

    print("Labels'shape: ",labels.shape)
    print("one_hot'shape: ",one_hot.shape)
    

    输出:


    image.png

    上例中当axis = -1时,维度变化情况为(1, 5)->(1, 5, 4)。

    那么axis = 0时呢?

    import tensorflow as tf
    import numpy as np
    
    labels = np.array([[1, 2, 0, 1, 3]])
    one_hot = tf.one_hot(indices=labels,depth=4,axis=0)
    
    with tf.Session() as sess:
        print(sess.run(one_hot))
    

    输出:纵向转换


    image.png

    我们再细究一下label的shape,以及输出的shape。


    image.png

    上例中当axis = 0时,维度变化情况为(1, 5)->(4, 1, 5)。

    可见在使用tf.one_hot()确实会有维度变化,要小心一些,以免后面矩阵相乘会报错。

    小结,回顾一下我们对数据做的所有预处理之后的结果

    print ("number of training examples = " + str(X_train.shape[1]))
    print ("number of test examples = " + str(X_test.shape[1]))
    print ("X_train shape: " + str(X_train.shape))
    print ("Y_train shape: " + str(Y_train.shape))
    print ("X_test shape: " + str(X_test.shape))
    print ("Y_test shape: " + str(Y_test.shape))
    
    image.png

    4.0 model会用到的functions

    4.1 create_placeholder(n_x, n_y)

    placeholder 是 Tensorflow 中的占位符,暂时储存变量.

    比较形象的比喻,可以想象成我们在大学期间,每次上课前都要先去占位置,虽然占完座位也不会马上去学习啦咳咳,但是位置还是要占的!

    Tensorflow 如果想要从外部传入data, 那就需要用到 tf.placeholder(), 然后以这种形式传输数据 sess.run(***, feed_dict={input: **}).

    在这里我们先为输入变量X,和输出Y占好位置,占的位置还要符合自己的身材。

    def create_placehoder(n_x, n_y):
        #n_x, n_y是输入X和输出Y的维度
        X = tf.placeholder(dtype=tf.float32, name='X', shape=[n_x, None])
        Y = tf.placeholder(dtype=tf.float32, name='Y', shape=[n_y,None])
        
        return X, Y
    

    接下来, 传值的工作交给了 sess.run() , 需要传入的值放在了feed_dict={} 并一一对应每一个 input. placeholder 与 feed_dict={} 是绑定在一起出现的.

    4.2 initialize_paremeters()

    初始化参数有多种方法,包括

    • 零初始化
    • 随机初始化
    • He初始化
    • 等等

    在这里我们用Xavier Initialization 初始化权重(weights),用Zero Initialization初始化偏置(biases).

    因为我们的目标是构建一个三层的神经网络,所以这三层的权重和偏置分别是

    第一层W1, b1, 神经元数25;

    第二层W2, b2, 神经元数12;

    第三层W3, b3 神经元数6。

    代码

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

    这里用到了tf.get_variable()。使用 tf.get_variable 创建变量,最简单的方法只需提供名称和形状即可。

    my_variable=tf.get_variable("my_variable", [1, 2, 3])

    这将创建一个名为“my_variable”的变量,该变量是形状为 [1, 2, 3] 的三维张量。默认情况下,此变量将具有 dtypetf.float32,其初始值将通过 tf.glorot_uniform_initializer 随机设置。

    只是这里我们分别用Xavier Initialization和Zero Initialization来进行初始化。

    4.3 forward_propagation(X, parameters)

    前向传播过程:

    LINEAR -> RELU -> LINEAR -> RELU -> LINEAR -> SOFTMAX

    Z^{[1]} = W^{(1)}X + b^{[1]}

    A^{[1]} = relu(Z^{[1]})

    Z^{[2]} = W^{(2)}A^{[1]} + b^{[2]}

    A^{[2]} = relu(Z^{[2]})

    Z^{[3]} = W^{(3)}A^{[2]} + b^{[3]}

    A^{[3]} = softmax(Z^{[3]})

    落实到代码

    def forward_propagation(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, 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)
        
        return Z3
    

    这里着重说一下为什么只计算到Z3.

    因为在tensorflow中,前馈中最后的线性输出Z^{[3]} = W^{(3)}A^{[2]} + b^{[3]}会直接作为输入喂给损失函数,所以不需要计算A^{[3]}(当然写上也没有影响).

    4.4 compute_cost(Z3, Y)

    计算损失函数,在如tensorflow在内的深度学习框架下一句话就可以完成

    cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits = ..., labels = ...))

    但是这里需要说明的是对于参数logitslabels,shape需要是

    (number of examples, num_classes)

    但是我们预处理出来的数据的shape是

    (num_classes, number of examples)

    所以需要转置。

    代码如下:

    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 
    

    4.5 random_mini_batches(X, Y, mini_batch_size = 64, seed = 0)

    我们之所以要定义这个函数是因为在后馈运算我们用的优化算法是 mini-batch gradient descent。这里不赘述mini-batch gradient descent的原理。

    直接看代码:

    def random_mini_batches(X, Y, mini_batch_size = 64, seed = 0):
        m = X.shape[1] #m即样本数
        mini_batches = [] #空列表,为了后面存储mini_batch
        np.random.seed(seed)
        
        permutation = list(np.random.permutation(m)) #得到[0, m)]间m个整数数的随机排列
        shuffled_X = X[:, permutation]#按照上诉顺序重排样本特征顺序
        shuffled_Y = Y[:, permutation]#按照上诉顺序重排样本标签顺序
        
        #样本数/mini_batch_size不一定是整数,那么先取整
        num_complete_minibatches = m//mini_batch_size
        
        for k in range(num_complete_minibatches):
            #对X,Y切片
            mini_batch_X = shuffled_X[:, mini_batch_size*(k): mini_batch_size*(k + 1)]
            mini_batch_Y = shuffled_Y[:, mini_batch_size*(k): mini_batch_size*(k + 1)]
            mini_batch = (mini_batch_X, mini_batch_Y)
            mini_batches.append(mini_batch)
        
        #剩余部分    
        if m%mini_batch_size != 0:
            mini_batch_X = shuffled_X[:, mini_batch_size*num_complete_minibatches: m]
            mini_batch_Y = shuffled_Y[:, mini_batch_size*num_complete_minibatches: m]
            mini_batch = (mini_batch_X, mini_batch_Y)
            mini_batches.append(mini_batch)
            
        return mini_batches
        
    

    5.0 model

    终于到了model,把上面那些functions用起来~

    代码走一波:

    def model(X_train, Y_train, X_test, Y_test, learning_rate = 0.0001,
             num_epochs = 1500, minibatch_size = 32, print_cost = True):
        
        ops.reset_default_graph()
        tf.set_random_seed(1)
        seed = 3
        (n_x, m) = X_train.shape
        n_y = Y_train.shape[0]
        costs = []
        
        X, Y = creat_placeholders(n_x, n_y)
        
        parameters = initialize_parameters()
        
        Z3 = forward_propagation(X, parameters)
        
        cost = compute_cost(Z3=Z3, Y=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):
                
                epoch_cost = 0.
                num_minibatches = m//minibatch_size
                seed += 1
                minibatches = random_mini_batches(X_train, Y_train, minibatch_size, seed)
                
                for minibatch in minibatches:
                    (minibatch_X, minibatch_Y) = minibatch
                    
                    _, minibatch_cost = sess.run([optimizer, cost], feed_dict = {X: minibatch_X, Y: minibatch_Y})
                    
                    epoch_cost += minibatch_cost/num_minibatches
                    
                if print_cost and epoch%100 == 0:
                    print('Cost after epoch {}: {}'.format(epoch, epoch_cost))
                if print_cost and epoch%5 == 0:
                    costs.append(epoch_cost)
                
            plt.plot(np.squeeze(costs))
            plt.ylabel('cost')
            plt.xlabel('iterations (per tens)')
            plt.title("Learning rate =" + str(learning_rate))
            plt.show()
            
            correct_prediction = tf.equal(tf.argmax(Z3), tf.argmax(Y))
            
            accuracy = tf.reduce_mean(tf.cast(correct_prediction, 'float'))
            
            print("Train Accuracy:", accuracy.eval({X: X_train, Y: Y_train}))
            print("Test Accuracy:", accuracy.eval({X: X_test, Y: Y_test}))
            
            return parameters
    

    tensorflow的逻辑是,先画好计算图,再往里面输入数据。所以看到

    X, Y = creat_placeholders(n_x, n_y)
    
    parameters = initialize_parameters()
    
    Z3 = forward_propagation(X, parameters)
    
    cost = compute_cost(Z3=Z3, Y=Y)
    
    optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost)
    
    init = tf.global_variables_initializer()
    

    这些都是在构建计算图,此时并没有计算。

    接下来激活计算图,传入数据,画图,返回参数parameters等,都要在with tf.Session() as sess:之下。

    上一下结果


    image.png

    以及在训练集和测试集的正确率。


    image.png

    从正确率可以看出 训练集正确率>>测试集正确率,且训练集正确率接近100%,说明过拟合。接下来可以考虑正则化,增大数据集等方法。

    总结

    没什么总结的。就宝宝❤️新年快乐吧。笑

    相关文章

      网友评论

          本文标题:手势识别:用tensorflow搭建一个三层的神经网络

          本文链接:https://www.haomeiwen.com/subject/kcaxeqtx.html