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,简单来说就是保证它是一个数。
对于数据,我们还经常对其做归一化处理。在图像处理中,图像的每个像素点可以看作一种特征。
这里我们要做的是
- 先把图片的像素点(这里每张图片的像素点有个)展开。
- 再除以255,因为每个像素点的最大值就是255,这样可以保证每个像素点大小的范围都控制在之间。
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
简单说明:
-
np.eye()返回一个单位矩阵。
-
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).
因为我们的目标是构建一个三层的神经网络,所以这三层的权重和偏置分别是
第一层 神经元数25;
第二层 神经元数12;
第三层 神经元数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
落实到代码
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中,前馈中最后的线性输出会直接作为输入喂给损失函数,所以不需要计算(当然写上也没有影响).
4.4 compute_cost(Z3, Y)
计算损失函数,在如tensorflow在内的深度学习框架下一句话就可以完成
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits = ..., labels = ...))
但是这里需要说明的是对于参数logits
和labels
,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%,说明过拟合。接下来可以考虑正则化,增大数据集等方法。
总结
没什么总结的。就宝宝❤️新年快乐吧。笑
网友评论