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())