内容是对《Python深度学习》的摘录、理解、代码实践和遇到的问题。
TensorFlow:是一个基于Python的免费开源机器学习平台,拥有庞大的组件生态系统。包含很多底层张量计算API。
Keras:构建于TensorFlow之上的,Python编写的高阶深度学习API。
通过TensorFlow,Keras可以在不同类型的硬件上运行,还可以扩展到数千台机器上。
训练神经网络主要围绕以下概念进行:
一、通过TensorFlow API实现的低阶张量操作
- 张量
- 张量运算
- 反向传播
二、通过Keras API实现的高阶深度学习概念
- 层
- 损失函数
- 优化器
- 指标:用于评估模型性能,例如精度
- 训练循环
常数张量
创建全1或全0张量:
import tensorflow as tf
x = tf.ones(shape=(2, 1))
print(x)
tf.Tensor(
[[1.]
[1.]], shape=(2, 1), dtype=float32)
x = tf.zeros(shape=(2, 1))
print(x)
tf.Tensor(
[[0.]
[0.]], shape=(2, 1), dtype=float32)
从随机分布中取值来创建张量
import tensorflow as tf
x = tf.random.normal(shape=(3, 1), mean=0., stddev=1.) # 从均值为0、标准差为1的正态分布中抽取的形状为(3,1)的随机张量
print(x)
tf.Tensor(
[[ 2.4061456 ]
[ 0.4366203 ]
[-0.14396065]], shape=(3, 1), dtype=float32)
import tensorflow as tf
x = tf.random.uniform(shape=(3, 1), minval=0., maxval=1.) # 从0和1之间的均匀分布中抽取的随机张量
print(x)
tf.Tensor(
[[0.6344434 ]
[0.48669887]
[0.44374537]], shape=(3, 1), dtype=float32)
注意TensorFlow张量是不可赋值的,以下代码会报错
x = tf.ones(shape=(3, 1))
x[0, 0] = 0.
如果需要更新状态,则应该使用变量。
变量
创建TensorFlow变量
import tensorflow as tf
v = tf.Variable(initial_value=tf.random.normal(shape=(3, 1), mean=0., stddev=1.))
print(v)
<tf.Variable 'Variable:0' shape=(3, 1) dtype=float32, numpy=
array([[-0.10057034],
[-0.94686824],
[ 1.5012082 ]], dtype=float32)>
变量用常数张量进行初始赋值。
修改TensorFlow变量
变量的状态可以通过assign方法修改
v.assign(tf.ones(shape=(3, 1)))
print(v)
<tf.Variable 'Variable:0' shape=(3, 1) dtype=float32, numpy=
array([[1.],
[1.],
[1.]], dtype=float32)>
这种方法也适用于变量的子集
v[0, 0].assign(3.)
print(v)
<tf.Variable 'Variable:0' shape=(3, 1) dtype=float32, numpy=
array([[3.],
[1.],
[1.]], dtype=float32)>
assign_add()和assign_sub()分别等同于+=和-=
v.assign_add(tf.ones(shape=(3, 1)))
print(v)
<tf.Variable 'Variable:0' shape=(3, 1) dtype=float32, numpy=
array([[4.],
[2.],
[2.]], dtype=float32)>
张量运算
a = tf.ones(shape=(2, 2))
b = tf.square(a) # 求平方
c = tf.sqrt(a) # 求平方根
d = b + c # 两个张量逐元素相加
e = tf.matmul(a, b) # 计算两个张量的点积
f = e * d # 计算两个张量逐元素相乘(叉积)
GradientTape API
TensorFlow可以检索任意可微表达式相对于其输入的梯度,只需要创建一个GradientTape作用域,对一个或多个输入张量做一些计算后,可以检索计算结果相对于输入的梯度。
import tensorflow as tf
input_var = tf.Variable(initial_value=3.)
with tf.GradientTape() as tape:
result = tf.square(input_var)
gradient = tape.gradient(result, input_var)
print(gradient)
tf.Tensor(6.0, shape=(), dtype=float32)
利用嵌套的梯度带还能计算二阶梯度(梯度的梯度)
import tensorflow as tf
time = tf.Variable(0.)
with tf.GradientTape() as outer_tape:
with tf.GradientTape() as inner_tape:
position = 4.9 * time ** 2
speed = inner_tape.gradient(position, time)
accelerate = outer_tape.gradient(speed, time)
print(accelerate)
tf.Tensor(9.8, shape=(), dtype=float32)
用TensorFlow实现线性分类器
在二维平面上随机生成两个类别的点
生成两个点云,设定两个点云的协方差矩阵相同但均值不同,也即两个点云具有相同的形状但位置不同。
import tensorflow as tf
import matplotlib.pyplot as plt
num_samples_per_class = 1000
# 生成第一个类别的点云
negative_samples = tf.random.normal(shape=(num_samples_per_class, 2), mean=-3., stddev=1)
# 生成第二个类别的点云
positive_samples = tf.random.normal(shape=(num_samples_per_class, 2), mean=4., stddev=1)
# 将二者堆叠,形成一个形状为(2000,2)的数组
# tf.concat([tensor1, tensor2, ...], axis)第一个参数表示需要拼接的张量是哪些,第二个参数表示在哪个轴上拼接
inputs = tf.concat([negative_samples, positive_samples], 0)
# 生成对应的目标标签,以0标识negative_samples,以1标识positive_samples,根据上面的堆叠,前1000个标签应为0,后1000个应为1
targets = tf.concat([tf.zeros(shape=(num_samples_per_class,)), tf.ones(shape=(num_samples_per_class,))], 0)
# 绘制两个点类的图像
plt.scatter(inputs[:, 0], inputs[:, 1], c=targets[:])
plt.show()
实现线性分类器
采用仿射变换(prediction = W·input + b)来创建分类器
# 创建线性分类器的变量
input_dim = 2 # 输入样本是二维点坐标
output_dim = 1 # 每个样本输出一个预测值,如果预测该点接近分类0则值会更接近0,反之更接近1
W = tf.Variable(initial_value=tf.random.uniform(shape=(input_dim, output_dim)))
b = tf.Variable(initial_value=tf.zeros(shape=(output_dim, )))
# 向前传播函数
def model(inputs_):
return tf.matmul(inputs_, W) + b
# 均方误差损失函数
def square_loss(targets_, predictions_):
per_sample_losses = tf.square(targets_ - predictions_) # 该变量与targets_、predictions_形状相同,其中包含每个样本的损失值
return tf.reduce_mean(per_sample_losses) # 取所有样本损失值的平均值,通过reduce_mean()实现
# 训练步骤函数
learning_rate = 0.02
def training_step(inputs_, targets_):
# 以下三行是在一个梯度带内进行了一次向前传播
with tf.GradientTape() as tape:
predictions_ = model(inputs_)
loss_ = square_loss(targets_, predictions_)
grad_loss_wrt_W, grad_loss_wrt_b = tape.gradient(loss_, [W, b]) # 检索损失loss相对于权重W、b的梯度
# 更新权重
W.assign_sub(learning_rate * grad_loss_wrt_W)
b.assign_sub(learning_rate * grad_loss_wrt_b)
return loss_
# 为简单起见,不进行分批,直接在所有数据上进行批量训练,运行时间要更长,但每次更新都包含所有的样本信息所以所需迭代次数更少
for step in range(40): # 迭代40次
loss = training_step(inputs, targets)
print(f"Loss at step {step}: {loss:.4f}")
# 以0.5为分界线划分两类,绘制处模型预测情况
predictions = model(inputs)
plt.scatter(inputs[:, 0], inputs[:, 1], c=predictions[:] > 0.5)
plt.show()
遇到的问题
每一步应该都理解了,但是损失小时模型预测出的图像和原图像完全不像,损失大时模型预测出的图像反而比较吻合
learning_rate = 0.1
learning_rate = 0.02
不过从上图能看出线性分类器的作用:找到一条直线(或高维空间中的一个超平面)的参数,将两类数据整齐地分开.