TensorFlow2.0学习笔记(二)

  • 基础运算API的使用
  • tf.constant 创建一个常量:
  • 简单运算,加,平方,转置后相乘:
  • numpy conversion:
  • string类型的变量以及数组,如何初始化,如何查看长度:
  • ragged tensor
  • ragged tensor 拼接:
  • ragged tensor转化为tensor
  • sparse tensor
  • 乘法、矩阵相乘
  • 变量
  • 变量的赋值操作
  • 自定义损失函数
  • 自定义层(线性层、soft层)
  • tf.function函数转换
  • tf.function和python_function
  • 第二种方法@tf.function
  • 函数需对变量进行操作
  • @tf.function参数设置,对函数的参数进行规定限制
  • get_concrete_function (可以保存的function)
  • 近似求导,自定义求导
  • 一阶求导
  • 二阶求导
  • 对变量求导(梯度),更新变量
  • 递归函数中应用自定义的求导

基础运算API的使用

首先引入头文件和库。同时因为TensorFlow会默认去调用gpu,所以我们这边将CUDA_VISIBLE_DEVICES设置为-1,让其只使用CPU。

import matplotlib as mpl
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
import sklearn
import pandas as pd
import os
import sys
import time
import tensorflow as tf

from tensorflow import keras

print(tf.__version__)
print(sys.version_info)
for module in mpl, np, pd, sklearn, tf, keras:
    print(module.__name__, module.__version__)
import os
os.environ["CUDA_VISIBLE_DEVICES"]="-1"

输出结果,也就是我们的环境配置:

2.0.1
sys.version_info(major=3, minor=7, micro=6, releaselevel='final', serial=0)
matplotlib 3.2.1
numpy 1.18.2
pandas 1.0.3
sklearn 0.22.2.post1
tensorflow 2.0.1
tensorflow_core.keras 2.2.4-tf

tf.constant 创建一个常量:

t = tf.constant([[1., 2., 3.], [4., 5., 6.]])

# index
print(t)
print(t[:, 1:])
print(t[..., 1])

print(t[…, 1])是指输出第二列,并整合成一维。

输出结果为:

tf.Tensor(
[[1. 2. 3.]
 [4. 5. 6.]], shape=(2, 3), dtype=float32)
tf.Tensor(
[[2. 3.]
 [5. 6.]], shape=(2, 2), dtype=float32)
tf.Tensor([2. 5.], shape=(2,), dtype=float32)

简单运算,加,平方,转置后相乘:

# ops
print(t+10)
print(tf.square(t))
print(tf.transpose(t))
print(t @ tf.transpose(t))

输出结果为:

tf.Tensor(
[[11. 12. 13.]
 [14. 15. 16.]], shape=(2, 3), dtype=float32)
tf.Tensor(
[[ 1.  4.  9.]
 [16. 25. 36.]], shape=(2, 3), dtype=float32)
tf.Tensor(
[[1. 4.]
 [2. 5.]
 [3. 6.]], shape=(3, 2), dtype=float32)
tf.Tensor(
[[14. 32.]
 [32. 77.]], shape=(2, 2), dtype=float32)

numpy conversion:

将tensor类型转化为numpy,以及将numpy转化为Tensor类型。

# numpy conversion
print(t.numpy())
print(np.square(t))
np_t = np.array([[1., 2., 3.], [4., 5., 6.]])
print(tf.constant(np_t))

输出结果为:

[[1. 2. 3.]
 [4. 5. 6.]]
[[ 1.  4.  9.]
 [16. 25. 36.]]
tf.Tensor(
[[1. 2. 3.]
 [4. 5. 6.]], shape=(2, 3), dtype=float64)

string类型的变量以及数组,如何初始化,如何查看长度:

# strings
t = tf.constant("cafe")
print(t)
print(tf.strings.length(t))
print(tf.strings.length(t, unit="UTF8_CHAR"))
print(tf.strings.unicode_decode(t, "UTF8"))
# string array
t = tf.constant(["cafe", "coffee", "咖啡"])
print(tf.strings.length(t, unit="UTF8_CHAR"))
r = tf.strings.unicode_decode(t, "UTF8")
print(r)

输出结果为:

tf.Tensor(b'cafe', shape=(), dtype=string)
tf.Tensor(4, shape=(), dtype=int32)
tf.Tensor(4, shape=(), dtype=int32)
tf.Tensor([ 99  97 102 101], shape=(4,), dtype=int32)

tf.Tensor([4 6 2], shape=(3,), dtype=int32)
<tf.RaggedTensor [[99, 97, 102, 101], [99, 111, 102, 102, 101, 101], [21654, 21857]]>

ragged tensor

接下来我们介绍一下ragged tensor,他和tensor的不同的得话,主要是他每一行的元素的个数可以是不同的,如下图所示,r[0]有2个元素,而r[1]有3个元素。

# ragged tensor
r = tf.ragged.constant([[11,22], [21, 22, 23], [], [41]])
# index op
print(r)
print(r[1])
print(r[1:3])

输出结果为:

<tf.RaggedTensor [[11, 22], [21, 22, 23], [], [41]]>
tf.Tensor([21 22 23], shape=(3,), dtype=int32)
<tf.RaggedTensor [[21, 22, 23], []]>

ragged tensor 拼接:

axis=0的时候按照行来拼接,axis=1的时候按照列来拼接,但是按照列拼接有一个前提条件,他们的行数要相同,这样才可以。

# ops on ragged tensor
r = tf.ragged.constant([[11,22], [21, 22, 23], [], [41]])
#按照行来拼接
r2 = tf.ragged.constant([[51,52],[],[71]])
print(tf.concat([r, r2], axis=0))
#按照列来拼接
r3 = tf.ragged.constant([[13, 14], [15], [], [42,43]])
print(tf.concat([r, r3], axis=1))

输出结果为:

<tf.RaggedTensor [[11, 22], [21, 22, 23], [], [41], [51, 52], [], [71]]>

<tf.RaggedTensor [[11, 22, 13, 14], [21, 22, 23, 15], [], [41, 42, 43]]>

ragged tensor转化为tensor

其中因为每一行的元素的个数是不同,所以我们按照元素个数最多的那一行进行扩展,补零。在其他个数较少的行的末尾进行补零

print(r.to_tensor())

结果为:

tf.Tensor(
[[11 22  0]
 [21 22 23]
 [ 0  0  0]
 [41  0  0]], shape=(4, 3), dtype=int32)

sparse tensor

sparse tensor是一种密集矩阵的表示,他给出了矩阵的几个位置以及这几个位置的值。其他的位置默认为0。同时我们也可以通过函数将这种密集矩阵的表示方法转化为tensor稀疏矩阵的表示方法。

# sparse tensor
s = tf.SparseTensor(indices = [[0,1], [1,0], [2,3]], # 必须排好序 
                    values = [1., 2., 3.],
                    dense_shape = [3, 4])
#这里有一个坑的点就是indices必须要排好序,如[0,1]必须在[0,2]之前,否则的放在转tensor之前需要进行排序
#s2=tf.sparse.reorder(s)
print(s)
print(tf.sparse.to_dense(s))

这里有一个坑的点就是indices必须要排好序,如[0,1]必须在[0,2]之前,否则的放在转tensor之前需要进行排序
s2=tf.sparse.reorder(s)
输出结果为:

SparseTensor(indices=tf.Tensor(
[[0 1]
 [1 0]
 [2 3]], shape=(3, 2), dtype=int64), values=tf.Tensor([1. 2. 3.], shape=(3,), dtype=float32), dense_shape=tf.Tensor([3 4], shape=(2,), dtype=int64))
tf.Tensor(
[[0. 1. 0. 0.]
 [2. 0. 0. 0.]
 [0. 0. 0. 3.]], shape=(3, 4), dtype=float32)

乘法、矩阵相乘

对于sparse tensor这类密集矩阵,进行乘法也就是对其的每个元素相乘。同时他不支持加法元素。矩阵相乘的话,是将密集矩阵转化为填充了0之后的稀疏矩阵之后再进行矩阵乘法,例子如下:

# ops on sparse tensors
s2 = s * 2.0
print(s2)

try:
    s3 = s + 1
except TypeError as ex:
    print(ex)

s4 = tf.constant([[10., 20.],
                  [30., 40.],
                  [50., 60.],
                  [70., 80.]])
print(tf.sparse.sparse_dense_matmul(s, s4))

输出结果为:

SparseTensor(indices=tf.Tensor(
[[0 1]
 [1 0]
 [2 3]], shape=(3, 2), dtype=int64), values=tf.Tensor([2. 4. 6.], shape=(3,), dtype=float32), dense_shape=tf.Tensor([3 4], shape=(2,), dtype=int64))
unsupported operand type(s) for +: 'SparseTensor' and 'int'
tf.Tensor(
[[ 30.  40.]
 [ 20.  40.]
 [210. 240.]], shape=(3, 2), dtype=float32)

变量

变量的创建初始化方法。以及转化为tensor和numpy。

# Variables
v = tf.Variable([[1., 2., 3.], [4., 5., 6.]])
print(v)
print(v.value())
print(v.numpy())

输出结果为:

<tf.Variable 'Variable:0' shape=(2, 3) dtype=float32, numpy=
array([[1., 2., 3.],
       [4., 5., 6.]], dtype=float32)>
tf.Tensor(
[[1. 2. 3.]
 [4. 5. 6.]], shape=(2, 3), dtype=float32)
[[1. 2. 3.]
 [4. 5. 6.]]

变量的赋值操作

可以对整个变量赋值,也可以对变量中的某一个元素或者某一行进行赋值

# assign value
v.assign(2*v)
print(v.numpy())
v[0, 1].assign(42)
print(v.numpy())
v[1].assign([7., 8., 9.])
print(v.numpy())

输出结果:

[[ 2.  4.  6.]
 [ 8. 10. 12.]]
[[ 2. 42.  6.]
 [ 8. 10. 12.]]
[[ 2. 42.  6.]
 [ 7.  8.  9.]]

这里需要注意的一个点就是,赋值需要用函数assign,而不能直接使用“=”进行赋值

自定义损失函数

自己实现mse均方差损失函数。

def customized_mse(y_true, y_pred):
    return tf.reduce_mean(tf.square(y_pred - y_true))

model = keras.models.Sequential([
    keras.layers.Dense(30, activation='relu',
                       input_shape=x_train.shape[1:]),
    keras.layers.Dense(1),
])
model.summary()
model.compile(loss=customized_mse, optimizer = "sgd",
              metrics = ["mean_squared_error"])
callbacks = [keras.callbacks.EarlyStopping(patience=5, min_delta=1e-3)]

自定义层(线性层、soft层)

通过自己实现的CustomizedDenseLayer类,在这个类中定义参数以及运算的公式来自定义一个线性层,

# customized dense layer
class CustomizedDenseLayer(keras.layers.Layer):
	#定义必要的几个固定的参数的初始化赋值
    def __init__(self, units, activation=None, **kwargs):
        self.units = units
        self.activation = keras.layers.Activation(activation)
        super(CustomizedDenseLayer, self).__init__(**kwargs)
    def build(self, input_shape):
        """构建所需要的参数"""
        self.kernel = self.add_weight(name = "kernel",
                                      shape = (input_shape[1], self.units),
                                      initializer = 'uniform',
                                      trainable = True)
        self.bias = self.add_weight(name = "bias",
                                    shape = (self.units,),
                                    initializer = 'zeros',
                                    trainable = True)
        super(CustomizedDenseLayer, self).build(input_shape)
    
    def call(self, x):
        return self.activation(x @ self.kernel + self.bias)
      
# tf.nn.softplus: log(1+e^x)  
customized_softplus = keras.layers.Lambda(lambda x: tf.nn.softplus(x))

model = keras.models.Sequential([
    CustomizedDenseLayer(30, activation='relu',
                       input_shape=x_train.shape[1:]),
    CustomizedDenseLayer(1),
    customized_softplus,
    # keras.layers.Dense(1, activation="softplus"),
    # keras.layers.Dense(1), keras.layers.Activation("softplus"),
])
model.summary()
model.compile(loss="mean_squared_error", optimizer = "sgd")
callbacks = [keras.callbacks.EarlyStopping(patience=5, min_delta=1e-3)]

tf.function函数转换

tf.function和python_function

tf.function把普通python函数写的代码块给变成TensorFlow里面的图实现的模块。autograph就是实现这个机制。

tf.function的作用主要是对程序进行加速的作用,因为TensorFlow的图实现对函数的实现进行了优化。

# tf.function and autograph
def scaled_elu(z, scale=1.0, alpha=1.0):
    # z>=0 ? scale*z : scale*alpha*tf.nn.elu(z)
    is_positive = tf.greater_equal(z, 0.0)
    return scale * tf.where(is_positive, z, alpha*tf.nn.elu(z))

print(scaled_elu(tf.constant(-3.)))
print(scaled_elu(tf.constant([-3., -2.5])))


scaled_elu_tf = tf.function(scaled_elu)
print(scaled_elu_tf(tf.constant(-3.)))
print(scaled_elu_tf(tf.constant([-3., -2.5])))

print(scaled_elu_tf.python_function is scaled_elu)

输出结果如下,我们可以看到两者的结果是一样的。我们需要重点关注的是两个函数,一个函数是tf.function,他是将python实现的函数转化为tf.function的TensorFlow图实现的函数。另一个函数是scaled_elu_tf.python_function ,他是前者的逆变换,从图实现重新转化为原来的python实现。

tf.Tensor(-0.95021296, shape=(), dtype=float32)
tf.Tensor([-0.95021296 -0.917915  ], shape=(2,), dtype=float32)
tf.Tensor(-0.95021296, shape=(), dtype=float32)
tf.Tensor([-0.95021296 -0.917915  ], shape=(2,), dtype=float32)
True

接下来我们再比较一下两个者运行的速度的比较。

%timeit scaled_elu(tf.random.normal((1000, 1000)))
%timeit scaled_elu_tf(tf.random.normal((1000, 1000)))

结果如下,我们可以看到转换为TensorFlow的图实现后速度加快,用的时间更少,并且这只是在cpu上实现的结果,如果使用gpu且数据量更大的时候,这个加速的效果会更加的明显。

10 loops, best of 3: 24.4 ms per loop
The slowest run took 5.39 times longer than the fastest. This could mean that an intermediate result is being cached.
10 loops, best of 3: 22.2 ms per loop

第二种方法@tf.function

在函数前添加@tf.function,使得函数从python函数变成进行过图优化的函数。例子如下:

# 1 + 1/2 + 1/2^2 + ... + 1/2^n
@tf.function
def converge_to_2(n_iters):
    total = tf.constant(0.)
    increment = tf.constant(1.)
    for _ in range(n_iters):
        total += increment
        increment /= 2.0
    return total

print(converge_to_2(20))

函数需对变量进行操作

需要主要的是,变量的定义需要放在函数的外部!!

var = tf.Variable(0.)

@tf.function
def add_21():
    return var.assign_add(21)

print(add_21())

@tf.function参数设置,对函数的参数进行规定限制

可以看到input_signature指的就是输入的参数,他对输入参数的大小,参数的类型,名字等都进行了定义,如果输入参数的类型不符合的话,他就会报错。

@tf.function(input_signature=[tf.TensorSpec([None], tf.int32, name='x')])
def cube(z):
    return tf.pow(z, 3)
try:
    print(cube(tf.constant([1., 2., 3.])))
except ValueError as ex:
    print(ex)
print(cube(tf.constant([1, 2, 3])))

get_concrete_function (可以保存的function)

我们直接通过定义好的函数,调用get_concrete_function 来设置输入参数input_signature的相关信息即可。

# @tf.function py func -> tf graph
# get_concrete_function -> add input signature -> SavedModel

cube_func_int32 = cube.get_concrete_function(
    tf.TensorSpec([None], tf.int32))

print(cube_func_int32)

以上的方法是普遍通用的,不知道输入数据的维度,如果知道的话也可以直接用以下的方法直接定义。下面两者的值都为true

print(cube_func_int32 is cube.get_concrete_function(
    tf.TensorSpec([5],tf.int32)))

print(cube_func_int32 is cube.get_concrete_function(
    tf.constant([1,2,3])))

近似求导,自定义求导

一阶求导

首先用数学中最简单暴力的方法求导,也就是x=a时的导数,等于f(a+eps)-f(a-eps)/2*eps

def f(x):
    return 3. * x ** 2 + 2. * x - 1

def approximate_derivative(f, x, eps = 1e-3):
    return (f(x+eps) - f(x-eps)) / (2. * eps)

print(approximate_derivative(f, 1.))

结果为7.999999999999119无限接近8.。
接下来,假设我们的函数不是一元函数,而是二元函数,有两个变量的话,我们的求导方式一般是将其中的一个值规定,然后按照上诉的方法对剩下那个变量进行相当于单变量的求导。

def g(x1, x2):
    return (x1 + 5) * (x2 ** 2)

def approximate_gradient(g, x1, x2, eps=1e-3):
    dg_x1 = approximate_derivative(lambda x: g(x, x2), x1, eps)
    dg_x2 = approximate_derivative(lambda x: g(x1, x), x2, eps)
    return dg_x1, dg_x2

print(approximate_gradient(g, 2., 3.))

结果为(8.999999999993236, 41.999999999994486)

接下来我们对tf的变量进行求导。

对二元公式求导,有两个变量。这里需要注意的是persistent 要设置成true,不然tape在求了一次导数之后就会自动清除掉,这个就是会永久保留。但是需要注意的是,我们需要在最后手动del删除,释放系统资源。

x1 = tf.Variable(2.0)
x2 = tf.Variable(3.0)

with tf.GradientTape(persistent = True) as tape:
    z = g(x1, x2)

dz_x1 = tape.gradient(z, x1)
dz_x2 = tape.gradient(z, x2)
print(dz_x1, dz_x2)

del tape

上面的方法是两个变量,分两次求导,也可以写在一起,写法如下:

x1 = tf.constant(2.0)
x2 = tf.constant(3.0)

with tf.GradientTape() as tape:
    z = g(x1, x2)

dz_x1x2 = tape.gradient(z, [x1, x2])
print(dz_x1x2)

输出结果为:
[<tf.Tensor: id=149, shape=(), dtype=float32, numpy=9.0>, <tf.Tensor: id=161, shape=(), dtype=float32, numpy=42.0>]

对于常量求导的话,我们需要注意在tape内定义函数的时候,我们需要tape.watch关注那个常量,否则常量求导的值就为none,他是无法求导的。

x1 = tf.constant(2.0)
x2 = tf.constant(3.0)

with tf.GradientTape() as tape:
    tape.watch(x1)
    tape.watch(x2)
    z = g(x1, x2)

dz_x1x2 = tape.gradient(z, [x1, x2])
print(dz_x1x2)

输出结果为:

[<tf.Tensor: id=192, shape=(), dtype=float32, numpy=9.0>, <tf.Tensor: id=204, shape=(), dtype=float32, numpy=42.0>]

接下的话,假如函数有多个,但变量只有一个的情况,同时两个函数对这个变量求导,在将两个函数分别算出来的导数相加。

x = tf.Variable(5.0)
with tf.GradientTape() as tape:
    z1 = 3*x
    z2 = x**2
tape.gradient([z1, z2], x)

输出结果为:

<tf.Tensor: id=261, shape=(), dtype=float32, numpy=13.0>

二阶求导

二阶求导的话,需要设置两层的tape,然后在外层的outer_tape中对内层的inner_tape求导,值放在inner_grads中。
然后我们在tape外对inner_grads求导,这样就相当于求了两次导数。

x1 = tf.Variable(2.0)
x2 = tf.Variable(3.0)

with tf.GradientTape(persistent = True) as outer_tape:
    with tf.GradientTape(persistent = True) as inner_tape:
        z = g(x1, x2)

    inner_grads = inner_tape.gradient(z, [x1,x2])
outer_grads = [outer_tape.gradient(inner_grad, [x1,x2])
               for inner_grad in inner_grads]
print(outer_grads)

del inner_tape
del outer_tape

对变量求导(梯度),更新变量

设置学习率,每次迭代都对x求导数,计算出梯度,然后更新变量x,使得其不断向最低点更新。有两种写法一种是自己手写的更新变量,一种是设置optimizer优化器,让你自动去更新。

第一种:

learning_rate = 0.1
x = tf.Variable(0.0)

for _ in range(100):
    with tf.GradientTape() as tape:
        z = f(x)
    dz_dx = tape.gradient(z, x)
    x.assign_sub(learning_rate * dz_dx)
print(x)

第二种:

learning_rate = 0.1
x = tf.Variable(0.0)

optimizer = keras.optimizers.SGD(lr = learning_rate)
for _ in range(100):
    with tf.GradientTape() as tape:
        z = f(x)
    dz_dx = tape.gradient(z, x)
    optimizer.apply_gradients([(dz_dx, x)])
print(x)

两种方法的输出结果都是一样的,都是:
<tf.Variable ‘Variable:0’ shape=() dtype=float32, numpy=-0.3333333>

递归函数中应用自定义的求导

首先我们看一下metric 的使用,使用算均方差。前后两者的差的平方,然后他会将之前的值也会累加,最后再除以一个个数算平均。

# metric使用
metric = keras.metrics.MeanSquaredError()
print(metric([0.], [1.]))
print(metric([5.], [2.]))

print(metric([0.], [1.]))
print(metric.result())

metric.reset_states()
metric([1.], [3.])
print(metric.result())

输出结果为:

tf.Tensor(1.0, shape=(), dtype=float32)
tf.Tensor(5.0, shape=(), dtype=float32)
tf.Tensor(3.6666667, shape=(), dtype=float32)
tf.Tensor(3.6666667, shape=(), dtype=float32)
tf.Tensor(4.0, shape=(), dtype=float32)

接下来就开始我们搭建我们的模型,其中的梯度更新由我们自己定义:
random_batch这个函数的作用就是从训练的数据中随机的取出大小为batch_size的训练数据,作为一个step的输入数据去训练。
这里还有一个比较注意的是理解一下loss 和metric两者的作用,两个都是在求均方差,但是loss的话就是只计算当前这组输出结果的均方差,没有累计之前的结果,而metric有累计之前均方差的效果,他可以把一个epoch内每个step算出来的均方差都计算后累计,最后求平均。

epochs = 100
batch_size = 32
steps_per_epoch = len(x_train_scaled) // batch_size
optimizer = keras.optimizers.SGD()
metric = keras.metrics.MeanSquaredError()

def random_batch(x,y, batch_size=32):
    idx = np.random.randint(0, len(x), size=batch_size)
    return x[idx], y[idx]

model = keras.models.Sequential([
    keras.layers.Dense(30, activation='relu',
                       input_shape=x_train.shape[1:]),
    keras.layers.Dense(1),
])

for epoch in range(epochs):
    metric.reset_states()
    for step in range(steps_per_epoch):
        x_batch, y_batch = random_batch(x_train_scaled, y_train, batch_size)
        with tf.GradientTape() as tape:
            y_pred = model(x_batch)
            loss = tf.reduce_mean(
                    keras.losses.mean_squared_error(y_batch, y_pred))
            metric(y_batch, y_pred)
        grads = tape.gradient(loss, model.variables)
        grads_and_vars = zip(grads, model.variables)
        optimizer.apply_gradients(grads_and_vars)
        print("\rEpoch", epoch, "train mse:",
              metric.result().numpy(), end="")
    y_valid_pred = model(x_valid_scaled)
    valid_loss = tf.reduce_mean(
                keras.losses.mean_squared_error(y_valid_pred, y_valid))
    print("\t", "valid mse: ", valid_loss.numpy())