附录6:TensorFlow基础(二)

TensorFlow实现逻辑回归

加载mnist数据集

为了简单起见,使用小型而经典的mnist数据集,在tensorflow中,已有处理mnist数据集的内置工具:tensorflow.examples.tutorials.mnist
数据集mnist的下载链接为:mnist
下载完成后,在本地mnist目录下有以下4个压缩文件:
fig1
利用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)

fig2
由于归一化处理的原因,显示的图像不是纯黑白的;

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=0C1exp(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()

fig3

补充:分析计算图

使用tensorboard查看计算图,可以得到:
fig4
左边的计算图表示需要计算梯度的op,右边计算图表示如何执行minimize这个op,minimize就是优化模型的过程,所以需要weightsbias以及更新它们的gridents

fig5容易看出,执行optimizer时,原计算图自动添加并执行了gradients(),左边分支为reduce_mean_cross_entropy用于计算loss,右边分支为reduce_mean_correct用于计算accuracy,weightsbias在执行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+aw
  • 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()可以打印模型结构:
fig6
指定优化方法与损失函数通过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
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值