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

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%,说明过拟合。接下来可以考虑正则化,增大数据集等方法。

总结

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,175评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,674评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,151评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,597评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,505评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,969评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,455评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,118评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,227评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,213评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,214评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,928评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,512评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,616评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,848评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,228评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,772评论 2 339

推荐阅读更多精彩内容