Andrew NG老师给的不是一张张的数据图片,是以h5的格式存储的图片作为数据集
# 导入h5py库,Python带的库
import h5py
# 导入训练的原始数据,使用h5py.File的方法
train_data = h5py.File('datasets/train_catvnoncat.h5','r')
test_data = h5py.File('datasets/test_catvnoncat.h5','r')
h5文件的格式类似字典,数据由键和值组成。
# 含有三个键值,必须得遍历才能打印出每个键
for key in train_data.keys():
print(key)
打印结果如下:
list_classes
train_set_x
train_set_y
说明这个数据包里包含三个键,通过三个键存储训练集,测试集以及标签
list_classes: | 类别的标签,即是猫还是不是猫 |
---|---|
train_set_x: | 输入的图片 |
train_set_y: | 对输入图片的分类 |
首先我们要弄清楚这个数据包里的数据形式,可以通过字典的形式查看数据,通过np.shape
来打印出数据的维度以便观察。代码如下:
train_data['train_set_x'].shape
输出结果:(209, 64, 64, 3)
输出结果表示训练集含有209张图片,每张图片的像素为64*64,每个像素点含有RGB三原色。
再来看看训练集的标签的维度:
train_data['train_set_y'].shape
输出结果:(209,)
输出结果表示:
- 标签的输出为209,每个样本对应一个0或1的输出值
- 此处输出的是元组类型,是一个包含209个元素的数组
同理测试集的维度如下:
test_data['test_set_x'].shape
test_data['test_set_y'].shape
测试集数据的维度:(50, 64, 64, 3)
测试集标签的维度:(50,)
我们已经成功清楚了该数据包里数据的结构组成,但是现在数据还存在原始的h5文件属性里,里面的数据带键和值很麻烦,所以我们直接取出来用变量存取,现在取出来的是原始的数据,后面我们还要对数据进行处理。
# 取出训练集 测试集数据
train_data_org = train_data['train_set_x'][:]
train_labels_org = train_data['train_set_y'][:]
test_data_org = test_data['test_set_x'][:]
test_labels_org = test_data['test_set_y'][:]
原始的数据其实是一张张图片,我们现在可以查看图片里面都是些什么,举一个例子,我们现在对其中一张图片进行索引展示:
import matplotlib.pyplot as plt
%matplotlib inline
# 显示训练集里的第178张图片,刚好它是一只猫,图片模糊的原因是像素太低
plt.imshow(train_data_org[177])
输出显示如下,太巧了它正好是一只猫:
Tips:
- 输入
%matplotlib inline
命令的意思是可以在线显示图片,因为我们在后面做完相关算法之后经常会显示一些图片来观察算法的性能,这样以后不用每次打印图片就使用 plt.imshow()
命令来显示图片,一般在Jupyter环境里才需要加入这个指令。
通过对逻辑回归问题里公式的推导我们确定了训练集的维度,标签的维度,以及参数的维度,推导过程如下:
但是数据包里提供的训练集和标签的维度与我们目标的维度不同,所以我们接下来要对数据的维度进行处理,维度关系如下:
程序处理如下:
# 数据维度的处理
# 取出的是维度里的第一个数,也就是样本格式
m_train = train_data_org.shape[0] #获取训练集的样本个数
m_test = test_data_org.shape[0] #获取测试集的样本个数
# 原始数据做维度的处理,64*64*3合并
train_data_tran = train_data_org.reshape(m_train,-1).T
test_data_tran = test_data_org.reshape(m_test,-1).T
print(train_data_tran.shape)
print(test_data_tran.shape)
调整后训练集的维度:(12288, 209)
调整后测试集的维度:(12288, 50)
同理我们对标签的维度进行处理:
# 将209*1列的维度转为1*209
import numpy as np
train_labels_tran = train_labels_org[np.newaxis,:] #行增加一个维度
test_labels_tran = test_labels_org[np.newaxis,:] #行增加一个维度
print(train_labels_tran.shape)
print(test_labels_tran.shape)
训练集标签维度:(1, 209)
测试集标签维度:(1, 50)
为了表示彩色图像,必须为每个像素指定红色,绿色和蓝色通道(RGB),因此像素值实际上是从0到255范围内的三个数字的向量。机器学习中一个常见的预处理步骤是对数据集进行居中和标准化,这意味着可以减去每个示例中整个numpy数组的平均值,然后将每个示例除以整个numpy数组的标准偏差。但对于图片数据集,它更简单,更方便,几乎可以将数据集的每一行除以255(像素通道的最大值),因为在RGB中不存在比255大的数据,所以我们可以放心的除以255,让标准化的数据位于[0,1]之间,现在标准化我们的数据集:
归一化公式:(train_data_tran - min)/(max - min)
# 标准化数据
train_data_sta = train_data_tran / 255
test_data_sta = test_data_tran / 255
print(train_data_sta[:9,:9])
[[0.06666667 0.76862745 0.32156863 0.00392157 0.03529412 0.32941176
0.21960784 0.0745098 0.24705882]
[0.12156863 0.75294118 0.27843137 0.08627451 0.03529412 0.30980392
0.22352941 0.08627451 0.26666667]
[0.21960784 0.74509804 0.26666667 0.00784314 0.01960784 0.19607843
0.09019608 0.00784314 0.23921569]
[0.08627451 0.75686275 0.34901961 0.00392157 0.03921569 0.32941176
0.20392157 0.09019608 0.25098039]
[0.12941176 0.72941176 0.3254902 0.05490196 0.03529412 0.30980392
0.21568627 0.10196078 0.26666667]
[0.23137255 0.71372549 0.3254902 0.00784314 0.02352941 0.2
0.0745098 0.00784314 0.23529412]
[0.09803922 0.7372549 0.39215686 0.00392157 0.03529412 0.30196078
0.16862745 0.10588235 0.26666667]
[0.1372549 0.70196078 0.38431373 0.05098039 0.03529412 0.28235294
0.20784314 0.11372549 0.27843137]
[0.24313725 0.68235294 0.40784314 0.00392157 0.02352941 0.17254902
0.06666667 0.00784314 0.22745098]]
以上预备工作就完成了,下面就可以搭建神经网络雏形了,以上需要注意的是对维度的转化一定要和推导的公式相对应。
对反向传播的推导过程如下:
这只是理论上的实现,在代码中实现时我们要对其进行向量化,如下:
建立神经网络的主要步骤是:
- 定义模型结构(例如输入特征的数量)
- 初始化模型的参数
- 循环:
3.1 计算当前损失(正向传播)
3.2 计算当前梯度(反向传播)
3.3 更新参数(梯度下降)
# 定义sigmoid函数
def sigmoid(z):
a = 1 / (1 + np.exp(-z))
return a
# 初始化参数
n_dim = train_data_sta.shape[0]
print(n_dim)
w = np.zeros((n_dim,1)) #初始化n行1列矩阵
b = 0
# 定义前向传播函数、代价函数以及梯度下降的偏微分
def propagate(w,b,X,y):
# 1.前向传播函数,即算A的值
z = np.dot(w.T,X) + b
A = sigmoid(z)
# 2.代价函数
m = X.shape[1] #列是样本个数
# 此处默认按列求和的,axis可以控制对列或者对行求和
J = -1/m * np.sum(y * np.log(A) + (1 - y) * np.log(1-A))
# 3.梯度下降
dw = 1/m * np.dot(X,(A-y).T)
db = 1/m * np.sum(A-y)
grands = {'dw':dw,'db':db}
return grands,J
# 优化部分
def optimize(w,b,X,y,alpha,n_iters,print_cost):
costs = [] #用来画图像
for i in range(n_iters):
grands,J = propagate(w,b,X,y)
dw = grands['dw']
db = grands['db']
# 迭代
w = w - alpha * dw
b = b - alpha * db
if i % 100 == 0:
costs.append(J)
if print_cost:
print('n_iters is ',i,' cost is ',J)
grands = {'dw':dw,'db':db}
params = {'w':w,'b':b}
return grands,params,costs
# 用优化好的w,b预测测试集,所以此处传入的形参为X_test方便观察
def predict(w,b,X_test):
z = np.dot(w.T,X_test) + b
A = sigmoid(z)
#取出样本数
m = X_test.shape[1]
y_pred = np.zeros((1,m)) #存放预测值
# 遍历所有的样本做判断
for i in range(m):
if A[:,i] > 0.5:
y_pred[:,i] = 1
else:
y_pred[:,i] = 0
return y_pred
- 上述传播函数里,我们把求导部分也放在了里面,是为了方便。
- 预测函数里需要对测试集重新求一次z和a。
下面对以上函数进行封装组件Logistic回归模型:
# 模型整合
def model(w,b,X_train,y_train,X_test,y_test,alpha,n_iters,print_cost):
grands,params,costs = optimize(w,b,X_train,y_train,alpha,n_iters,print_cost)
w = params['w']
b = params['b']
y_pred_train= predict(w,b,X_train)
y_pred_test = predict(w,b,X_test)
print('the train acc is',np.mean(y_pred_train == y_train)*100,'%')
print('the test acc is',np.mean(y_pred_test == y_test)*100,'%')
d = {
'w':w,
'b':b,
'costs':costs,
'y_pred_train':y_pred_train,
'y_pred_test':y_pred_test,
'alpha':alpha
}
return d
通过调用整合模型:
d = model(w,b,train_data_sta,train_labels_tran,test_data_sta,test_labels_tran,alpha=0.002,n_iters=2000,print_cost = True)
可以得出此时训练的过程以及准确率:
n_iters is 0 cost is 0.6931471805599453
n_iters is 100 cost is 0.5557515010876188
n_iters is 200 cost is 0.5068465769831776
n_iters is 300 cost is 0.47107861667750084
n_iters is 400 cost is 0.44232364132109203
n_iters is 500 cost is 0.4181394049164148
n_iters is 600 cost is 0.3972467456256616
n_iters is 700 cost is 0.37886680661383465
n_iters is 800 cost is 0.3624799165374718
n_iters is 900 cost is 0.3477178030467021
n_iters is 1000 cost is 0.33430809895082164
n_iters is 1100 cost is 0.32204279990791806
n_iters is 1200 cost is 0.31075900404024415
n_iters is 1300 cost is 0.30032649931928485
n_iters is 1400 cost is 0.2906394205566752
n_iters is 1500 cost is 0.2816104491476511
n_iters is 1600 cost is 0.2731666668190496
n_iters is 1700 cost is 0.26524652141708316
n_iters is 1800 cost is 0.25779756133061105
n_iters is 1900 cost is 0.25077471388028166
the train acc is 95.69377990430623 %
the test acc is 74.0 %
观察到,在训练集上的准确率很高,而在测试集上的准确率很低,发生了过拟合,以后再添加消除过拟合的方法。
我们可以打印一下代价函数与迭代次数的关系图像:
plt.plot(d['costs'])
plt.xlabel('per hundred iters')
plt.ylabel('costs')
也可以选择一个测试集的一个样本试试这组参数对于测试集的判断效果:
index = 2
print('y is ',test_labels_tran[0,index])
print('y_predition is ',int(d['y_pred_test'][0,index]))
输出结果:
y is 1
y_predition is 1
可以看出此时判断正确,它确实是一只猫。
下面我们通过改变一个超参数学习率看看准确率会有什么改变:
alphas = [0.01,0.001,0.0001]
for i in alphas:
print('alpha = ', i)
d = model(w,b,train_data_sta,train_labels_tran,test_data_sta,test_labels_tran,alpha = i,n_iters=2000,print_cost = False)
print('================')
plt.plot(d['costs'],label = str(i))
plt.xlabel(['per hundred iters'])
plt.ylabel('cost')
plt.legend
输出结果如下,同时打印了不同学习率下的曲线:
alpha = 0.01
the train acc is 99.52153110047847 %
the test acc is 70.0 %
================
alpha = 0.001
the train acc is 91.38755980861244 %
the test acc is 68.0 %
================
alpha = 0.0001
the train acc is 71.29186602870813 %
the test acc is 40.0 %
================
为了测试这组参数对于全新样本判断的正确性,我在网上找了一张猫的图片来测试它的准确性:
fname = 'datasets/kit.jpg'
image = plt.imread(fname)
plt.imshow(image)
通过查看该图片的维度,我们发现它与我们需要的维度不符合,所以要对其进行调整:
image.shape
原图像维度:(640, 1024, 3)
我们对原图像进行尺寸变换:
# 尺寸变换
from skimage import transform
image_tran = transform.resize(image,(64,64,3)).reshape(64*64*3,1)
变换后的维度如下:
image_tran.shape
变换后的维度:(12288, 1)
最后我们通过学习到的参数来进行预测:
y = predict(d['w'],d['b'],image_tran)
print(int(y))
可惜结果…它居然判断它不是一只猫,可见我们学习到的参数还不好,以后会加入对参数调整的方案我们才能调整学习的准确性。