Classifying MNIST digits using Logistic Regression
在本节我们会展示Theano是怎样实现一个最基本的逻辑斯特回归。我们快速过一下模型并且回顾一下符号和数学表达式。
在机器学习传统中,这个教程会解决MNIST手写识别这个令人振奋的问题。
The Model
逻辑斯特是一个概率,线性分类器,以权重矩阵W和偏置项b。分类器输入与一个类相关的一个超平面。输入超平面的距离映射到相关类的概率分布中。
数学上,输入向量x的概率是一个类i的概率。:
模型的预测 y~pred~是一个使概率最大的类,特别地:
Theano的代码实现 :
# 权值初始化0
self.W = theano.shared(
value = numpy.zero(
(n_in, n_out),
dtype=theano.config.floatX,
),
name = 'W',
borrow = True
)
# 初始化偏置项为0
self.b = theano.shared(
value = numpy.zeros(
(n_out,),
dtype = theano.config.floatX,
),
name = 'b',
borrow = True
)
# 符号表达式来计算类成员概率矩阵
# 其中:
# W是一个矩阵,第k列代表第k类的权值
# x是一个矩阵,第j行代表第j个样本输入
# b是一个向量,其中第k个代表第k个超平面的只有参数
self.p_y_given_x = T.nnet.softmax(T.dot(input, self.W) + self.b)
# 怎样使用符号描述来预测
self.y_pred = T.argmax(self.p_y_given_x, axis=1)
当模型的参数一定要拥有整个训练过程都持久的状态时,我们使用共享变量W,b。这两个都是Theano的符号变量同时初始化内容。点乘和softmax运算是用来计算P(Y|x,W,b)。结果p_y_given_x
是一个向量类型的符号变量。
为了获得准确的预测模型,我们可以使用T.argmax
运算,它会返回一个是p_y_given_x
最大的下标。
当然这个刚定义的模型还没什么用,只要它是还是初始状态。所以下面我们来学习怎样学习最佳参数。
Defining a Loss Function
学习最佳模型参数设计最小化损失函数。在多类逻辑斯特回归中,最常用的是负对数似然。这等于最大化以theta为参数的D数据集的似然函数。让我们定义似然函数 L 和 损失 l。
当整本书都在说最小化,梯度下降是最简单的最小化任意非线性函数。本教材会使用小批量随机梯度下降方法(MSGD)。
下面的Theano代码定义了给定小批量的符号化损失:
# y.shape[0]是y的行数目,例如,小批量里样本的数量。
# T.arange(y.shape[0])是一个向量,包括[0,1,2,...,n-1]
# T.log(self.p_y_given_x)是一个对数概率(LP)矩阵,每一行对应一个样本
# 和每一列对应一个类。 LP[T.arange(y.shape[0]), y)]是一个向量v,包括
# [LP[0, y[0]], LP[1, y[1]], LP[2, y[2]],...]和T.mean()是一个v的平均值
return -T.mean(T.log(self.p_y_given_x)[T.arange(y.shape[0]), y])
尽管损失函数是数据集的 sum。我们使用 mean,这样使学习率少依赖于小批量
Creating a LogisticRegression class
现在我们拥有所有LogisticRegression
的部件。这跟我们之前的很像,而且加了注释。
class LogisticRegression(object) :
""" 多类逻辑斯特分类
逻辑斯特回归决定于一个权重矩阵:W 和偏置项向量:b。
分类器是把数据点放在一系列超平面,到超平面的距离决定
分类的概率
"""
def __init__(self, input, n_in, n_out) :
""" 初始化逻辑斯特回归的参数
: type input : theano.tensor.TheanoType
: param input : 一个小批量的符号变量输入
: type n_int : int
: param n_in : 输入单元的数量,数据点在空间的维数
: type n_out : int
: param n_out : 输出单元的数量,标签在空间的维数
"""
# 开始 - 第一部分
# 初始化(n_in, n_out)矩阵的权值为0
self.W = theano.shared(
value = numpy.zeros(
(n_in, n_out),
dtype = theano.config.floatX
),
name = 'W',
borrow = True
)
# 初始化偏置项b为0
self.b = theano.shared(
value = numpy.zeros(
(n_out,),
dtype = theano.config.floatX
),
name = 'b',
borrow = True
)
# 计算类成员概率的符号表达式
# 其中:
# W 的第k列代表第k类的超平面
# x 的第k行代表第j个训练集
# b 的第k个元素代表第k超平面的偏置项
self.p_y_given_x = T.nnet.softmax(T.dot(input, self.W) + self.b)
# 怎样计算使预测值概率最大
self.y_pred = T.argmax(self.p_y_given_x, axis = 1)
# 结束 - 第一部分
# 模型的参数
self.params = [self.W, self.b]
# 保持模型的输入
self.input - input
def negetive_log_likelihood(self, y) :
""" 返回预测值的负似然平均值
: type y : theano.tensor.TensorType
: param y : 每个样本对应的标签
"""
return -T.mean(T.log(self.p_y_given_x)[T.arange(y.shape[0]), y])
def error(self, y) :
""" 返回一个浮点数代表每个小批量的误差;小批量的零一误差
: type y : theano.tensor.TensorType
: param y : 对应标签
"""
# 检查y和y_pred是否为相同维数
if y.ndim != self.y_pred.ndim :
raise TypeError(
'y should have the same shape as self.y_pred',
('y', y.type, 'y_pred', self.y_pred.type)
)
# 检查y是否为正确数据类型
if y.dtype.startswith('int') :
# T.neq返回一个0和1的向量,1代表预测错误
return T.mean(T.neq(self.y_pred, y))
else :
raise NotImplementedError()
我们如下实例化类:
# 生成符号变量,输入x和y是一个小批量
x = T.matrix('x')
y = T.ivector('y')
# 构建逻辑斯特回归类
# 每个图片为 28*28
classifier = LogisticRegression(input=x, n_in=28*28, n_out=10)
我新建一个test_model
函数和一个validate_model
函数。validate_model
是提早结束的关键。Theano函数获得一个小批量的下标并且计算,在小批量的样本里,数字被模型分类。唯一不同的就是test_model
从测试集获取数据,validate_model
从验证集获得数据。
# 编译Theano函数,计算小批量的误差
test_model = theano.function(
inputs = [index],
outputs = classifier.errors(y),
givens = {
x : test_set_x[index * batch_size : (index + 1) * batch_size],
y : test_set_y[index * batch_size : (index + 1) * batch_size]
}
)
validate_model = theano.function(
inputs = [index],
outputs = classifier.error(y),
givens = {
x : test_set_x[index * batch_size : (index + 1) * batch_size],
y : test_set_y[index * batch_size : (index + 1) * batch_size]
}
)
Putting it All Together
代码见原网站