逻辑斯蒂分类模型
几个关键值
加权输入f(x) = (w1x1+w2x2+...+wnn)+b
或扩展为f(x) = (w0x0+w2x2+...+wnn),其中w0=b,x0=1
激活值 a(activation) = delta(f),delta为激活函数,一般可选择sigmoid、relu、tanh、leak_relu等等
sigmoid激活函数
sigmoid(x) = 1/(1+e^-x)
性质
- 输出在0-1之间
- 在-6至6之间曲线比较陡峭
- 在-6和6之外曲线平滑,分别接近0和1
- 图像呈对称分布
类比
由于函数的输出范围在0-1之间,可以类比于概率分布。
逻辑斯蒂二分类模型
注:T 符号表示矩阵的转置
Y是”0“类的概率:
P(Y=0|x) = h(x) = 1/(1+e-wTx)
Y是”1“类的概率:
P(Y=1|x) = h(x) = 1 - 1/(1+e-wTx) = e-wTx/(1+e-wTx)
逻辑斯蒂多分类模型
二分类中,P(Y=0|x) = 1/(1+e-wTx) ,P(Y=1|x) = e-wTx/(1+e-wTx),其实可以理解为P(Y=0|x) = 1,P(Y=1|x) = e-wTx,他们加起来不为0,因此我们需要进行归一化操作,即让这两个值分别除以他们的和(1+e-wTx) ,也就得到了二分类的两个概率分布公式。
等同的,多分类中,W从二分类的向量变成了矩阵,我们将它拆分为多个向量即w1,w2,w3等等,然后各个分类的输出就变成了 e-w1Tx、 e-w2Tx、 e-w3Tx...、1,他们加起来同样是不等于1的,进行归一化操作后分别为
P(Y=0|x) = h(x) = 1/(1+Σe-wiTx)
P(Y=i|x) = h(x) = e-wiTx/(1+Σe-wiTx)
其中i表示将W矩阵拆分为向量后的第i个。
类似的,可以另w0 = 0,有e-w0Tx=1。这时就可以将Y=0时的概率和不为0时的概率统一起来了,即
P(Y=i|x) = h(x) = e-wiTx/(1+Σe-wiTx) ,其中当w0=0。
模型的学习
目标函数/损失函数
将会基于目标函数(损失函数)进行调整模型,即基于指定的目标函数(损失函数),通过学习的数据进行模型优化和模型调整,以形成一定程度上的最优模型(即数据和模型的拟合程度良好的一个模型)。
一般多用损失函数(y真实-y预测)作为目标函数
简单示例
二分类
例如多维度特征的x,其真实分类是1,在模型中根据其特征计算得到的分类是0.8,目标函数(损失函数) = 1-0.8=0.2。我们进行学习的目的就是增大y1',减小y1-y1'的值,即减小损失误差。实际中我们不仅仅需要增大y1'和减小y1-y1',而是需要对全部的y和y’进行同样的操作,因此就需要对其方差等进行操作,而不是对某一个的y或y-y'进行操作。
(x1,y1) =([10,3,4,23,....],1)
y1' = model(x1) = 0.8
y1-y1' = 0.2
多分类
例如下面的一个五分类问题,多维特征的x,其真实分类是3,在模型计算后得到各个分类的概率分别是[0.1,0.05,0.2,0.6,0.05],我们对分类3进行onehot变换得到[0,0,0,1,0],用onehot的值减去预测概率y1‘的值并取绝对值,得到[0.1,0.05,0.2,0.4,0.05],这些在各个类别上概率损失值的和即为我们所求的多分类模型的整体损失函数或模板函数。
(x1,y1) = ([12,13,15,52,63,...],3)
y1' = model(x1) = [0.1,0.05,0.2,0.6,0.05]
y1_onehot= [0,0,0,1,0]
abs(y1_onehot - y1' )= [0.1,0.05,0.2,0.4,0.05]
sum_loss =0.1+0.3+0.2+0.4+0.05 = 0.8
需要注意的是,我们当然能直接根据单分类中概率0.8大于0.5而判定是归属1分类,多分类中在第四个位置上的概率值是最大的(0.6)而判定是归属第四个分类及分类3。但是这不并不是我们的目的,我们当前的目标是通过已有数据拟合模型,并根据损失函数进行优化从而得到最好的分类器(实际中我们将会采取不同的办法降低损失函数的值,而得到更好的模型,并不断重复进行”计算当前模型的损失函数-得到新的模型“这种迭代过程,以达到一定程度上最优的模型)。即当前损失函数的计算是为了学习,而不是为了预测。
真实应用中的目标函数
模型训练的目的就是让损失函数达到最小
平方差损失函数
Loss =1/2n * (Σ(y-y')2)
除以2n而不是n是为了求导时的方便
交叉熵损失函数
Loss = 1/n * Σ(y*ln(y'))
梯度下降
在训练中不再使用求导方式进行最小值求解。一是因为参数太多,求导时的数学推导会非常复杂;二是因为因为模型是基于训练数据集的,当前通过求导得到的只是当前数据集中的最小点,不具有普遍意义。例如Loss整理得到aw12+bw22+cw1w2,求(偏)导并取极值点得到的w1和w2只是在给定数据集计算得到abc这三个的限制下的最小点,更换数据集后abc就会发生变化从而会导致之前计算的w1w2也不在有效。
而梯度下降是根据损失函数的移动趋势得到的最小点,大数据集下相对更具有普遍意义。而且梯度下降更重要的一点是他会逐步进行计算,每新加入一个数据后都会在当前基础上再次计算新的最低点。而求导则需要再新数据集下重新计算各个偏导值,并进行极值计算,相对计算量会大很多。
theta = alpha*(Loss在theta上的偏导),其中alpha表示学习率。
TensorFlow
计算图模型
命令式编程
x = 1
y = 2
z = x + y
print(z)
声明式编程
import theano.tensor as T
from theano import function
x = T.dscalar('x')
y = T.dscalar('y')
z = x + y
f = function([x, y],z)
print(f(1, 2))
声明式编程会先构造计算图,然后通过输入计算从而得到输出结果。
Tensorflow(r1)
基本概述
- 使用图 (graph) 来表示计算任务.
- 在被称之为 会话 (Session) 的上下文 (context) 中执行图
- 使用 tensor 表示数据
- 通过 变量 (Variable) 维护状态
- 使用 feed 和 fetch 可以为任意的操作(arbitrary operation) 赋值或者从其中获取数据
TensorFlow 是一个编程系统, 使用图来表示计算任务. 图中的节点被称之为 op (operation 的缩写). 一个 op 获得 0 个或多个 Tensor, 执行计算, 产生 0 个或多个 Tensor. 每个 Tensor 是一个类型化的多维数组.
Tensor
占位符
tf.placeholder(tf.float32, shape=(2,3))
常量
全1矩阵: tf.ones([2, 3], tf.int32) # [[1, 1, 1], [1, 1, 1]]
全0矩阵:tf.zeros([2, 3], tf.int32) # [[0, 0, 0], [0, 0, 0]]
指定值填充的矩阵:fill([2, 3], 9) # [[9, 9, 9]
自定义常量,可以直接传入一个list来初始化 vaule ,也可以指定 value 和 shape 来进行填充。:tf.constant( value, dtype=None, shape=None, name='Const', verify_shape=False )
变量
变量用tf.Variable
类对象进行保存,通过initial_value
参数指定初始化函数(如整体分布函数tf.random_normal
、均匀分布函数tf.random_uniform
、Gamma分布函数tf.random_gamma
),并通过name
参数指定变量的名称。
如:
v1 = tf.Variable(tf.rnadom_normal([2, 3], stddev=1))
v2 = tf.Variable(tf.zeros([2, 3], tf.int))
变量初始化
同常量不一样的是,变量在类定义中的初始化函数只是描述了初始化的方法,但是并没有实际进行初始化操作。变量需要在定义后进行单独的初始化即tf.Variable.initializer
。
tensorflow提供了统一初始化的函数,方便进行变量的初始化操作:
init_op = tf.global_variables_initializer()
sess.run(init_op)
tf.get_variable
除了通过 tf.Variable 类进行变量定义和初始化以外,还可以使用tf.get_variable
进行变量声明。get_variable会进行校验,当不存在同名变量时创建新的变量,出现重名时则会根据上下文进行处理(一般会直接报错)。如:
y1 = tf.get_variable(name='y1', initializer=1.0)
#此时由于存在以'y1'为名称的变量,再次定义name为'y1'的变量会直接报错
y2 = tf.get_variable(name='y1', initializer=1.0)
另外,与通过Variable类定义变量不同的是,get_variable需要指定初始化类 initializer 参数才能生成不同分布的数据。常用的initializer方法有常量分布tf.constant_initializer
、全0分布tf.zeros_initializer
、全1分布tf.ones_initializer
、正太分布tf.random_normal_initializer
、均匀分布tf.random_uniform_initializer
。
变量上下文
变量上下文tf.variable_scope()
就是解决这种变量的name存在时,重复定义变量出错的情况。参数 reuse
可以指定在对应上下文中 tf.get_variable()
函数的行为。reuse设置为False时将会生成新的变量,此时如果出现name重名就会报错。设置为True时将会判断当前变量是否已存在,如果不存在就将会报错,存在时将对变量进行重新定义。当为tf.AUTO_REUSE时,则会进行自动判断。如果是None,则我们继承父范围的重用标志。如果直接修改variable_scope的name,表示将工作在不同的作用域下,而在不同作用域下变量是不可交互的,也就不存在两个不同作用域的变量他们name相同的问题。
例如:
with tf.variable_scope('test1'):
get_var1 = tf.get_variable(name='firstvar',shape=[2],dtype=tf.float32)
with tf.variable_scope('test2'):
get_var2 = tf.get_variable(name='firstvar',shape=[2],dtype=tf.float32)
常用函数、方法
常用操作函数
判断a与b是否相等 tf.equal(a,b)
获取张量a各个方向上的维度 tf.shape(a)
将张量a根据第2个参数改变形状,-1表示根据a原本的形状计算该位置的值 tf.reshape(a, [-1, 28, 28, 1])
交换a的维度0和1,通过perm参数可以交换任意几个维度 tf.transpose(a,perm=[1,0])
将a映射到数据类型dtype tf.cast(a, dtype)
添加一个全连接层 tf.layers.dense
计算x与y两个矩阵的乘积 tf.matmul(x,y)
将张量a按照第2个参数进行求和,维度会减少,如果要保持维度,指定keep_dims=True tf.reduce_sum(a,[],keep_dims=False)
将张量a,b根据axis合并 tf.stack([a,b],axis=)
将张量a根据分类数量为depth进行one hot编码 tf.one_hot(a,depth)
常用激活函数
tf.nn.sigmoid(x, name=None)
tf.nn.tanh(x, name=None)
tf.nn.relu(features, name=None)
tf.nn.relu6(features, name=None) #relu6相对于普通relu更容易学习到稀疏特征。
tf.nn.softplus(features, name=None)
常用损失函数
均方差函数
loss = tf.reduce_mean(tf.square(logits-labels))
loss = tf.reduce_mean(tf.square(tf.sub(logits, labels)))
loss = tf.losses.mean_squared_error(logits,labels)
交叉熵函数
#计算方式:对输入的logits先通过sigmoid函数计算,再计算它们的交叉熵但是它对交叉熵的计算方式进行了优化,使得结果不至于溢出。
loss = tf.nn.sigmoid_cross_entropy_with_logits(labels=y,logits=logits)
#计算方式:对输入的logits先通过softmax函数计算,再计算它们的交叉熵,但是它对交叉熵的计算方式进行了优化,使得结果不至于溢出。
loss = tf.nn.softmax_cross_entropy_with_logits(labels=y,logits=logits)
REFERENCE:
https://blog.csdn.net/liaopiankun0618/article/details/84231119
Session
构造阶段完成后, 才能启动图. 启动图的第一步是创建一个 tf.Session
对象, 如果无任何创建参数, 会话构造器将启动默认图.
sess = tf.Session()
sess.run()
sess.close()
或:
with tf.Session() as sess:
sess.run()
或交互式session:
sess = tf.InteractiveSession()
Fetch
为了取回操作的输出内容, 可以在使用 Session 对象的 run() 调用 执行图时, 传入一些 tensor, 这些 tensor 会帮助你取回结果.
# 需要获取的多个 tensor 值,在 op 的一次运行中一起获得,而不是逐个去获取 tensor。
with tf.Session():
result = sess.run([tensor1, tensor2, tensor3...])
Feed
feed 机制可以临时替代图中的任意操作中的 tensor 可以对图中任何操作提交补丁, 直接插入一个 tensor。
feed 使用一个 tensor 值临时替换一个操作的输出结果. 你可以提供 feed 数据作为 run() 调用的参数. feed 只在调用它的方法内有效, 方法结束, feed 就会消失. 最常见的用例是将某些特殊的操作指定为 "feed" 操作, 标记的方法是使用 tf.placeholder() 为这些操作创建占位符。
如下面的例子,input1和input2分别定义了两个占位符,通过计算图输出output,而在session的run中,将自定义的两个tensor传递到计算图进行计算(替换原有的input1和input2,且只在本次run中有效),并返回本次计算结果。
input1 = tf.placeholder(tf.types.float32)
input2 = tf.placeholder(tf.types.float32)
output = tf.mul(input1, input2)
with tf.Session() as sess:
result = sess.run([output], feed_dict={input1:[7.], input2:[2.]})
训练优化器
tf.train.AdamOptimizer(1e-3).minimize(loss_function)
通过TensorFlow搭建逻辑斯蒂分类模型
简单二分类示例代码
import tensorflow as tf
import os
import pickle
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
data_path = '/workspace/deep_learning/nn/cifar10'
filenames = ["data_batch_"+str(i+1) for i in range(5)]
file_path = [os.path.join(data_path,each) for each in filenames]
test_file_path = os.path.join(data_path,'test_batch')
def load_data(path):
"""加载数据"""
# return: dict
# type: data-numpy.ndarray, labels-list, bathch_label-str, filenames-list
# data-shape: (n,32*32*3) / (n,3072)
# data-format: R(32)R(32)-G(32)G(32)-B(32)B(32)
return pickle.load(open(path,'rb'),encoding='latin1')
def img_show(data):
assert len(data)==3072,'Img data err.'
data = np.reshape(data,(3,32,32))
data = np.transpose(data,(1,2,0))
plt.imshow(data)
def data_select(data,label):
"""返回指定label的数据"""
#return: data(filted),label
x = data['data']
y = np.array(data['labels']).reshape(-1,1)
data = np.hstack([x,y])
data = data[data[:,-1]==label]
return data[:,:-1],data[:,-1]
#测试图像数组的transpose是否正确
test = load_data(test_file_path)['data']
img_show(test[np.random.randint(0,len(test))])
"""构建计算图"""
tf.reset_default_graph()
# input
x = tf.placeholder(tf.float32,[None,3072])
y = tf.placeholder(tf.int64,[None])
# parms
w = tf.get_variable('w',
[x.get_shape()[-1],1],
initializer=tf.random_normal_initializer(0,1))
b = tf.get_variable('b',
[1],
initializer=tf.random_normal_initializer(0,1))
# output
y_ = tf.matmul(x, w) + b
y_a = tf.nn.sigmoid(y_) # sigmoid激活函数
# loss
y_reshaped = tf.reshape(y, [-1,1])
y_reshaped_cast = tf.cast(y_reshaped, tf.float32)
loss = tf.reduce_mean(tf.square(y_reshaped_cast-y_a)) # 均方误差
pred = tf.cast((y_a>0.5), tf.int64)
y_equal = tf.equal(pred, y_reshaped)
accuracy = tf.reduce_mean(tf.cast(y_equal, tf.float32))
with tf.name_scope('train_op'):
train_op = tf.train.AdamOptimizer(1e-3).minimize(loss)
with tf.Session() as sess:
init = tf.global_variables_initializer()
sess.run(init)
tmp_data = load_data(file_path[1])
x_,y_ = data_select(tmp_data,1)
#正常来说这里应该是使用训练集进行迭代训练,
#然后使用验证/测试集进行评估预测的,
#但是我们这里只是测试tensorflow单层神经元的计算图能否正常运行,
#就不引入测试集进行预测评估了
l,a,_ = sess.run([loss,accuracy,y_equal], feed_dict={x:x_.tolist(), y:y_.tolist()})
print('loss:')
print(l)
print('accuracy:')
print(a)
#result
loss :
0.07743787
accuracy :
0.9225422
单层神经元网络的多分类误差非常大,预测结果没有实际意义,就不再处理了。
通过TensorFlow搭建神经网络
搭建简单神经网络,训练图像二分类问题模型。
- 数据集:cifa10
- 数据集筛选:选择其中的两个分类数据,用于二分类模型的训练和预测(训练集合测试集选择相同的两个分类)
- 数据预处理:数据进行了维度变换和数据通道调整,即将100003072(二分类50002条数据记录,3072个像素点数据)转换为了(10000,3,32,32)思维数组,根据cifar10官网介绍,将数组通道调换位置为(10000,32,32,3),并对每个数据点值的大小缩放至0-1之间便于计算。
- 网络结构:使用的是随意设计的一个神经网络结构,数据输入进来后,首先进行维度变换将压扁的3072个像素点通过cifar10官网的介绍拆解为RGB3通道3232像素的图像数据,用做神经网络的输入。神经网络中,第一层卷积层包含32个大小均为33的卷积核,后接2*2的maxpooling池化层,后面两个隐藏层也是这样的结构(隐藏层设计完全是随意设计的,没有加入任何优化及其他考量),之后通过flatten接入全连接层,在接一个dense输出一个分类结果。
- 模型训练优化方式:AdamOptimizer,学习速率设定为0.001。
- 模型激活函数及损失函数:在隐藏层中使用的激活函数是relu,全连接层使用的激活函数是sigmoid。损失函数使用的是均方误差损失函数。
- 训练:数据会根据指定的batch-size进行切分,训练时每次填入batch-size大小的数据进行学习,学完一遍训练集成为一个epoch。进行100个epoch的迭代学习。并在每次迭代完后用测试集进行测试,返回拟合后的误差和准确度。
- 在测试集上的评估结果:
两个epoch后准确度突破80%,10个epoch后准确度突破90%,30个epoch后准确度突破94%,并在94.5%-95%之间不停浮动,大约80次迭代后,准确度稳定在95%以上,在100次时的准确率为95.25%。
epoch: 100
loss: 0.0378034
accuracy: 0.9525
示例代码如下
import tensorflow as tf
import os
import pickle
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
data_path = '/Users/michealki/Desktop/Python/deep_learning/workspace/deep_learning/nn/cifar10'
filenames = ["data_batch_"+str(i+1) for i in range(5)]
file_path = [os.path.join(data_path,each) for each in filenames]
test_file_path = os.path.join(data_path,'test_batch')
def load_data(path):
"""加载数据"""
# return: dict
# type: data-numpy.ndarray, labels-list, bathch_label-str, filenames-list
# data-shape: (n,32*32*3) / (n,3072)
# data-format: R(32)R(32)-G(32)G(32)-B(32)B(32)
return pickle.load(open(path,'rb'),encoding='latin1')
def img_show(data):
assert len(data)==3072,'Img data err.'
data = np.reshape(data,(3,32,32))
data = np.transpose(data,(1,2,0))
plt.imshow(data)
def data_select(data,label):
"""返回指定label的数据"""
#return: data(filted),label
x = data['data']
y = np.array(data['labels']).reshape(-1,1)
data = np.hstack([x,y])
data = data[data[:,-1]==label]
return data[:,:-1],data[:,-1]
class CifaData:
def __init__(self,path,batch_size=None,need_shuffle=False,label=None):
self.label = label #标签选择
self.path = path
self.need_shuffle = need_shuffle
self.src_data = self.__load(path)
self.lens = self.src_data.shape[0]
self.feature_cnts = self.src_data.shape[1]-1
self.batch_size = self.lens if not batch_size else batch_size
self.batchs = []
self.N = 0 #batch_cnts
self.indicator = 0
self.__split()
def __load(self,path):
data = []
if isinstance(self.path,list):
for each in self.path:
tmp_ = load_data(each)
tmp_data = (np.array(tmp_['data']) / 127.5 -1) #将像素数据缩放至0-1之间
tmp_labels = np.array(tmp_['labels']).reshape([-1,1])
data.append(np.hstack([tmp_data,tmp_labels]))
else:
tmp_ = load_data(path)
tmp_data = (np.array(tmp_['data']) / 127.5 -1) #将像素数据缩放至0-1之间
tmp_labels = np.array(tmp_['labels']).reshape([-1,1])
data.append(np.hstack([tmp_data,tmp_labels]))
for i,each in enumerate(data):
if i == 0:
res = each
else:
res = np.vstack([res,each])
if self.label:
if not isinstance(self.label,list):
self.label = [self.label]
m_ = list(map(lambda x:x in(self.label),res[:,-1]))
res = res[m_]
return res
def __split(self):
last = 0
end = 0
batchs = []
N = self.lens//self.batch_size
np.random.shuffle(self.src_data)
for i in range(N):
end = last + self.batch_size
if end > self.lens:
raise Exception("param 'batch_size' is larger than data size.")
batchs.append(self.src_data[last:end,:])
last = end
if last < self.lens and self.need_shuffle:
p = np.random.permutation(self.lens)
tmp = self.src_data[p,:]
batchs.append(tmp[0:self.batch_size,:])
N = N+1
self.N = N # batch_cnts
self.batchs = batchs
def next_batch(self):
if self.indicator >= self.N:
return None
res = self.batchs[self.indicator]
self.indicator += 1
return [res[:,:-1],res[:,-1]]
def next_batch_rnd(self,batch_size):
p = np.random.permutation(batch_size)
if batch_size > self.lens:
raise Exception("param 'batch_size' is larger than data size.")
else:
batch = self.src_data[p,:]
return [batch[:,:-1],batch[:,-1]]
"""构建计算图"""
tf.reset_default_graph()
# input
x = tf.placeholder(tf.float32,[None,3072])
y = tf.placeholder(tf.int64,[None])
#hidden
x_t = tf.reshape(x,[-1,3,32,32])
x_t = tf.transpose(x_t,perm=[0,2,3,1])
conv1 = tf.layers.conv2d(x_t,32,(3,3),padding = 'SAME',activation=tf.nn.relu)
pooling1 = tf.layers.max_pooling2d(conv1,(2,2),(2,2))
conv2 = tf.layers.conv2d(pooling1,32,(3,3),padding = 'SAME',activation=tf.nn.relu)
pooling2 = tf.layers.max_pooling2d(conv2,(2,2),(2,2))
conv3 = tf.layers.conv2d(pooling2,32,(3,3),padding = 'SAME',activation=tf.nn.relu)
pooling3 = tf.layers.max_pooling2d(conv3,(2,2),(2,2))
#fc
flatten = tf.layers.flatten(pooling3)
fc = tf.layers.dense(flatten,50,tf.nn.sigmoid)
# output
y_ = tf.layers.dense(fc,1)
y_a = tf.nn.sigmoid(y_)
# loss
y_reshaped = tf.reshape(y, [-1,1])
y_reshaped_cast = tf.cast(y_reshaped, tf.float32)
loss = tf.reduce_mean(tf.square(y_reshaped_cast-y_a)) # 均方误差
pred = tf.cast((y_a>0.5), tf.int64)
y_equal = tf.equal(pred, y_reshaped)
accuracy = tf.reduce_mean(tf.cast(y_equal, tf.float32))
with tf.name_scope('train_op'):
train_op = tf.train.AdamOptimizer(1e-3).minimize(loss)
with tf.Session() as sess:
init = tf.global_variables_initializer()
sess.run(init)
epoch = 1000
l,a = 0,0
for each in range(epoch):
cifa = CifaData(file_path,batch_size=1000,label=[0,1],need_shuffle=True)
while(True):
d_ = cifa.next_batch()
if not d_:
break
l,a,_ = sess.run([loss,accuracy,train_op], feed_dict={x:d_[0], y:d_[1]})
print('[Train] epoch:{}, Loss:{:.2}, Accuracy:{:.2}'.format(each+1, l, a))
if (each+1)%10 == 0:
test_data,test_label = CifaData(test_file_path,label=[0,1]).next_batch()
l,a,y_pred = sess.run([loss,accuracy,pred],feed_dict={x: test_data, y: test_label})
print('[Test] epoch:{}, Loss:{:.2}, Accuracy:{:.2}'.format(each+1, l, a))