目录
TensorFlow实现逻辑回归
加载mnist数据集
为了简单起见,使用小型而经典的mnist数据集,在tensorflow中,已有处理mnist数据集的内置工具:tensorflow.examples.tutorials.mnist
;
数据集mnist的下载链接为:mnist
下载完成后,在本地mnist目录下有以下4个压缩文件:
利用tensorflow的工具加载数据集:
from tensorflow.examples.tutorials.mnist import input_data
mnist=input_data.read_data_sets("./mnist",one_hot=True)
print(mnist.train.images.shape)
print(mnist.train.labels.shape)
"""
(55000, 784)
(55000, 10)
"""
数据集中,images中每个图像已经是归一化处理的张量,且张量 ( 1 , 28 , 28 ) (1,28,28) (1,28,28)已经被reshape到 ( 784 , ) (784,) (784,),其类别的形状为 ( 10 , ) (10,) (10,):
import matplotlib.pyplot as plt
plt.imshow(mnist.train.images[6].reshape([28,28]))
plt.show()
print(mnist.train.labels[6])
print(mnist.train.images[6].shape)
print(mnist.train.labels[6].shape)
由于归一化处理的原因,显示的图像不是纯黑白的;
Logistic Regression
逻辑回归的分类原理来自sigmoid函数,如果对一个样本,输入
n
n
n维特征向量为
X
=
[
x
1
,
x
2
,
.
.
.
,
x
n
]
T
X=[x_{1},x_{2},...,x_{n}]^{T}
X=[x1,x2,...,xn]T,则利用逻辑回归二分类的模型为:
l
o
g
i
s
t
i
c
(
X
)
=
1
1
+
e
−
(
W
X
+
b
)
logistic(X)=\frac{1}{1+e^{-(WX+b)}}
logistic(X)=1+e−(WX+b)1
W
W
W和
b
b
b是模型的待学习参数;
广义地,如果面对多类别问题,假设共
C
C
C类,其实可以用softmax代替sigmoid,对于第
i
i
i类对象,分类概率计算为:
z
=
W
X
+
b
z=WX+b
z=WX+b
s
o
f
t
m
a
x
(
z
)
=
e
x
p
(
z
[
i
]
)
∑
j
=
0
C
−
1
e
x
p
(
z
[
j
]
)
softmax(z)=\frac{exp(z[i])}{\sum_{j=0}^{C-1}exp(z[j])}
softmax(z)=∑j=0C−1exp(z[j])exp(z[i])
定义计算图
首先,设置超参数与定义占位符:
import tensorflow as tf
# Model Init
batch_size=200
lr=1e-1
num_epochs=50
num_train,num_feats=mnist.train.images.shape
num_classes=mnist.train.labels.shape[1]
num_test=mnist.test.images.shape[0]
train_x=tf.placeholder(tf.float32,[None,num_feats],name="train_x")
train_y=tf.placeholder(tf.float32,[None,num_classes],name="train_y")
定义待学习参数:
w=tf.Variable(tf.random_normal(shape=[num_feats,num_classes],stddev=0.1),name="weights")
b=tf.Variable(tf.zeros([num_classes]),name="bias")
定义模型,并计算交叉熵损失函数:
# Model : softmax(Wx+b)
logits=tf.add(tf.matmul(train_x,w,name="matdot"),b,name="Add")
cross_entropy=tf.nn.softmax_cross_entropy_with_logits(logits=logits,labels=train_y,name="loss")
函数softmax_cross_entropy_with_logits
先根据输入logits的得分计算softmax概率,再结合labels计算交叉熵,labels为one-hot编码的向量,tensorflow不同于pytorch的索引取值,tensorflow必须直观地传递one-hot编码向量;
tensorflow.nn.softmax_cross_entropy_with_logits(
logits,
labels,
axis=-1,
name=None)
axis=-1
代表在输入张量最后一个轴方向进行操作,在这个实验中,输入张量logits为
(
N
o
n
e
,
10
)
(None,10)
(None,10),每个样本计算softmax后就变成了一组概率值
(
10
,
)
(10,)
(10,),交叉熵结合标签对真实类别的概率值取对数并取相反数,得到一个数值,所以cross_entropy形状为
(
N
o
n
e
,
)
(None,)
(None,)
基于每个样本的交叉熵计算损失:
# 如果不指定axis,则计算所有元素的均值
loss=tf.reduce_mean(cross_entropy,name="reduce_mean_cross_entropy")
计算模型的分类准确率:
preds=tf.nn.softmax(logits,name="softmax") # [None,10]
# tf.equal返回和输入两张量形状相同的布尔型张量
correct_preds=tf.equal(tf.argmax(preds,1,name="preds_arg"),tf.argmax(train_y,1,name="label_arg"),name="equal")
# tf.cast 数据类型转换
accuracy=tf.reduce_mean(tf.cast(correct_preds,tf.float32),name="reduce_mean_correct")
用梯度下降优化模型:
optimizer=tf.train.GradientDescentOptimizer(lr,name="GradientDescent").minimize(loss,name="minimize")
optimizer实际上是一个计算图,依赖于子计算图:loss和源op:lr;
定义其他对象:
import time
num_batches=num_train/batch_size
losses=[]
train_accs,valid_accs=[],[]
time_start=time.time()
关于参数更新在pytorch上的比较
- pytorch中,从torch.optim选择优化方法,并将
model.parameters()
作为参数传入,其本质也是类似上面创建了新的计算图,执行optimizer.step()
后就根据张量自身的对象grad
,按照优化算法的流程更新parameters()
; - tensorflow的张量没有对象
grad
,所以在会话执行optimizer
时,会调用gradients()
计算梯度,再更新计算图中的Variable
对象; - tensorflow的张量自身不设置对象
grad
,这避免了grad的累加,从而不需要pytorch中的model.zero_grad()
操作;
执行计算图
参数更新已在上面进行了分析;所以,只需要把optimizer加入到会话,运行这个计算图,便可以实现训练:
with tf.Session() as sess:
writer=tf.summary.FileWriter("./LRGraphs",sess.graph)
# 初始化模型的参数
sess.run(tf.global_variables_initializer())
for i in range(num_epochs):
total_loss=0.0
for _ in range(int(num_batches)):
x_batch,y_batch=mnist.train.next_batch(batch_size)
_,loss_batch=sess.run([optimizer,loss],feed_dict={train_x:x_batch,train_y:y_batch})
total_loss += loss_batch
train_acc = sess.run([accuracy], feed_dict={train_x: mnist.train.images, train_y: mnist.train.labels})
valid_acc = sess.run([accuracy],feed_dict={train_x: mnist.validation.images, train_y: mnist.validation.labels})
losses.append(total_loss/num_batches)
train_accs.append(train_acc)
valid_accs.append(valid_acc)
print("Number of iteration: {}, total_loss = {}, train accuracy = {}, validation accuracy = {}".format(i, total_loss/num_batches, train_acc, valid_acc))
test_acc = sess.run([accuracy], feed_dict={train_x: mnist.test.images, train_y: mnist.test.labels})
time_end = time.time()
print("Time used for training = {} seconds.".format(time_end - time_start))
print("MNIST image classification accuracy on test set = {}".format(test_acc))
writer.close()
绘制学习曲线:
# Plot the losses during training.
plt.figure()
plt.title("Logistic regression with TensorFlow")
plt.plot(losses, "b-o", linewidth=2)
plt.grid(True)
plt.xlabel("Iteration")
plt.ylabel("Cross-entropy")
plt.show()
补充:分析计算图
使用tensorboard查看计算图,可以得到:
左边的计算图表示需要计算梯度的op,右边计算图表示如何执行minimize
这个op,minimize
就是优化模型的过程,所以需要weights
和bias
以及更新它们的gridents
;
容易看出,执行optimizer时,原计算图自动添加并执行了gradients()
,左边分支为reduce_mean_cross_entropy
用于计算loss,右边分支为reduce_mean_correct
用于计算accuracy,weights
和bias
在执行gradients()
的同时也执行了minimize
;
使用高层的封装Keras
Keras 是一个用 Python 编写的高级神经网络 API,它能够以 TensorFlow,CNTK,或者 Theano 作为后端运行。Keras 的开发重点是支持快速的实验。能够以最小的时延把你的想法转换为实验结果,是做好研究的关键。
深度学习发展到现在,Keras已经融入了TensorFlow,使用Keras类似于使用pytorch中的nn库,让研究人员用搭建积木的方式实现模型。
Keras Sequential
首先以Dense(类似torch.nn.Linear)为例了解其使用:
keras.layers.Dense(units,
activation=None,
input_shape=None,
use_bias=True,
kernel_initializer='glorot_uniform',
bias_initializer='zeros',
kernel_regularizer=None,
bias_regularizer=None)
- units: 正整数,输出空间维度
- activation:激活函数
- input_shape:输入张量去除batch_size维度后的形状
- kernel_initializer:权值矩阵的初始化方法
- kernel_regularizer:权值矩阵的正则化方法
关于正则化
关于正则化:
正则化允许在优化过程中对层的参数进行惩罚,把待学习参数
w
w
w与惩罚系数
a
a
a融入损失函数
J
J
J中;
- L1正则化后损失函数为:
J = J + a ∣ w ∣ J=J+a|w| J=J+a∣w∣ - L2正则化后损失函数为:
J = J + a w 2 J=J+aw^{2} J=J+aw2
可见,惩罚项加入损失,迫使参数值减小,而对于一个线性层
z
=
W
X
+
b
z=WX+b
z=WX+b来说,其输出
z
z
z的绝对值将减小,如果让其通过sigmoid函数,值小的
z
z
z更容易落入激活函数的线性区间,网络捕捉线性分布是容易的,模型不易于复杂化,也就避免了过拟合。
Keras有已经定义好的正则化方法:
keras.regularizers.l1(0.)
keras.regularizers.l2(0.)
keras.regularizers.l1_l2(l1=0.01, l2=0.01)
# 使用
Dense(kernel_regularizer=regularizers.l2(0.01))
也可以自己定义:
from keras import backend as K
def l1_reg(weight_matrix):
return 0.01 * K.sum(K.abs(weight_matrix))
# 使用
Dense(kernel_regularizer=l1_reg)
基于Keras Sequential的mnist分类
因为keras封装了tensorflow,所以不需要显式地执行计算图,keras符合研究人员的习惯:定义模型,指定优化方法与损失函数,训练,验证;
同样使用本篇第一部分的mnist数据集,使用Keras Sequential定义模型为:
model=tf.keras.models.Sequential(
[
tf.keras.layers.Dense(128,
activation="relu",
input_shape=(784,) # 输入张量为(None,784)
),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Dense(10,
activation="softmax",
input_shape=(128,) #可以省略
)
]
)
# 打印模型结构
model.summary()
model.summary()
可以打印模型结构:
指定优化方法与损失函数通过compile实现:
model.compile(optimizer="adam",
loss="categorical_crossentropy",
metrics=['accuracy']
)
训练模型:
x_train=mnist.train.images
y_train=mnist.train.labels
model.fit(x_train, y_train, epochs=5)
验证:
x_test=mnist.validation.images
y_test=mnist.validation.labels
model.evaluate(x_test,y_test)
继承Keras Model
准备工作
首先导入需要的包和模块;
注意,由于后面会使用生成器方式:tf.data.Dataset.from_tensor_slices
加载数据,在tensorflow1.x版本中,dataset.__iter__()
仅支持在eager execution
为 enabled 的情况下可调用,所以要执行tf.enable_eager_execution()
:
import tensorflow as tf
from tensorflow.keras.layers import Dense, Flatten, Conv2D
from tensorflow.keras import Model
tf.enable_eager_execution()
加载mnist数据集:
from tensorflow.examples.tutorials.mnist import input_data
mnist=input_data.read_data_sets("./mnist",one_hot=True)
print(mnist.train.images.shape) # (55000, 784)
print(mnist.train.labels.shape) # (55000, 10)
由于要对图像进行卷积,所以要改变数据的形状,使之成为单通道的图像张量;
tf.expand_dims可以增加input张量维度,axis为新增维度的轴序号;
tf.expand_dims(
input,
axis=None,
name=None
)
x=tf.zeros([5,3])
# -1表示新增维度在最后一维
x=tf.expand_dims(x,axis=-1)
"""
<tf.Tensor 'ExpandDims:0' shape=(5, 3, 1) dtype=float32>
"""
改变张量为:
x_train=mnist.train.images.reshape(-1,28,28)
y_train=mnist.train.labels
x_test=mnist.validation.images.reshape(-1,28,28)
y_test=mnist.validation.labels
# 由于要使用卷积Conv2D,所以增加一个通道
x_train=tf.expand_dims(x_train,axis=-1) # (55000, 28, 28, 1)
x_test=expand_dims(x_test,axis=-1) # (5000, 28, 28, 1)
tf.data.Dataset.from_tensor_slices数据加载
本质是基于生成器加载数据,类似于pytorch中的dataloader;
在使用前需要先知道shuffle与batch的机制:
- 对于
shuffle(buffer_size)
,参数buffer_size值越大,意味着数据混乱程度也越大;
假设buffer_size = 9,也即先从数据集中取出 9 个batch到 buffer 中,真正训练数据的样本将从 buffer 中获取。 从 buffer 随机取出一个batch即 “item7”,现在 buffer 内只有 8 个batch,然后从数据集中按顺序取出batch即 “item10” 到 buffer 区域填补空缺; batch(batch_size)
即为设置batch的数据数量;- 如果没有shuffle,则根据batch顺序加载数据;
因此,返回一个dataloader有:
train_ds = tf.data.Dataset.from_tensor_slices(
(x_train, y_train)).shuffle(10000).batch(32)
test_ds = tf.data.Dataset.from_tensor_slices(
(x_test, y_test)).batch(32)
获取一个batch:
batch=next(iter(train_ds))
print(batch[0].shape) # (32, 28, 28, 1)
print(batch[1].shape) # (32, 10)
Keras Model继承
和pytorch中继承torch.nn.Module
是相似的,继承tf.keras.Model
至少需要实现两个实例方法:
- 1.要求导的层需要定义在__init__下
- 2.层的前向传播过程写在call下
Conv2D,Flatten,Input
Conv2D:
keras.layers.Conv2D(filters,
kernel_size,
strides=(1, 1),
padding='valid',
data_format=None,
dilation_rate=(1, 1),
activation=None,
use_bias=True,
kernel_initializer='glorot_uniform',
bias_initializer='zeros',
kernel_regularizer=None,
bias_regularizer=None)
- filters:输出张量的通道数
- data_format:默认为channels_last;
如果data_format='channels_first'
,则输入输出张量格式为 ( b a t c h , c h a n n e l , h e i g h t , w i d t h ) (batch,channel,height,width) (batch,channel,height,width),否则默认为 ( b a t c h , h e i g h t , w i d t h , c h a n n e l ) (batch,height,width,channel) (batch,height,width,channel)
Flatten:
keras.layers.Flatten(data_format=None)
- data_format:默认为channels_last;
将输入张量reshape为一个向量,但batch维度不变,即 ( b a t c h , h e i g h t , w i d t h , c h a n n e l ) (batch,height,width,channel) (batch,height,width,channel)变为 ( b a t c h , h e i g h t × w i d t h × c h a n n e l ) (batch,height\times width\times channel) (batch,height×width×channel)
Input:
用于初始化一个张量实例
tf.keras.layers.Input(
shape=None,
batch_size=None,
name=None,
dtype=None
)
shape不包含batch维度;
比如:
x=tf.keras.layers.Input(shape=(28,28,1),batch_size=32)
"""
<tf.Tensor 'input:0' shape=(32, 28, 28, 1) dtype=float32>
"""
定义模型:
class MyModel(Model):
def __init__(self):
super().__init__()
self.conv1=Conv2D(32,3,activation="relu")
self.flatten=Flatten()
self.d1=Dense(128,activation="relu")
self.d2=Dense(10,activation="softmax")
def call(self,x):
x=self.conv1(x)
x=self.flatten(x)
x=self.d1(x)
return self.d2(x) # (None,10)
model=MyModel()
剩下部分与版本相关,个人认为版本问题的确是tensorflow的大问题
tensorflow1.x适用
简单调用fit与evaluate,注意必须compile模型:
model.compile(optimizer="adam",
loss="categorical_crossentropy",
metrics=['accuracy']
)
训练与验证,注意,fit和evaluate需要输入ndarray类型的数据:
NUM_EPOCHS=5
for epoch in range(NUM_EPOCHS):
for idx,(images,labels) in enumerate(train_ds):
# 虽然写了epochs=1,实际上是一个batch作为此次fit的epoch
# fit和evaluate需要输入ndarray类型的数据
model.fit(images.numpy(),labels.numpy(),epochs=1)
for idx,(t_images,t_labels) in enumerate(test_ds):
# 分别在不同batch上验证
model.evaluate(t_images.numpy(),t_labels.numpy())
tensorflow2.0 Beta 适用
在tensorflow2.0 Beta中,Google工程师完成了2.0 API的重命名和弃用符号,意味着这将是2.0最终版本的API,不用担心使用2.0 API 编写的代码在将来不可用;
Alpha 指的是内测,即现在说的 CB,即开发团队内部测试的版本或者有限用户的体验测试版本。Beta 指的是公测,即针对所有用户公开的测试版本。而做过一些修改,成为正式发布的候选版本时(现在叫做 RC - Release Candidate),叫做 Gamma;
安装tensorflow2.0 Beta:
pip install tensorflow-gpu==2.0.0-beta0
选择损失函数与优化方法:
loss_object = tf.keras.losses.CategoricalCrossentropy()
optimizer = tf.keras.optimizers.Adam()
选择衡量指标来度量模型的损失(loss)和准确率(accuracy),这些指标在调用时累积值,使用.result()
可以返回结果:
train_loss = tf.keras.metrics.Mean()
train_accuracy = tf.keras.metrics.CategoricalAccuracy()
test_loss = tf.keras.metrics.Mean()
test_accuracy = tf.keras.metrics.CategoricalAccuracy()
使用 tf.GradientTape
来训练模型:
def train_step(images, labels):
with tf.GradientTape() as tape:
preds=model.call(images)
loss=loss_object(labels,preds)
# 计算梯度
gradients=tape.gradient(loss,model.trainable_variables)
# 更新参数
optimizer.apply_gradients(zip(gradients,model.trainable_variables))
# 计算评价指标
train_loss(loss)
train_accuracy(labels,preds)
验证模型:
def test_step(images,labels):
preds=model.call(images)
t_loss=loss_object(labels,preds)
# 计算评价指标
test_loss(t_loss)
test_accuracy(labels,preds)
加载数据训练模型并验证:
NUM_EPOCHS=5
for epoch in range(NUM_EPOCHS):
# 在每一个epoch开始时,重置评估指标
train_loss.reset_states()
train_accuracy.reset_states()
test_loss.reset_states()
test_accuracy.reset_states()
for idx,(images,labels) in enumerate(train_ds):
train_step(images,labels)
for idx,(t_images,t_labels) in enumerate(test_ds):
test_step(t_images,t_labels)
template="Epoch {},Loss:{},Accuracy:{},Test Loss:{},Test Accuracy:{}"
print(template.format(epoch+1,
train_loss.result(),
train_accuracy.result(),
test_loss.result(),
test_accuracy.result()))
结果为:
Epoch 1, Loss: 0.13825324177742004, Accuracy: 95.89166259765625, Test Loss: 0.07461485266685486, Test Accuracy: 97.47999572753906
Epoch 2, Loss: 0.04554400220513344, Accuracy: 98.61666870117188, Test Loss: 0.05126383528113365, Test Accuracy: 98.29000091552734
Epoch 3, Loss: 0.024927066639065742, Accuracy: 99.18500518798828, Test Loss: 0.05301696062088013, Test Accuracy: 98.30999755859375
Epoch 4, Loss: 0.014068767428398132, Accuracy: 99.52832794189453, Test Loss: 0.051672786474227905, Test Accuracy: 98.58000183105469
Epoch 5, Loss: 0.009344187565147877, Accuracy: 99.69166564941406, Test Loss: 0.06102905049920082, Test Accuracy: 98.25