具有神经网络思维的logistic回归
前言
构建一个简单的图像识别算法,这个算法可以将图片正确分类为猫和非猫
声明本文参考【Kulbear】的github上的文章
一.数据集的分类与理解
其实我在最开始的时候,对于什么是训练集和测试集甚至都感到过困惑。
这里参考了@Kieven2oo8的博客内容训练集、验证集、测试集以及交验验证的理解
一个形象的比喻:
训练集-----------学生的课本;学生 根据课本里的内容来掌握知识。
验证集------------作业,通过作业可以知道 不同学生学习情况、进步的速度快慢。
测试集-----------考试,考的题是平常都没有见过,考察学生举一反三的能力。
二.导入包和数据
1.需要用到的包
- numpy是使用Python进行科学计算的基本软件包。
- h5py是与存储在H5文件中的数据集进行交互的通用软件包。
- matplotlib是著名的Python图形库。
- PIL和scipy用来测试模型,最后带有自己的图片。
import numpy as np
import matplotlib.pyplot as plt
import h5py
import scipy
from PIL import Image
from scipy import ndimage
from lr_utils import load_dataset
%matplotlib inline
2.导入数据
# Loading the data (cat/non-cat)
train_set_x_orig, train_set_y, test_set_x_orig, test_set_y, classes = load_dataset()
解释一下上面各个加载数据的值的含义:
- train_set_x_orig :保存的是训练集里面的图像数据(本训练集有209张64x64的图像)。
- train_set_y_orig :保存的是训练集的图像对应的分类值(【0 | 1】,0表示不是猫,1表示是猫)。
- test_set_x_orig :保存的是测试集里面的图像数据(本训练集有50张64x64的图像)。
- test_set_y_orig :保存的是测试集的图像对应的分类值(【0 | 1】,0表示不是猫,1表示是猫)。
- classes :保存的是以bytes类型保存的两个字符串数据,数据为:[b’non-cat’ b’cat’]。
train_set_x_orig和test_set_x_orig的每一行都是代表图像的数组。
我们可以通过运行以下代码来形象化地看一下,
加载进来的数据集 它里面的图片是什么样的。
更改index的值并重新运行可以查看其他图像。
# Example of a picture
index = 23
plt.imshow(train_set_x_orig[index])
print ("y = " + str(train_set_y[:,index]) + ", it's a '" + classes[np.squeeze(train_set_y[:,index])].decode("utf-8") + "' picture.")
y = [0], it’s a ‘non-cat’ picture.
3.预处理新数据集的常见步骤
- 找出问题的维度(尺寸)和形状(m_train,m_test,num_px等)
- 重塑数据集的形状,使每个示例现在都是大小为(num_px * num_px * 3,1)的向量
- “标准化”数据
Many software bugs in deep learning come from having matrix/vector dimensions that don’t fit. If you can keep your matrix/vector dimensions straight you will go a long way toward eliminating many bugs.
1) 查找以下值
- m_train :number of training examples训练集里图片的数量
- m_test :number of test examples测试集里图片的数量
- num_px := height = width of a training image 训练集图片的高度或宽度(均为64*64)
⚠️注意:train_set_x_orig是一个维度为(m_train,num_px,num_px,3)的数组
m_train = train_set_y.shape[1]
m_test = test_set_y.shape[1]
num_px = train_set_x_orig.shape[1]
#看一下加载的具体情况
print ("Number of training examples: m_train = " + str(m_train))
print ("Number of testing examples: m_test = " + str(m_test))
print ("Height/Width of each image: num_px = " + str(num_px))
print ("Each image is of size: (" + str(num_px) + ", " + str(num_px) + ", 3)")
print ("train_set_x shape: " + str(train_set_x_orig.shape))
print ("train_set_y shape: " + str(train_set_y.shape))
print ("test_set_x shape: " + str(test_set_x_orig.shape))
print ("test_set_y shape: " + str(test_set_y.shape))
2) 重塑数据集的形状
为了方便,我们要把维度为(64,64,3)的numpy数组重新构造为(64 x 64 x 3,1)
这样操作以后,我们的训练集和测试集就被调整为了每列都代表一个平坦的图像的这样一个数组
也就是把像素大小为(num_px,num_px,3)的图像压成大小为(num_px num_px3,1)的向量
当你想要把一个形状为(a,b,c,d)的向量压成形状大小为(bcd,a)的二维向量的时候,可以使用这样一个小技巧:
X_flatten = X.reshape(X.shape[0],-1).T
#X.T是X的转置
*==========================================================*
此处补充一句:我当时不懂reshape函数中的参数‘-1’是什么意思
那么举个例子:假设有个一位数组a
>>> a=np.array([1,2,3,4,5,6,7,8,9,10])
>>> a.shape
(10,)
我想让它变成一个2列的二维数组,我要自己计算它的行数,10/2=5
>>> a.reshape(5,2)
array([[ 1, 2],
[ 3, 4],
[ 5, 6],
[ 7, 8],
[ 9, 10]])
如果我想要一个2列的二维数组,但是我又不想计算行数
>>> a.reshape(-1,2)
得到array([[ 1, 2],
[ 3, 4],
[ 5, 6],
[ 7, 8],
[ 9, 10]])
同理我想要一个5行的二维数组时
>>> a.reshape(5,-1)
得到array([[ 1, 2],
[ 3, 4],
[ 5, 6],
[ 7, 8],
[ 9, 10]])
*==========================================================*
train_set_x_flatten=train_set_x_orig.reshape(train_set_x_orig[0],-1).T
test_set_x_flatten=test_set_x_orig.reshape(test_set_x_orig[0],-1).T
train_set_x_orig[0]是训练集里面图片的数量,一共有209张图,那么我想要一个209行的二维矩阵,但是我懒得算列有多少,于是我就用-1告诉程序你帮我算,最后程序算出来时12288列,我再最后用一个T表示转置,这就变成了12288行,209列。测试集亦如此。
然后打印一下,看看具体规格是多少
print ("train_set_x_flatten.shape: " + str(train_set_x_flatten.shape))
print ("train_set_y.shape : " + str(train_set_y.shape))
print ("test_set_x_flatten.shape: " + str(test_set_x_flatten.shape))
print ("test_set_y.shape : " + str(test_set_y.shape))
print ("sanity check after reshaping: " + str(train_set_x_flatten[0:5,0]))
train_set_x_flatten.shape: (12288, 209)
train_set_y.shape : (1, 209)
test_set_x_flatten.shape: (12288, 50)
test_set_y.shape : (1, 50)
sanity check after reshaping: [17 31 56 22 33]
3) “标准化”数据
为了表示彩色图像,必须为每个像素指定红色,绿色和蓝色通道(RGB),因此像素值实际上是从0到255范围内的三个数字的向量。机器学习中一个常见的预处理步骤是对数据集进行中心化和标准化,这意味着可以减去每个示例中整个numpy数组的平均值,然后将每个示例除以整个numpy数组的标准偏差。但对于图片数据集,它更简单,更方便,几乎可以将数据集的每一行除以255(像素通道的最大值),因为在RGB中不存在比255大的数据,所以我们可以放心的除以255,让标准化的数据位于[0,1]之间,现在标准化我们的数据集:
train_set_x = train_set_x_flatten / 255
test_set_x = test_set_x_flatten / 255
三.学习算法的(一般结构)理论知识
然后我们就快要进入算法的主体部分了。将利用神经网络思维建立一个Logistic回归。这张图解释了Logistic回归实际上是一个非常简单的神经网络。
算法的数学表达式:
建立神经网络的关键步骤:
- Initialize the parameters of the model
- Learn the parameters for the model by minimizing the cost
- Use the learned parameters to make predictions (on the test set)
- Analyse the results and conclude
四.算法的主体部分
1.辅助函数sigmoid()
sigmoid(w ^ T x + b)
z=w ^ T x + b
def sigmoid(z):
s = 1 / (1 + np.exp(-z))
return s
可以测试一下这个函数
#测试sigmoid()
print("==================== 测试sigmoid ====================")
print ("sigmoid(0) = " + str(sigmoid(0)))
print ("sigmoid(9.2) = " + str(sigmoid(9.2)))
==================== 测试sigmoid ====================
sigmoid(0) = 0.5
sigmoid(9.2) = 0.999898970806
2.初始化参数
将w初始化为一个零向量,对b初始化为0
def initialize_with_zeros(dim):
w = np.zeros(shape=(dim, 1))
b = 0
# 使用断言来确保我要的数据是正确的
assert (w.shape == (dim, 1))
assert (isinstance(b, float) or isinstance(b, int))
return w, b
然后测试一下初始化函数
dim = 2
w, b = initialize_with_zeros(dim)
print ("w = " + str(w))
print ("b = " + str(b))
w = [[0.]
[0.]]
b = 0
3.前向传播和后向传播
前向传播:计算成本函数
后向传播:计算它的梯度,我的理解也就是对于w和b计算偏导数
def propagate(w, b, X, Y):
"""
实现前向和后向传播的成本函数及其梯度。
参数:
w - 权重,大小不等的数组(num_px * num_px * 3,1)
b - 偏差,一个标量
X - 矩阵类型为(num_px * num_px * 3,训练数量)
Y - 真正的“标签”矢量(如果非猫则为0,如果是猫则为1),矩阵维度为(1,训练数据数量)
返回:
cost- 逻辑回归的负对数似然成本
dw - 相对于w的损失梯度,因此与w相同的形状
db - 相对于b的损失梯度,因此与b的形状相同
"""
#m是图片的数量
m = X.shape[1]
# 向前:计算代价
A = sigmoid(np.dot(w.T, X) + b)
cost = (- 1 / m) * np.sum(Y * np.log(A) + (1 - Y) * np.log(1 - A))
# 向后:计算梯度
dw = (1 / m) * np.dot(X, (A - Y).T)
db = (1 / m) * np.sum(A - Y)
#使用断言确保我的数据是正确的
assert(dw.shape == w.shape)
assert(db.dtype == float)
cost = np.squeeze(cost)
assert(cost.shape == ())
#创建一个字典,把dw和db保存起来。
grads = {"dw": dw,
"db": db}
return grads, cost
测试一下
w, b, X, Y = np.array([[1], [2]]), 2, np.array([[1,2], [3,4]]), np.array([[1, 0]])
grads, cost = propagate(w, b, X, Y)
print ("dw = " + str(grads["dw"]))
print ("db = " + str(grads["db"]))
print ("cost = " + str(cost))
dw = [[0.99993216]
[ 1.99980262]]
db = 0.499935230625
cost = 6.00006477319
4.最优化–使用梯度下降方法来更新参数
学习其实就是学习w和b,用最合适的w和b来预测给定样本的标签。
通过最小化成本函数J,来学习w和b,学习其实也就是来更新w和b。
对于参数 θ,更新的规则是
这里的α就是学习率
def optimize(w , b , X , Y , num_iterations , learning_rate , print_cost = False):
"""
num_iterations - 优化循环的迭代次数
learning_rate - 梯度下降更新规则的学习率
print_cost - 每100步打印一次损失值
返回:
params - 包含权重w和偏差b的字典
grads - 包含权重和偏差相对于成本函数的梯度的字典
成本 - 优化期间计算的所有成本列表,将用于绘制学习曲线。
"""
costs = []
for i in range(num_iterations):
grads, cost = propagate(w, b, X, Y)
dw = grads["dw"]
db = grads["db"]
w = w - learning_rate * dw
b = b - learning_rate * db
#记录成本
if i % 100 == 0:
costs.append(cost)
#打印成本数据
if (print_cost) and (i % 100 == 0):
print("迭代的次数: %i , 误差值: %f" % (i,cost))
params = {
"w" : w,
"b" : b }
grads = {
"dw": dw,
"db": db }
return (params , grads , costs)
测试一下
params, grads, costs = optimize(w, b, X, Y, num_iterations= 100, learning_rate = 0.009, print_cost = False)
print ("w = " + str(params["w"]))
print ("b = " + str(params["b"]))
print ("dw = " + str(grads["dw"]))
print ("db = " + str(grads["db"]))
w = [[ 0.1124579 ]
[ 0.23106775]]
b = 1.55930492484
dw = [[ 0.90158428]
[ 1.76250842]]
db = 0.430462071679
五.用学习到的参数,预测标签
然后这一步,我们要用已经学习到的w和b预测数据集X的标签。
现在我们要实现预测函数predict()。计算预测有两个步骤:
1.计算
2.将a的值变为0(如果激活值<= 0.5)或者为1(如果激活值> 0.5),
然后将预测值存储在向量Y_prediction中。
def predict(w, b, X):
'''
返回:Y_prediction - 包含X中所有图片的所有预测【0 | 1】的一个numpy数组(向量)
'''
#图片的数量
m = X.shape[1]
Y_prediction = np.zeros((1, m))
w = w.reshape(X.shape[0], 1)
A = sigmoid(np.dot(w.T, X) + b)
for i in range(A.shape[1]):
Y_prediction[0, i] = 1 if A[0, i] > 0.5 else 0
assert (Y_prediction.shape == (1, m))
return Y_prediction
测试一下
w, b, X, Y = np.array([[1], [2]]), 2, np.array([[1,2], [3,4]]), np.array([[1, 0]])
print("predictions = " + str(predict(w, b, X)))
predictions = [[ 1. 1.]]
六.把所有的函数整合到一个模块中
def model(X_train , Y_train , X_test , Y_test , num_iterations = 2000 , learning_rate = 0.5 , print_cost = False):
"""
参数:
num_iterations - 表示用于优化参数的迭代次数的超参数
learning_rate - 表示optimize()更新规则中使用的学习速率的超参数
print_cost - 设置为true以每100次迭代打印成本
"""
#先初始化w和b
w , b = initialize_with_zeros(X_train.shape[0])
#用最优化函数,学习w和b,返回字典
parameters , grads , costs = optimize(w , b , X_train , Y_train,num_iterations , learning_rate , print_cost)
#从字典“参数”中检索参数w和b
w , b = parameters["w"] , parameters["b"]
#预测测试/训练集的例子
Y_prediction_test = predict(w , b, X_test)
Y_prediction_train = predict(w , b, X_train)
#打印训练后的准确性
print("训练集准确性:" , format(100 - np.mean(np.abs(Y_prediction_train - Y_train)) * 100) ,"%")
print("测试集准确性:" , format(100 - np.mean(np.abs(Y_prediction_test - Y_test)) * 100) ,"%")
d = {
"costs" : costs,
"Y_prediction_test" : Y_prediction_test,
"Y_prediciton_train" : Y_prediction_train,
"w" : w,
"b" : b,
"learning_rate" : learning_rate,
"num_iterations" : num_iterations }
return d
测试一下
print("====================测试model====================")
#这里加载的是真实的数据,请参见上面的代码部分。
d = model(train_set_x, train_set_y, test_set_x, test_set_y, num_iterations = 2000, learning_rate = 0.005, print_cost = True)
更改迭代次数和学习率,试一下结果