003.00 监督式学习
003.00 监督式学习#
建檔日期: 2019/09/17#
更新日期: None#
相关软件信息:
Win 10 | Python 3.7.2 |
说明:所有内容欢迎引用,只需注明来源及作者,本文内容如有错误或用词不当,敬请指正.
主题: 003.00 监督式学习#
前言:#
看到人工智能,机器学习,再到深度学习,一大堆杂七杂八的数据,得花多久的时间才能搞懂作什么,先找了一个范例,这个范例是采用 100 张的明星照片,建立一个模型用来分辨照片中的明星是男是女,先区分为 80 张的训练组及 20 张的测试组,除了图片以外,还要自行确认每张照片中的明星是男是女,建立性别数据。该模型输出只有两种类别,男或女,或者说是不是男的,或者说是不是女的,这是一种二选一分类的模型,不同的功能会有不同的模型,这里采用的是逻辑回归的算法.
#
本范例主要的是采用 numpy 来作所有的处理,而不是如其的人工智能库给你一个函数,什么都作完了,这样你不会清楚知道发生什么事。在本范例中了解了整个模型建构及算法细节,感觉在人工智能上跨了一大步.#
范例来源: https://datascienceintuition.wordpress.com...
要一步一步的说明细节,不如看下图比较直接,后面再稍作解释,就容易看懂了.#
1. 准备数据作为输入#
a. 照片来源#
照片当然可以自己拍,不过挺花时间的,在学习阶段,直接上网下载就行,网上有很多数据库免费提供,比如: https://lionbridge.ai/datasets/the-50-best....
一般的库大都是一行就下载下来,这里是从网页中读取 100 张的照片,存放到子目录中,下次就不用再下载一次
def load_100_pictures():
if not os.path.exists('img_align_celeba'):
os.mkdir('img_align_celeba')
for img_i in range(1, 101):
f = '000%03d.jpg' % img_i
url = 'https://s3.amazonaws.com/cadl/celeb-align/' + f
print(url, end='\r')
urllib.request.urlretrieve(url, os.path.join('img_align_celeba', f))
print('Celeb Net dataset already downloaded')
b. 资料分为两群,训练群及测试群.#
一般测试群大约占 1/4 数据,这个比例可以自行分配。训练群用来让模型找到自己内部最佳的参数,也就是 wi 及 b. 测试群用来测试最后模型的准确性.
c. 照片为 jpg 檔#
每一个像素都含有三个 0~255 的 R,G,B 值,光的三要素 R (红),G (绿),B (蓝) 合成在一起,才是该像素的颜色.
d. 照片资料的 shape 为 (100 张,218 高,178 寛,3 色)#
在输入到模型中,必须先处理一下,才能符合模型的输入要求。除了简单化,也可以减少内存的使用和处理的时间。不过,过度的简化可能会造成信息量的流失,造成模型建造失败.
e. 每张照片高 218 像素,寛 178 像素#
我们先裁成 (178, 178) 的正方形。照片群就变成 (100 张,178 高,178 寛,3 色), 数据量一下就少了 20% 左右.
def imcrop_tosquare(img):
if img.shape[0] > img.shape[1]:
extra = (img.shape[0] - img.shape[1]) // 2
crop = img[extra:-extra, :]
elif img.shape[1] > img.shape[0]:
extra = (img.shape[1] - img.shape[0]) // 2
crop = img[:, extra:-extra]
else:
crop = img
return crop
f. 将照片的尺寸由 (178, 178) 改为 (64, 64)#
缩小的照小在人眼看来,仍然可以轻易辨认出性别来。照片群就变成 (100 张,64 高,64 寛,3 色), 因为这个尺寸是以平方来计算的,所以数据量一下就少了 87% 左右,总计数据量只有约原来的 10%. 当然,我们还可以再把 RGB 三色转换为灰阶,数据量就会变成只有约原来的 3.3%, 不过我们还是保留没有这样作,当然各位可以试试.
imgs = []
for file_i in files:
# 照片格式都是(218, 178, 3), 高218, 寛178, RGB三色/每点
img = plt.imread(file_i)
# 裁成(178,178,3), 正方形照片
square = imcrop_tosquare(img)
# 以LANCZOS方法来重设大小为(64,64,3),
# 足以辨识就可以了, 照片太大, 处理时间与内存都会占用太多
rsz = np.array(Image.fromarray(square).resize((64, 64), resample=Image.LANCZOS))
# list结果为(100, 64, 64, 3), 100笔, 64x64, RGB三色的照片资料
imgs.append(rsz)
g. 再把数据从 0 ~ 255 转为 0 ~ 1#
在逻辑回归計算时,有使用到指数的运算,大于 1 的数,很容易就会造成系统数值溢出错误.
data = np.array(imgs)/255
h. 建立資料标签#
照片资料中并没有我们所要的性别数据,所以我们要自己看照片判断建立。还有建立两个分类的标签 [‘Male 男’, ‘Female 女’], 这里我们定义 0 为男性,1 为女性.
classes=np.array(['Male', 'Female'])
y=np.array([1,1,0,1,1,1,0,0,1,1,1,0,0,1,0,0,1,1,1,0,0,1,0,1,0,1,1,1,1,0,1,0,0,1,1,0,0,0,1,1,0,1,1,1,1,1,1,0,0,0,0,0,0,1,0,0,1,1,1,0,0,1,1,0,0,1,0,0,0,0,1,0,1,1,1,0,1,1,0,0,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1])
y=y.reshape(1,y.shape[0])
i. 把照片分为两群,训练群及测试群#
train_x_orig=data[:80,:,:,:]
test_x_orig=data[80:,:,:,:]
y_train=y[:,:80]
y_test=y[:,80:]
j. 最后我们要把二维的像素数据转换为一维的像素数据#
这样才能符合模型的输入要求。转换的方式是把每一行的点连续接在一起再转置,如此一来,训练组照片从 (80, 64, 64, 3) 变为 (12288,80), 测试组照片从 (20,64,64,3) 变为 (12288,20).
# 训练数据80笔, 测试资料20笔, 照片每边64点
m_train = train_x_orig.shape[0]
m_test = y_test.shape[1]
num_px = train_x_orig.shape[1]
# (80, 64, 64, 3) -> (1228,80), (20,64,64,3) -> (1228,20)
train_x = train_x_orig.reshape(train_x_orig.shape[0],-1).T
test_x = test_x_orig.reshape(test_x_orig.shape[0],-1).T
2. 对每一笔输入训练用数据的特征值 X 或 xi 随意设置权重 W 或 wi, 并随意设置一个偏差值 b#
在这里,简单为主,我们都设为 0
w = np.zeros((1,12288))
b = 0
3. 计算 sigma, Σ 值#
只要在计算前把矩阵的 shape, 也就是数据摆对方向及位置,就会很容易计算,这也是 numpy 数组强大的所在,简单的一个运算就把事情作完了.
sigma =np.dot(w,X)+b
4. 使用 sigmoid 函数#
将 Σ 作非线性转换成 - 1 ~ 1 的数值,得到我们的预测值 A 或 a, A 中的每一项 a 就代表该照片经模型预测的性别结果,因为在模型中,利用预测 A 与实际 Y 的差异去调整权重 wi 及偏差 b, 以得到更接近实际 Y 的预测 A, 这也是负回馈的一种应用.
def sigmoid(z):
# 建立sigmoid函数, 将输入值非线性转换为-1~+1的数值
s = 1/(1+np.exp(-z))
return s
A = sigmoid(np.dot(w,X)+b)
5. 以逻辑回归的方式,计算误差值#
这里称为成本 cost, 原则上,cost 越小越好,因为 cost 可以代表所有预测值与实际值的差异或误差值总和.
cost = (-1/m)*(np.dot(Y,np.log(A.T))+ np.dot((1-Y),np.log((1-A).T)))
6. 根据输入的特征值,预测值及测试用的实际值,计算成本对权重 wi 的导数 dw (实际上是 dcost/dw), 以及对偏差值 b 的导数 db (实际上是 dcost/db)#
在 sigma Σ 中,xi 不是一个变量,是已知的输入数据,wi 及 b 才是在模型作训练时的变量,所以我们要对 wi 及 b 来求导数,用来作为回归方法中的反馈参数。导数代表的意义是指该变量的变化量,所造成输出的变化量,导数值越大,变化量越大,我们要调整 wi 或 b 的量就越大,越接近最低点,导数值就越小,一直到导数值为 0, 或足够接近 0, 最低点的导数或斜率就是 0.
dw = (1/m)*np.dot((A-Y),X.T)
db = (1/m)*np.sum((A-Y))
7. 根据成本对权重 wi 的导数,对偏差值 b 的导数,以及 alpha 或 α, 修改 wi 及 b#
这里的 alpha 或 α, 用来对调整的量作一个限制,因为调整太多,会找不到最低点,调整太少会花太多的时间在调整上,所以 alpha 或 α 被称为学习速率。一般来说,alpha 值大都在 1 以下,当然也有使用 1 以上的情况,甚至可能是上千。这个值的设置是随情况而定的,也可以说是试出来的.
w = w-learning_rate*dw
b = b-learning_rate*db
8. 重复 N 次步骤 3 到步骤 7, 得到最小的 cost, 最适当的 wi 及 b 值.#
N 是模型更新 wi 及 b 的次数,多少次才是对的,这是不一定的,因为要看模型的复杂度,alpha 值的选择,通常有个上限,因为作得再多次,结果变化也不大。模型中所用的方法是数值分析的一种,基本上,作的更多次,会更靠近目标点,只是更靠近,并不一定会落在目标点上。简单的模型,可能很快就可以找到目标点,但在复杂的模型中,作得太多次可能会更糟糕,这种情形就叫作 over-fitting 过度拟合或过适,因为这里追求的是训练组的磨合,结果可能测试组的结果变差了,而我们真正要的结果是测试组的结果.
9. 使用该模型,根据已知最适当的 wi 及 b 值,计算出训练组数据的预测值#
与实际值作比较,确认训练后的正确率是否够好,不够好可以调整 alpha 值及 N 值,也有可能是该模型的建构方式无法符合要求,必须采用不同的模型来重新训练及测试.
10. 使用该模型,根据已知最适当的 wi 及 b 值,计算出测试组数据的预测值#
与实际值作比较,确认训练后,该模型的正确率是否够好,是否可以适用到一般的应用上.
结论:本文所述的模型因为简单,所以在输入层与输出层中,只有一层的 wi 及 b, 所计算出来的训练组正确率达到 100%, 但测试组的正确率却只有 65%, 可以有很多的方式来改善,比如:#
- 这里用的梯度下降法是最常用的方法,但只能在简单的模型中找到目标点;在复杂的模型中可能只是找到局部的目标点,而不是全局的目标点.
- 使用更多层的 wi 和 b, 这代表更复杂的模型,在很多人工智能的建模都可以看到,如下图.
- 数据量的保留,缩减通常会造成信息的流失
- 改变不同的算法,比如模型中的 sigma Σ, sigmoid, cost, 回归法等等
输出结果:#
运算次数与 cost 的对照表
测试组中预测错误的七张照片
原照片与经加权 / 偏移计算的照片比对
附件 (程序)#
# -*- coding: utf-8 -*-
import os
import urllib.request
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
def load_100_pictures():
if not os.path.exists('img_align_celeba'):
os.mkdir('img_align_celeba')
for img_i in range(1, 101):
f = '000%03d.jpg' % img_i
url = 'https://s3.amazonaws.com/cadl/celeb-align/' + f
print(url, end='\r')
urllib.request.urlretrieve(url, os.path.join('img_align_celeba', f))
print('Celeb Net dataset already downloaded')
# 从网站下载100张明星照片, 在到img_align_celeba目录下, 如果目录已存在, 就不下载了
load_100_pictures()
# 从目录中取得所有JPG文件的档名(长度100的list)
files = [os.path.join('img_align_celeba', file_i) for file_i in os.listdir('img_align_celeba') if '.jpg' in file_i]
# 自行建立一个数组, 内容为各照片中明星的性别, 0为男性, 1为女性
# shape从(100,)改为(1,100)
y=np.array([1,1,0,1,1,1,0,0,1,1,1,0,0,1,0,0,1,1,1,0,0,1,0,1,0,1,1,1,1,0,1,0,0,1,1,0,0,0,1,1,0,1,1,1,1,1,1,0,0,0,0,0,0,1,0,0,1,1,1,0,0,1,1,0,0,1,0,0,0,0,1,0,1,1,1,0,1,1,0,0,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1])
y=y.reshape(1,y.shape[0])
# 建立分类标签内容'Male', 'Female'
classes=np.array(['Male', 'Female'])
# 目标分为两群, 前80张为训练用群, 后20张为测试群
y_train=y[:,:80]
y_test=y[:,80:]
# 截取照片成最大的正方形
def imcrop_tosquare(img):
if img.shape[0] > img.shape[1]:
extra = (img.shape[0] - img.shape[1]) // 2
crop = img[extra:-extra, :]
elif img.shape[1] > img.shape[0]:
extra = (img.shape[1] - img.shape[0]) // 2
crop = img[:, extra:-extra]
else:
crop = img
return crop
# 读入所有的照片
imgs = []
for file_i in files:
# 照片格式都是(218, 178, 3), 高218, 寛178, RGB三色/每点
img = plt.imread(file_i)
# 裁成(178,178,3), 正方形照片
square = imcrop_tosquare(img)
# 以LANCZOS方法来重设大小为(64,64,3),
# 足以辨识就可以了, 照片太大, 处理时间与内存都会占用太多
rsz = np.array(Image.fromarray(square).resize((64, 64), resample=Image.LANCZOS))
# list结果为(100, 64, 64, 3), 100笔, 64x64, RGB三色的照片资料
imgs.append(rsz)
# 降低数据大小, 避免在逻辑回归时, 指数运算造成系统数值溢出错误
data = np.array(imgs)/255
# 资料分为两群, 前80张为训练用群, 后20张为测试群
train_x_orig=data[:80,:,:,:]
test_x_orig=data[80:,:,:,:]
# 对只有两种结果的事件, 采用逻辑回归法
# 训练数据80笔, 测试资料20笔, 照片每边64点
m_train = train_x_orig.shape[0]
m_test = y_test.shape[1]
num_px = train_x_orig.shape[1]
# (80, 64, 64, 3) -> (12288,80), (20,64,64,3) -> (12288,20)
train_x = train_x_orig.reshape(train_x_orig.shape[0],-1).T
test_x = test_x_orig.reshape(test_x_orig.shape[0],-1).T
# Initialize parameters W and b
def initialize_with_zeros(dim):
# 建立w是一个内容都为0, 点数大小的数组(1, 点数12288), b为0
w = np.zeros((1,dim))
b = 0
# 确认w及b符合要求
assert(w.shape == (1, dim))
assert(isinstance(b, float) or isinstance(b, int))
return w, b
def sigmoid(z):
# 建立sigmoid函数, 将输入值非线性转换为-1~+1的数值
s = 1/(1+np.exp(-z))
return s
def propagate(w, b, X, Y):
'''
输入
w -- 加权数组, 对应到一张照片所有的点的三颜色, (点数*3, 1) (12288, 1)
b -- 偏差, 纯量
X -- 输入的数据数组, (点数*3, 训练用照片数) (12288, 80)
Y -- 结果卷标0, 1的数组, (1, 训练用照片数) (1, 80)
输出:
cost -- 负对数可能值, 是逻辑回归法中的成本或误差值
dw -- dcost/dw, (点数*3, 1)
db -- dcost/db, 纯量
'''
m = X.shape[1]
# 正向传递 (从输入到成本 X -> cost), A预测值(1, 80), cost = -1/m*sum(y*log(a)+(1-y)*log(1-a)
A = sigmoid(np.dot(w,X)+b)
cost = (-1/m)*(np.dot(Y,np.log(A.T))+ np.dot((1-Y),np.log((1-A).T)))
# 反向传递 (找出梯度grads), dZ=A-Y, dw = 1/m*(dZ . XT), db = mean(dZ)
dw = (1/m)*np.dot((A-Y),X.T)
db = (1/m)*np.sum((A-Y))
assert(dw.shape == w.shape)
assert(db.dtype == float)
cost = np.squeeze(cost)
assert(cost.shape == ())
grads = {"dw": dw,
"db": db}
return grads, cost
def optimize(w, b, X, Y, num_iterations, learning_rate, print_cost = False):
"""
利用梯度下降算法优化 w, b
输入
w -- 加权数组, 对应到一张照片所有的点的三颜色, (点数*3, 1) (12288, 1)
b -- 偏差, 纯量
X -- 所有输入的数据数组, (点数*3, 训练用照片数) (12288, 80)
Y -- 结果卷标0, 1的数组, (1, 训练用照片数) (1, 80)
num_iterations -- 参数优化运算次数
learning_rate -- 参数修正学习速率
print_cost -- 是否每100次运算, 打印成本cost
输出
params -- { } w, b
grads -- {'dw':dw, 'db':db'} cost对w的导数, cost对b的导数
costs -- [ ] 所有cost记录, 供画图使用
"""
costs = []
for i in range(num_iterations):
# 按输入数据, 目标卷标, 模型中的权重及偏移值计算成本Cost及梯度
grads, cost = propagate(w, b, X, Y)
# 从grads中取回dw, db
dw = grads["dw"]
db = grads["db"]
# 更新权重及偏差
w = w-learning_rate*dw
b = b-learning_rate*db
# 每100次记录下cost值
if i % 100 == 0:
costs.append(cost)
# 是否每100次打印cost
if print_cost and i % 100 == 0:
print ("Cost after iteration %i: %f" %(i, cost))
# 画cost曲线
plt.rcParams['figure.figsize'] = (10.0, 10.0)
plt.plot(np.squeeze(costs))
plt.ylabel('cost')
plt.xlabel('iterations (per hundreds)')
plt.title("Learning rate =" + str(learning_rate))
plt.show()
params = {"w": w, "b": b}
grads = {"dw": dw, "db": db}
return params, grads, costs
def predict(w, b, X):
'''
按已优化的 w, b 预测输入的照片结果
输入
w -- 模型中已优化的权重 (12288, 1)
b -- 偏差值
X -- 输入料数组 (12288, 照片数)
输出
Y_prediction -- 预测结果
'''
m = X.shape[1]
Y_prediction = np.zeros((1,m))
#w = w.reshape(X.shape[0], 1)
# 计算预测值, 并转成0~1的机率值, 再四舍五入取整数
A = sigmoid(np.dot(w,X)+b)
Y_prediction=np.round(A)
assert(Y_prediction.shape == (1, m))
return Y_prediction
def model(X_train, Y_train, X_test, Y_test, num_iterations = 2000, learning_rate = 0.5, print_cost = False):
"""
建立逻辑回归模型
输入参数
X_train -- 训练用输入的数据数组, (点数*3, 训练用照片数) (12288, 80)
Y_train -- 训练用结果卷标0, 1的数组, (1, 训练用照片数) (1, 80)
X_test -- 测试用输入的数据数组, (点数*3, 测试用照片数) (12288, 20)
Y_test -- 测试用结果卷标0, 1的数组, (1, 训练用照片数) (1, 20)
num_iterations -- 参数优化运算次数
learning_rate -- 参数修正学习速率
print_cost -- 是否每100次运算, 打印成本cost
输出:
d -- { } 有关模型的信息
"""
# 初值选定 (1, 12288)
m_train=X_train.shape[0]
w, b = initialize_with_zeros(m_train)
# 梯度下降优化
parameters, grads, costs = optimize(w, b, X_train, Y_train, num_iterations= num_iterations, learning_rate = learning_rate, print_cost = print_cost)
# 取出 w, b
w = parameters["w"]
b = parameters["b"]
# 检查训练组及测试组
Y_prediction_test = predict(w, b, X_test)
Y_prediction_train = predict(w, b, X_train)
# 打印检查正确率
print("train accuracy: {} %".format(100*(1 - np.mean(np.abs(Y_prediction_train - Y_train)) )))
print("test accuracy: {} %".format(100*(1 - np.mean(np.abs(Y_prediction_test - Y_test)) )))
d = {"costs": costs,
"Y_prediction_test": Y_prediction_test,
"Y_prediction_train" : Y_prediction_train,
"w" : w,
"b" : b,
"learning_rate" : learning_rate,
"num_iterations": num_iterations}
return d
d = model(train_x, y_train, test_x, y_test, num_iterations = 1000, learning_rate = 0.005, print_cost = True)
def print_mislabeled_images(classes, X, y, p):
"""
显示在测试组判断错误的照片
X -- 照片资料组
y -- 正确标签组
p -- 预测标签组
"""
a = p + y
mislabeled_indices = np.asarray(np.where(a == 1))
plt.rcParams['figure.figsize'] = (40.0, 40.0) # set default size of plots
num_images = len(mislabeled_indices[0])
for i in range(num_images):
index = mislabeled_indices[1][i]
plt.subplot(2, num_images, i + 1)
plt.imshow(X[:,index].reshape(64,64,3), interpolation='sinc')
plt.axis('off')
plt.rc('font', size=10)
plt.title("Prediction: " + classes[int(p[0,index])] + " \n Class: " + classes[y[0,index]])
plt.show()
print_mislabeled_images(classes, test_x, y_test, d["Y_prediction_test"])
# 使用sinc函数来重建经过比重后的照片
test=d["w"].T*train_x*255
test=test.T.reshape(80,64,64,3)
plt.rcParams['figure.figsize'] = (10.0, 10.0)
plt.imshow(test[0], interpolation='sinc')
# reconstructed data by others
methods = [None, 'none', 'nearest', 'bilinear', 'bicubic', 'spline16',
'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric',
'catrom', 'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos']
# Fixing random state for reproducibility
np.random.seed(19680801)
fig, axes = plt.subplots(3, 6, figsize=(24, 12),
subplot_kw={'xticks': [], 'yticks': []})
fig.subplots_adjust(hspace=0.3, wspace=0.05)
for ax, interp_method in zip(axes.flat, methods):
ax.imshow(test[0], interpolation=interp_method, cmap=None)
ax.set_title(interp_method)
plt.show()
# compare the reconstructed images vs original.
def montage(images, saveto='montage.png'):
"""Draw all images as a montage separated by 1 pixel borders.
Also saves the file to the destination specified by `saveto`.
Parameters
----------
images : numpy.ndarray
Input array to create montage of. Array should be:
batch x height x width x channels.
saveto : str
Location to save the resulting montage image.
Returns
-------
m : numpy.ndarray
Montage image.
"""
if isinstance(images, list):
images = np.array(images)
img_h = images.shape[1]
img_w = images.shape[2]
n_plots = int(np.ceil(np.sqrt(images.shape[0])))
if len(images.shape) == 4 and images.shape[3] == 3:
m = np.ones(
(images.shape[1] * n_plots + n_plots + 1,
images.shape[2] * n_plots + n_plots + 1, 3)) * 0.5
else:
m = np.ones(
(images.shape[1] * n_plots + n_plots + 1,
images.shape[2] * n_plots + n_plots + 1)) * 0.5
for i in range(n_plots):
for j in range(n_plots):
this_filter = i * n_plots + j
if this_filter < images.shape[0]:
this_img = images[this_filter]
m[1 + i + i * img_h:1 + i + (i + 1) * img_h,
1 + j + j * img_w:1 + j + (j + 1) * img_w] = this_img
#plt.imsave(arr=m, fname=saveto)
return m
compare = np.concatenate((test[52:54], data[52:54]), axis=0)
compare.shape
plt.imshow(montage(compare,saveto='montage.png'),interpolation='spline36')
plt.show()
plt.imshow(montage(compare,saveto='montage.png'),interpolation='bicubic')
plt.show()
# generate the montage with different interpolations for comparison.
methods = [None, 'none', 'nearest', 'bilinear', 'bicubic', 'spline16',
'spline36', 'hanning', 'hamming', 'hermite', 'kaiser', 'quadric',
'catrom', 'gaussian', 'bessel', 'mitchell', 'sinc', 'lanczos']
# Fixing random state for reproducibility
np.random.seed(19680801)
fig, axes = plt.subplots(3, 6, figsize=(24, 12),
subplot_kw={'xticks': [], 'yticks': []})
fig.subplots_adjust(hspace=0.3, wspace=0.05)
for ax, interp_method in zip(axes.flat, methods):
ax.imshow(montage(compare,saveto='montage.png'), interpolation=interp_method, cmap=None)
ax.set_title(interp_method)
plt.show()
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: