在本实验中,我们将使用Tensorflow构建一个小型神经网络。
import numpy as np
import matplotlib.pyplot as plt
plt.style.use('./deeplearning.mplstyle')
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from lab_utils_common import dlc
from lab_coffee_utils import load_coffee_data, plt_roast, plt_prob, plt_layer, plt_network, plt_output_unit
import logging
logging.getLogger("tensorflow").setLevel(logging.ERROR)
tf.autograph.set_verbosity(0)
数据集
X,Y = load_coffee_data();
print(X.shape, Y.shape)
让我们在下面绘制咖啡烘焙数据。两个功能是温度(摄氏)和持续时间(分钟)。在家烘焙咖啡建议时间最好保持在12到15分钟之间,温度应该在175到260摄氏度之间。当然,随着温度的升高,持续时间应该会缩短。
plt_roast(X,Y)
规范数据 /标准化数据
如果数据归一化,对数据的权重拟合(反向传播,将在下周的讲座中介绍)将进行得更快。这与课程1中使用的过程相同,其中数据中的每个特征都被归一化以具有相似的范围。下面的过程使用Keras规范化层。它有以下步骤:
- 创建一个“规范化层”。注意,正如这里应用的那样,这不是模型中的一个层。
- “调整”数据。它学习数据集的均值和方差,并在内部保存这些值。
- 规范化数据。
将规范化应用于任何使用学习模型的未来数据是很重要的。
print(f"Temperature Max, Min pre normalization: {np.max(X[:,0]):0.2f}, {np.min(X[:,0]):0.2f}")
print(f"Duration Max, Min pre normalization: {np.max(X[:,1]):0.2f}, {np.min(X[:,1]):0.2f}")
norm_l = tf.keras.layers.Normalization(axis=-1)
norm_l.adapt(X) # learns mean, variance
Xn = norm_l(X)
print(f"Temperature Max, Min post normalization: {np.max(Xn[:,0]):0.2f}, {np.min(Xn[:,0]):0.2f}")
print(f"Duration Max, Min post normalization: {np.max(Xn[:,1]):0.2f}, {np.min(Xn[:,1]):0.2f}")
平铺/复制我们的数据以增加训练集的大小并减少训练epoch的数量。
Xt = np.tile(Xn,(1000,1))
Yt= np.tile(Y,(1000,1))
print(Xt.shape, Yt.shape)
Tensorflow模型
模型
让我们建立在讲座中描述的“咖啡烘焙网络”。如下图所示,有两层s型激活:
tf.random.set_seed(1234) # applied to achieve consistent results
model = Sequential(
[
tf.keras.Input(shape=(2,)),
Dense(3, activation='sigmoid', name = 'layer1'),
Dense(1, activation='sigmoid', name = 'layer2')
]
)
注1:tf.keras.Input(shape=(2,))指定了期望的输入形状。这允许Tensorflow在此时确定权重和偏置参数的大小。这在探索Tensorflow模型时非常有用。该语句在实践中可以省略,Tensorflow将在模型中指定输入数据时确定网络参数的大小。符合声明。
注2:在最后一层包括乙状体激活并不被认为是最佳实践。相反,它将被计入损失,从而提高数值稳定性。这将在后面的实验中更详细地描述。
model.summary()提供了网络的描述:
model.summary()
摘要中显示的参数计数对应于权重和偏置数组中的元素数量,如下所示。
L1_num_params = 2 * 3 + 3 # W1 parameters + b1 parameters
L2_num_params = 3 * 1 + 1 # W2 parameters + b2 parameters
print("L1 params = ", L1_num_params, ", L2 params = ", L2_num_params )
让我们检查一下Tensorflow实例化的权重和偏差。权重
应该是大小(输入的特征数量,层中的单位数量),而偏差
尺寸应与层中单位的数量匹配:
- 在具有3个单元的第一层中,我们期望W的大小为(2,3)和应该有3个元素。
- 在具有1个单元的第二层中,我们期望W的大小为(3,1)和应该有一个元素。
W1, b1 = model.get_layer("layer1").get_weights()
W2, b2 = model.get_layer("layer2").get_weights()
print(f"W1{W1.shape}:\n", W1, f"\nb1{b1.shape}:", b1)
print(f"W2{W2.shape}:\n", W2, f"\nb2{b2.shape}:", b2)
以下语句将在第二周详细描述。现在:
- ’ model.compile '语句定义了一个损失函数并指定了编译优化。
- “模型”。Fit语句运行梯度下降并拟合数据的权重。
model.compile(
loss = tf.keras.losses.BinaryCrossentropy(),
optimizer = tf.keras.optimizers.Adam(learning_rate=0.01),
)
model.fit(
Xt,Yt,
epochs=10,
)
更新后的权重
拟合完成后,权重已更新:
W1, b1 = model.get_layer("layer1").get_weights()
W2, b2 = model.get_layer("layer2").get_weights()
print("W1:\n", W1, "\nb1:", b1)
print("W2:\n", W2, "\nb2:", b2)
接下来,我们将从之前的训练中加载一些保存的重量。这是为了使本笔记本随着时间的推移对Tensorflow的变化保持健壮。不同的训练可以产生不同的结果,下面的讨论适用于特定的解决方案。您可以随意重新运行笔记本,并将此单元格注释掉,以查看差异。
W1 = np.array([
[-8.94, 0.29, 12.89],
[-0.17, -7.34, 10.79]] )
b1 = np.array([-9.87, -9.28, 1.01])
W2 = np.array([
[-31.38],
[-27.86],
[-32.79]])
b2 = np.array([15.54])
model.get_layer("layer1").set_weights([W1,b1])
model.get_layer("layer2").set_weights([W2,b2])
预测
一旦你有了一个训练有素的模型,你就可以用它来进行预测。回想一下,我们模型的输出是一个概率。在这种情况下,烤好的概率。为了做出决定,必须将概率应用于阈值。在本例中,我们将使用0.5
让我们从创建输入数据开始。模型期望一个或多个示例,其中示例位于矩阵的行中。在这种情况下,我们有两个特征,所以矩阵是(m,2)其中m是样本的数量。回想一下,我们已经标准化了输入特征,所以我们也必须标准化我们的测试数据。
要进行预测,可以应用predict方法。
X_test = np.array([
[200,13.9], # postive example
[200,17]]) # negative example
X_testn = norm_l(X_test)
predictions = model.predict(X_testn)
print("predictions = \n", predictions)
批次和周期
在上面的编译语句中,epoch的数目被设置为10。这指定整个数据集应该在训练期间应用10次。在训练过程中,你会看到描述训练进度的输出,如下所示:
Epoch 1/10
6250/6250 [==============================] - 6s 910us/step - loss: 0.1782
第一行“Epoch 1/10”描述了模型当前运行的Epoch。为了提高效率,训练数据集被分成“批次”。Tensorflow中批处理的默认大小是32。在我们扩展的数据集或6250批中有200,000个示例。第二行’ 6250/6250[====]的符号描述了执行了哪个批处理。
为了将概率转换为决策,我们应用一个阈值:
yhat = np.zeros_like(predictions)
for i in range(len(predictions)):
if predictions[i] >= 0.5:
yhat[i] = 1
else:
yhat[i] = 0
print(f"decisions = \n{yhat}")
这可以更简洁地完成:
yhat = (predictions >= 0.5).astype(int)
print(f"decisions = \n{yhat}")
层函数
让我们来看看这些装置的功能,以确定它们在咖啡烘焙决策中的作用。我们将绘制每个节点的所有输入值(duration,temp)的输出。每个单元都是一个逻辑函数,其输出范围可以从0到1。图中的阴影表示输出值。
注意:在实验中,我们通常从0开始编号,而讲课可能从1开始。
plt_layer(X,Y.reshape(-1,),W1,b1,norm_l)
阴影显示每个单位负责一个不同的“坏烤”区域。当温度过低时,0单元的数值较大。当持续时间太短时,第1单元的数值会变大,而第2单元的数值会变大,因为时间/温度的组合不合适。值得注意的是,网络通过梯度下降的过程自行学习了这些函数。他们是非常相同的功能,一个人可能会选择做出相同的决定。
最后一层的函数图有点难以可视化。它的输入是第一层的输出。我们知道第一层使用s型曲线,所以它们的输出范围在0到1之间。我们可以创建一个3-D图来计算三个输入的所有可能组合的输出。如下所示。上面,高输出值对应于“坏烤”区域。下图中,最大输出为区域,其中三个输入值较小,对应于“好烤”区域。
plt_output_unit(W2,b2)
最后一张图显示了整个网络的运行情况。
左图是由蓝色阴影表示的最后一层的原始输出。这是覆盖在由X和O表示的训练数据上的。
右图是经过决策阈值后的网络输出。这里的X和O对应于网络做出的决定。
下面的程序需要一些时间来运行
netf= lambda x : model.predict(norm_l(x))
plt_network(X,Y,netf)
祝贺
你已经在Tensorflow中建立了一个小的神经网络。该网络展示了神经网络通过在多个单元之间划分决策来处理复杂决策的能力