利用非监督学习的自编码器,实现数据可视化降维
实验时长:90分钟
主要步骤:
数据准备
前向计算
误差反向传播
自编码器三层神经网络训练所需的参数
结果展示
虚拟机数量:1
系统版本:CentOS 7.5
Python版本: 2.7
Scipy版本:1.1.0
Numpy版本:1.15.1
Matplotlib版本:2.2.3
6.1实验数据准备
[zkpk@master ~]$ cp -r experiment/06/ /home/zkpk/
[zkpk@master ~]$ cd 06/
[zkpk@master 06]$ su
[root@master 06]$ yum install tkinter
[root@master 06]$ exit
exit
[zkpk@master 06]$
6.2自编码器实现
6.2.1创建python文件,编写代码unsupervised.py
[zkpk@master 06]$ vim unsupervised.py
6.2.2导入需要的包:
# -*- coding: utf-8 -*-
import scipy.io as scio
import numpy as np
import matplotlib.pyplot as plt
import random
6.2.3导入数据,数据是基于minist手写体数字数据库中的500张图片,每个数字(0-9)都有其对应的50张手写体图片,图片的维度为28 * 28
def data(data):
trainData = scio.loadmat(data) # loadmat加载MATLAB文件
unlabeled_data = trainData['trainData']
unlabeled_data = unlabeled_data[:, :] / 255.
return unlabeled_data
6.2.4前向计算代码实现:
#前向计算
# w:权值矩阵 a:神经网络内部节点 x:神经网络外部节点
def feedforward(w, a, x):
# 激活函数sigmoid
sig = lambda s: 1 / (1 + np.exp(-s)) # exp方法返回e的幂次方
# 将网络的内部及外部输入联合起来与权值矩阵进行加权叠加
# 这里使用的是矩阵运算使训练更加快捷
w = np.array(w)
temp = np.array(np.concatenate((a, x), axis=0)) # concatenate用来拼接两个数组
rs = np.dot(w, temp) # 若dot的两个参数是一维数组,则得到两数组的内积;若两个参数是二维数组,结果是矩阵积
# 返回计算的下一层神经元的计算结果
# 及未经过激活函数前的加权叠加结果
return sig(rs), rs
6.2.5误差反向传播代码实现:
#误差反向传播
# w: 权值矩阵 z: 当前层的未经过激活函数前的神经元值
# delta_next: 下一层的 δ
def backprop(w, z, delta_next):
# sigmoid 激活函数
sig = lambda s: np.array(1 / (1 + np.exp(-s)))
# 激活函数 sigmoid 的导数
df = lambda s: sig(s) * (1 - sig(s))
# 误差反向传播计算上一层的 δ 并返回
delta = df(z) * np.dot(w.T, delta_next)
return delta
6.2.6编写主函数并设置自编码器三层神经网络训练所需的参数:
def main():
alpha = 5 # 学习步长
max_epoch = 500 # 训练的最大迭代次数
mini_batch = 50 # 在线学习批训练一次训练的次数,50次就可以达到比较好的效果,与100次效果差别不大
imgSize = 28*28 # 每张图片的大小为28,共784个像素
unlabeled_data=data('trainData.mat')
# 定义神经网络结构
# 第一列为外部节点(神经元)
# 第二列为内部节点,最后一层为网络输出
layer_struc = [[imgSize, 1],
[0, 32],
[0, imgSize]]
layer_num = 3
6.2.7初始化权值矩阵
# 初始化权值矩阵
w = []
for l in range(layer_num - 1):
w.append(np.random.randn(layer_struc[l + 1][1], sum(layer_struc[l])))
dataset_size = 500 # 训练数据集大小
6.2.8定义神经网络的外部输入
# 定义神经网络的外部输入
# 虽然只有第一层外部输入
# 但是为了训练时代码的统一,也设置了最后两层空的外部输入
X = []
X.append(np.array(unlabeled_data[:, :]))
X.append(np.zeros((0, dataset_size)))
X.append(np.zeros((0, dataset_size)))
6.2.9初始化误差反向传播
#初始化误差反向传播
delta = []
for l in range(layer_num):
delta.append([])
6.2.10定义结果展示的参数
# 定义结果展示的参数
# 每隔100个epoch展示一次自编码结果
# 加上第一行的原始图片展示
nRow = max_epoch / 100 + 1
nColumn = 10 # 每一行展示0-9十个数字
trainimage = 50 # 每个数字都有50张训练图片
6.2.11自编码器的训练
#自编码器的训练,及结果展示
for iImg in range(nColumn):
ax = plt.subplot(nRow, nColumn, iImg + 1)
plt.imshow(unlabeled_data[:, trainimage * iImg + 1].reshape((28, 28)).T, cmap=plt.cm.gray)
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
count = 0 # 迭代次数计步器
print('Autoencoder training start..')
for iter in range(max_epoch):
# 定义shuffle时的下标
ind = list(range(dataset_size))
# 为每次训练时都将训练数据重新打乱一遍
random.shuffle(ind)
a = [] # 网络内部神经元
z = [] # 网络节点的加权叠加结果
z.append([])
# 对神经网络开始批训练
for i in range(int(np.ceil(dataset_size / mini_batch))):
a.append(np.zeros((layer_struc[0][1], mini_batch)))
x = []
for l in range(layer_num):
x.append(X[l][:, ind[i * mini_batch: min((i + 1) * mini_batch, dataset_size)]])
# 定义目标输出
y = unlabeled_data[:, ind[i * mini_batch:min((i + 1) * mini_batch, dataset_size)]]
# 调用前向计算函数计算网络每一层节点的值
for l in range(layer_num - 1):
a.append([])
z.append([])
a[l + 1], z[l + 1] = feedforward(w[l], a[l], x[l])
# 根据最小二乘代价函数计算最后一层的δ
# 即自编码器的实际输出与目标值的欧式距离
delta[layer_num - 1] = np.array(a[layer_num - 1] - y) * np.array(a[layer_num - 1])
delta[layer_num - 1] = delta[layer_num - 1] * np.array(1 - a[layer_num - 1])
# 误差反向传播过程
# 调用backprop函数逐层反向计算 δ 值
for l in range(layer_num - 2, 0, -1):
delta[l] = backprop(w[l], z[l], delta[l + 1])
for l in range(layer_num - 1):
dw = np.dot(delta[l + 1], np.concatenate((a[l], x[l]), axis=0).T) / mini_batch
w[l] = w[l] - alpha * dw
count = count + 1
# 展示自编码器编码结果
if np.mod(iter + 1, 100) == 0:
b = []
b.append(np.zeros((layer_struc[0][1], dataset_size)))
for l in range(layer_num - 1):
tempA, tempZ = feedforward(w[l], b[l], X[l])
b.append(tempA)
for iImg in range(nColumn):
ax = plt.subplot(nRow, nColumn, iImg + nColumn * (iter + 1) / 100 + 1)
dis_result = b[layer_num - 1][:, trainimage * iImg + 1].reshape(28, 28).T
plt.imshow(dis_result, cmap=plt.cm.gray)
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
print('Learning epoch:', count, '/', max_epoch)
plt.show()
if __name__ == '__main__':
main()
6.3运行代码,展示结果
[zkpk@master 06]$ python unsupervised.py
本次实验我们简单实现了基于非监督学习的自编码器,通过设置网络参数,可以影响自编码器的性能,训练步长一般是通过运行代码验证选取,并不是越大或者越小越好,只能通过经验选择适当的步长,但是通过增加迭代次数,可以使自编码器的训练结果越来越好,因此我们在设置这些参数的时候,大多数情况都靠经验,也因此会花费大量时间调参以获得更好的结果。