Theano介绍及简单应用
一、课程介绍
1.课程说明
本次课程主要是为引导大家入门几个常用的深度学习框架。在课程中有关各种算法的详细推导及思想我们不会做过多的陈诉,旨在通过让同学们动手操作搭建自己的神经网络模型,了解如何使用这些框架解决实际问题。
课程中我们会基于每个框架的官方文档,先带大家熟悉框架中基本的定义语法及常用函数(可看成深度模型的小零件),每个实验最后都会综合这些零件搭建一个简易的神经深度网络模型。学习过程中有问题可随时指出。
2.深度学习概念
深度学习 (deep learning):深度学习是机器学习中的一个分支,试图通过具有多个处理层的计算模型对数据进行多层抽象。这个抽象的结果即是数据一种表达,而深度学习
也可看成表达学习(表征学习)
的一种方法,深度学习的好处是用无监督式 Unsupervised或半监督式 Semi-supervised的特征学习和分层特征提取
高效算法来替代手工获取数据特征。
简单来说,深度学习
可以理解为具有很多层(>2)的人工神经网络模型算法,神经网络
的构建其实也可以看成一个点-线-面
的构造过程。点
:即神经网络里许多计算节点(在神经网络里,称为神经元 Cell),线
:由一些节点再构成一层处理层(这一层就可以理解为对数据的某个抽象表达),面
:由多个处理层构建起来就形成了一个神经网络。
再由点(神经元,Cell)
不同,或是这些点的连接方式
不同就可以形成各种各样的不同的表达模型。如下图所示,我们可以看到许多种神经网络结构,例如:Deep Feed Forward深度前向传播网络
,Recurrent Neural Network(RNN)递归神经网络
和Long/Short Term Memory(LSTM)长短期记忆神经网络
。
图片来自:THE ASIMOV INSTITUTE
3.本节内容
本次实验所包含的实验流程如下:
- Theano介绍及安装
- Theano基本使用
- Theano实现stacked Autoencoder(栈式自编码器)
4.实验相关知识点
在后续的课程中,需要预先有了解的知识点:
- Tensor: 张量,经典的方法把张量视为多维数组,它们是标量,1维向量和2维矩阵的n维推广。张量的"分量"是数组中的值。 这个思想可以进一步推广到张量场,那里张量的元素是函数,甚至微分。 —— 引自维基百科
-
GPU:graphics processing unit,图形处理器
-
Numpy:python中有关数值计算的库
-
Matrix conventions (矩阵约定) for machine learning :矩阵中的每行都是一个例子。如 一个10行5列的矩阵inputs[10,5], 代表着10个5维的实例。
-
Broadcasting: Numpy进行数值运算时广播不同维度的矩阵。如,当一个较小的矩阵a(1∗2维)与一个较大的矩阵b( 2∗3 维)进行加法运算时,numpy会将矩阵a广播为与矩阵b兼容的矩阵维度,再进行运算。也可通过下图帮助理解。
-
二、Theano介绍及安装
Theano是一个较为老牌和稳定的机器学习python库之一。Theano基于Python擅长处理多维数组(紧密集成了Numpy
),属于比较底层的框架,theano起初也是为了深度学习中大规模人工神经网络
算法的运算所设计,我们可利用符号化式语言定义想要的结果,接着theano
会对我们的程序进行编译,使其高效运行于GPU或CPU。
从Theano官方文档的欢迎页面上,我们可以看到其对Theano的特征介绍:
- tight integration with Numpy (紧密集成Numpy) —— 在Theano编译函数中使用numpy.ndarray
- transparent use of a GPU (GPU的透明使用) —— 使得其对浮点数的运输非常高速
- efficient symbolic differentiation (高效的符号分解) —— 也是Theano的发明初衷之一,可帮我们推导我们所定义的一个或多个输入的函数
- speed and stability optimizations (快速且稳定的优化) —— 在函数表达式$log(1+x)$中即使$x$非常小,也可以给出精确的答案
- dynamic C code generation (动态生成C代码) —— 使得表达式求值(evaluate expression)更加快速
- extensive unit-testing and self-verification (大量的单元测试及自我验证) —— 可检测及诊断许多类型的错误
在Installing Theano中可查看在各种系统上安装Theano的教程。这里我们将展示在实验楼的ubantu14.04环境的安装过程:
# 1. 先使用apt-get update命令进行更新,避免安装Python库时出现问题
$ sudo apt-get update
# 2. 接着安装在安装theano前需安装的Python库
$ sudo apt-get install python-numpy python-scipy python-matplotlib python-dev python-pip python-nose python-tk g++ libopenblas-dev git
# 3. 接着第三步便是使用pip命令安装theano
$ sudo pip install theano
三、Theano基本使用
3.1 Theano的基本运算及符号表达
通过python
进入Python终端中,先导入theano的相关模块以便后续操作:
>>> import theano
>>> import theano.tensor as T
>>> from theano.tensor import *
>>> from theano import shared
>>> import numpy as np
基本的张量功能:
Theano 支持Python中任何类型的对象,但它的核心是其支持符号矩阵表达式,如下例中:
>>> x = T.fmatrix()
x
是一个TensorVariable张量变量
的实例,T.fmatrix
对象本身也是TensorType张量类型
的一个实例。
Creation创造张量实例
Theano提供了一列预先定义好的张量类型可供使用者很方便地创造张量变量,所有的张量构造函数都接收一个 可选 的输入参数name
。如以下便构造了三个以myvar
为名字的 0维整型标量(scalar)变量
>>> x = T.scalar('myvar', dtype='int32')
>>> x = iscalar('myvar')
>>> x = TensorType(dtype='int32', broadcastable=())('myvar')
还可以通过vector
、row
、col
、matrix
、tensor3
及tensor4
分别构造向量、行向量、列向量、矩阵、三维张量及四维张量。
我们还可以通过以下方式创造张量类型实例
# 创造一个双精度浮点类型的无名矩阵
>>> x = dmatrix()
# 创造了一个双精度浮点类型的名为'x'的矩阵
>>> x = dmatrix('x')
# 创造了一个双精度浮点类型的名为'xyz'矩阵
>>> xyz = dmatrix('xyz')
Plural Constructor 多输出构造函数
Plural Constructor可以同时构造多个变量,以下便是Theano提供的多输出构造函数,其实就是int
,long
, float
及double
数据类型与标量scalars
, 向量vectors
, 行向量rows
, 列向量cols
和矩阵matrices
的组合:
- 返回一个或多个标量:iscalars, lscalars, fscalars, dscalars
- 返回一个或多个向量:ivectors, lvectors, fvectors, dvectors
- 返回一个或多个行变量:irows, lrows , frows, drows
- 返回一个或多个列变量:icols, lcols, fcols, dcols
- 返回一个或多个矩阵: imatrices, lmatrices, fmatrices, dmatrices
举一个'栗子':
# 构造三个无名双精度浮点类型矩阵
>>> x, y, z = dmatrices(3)
# 构造三个分别名为'x','y','z'的double类型矩阵
>>> x, y, z = dmatrices('x', 'y', 'z')
依次类推,Theano中还有其他的张量类型,除前边的章节介绍外的还有字节型byte,字型word和复数型complex的数据形成的张量。
自定义张量类型
如果上述所提供的构造函数不能构造出你想要的张量时,你可以自定义自己的张量类型TensorType
。比如,构造一个5维张量:
>>> dtensor5 = TensorType('float64', (False,)*5)
>>> x = dtensor5()
>>> z = dtensor5('z')
也可以重定义已经提供的张量构造函数。如重构一个 双精度浮点类型矩阵:
>>> my_dmatrix = TensorType('float64', (False,)*2)
>>> x = my_dmatrix() #构造一个无名2维矩阵实例
## 判断自定义的张量类型是否与theano库所提供的构造函数一致
>>> my_dmatrix == dmatrix
3.2 Theano中的线性运算
在进行线性运算前,先从theano中导入一个新的函数function:
>>> from theano import function
使用Theano进行线性运算过程就跟把大象放进冰箱的步骤差不多:
- 打开冰箱——定义好需要进行运算的张量变量
- 把大象放进冰箱——利用已定义的张量书写你的表达式
- 关上冰箱——将表达式转化为可执行的python函数
基于以上三个步骤我们实现两个张量的加运算:
# 1. 定义变量,可以是3.1节介绍的张量类型中的任一种,这里我定义的是double scalar双精度标量
>>> x = T.dscalar('x')
>>> y = T.dscalar('y')
# 或者你可以直接同时定义这两个变量
>>> x, y = T.dscalars('x', 'y')
# 2. 表达式
>>> z = x + y
# 3. 将表达式转化为可执行的python函数
>>> f = function([x, y], z)
第3个步骤中的function
函数,其接受的第一个参数类型为 a list of Variables 一组输入变量 ,将作为转化后的python函数f
的输入,第二个参数类型为 a single Variable or a list of variables一个或一组变量,将作为函数f
的输出。
接下来我们就可以使用函数f
进行表达式运算
>>> f(2.33, 6.66)
如果你想在函数f
中只个一个输入,而执行加运算。那么可以设置其中一个变量有缺省值 Default Value
>>> from theano import In
>>> f = function([x, In(y, value=1)], z)
# In理解为Instance实例
>>> f(1.33)
还有些时候,我们会在许多函数间用到同一个变量值,那么此时我们可以定义一个共享变量(shared Variable), 共享变量由使用其计算的函数所共享,可通过shared_constructor()
构造,可通过get_value
和set_value
函数分别获得和改变共享变量的值。
>>> np_array = np.zeros(2, dtype='float32')
# 创建共享变量
>>> shared_default = shared(np_array)
>>> shared_false = shared(np_array, borrow=False)
>>> shared_true = shared(np_array, borrow=True)
shared
函数中的参数borrow
的含义是,变量np_array
在函数中被使用时所创建共享变量shared_xxx
,在np_array
的值发生改变时共享变量是否要跟着发生变化,同学们可通过运行下边的代码获得答案:
>>> np_array += 1 #在数组np_array[0, 0]的值上加1
>>> shared_default.get_value()
>>> shared_false.get_value()
>>> shared_true.get_value()
3.3 练习
经过上边的介绍,我们来实现以下表达式:
- 向量
x
的表达式x+x10 - 矩阵
x
的表达式 s(x)=1+e−x1 - 基于表达式2实现s(x)的导数形式
# 表达式1
>>> x = T.vector()
>>> y = x + x ** 10
>>> f = function([x], y)
>>> f([0, 1, 2])
# 表达式2
>>> x = T.dmatrix('x')
>>> s = 1 / (1 + T.exp(-x))
>>> logistic = function([x], s)
>>> logistic([[0 , 1], [-1, -2]])
#表达式3
>>> s = T.sum(1 / (1 + T.exp(-x)))
>>> gradS = T.grad(s, x)
>>> dLogistic = function([x], gradS)
>>> dLogistic([[0, 1], [-1, -2]])
结束之后,利用Ctrl + D
退出python终端
四、Theano实现自编码器
本节我们将利用theano实现第一节的关于人工神经网络
附图中的自编码器(AutoEncoder, AE):
上图中的自编码器为最简单的三层‘深度’神经网络,在实验楼的课程基于无监督学习的自编码器实现中,我们已经对自编码器解释得比较详细,这里不做过多解释,直接动手做实验:
4.1定义自编码器类
创建AE_code.py
文件,在其中定义自编码器类
方便我们在之后时使用这个三层前向传播神经网络,对我们给定的数据进行自编码器的训练。
- 导入相关的python模块:
# -*- coding: utf-8 -*-
import numpy as np
import theano as th
from theano import tensor as T
from numpy import random as rng
- 创建
AutoEncoder
类:
class AutoEncoder(object):
# 在初始化函数中定义自编码器的参数
def __init__(self, data, hidden_size, actFun, outFun):
self.data = data
self.data = th.shared(name='data', value=np.asarray(self.data, dtype=th.config.floatX), borrow=True)
self.nIns = data.shape[0]
self.nFeatures = data.shape[1]
self.hidden_size = hidden_size
# W初始值的取值范围,在一个固定范围中取均匀分布的随机值
raw_W = np.asarray(random.uniform(
low = -4 * np.sqrt(6. / (self.hidden_size + self.nFeatures)),
high = 4 * np.sqrt(6. / (self.hidden_size + self.nFeatures)),
size=(self.nFeatures, self.hidden_size)), dtype=th.config.floatX)
self.W = th.shared(value=raw_W, name='W', borrow=True)
# 隐含层的偏置向量
self.b1 = th.shared(name='b1', value=np.zeros(shape=(self.hidden_size,),
dtype=th.config.floatX), borrow=True)
# 输出层的偏置向量
self.b2 = th.shared(name='b2', value= np.zeros(shape=(self.nFeatures,),
dtype=th.config.floatX), borrow=True)
self.actFun = actFun
self.outFun = outFun
通过上边的代码可知,在自编码器的权值初始化时是在区间[low, high]上取得,其中low和high的值分别为:
low=−4∗√nhidden+nvisible6
high=4∗√nhidden+nvisible6
而自编码器的隐含层和输出层的偏置
都设置为零向量
- 自编码器的训练
# 定义自编码器的训练函数
# 训练时的参数在这里有默认设置值,在实际调用时可根据输入数据进行修改
def train(self, n_epochs=100, mini_batch_size=10, learning_rate=0.1):
index = T.lscalar()
x = T.matrix('x')
params = [self.W, self.b1, self.b2]
hidden = self.actFun(T.dot(x, self.W) + self.b1)
output = T.dot(hidden, T.transpose(self.W)) + self.b2
output = self.outFun(output)
# cross-entropy loss
L = -T.sum(x * T.log(output) + (1 - x) * T.log(1 - output), axis=1)
cost = L.mean()
updates = []
# gradient with respect to W, b1, b2
gparams = T.grad(cost, params)
for param, gparam in zip(params, gparams):
updates.append((param, param - learning_rate * gparam))
# training given a mini-batch of the data
train = th.function(inputs=[index], outputs=[cost], updates=updates,
givens={x: self.data[index:index + mini_batch_size, :]})
import time
start_time = time.clock()
for epoch in xrange(n_epochs):
print "Epoch:", epoch
for row in xrange(0, self.nIns, mini_batch_size):
train(row)
end_time = time.clock()
print "Average time per epoch=", (end_time - start_time) /n_epochs
- 定义自编码器输出结果的获取
def get_output(self, data):
W = self.W.get_value()
b1 = self.b1.get_value()
b2 = self.b2.get_value()
x = T.dmatrix('x')
hidden = self.actFun(T.dot(x, W) + b1)
output = T.dot(hidden, W.T) + b2
output = self.outFun(output)
ae_output = th.function(inputs=[x], outputs=[output])
return ae_output(data)
4.2 调用自编码器类训练minist手写体的自编码网络
本次实验所用的数据集,与我们在课程基于无监督学习的自编码器实现所用的数据集一样,为方便大家在实验楼的在线环境中进行实验,在minist数据集中选用一部分并且已经读入到trainData.mat
文件中,节省文件读取的时间。
trainData.mat
可通过wget
命令从以下地址获得:
wget http://labfile.oss.aliyuncs.com/courses/696/trainData.mat
实验代码部分:
import matplotlib.pyplot as plt
def main():
trainData = scio.loadmat('trainData.mat')
inputData = trainData['trainData'].T
inputData = inputData[:,:] / 255.
activation_function = T.nnet.sigmoid
output_function = activation_function
A = AutoEncoder(inputData, 200, activation_function, output_function)
# 训练自编码器,只训练一个epoch,在训练过程中每次只通过一张图片进行权值改进
A.train(1, 1)
# 训练集中每个数字有对应50张训练图片
eachDigitNum = 50
showIndex = range(0, inputData.shape[0], eachDigitNum)
# Autoencoder类中的get_output函数返回的是一个list
# 因此需通过下标0,将list中的结果矩阵读出
output = A.get_output(inputData[showIndex, :])[0]
# 展示原始图片及自编码器输出结果
for iImg in xrange(10):
ax = plt.subplot(2, 10, iImg + 1 )
plt.imshow(inputData[showIndex[iImg],:].reshape(28, 28).T, cmap= plt.cm.gray)
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
ax = plt.subplot(2, 10, iImg + 11 )
plt.imshow(output[iImg, :].reshape(28, 28).T, cmap=plt.cm.gray)
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
plt.show()
if __name__ == '__main__':
main()
最终通过命令行运行AE_code.py
文件,查看自编码训练结果
python AE_code.py
程序运行结果截图:
五、总结
通过本次课程,我们学习了Theano
中的基本使用方法,了解了Theano的一些基本特性及不同的数据类型。通过实验了解常用的数据符号定义方法,及如何通过theano实现数据表达式。本节实验最后我们Theano
实现了一个三层FeedForward network 前馈网络
。Theano
还可实现许多人工神经网络,同学们可自己动手实现。
本次课程的所有源码,可通过以下命令获得:
wget http://labfile.oss.aliyuncs.com/courses/744/AE_code.py
六、参考阅读
七、本节习题
在本次实验中,我们实现了Autoencoder
类中的get_output
函数,来查看自编码器的输出结果。同学们可通过动手自定义get_hidden
函数,获得编码hidden
层的编码结果,并在main
函数中调用查看网络训练结束后的对每个图片的编码情况。