引言
神经网络是一种模拟人脑神经元连接和工作方式的计算模型,它是深度学习的基础,并在机器学习领域中扮演着重要角色
文章目录
- 引言
- 一、神经网络简介
- 1.1 结构组成
- 1.2 工作原理
- 1.3 学习过程
- 1.4 应用领域
- 1.5 感知器
- 1.6 功能特点
- 1.7 总结
- 二、用Tensorflow构建一个简单神经网络
- 2.1 主要步骤
- 2.2 简单例子,展示如何使用TensorFlow和Keras(TensorFlow的高级API)来构建一个简单的全连接(也称为密集)神经网络(用于分类MNIST数据集中的手写数字)
- 2.3 代码解释
- 2.4 总结
- 三、可选实验室 - 简单神经网络
- 3.1 导入第三方库
- 3.2 数据集
- 3.3 归一化数据
- 3.4 Tensorflow模型
- 3.5 预测
- 3.6 Epochs and batches
- 3.7 层函数
- 3.8 总结
一、神经网络简介
1.1 结构组成
神经网络主要由多层节点(或称为人工神经元)组成,包括输入层、隐藏层和输出层。每个节点与其他节点相连,并具有与之关联的权重和阈值。
1.2 工作原理
输入层接收外部数据,隐藏层进行数据处理和转换,输出层产生最终结果。节点间的连接权重决定了信息的传递方式
1.3 学习过程
神经网络通过训练数据学习。在训练过程中,网络不断调整权重和阈值,以减少输出错误,提高准确性
1.4 应用领域
神经网络在图像识别、语音识别、自然语言处理等人工智能领域表现出色
1.5 感知器
感知器是最早的人工神经元模型,它接受多个输入,通过权重和阈值产生一个输出
1.6 功能特点
神经网络具有强大的数据处理和模式识别能力,能够识别复杂模式和进行决策
1.7 总结
神经网络是一种高效的机器学习工具,通过模拟人脑的神经网络结构和功能,实现了对复杂数据的处理和模式识别
二、用Tensorflow构建一个简单神经网络
2.1 主要步骤
- 导入必要的库:首先,需要导入TensorFlow库和其他可能需要的库。
- 准备数据:通常,你需要有一组数据来训练你的神经网络。在这个例子中,我们将使用TensorFlow内置的数据集,例如MNIST,这是一个手写数字的数据集。
- 构建模型:定义神经网络的层和结构。
- 编译模型:选择损失函数、优化器和评估指标。
- 训练模型:使用训练数据来训练模型。
- 评估模型:使用测试数据来评估模型的性能。
2.2 简单例子,展示如何使用TensorFlow和Keras(TensorFlow的高级API)来构建一个简单的全连接(也称为密集)神经网络(用于分类MNIST数据集中的手写数字)
import tensorflow as tf
from tensorflow.keras import layers, models
# 1. 加载数据
mnist = tf.keras.datasets.mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
# 标准化数据
train_images = train_images.reshape((60000, 28, 28, 1)).astype('float32') / 255
test_images = test_images.reshape((10000, 28, 28, 1)).astype('float32') / 255
# 2. 构建模型
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))
# 3. 编译模型
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
# 4. 训练模型
model.fit(train_images, train_labels, epochs=5, batch_size=64)
# 5. 评估模型
test_loss, test_acc = model.evaluate(test_images, test_labels)
print(f'测试准确率: {test_acc}')
2.3 代码解释
- 这个例子中,构建了一个简单的卷积神经网络(CNN),它包含一个卷积层、一个池化层、一个展平层和两个全连接层
- 使用
adam
优化器和sparse_categorical_crossentropy
损失函数来编译模型,并在5个时期内训练模型 - 评估模型在测试数据集上的准确率
2.4 总结
注意点:
- 运行上述代码,你需要有TensorFlow环境
- 由于MNIST数据集已经过时,TensorFlow可能在未来版本中将其移除,因此建议使用
tf.keras.datasets.fashion_mnist
或其他数据集来替换
三、可选实验室 - 简单神经网络
3.1 导入第三方库
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)
3.2 数据集
X,Y = load_coffee_data();
print(X.shape, Y.shape)
让我们在下面绘制咖啡烘焙数据。这两个功能是以摄氏度为单位的温度和以分钟为单位的持续时间。在家烘焙咖啡建议持续时间最好保持在 12 到 15 分钟之间,而温度应在 175 到 260 摄氏度之间。当然,随着温度的升高,持续时间应该会缩短。
plt_roast(X,Y)
输出结果:
3.3 归一化数据
如果数据被归一化,那么将权重拟合到数据(反向传播)将更快地进行。这与您在课程 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}")
输出结果:
- 平铺/复制我们的数据以增加训练集大小并减少训练周期的数量
Xt = np.tile(Xn,(1000,1))
Yt= np.tile(Y,(1000,1))
print(Xt.shape, Yt.shape)
输出结果:
3.4 Tensorflow模型
让我们来构建讲座中描述的“咖啡烘焙网络”。有两层具有 sigmoid 激活
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 模型时非常有用。在实践中可以省略此语句,当在 model.fit 语句中指定输入数据时,Tensorflow 将调整网络参数的大小。
- 注2:在最后一层包含 sigmoid 激活不被视为最佳实践。相反,它将被计入损失中,从而提高了数值稳定性。
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),𝑏应有 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 语句定义了一个损失函数,并指定了一个编译优化
- model.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])
在机器学习中,模型的训练过程是一个迭代优化的过程,通过不断调整模型参数使得模型在训练数据上表现更好。在每次训练迭代中,通过计算损失函数关于参数的梯度,我们可以知道当前参数的变化方向,然后根据梯度下降算法来更新参数。
现在来解释一下如何理解这个过程:
- 随机初始化参数:在开始训练之前,我们通常会随机初始化模型参数。这样做是因为初始参数对模型性能有很大影响,随机初始化可以避免模型陷入局部最优解
- 计算梯度:在每次训练迭代中,通过前向传播计算损失函数,然后反向传播计算损失函数关于参数的梯度。梯度告诉我们当前参数的变化方向,即损失函数下降最快的方向
- 参数更新:根据梯度下降算法,我们将当前参数沿着负梯度方向进行一定步长的更新。学习率控制了参数更新的速度,较大的学习率可能导致参数更新过大而错过最优值,而较小的学习率可能导致收敛速度过慢
- 循环迭代:重复以上步骤,直到达到停止条件(如达到最大迭代次数、损失函数收敛等)为止
由于训练数据的随机性、模型的复杂性以及训练过程中可能存在的噪声等因素,即使使用相同的初始参数和相同的训练数据,每次训练得到的最终参数可能会有所不同。这种差异是正常的,而且可以通过一些技术手段来提高模型的稳定性和收敛性
3.5 预测
一旦有了经过训练的模型,就可以使用它来做出预测。回想一下,我们模型的输出是一个概率。在这种情况下,良好烤肉的概率。要做出决定,必须将概率应用于阈值。在本例中,我们将使用 0.5
- 让我们从创建输入数据开始。该模型需要一个或多个示例,其中示例位于矩阵的行中
- 我们有两个特征,因此矩阵将为 (m,2),其中 m 是样本的数量
- 我们已经对输入特征进行了归一化,因此我们也必须对测试数据进行归一化
- 要进行预测,需要应用预测方法
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)
输出结果:
3.6 Epochs and batches
在上面的编译语句中,epoch 的数量设置为 10。这指定了整个数据集应在训练期间应用 10 次。在训练期间,会看到描述训练进度的输出
- 第一行 Epoch 1/10 描述模型当前正在运行的纪元。为了提高效率,训练数据集被分解为“批次”。Tensorflow 中批处理的默认大小为 32。我们扩展的数据集中有 200000 个示例或 6250 个批次
- 第 2 行的符号 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}")
输出结果:
3.7 层函数
让我们检查一下单元的功能,以确定它们在咖啡烘焙决策中的作用。我们将绘制每个节点的所有输入值(持续时间、温度)的输出。每个单元都是一个逻辑函数,其输出范围可以从 0 到 1。图中的阴影表示输出值
plt_layer(X,Y.reshape(-1,),W1,b1,norm_l)
输出结果:
- 阴影显示每个单元负责不同的“不良烘焙”区域。当温度过低时,单元 0 的值更大。当持续时间太短时,单元 1 的值较大,而单元 2 的值对于时间/温度的错误组合具有较大的值。值得注意的是,网络通过梯度下降的过程自行学习了这些功能。它们与一个人可能选择做出相同决定的功能非常相似
- 最后一层的功能图有点难以可视化。它的输入是第一层的输出。我们知道第一层使用 sigmoid,因此它们的输出范围在 0 到 1 之间。我们可以创建一个 3D 图,用于计算三个输入的所有可能组合的输出。如下所示。在上面,高输出值对应于“不良烘焙”区域。下面,最大输出在区域中,其中三个输入是对应于“良好烘焙”区域的小值。
plt_output_unit(W2,b2)
输出结果:
最后一张图显示了整个网络的运行情况
左图是最后一层的原始输出,由蓝色阴影表示。这叠加在由 X 和 O 表示的训练数据上
右图是决策阈值之后网络的输出。这里的 X 和 O 对应于网络做出的决策。
以下内容需要一点时间才能运行完毕
netf= lambda x : model.predict(norm_l(x))
plt_network(X,Y,netf)
输出结果:
3.8 总结
已在 Tensorflow 中构建了一个小型神经网络,该网络展示了神经网络通过将决策划分到多个单元来处理复杂决策的能力