目录
4.2 使用keras.optimizers.SGD() 中的 apply_gradient特性,完成梯度下降算法
1. 导数/梯度的理论知识
2. 手撕求导函数
2.1 一元方程
def f(x):
return 3. * x ** 2 + 2. * x - 1
def approximae_derivative(f, x, eps=1e-3):
return (f(x + eps) - f(x - eps)) / (2. * eps)
print(approximae_derivative(f, 1.))
2.2 二元方程
# 定义二元方程
def g(x1, x2):
return (x1 + 5) * (x2 ** 2)
# 正确的对二元方程进行求导
def approximae_gradient(g, x1, x2, eps=1e-3):
# 使用一元方程的求导函数approximae_derivative
dg_x1 = approximae_derivative(lambda x: g(x, x2), x1, eps)
dg_x2 = approximae_derivative(lambda x: g(x1, x), x2, eps)
return dg_x1, dg_x2
print(approximae_gradient(g, 2., 3.))
3. 用tensorflow中的库函数完成求导
3.1 一阶函数求导/梯度
x1 = tf.Variable(2.0)
x2 = tf.Variable(3.0)
with tf.GradientTape() as tape:
z = g(x1, x2) # g(x1, x2) 在前面定义过
dz_x1 = tape.gradient(z, x1)
# note:tape执行完一遍之后就会被释放,所以再调用tape就会报错
try:
dz_x2 = tape.gradient(z, x2)
except RuntimeError as ex:
print(ex)
tape执行完一遍之后就会被释放,遇到n元函数,难道要定义n次tape?
———— 设置tf.GradientTape中的参数 persistent=True
x1 = tf.Variable(2.0)
x2 = tf.Variable(3.0)
with tf.GradientTape(persistent=True) as tape:
z = g(x1, x2) # g(x1, x2) 在前面定义过
dz_x1 = tape.gradient(z, x1)
dz_x2 = tape.gradient(z, x2)
print(dz_x1, dz_x2) # 输出结果和我们手动求导得到的结果一样哦
# 手动释放掉 tape
del tape
———— 在求梯度时,一次将所有的变量都传入
x1 = tf.Variable(2.0)
x2 = tf.Variable(3.0)
with tf.GradientTape() as tape: # 不设置 persistent=True
z = g(x1, x2) # g(x1, x2) 在前面定义过
dz_x1_x2 = tape.gradient(z, [x1, x2]) # 求偏导时,将变量一次性传入
print(dz_x1_x2) # 输出结果和我们手动求导得到的结果一样哦
若函数的变量被赋予具体的数值,被定义为常量————需要使用watch,关注一下该常量
x1 = tf.constant(2.0)
x2 = tf.constant(3.0)
with tf.GradientTape() as tape: # 不设置 persistent=True
# 和将变量定义为Variable不同的是,需要watch
tape.watch(x1)
tape.watch(x2)
z = g(x1, x2) # g(x1, x2) 在前面定义过
dz_x1_x2 = tape.gradient(z, [x1, x2]) # 求偏导时,将变量一次性传入
print(dz_x1_x2) # 输出结果和我们手动求导得到的结果一样哦
对多个函数同时求导
x = tf.Variable(5.0)
with tf.GradientTape() as tape:
z1 = 3 * x
z2 = x ** 2
d_z1_z2 = tape.gradient([z1, z2], x) # 得到 dz1 + dz2
print(d_z1_z2)
3.2 二阶函数求导
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_gradient, [x1, x2]) \
for inner_gradient in inner_grads]
print(outer_grads)
del inner_tape
del outer_tape
outer_grads 的输出为[[dz / d(x1)d(x1), dz / d(x1)d(x2)], [dz / d(x2)d(x1), dz / d(x2)d(x2)]]
4. 梯度下降算法的实现
4.1 手撕梯度下降算法
learning_rate = 0.1
x = tf.Variable(0.0)
for _ in range(100):
with tf.GradientTape() as tape:
z = f(x) # f(x) 前面有定义过,为:3. * x ** 2 + 2. * x - 1
dz_x = tape.gradient(z, x)
x.assign_sub(learning_rate * dz_x) # 梯度下降:x = x - learning_rate - dz_x
print(x)
4.2 使用keras.optimizers.SGD() 中的 apply_gradient特性,完成梯度下降算法
learning_rate = 0.1
x = tf.Variable(0.0)
optimizer = tf.optimizers.SGD(learning_rate = learning_rate)
for _ in range(100):
with tf.GradientTape() as tape:
z = f(x) # f(x) 前面有定义过,为:3. * x ** 2 + 2. * x - 1
dz_x = tape.gradient(z, x)
optimizer.apply_gradients([(dz_x, x)]) # 达到x = x - learning_rate - dz_x的效果
print(x)
相关代码详见:
5. 自定义求导在回归问题中的实践
(1)搭建好模型结构之后,batch遍历训练集metric
自动求导,更新参数
(2)每个epoch结束,得到验证集metric
自定义获得批量数据的函数
# 定义自动得到批量数据的函数
def random_batch(x, y, batch_size=32):
idx = np.random.randint(0, len(x), size = batch_size)
return x[idx], y[idx]
训练模型
epochs = 100
batch_size = 32
steps_per_epoch = len(x_train_scaled) // batch_size
optimizer = keras.optimizers.SGD()
metric = keras.metrics.MeanSquaredError()
for epoch in range(epochs):
metric.reset_states()
# batch 遍历训练集,得到训练集的 metric
for i in range(steps_per_epoch):
x_batch, y_batch = random_batch(
x_train_scaled, y_train, batch_size=batch_size)
with tf.GradientTape() as tape:
y_predict = model(x_batch)
loss = tf.reduce_mean(
keras.losses.mean_squared_error(y_batch, y_predict))
metric(y_batch, y_predict)
# 自定义求导
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="")
# 每个epoch结束,用验证集合进行验证
y_valid_predict = model.predict(x_valid_scaled)
valid_loss = tf.reduce_mean(
keras.losses.mean_squared_error(y_valid, y_valid_predict))
print("\t valid mse:", valid_loss.numpy())
代码详见: