目录
1 训练神经网络(Neural Network Training)
1.1 TensorFlow实现(TensorFlow Implementation)
2.1 Sigmoid的替代品(Alternatives to The Sigmoid Activation)
2.2 激活函数的选择(Choosing Activation Functions)
4 多类分类(Multiclass Classification)
摘要
本周继续学习吴恩达机器学习的神经网络部分。了解了TensorFlow训练神经网络的细节,以及如何选择激活函数,并用代码简单实现了手写识别数字0和1。然后了解了什么是多类分类和Softmax回归函数,并通过改进Softmax加强计算精确度,最后用代码实现了手写识别数字0到9。
Abstract
This week, I continued studying the neural network section of Andrew Ng's machine learning course. I learned the details of training neural networks with TensorFlow, including how to choose activation functions. I implemented a simple code for recognizing handwritten digits 0 and 1. Then, I explored what multi-class classification is and the Softmax regression function. By improving the Softmax implementation, I enhanced computational accuracy, and finally, I used code to recognize handwritten digits from 0 to 9.
1 训练神经网络(Neural Network Training)
1.1 TensorFlow实现(TensorFlow Implementation)
继续手写数字识别的运行示例,识别图像是0还是1,依旧使用之前的神经网络架构:
在TensorFlow中可以用来训练这个网络的代码如下:
import tensorflow as tf
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential([
Dense(units = 25, activation = 'sigmoid'),
Dense(units = 15, activation = 'sigmoid'),
Dense(units = 1, activation = 'sigmoid')
)]
用TensorFlow将三层神经网络依次串在一起,第一层隐藏层有25个单位和S形激活函数,接下来是第二层,最后是输出层。
第二步是让TensorFlow编译模型,关键在于指定要使用的损失函数,在这里使用到稀疏绝对交叉熵(Sparse Categorical Cross-entropy):
from tensorflow.keras.losses import BinaryCrossentropy
model.compile(loss = BinaryCrossentropy())
在指定了损失函数后第三步是调用fit函数,它告诉TensorFlow适配第一步中的模型和使用第二步中的损失代价函数到数据集中:
model.fit(X, Y, epochs = 100)
以上,在TensorFlow中训练神经网络需要三步:
- 构造模型;
- 定义代价函数,进行编译;
- 拟合函数。
1.2 训练细节(Training Details)
回想在学习逻辑回归时,是如何训练一个逻辑回归模型的:
- 建立一个逻辑回归模型,给定第一个过程中的输入特征 和参数 ,再应用S形函数 ;
- 训练逻辑回归模型,指定损失函数 和代价函数 ;
- 使用算法,特别是梯度下降算法最小化 。
同样,在神经网络中 :
- 指定如何计算输出给定的输入特征 和参数 ;
- 编译模型,告诉TensorFlow想用什么损失函数,也就是二元交叉熵(Binary Cross-entropy)损失函数;
- 调用一个作为神经网络参数的函数,试图最小化成本。
放大来看,第一步指定如何计算输出给定的输入特征 和参数 ,此代码段指定神经网络的整个体系结构,它告诉我们在第一个隐藏层中有25个隐藏单元,第二个隐藏层有15个,然后最后有一个输出单元。使用S形函数激活值,所以基于这个代码段我们也知道第一层、第二层或第三层的参数是什么。所以这个代码段指定了神经网络的整个架构。
常见的损失函数是二元交叉熵损失函数、平方差(Mean Squared Error)损失函数:
在TensorFlow里使用的是Fit函数,能够实现反向传播:
2 激活函数(Activation Functions)
2.1 Sigmoid的替代品(Alternatives to The Sigmoid Activation)
使用上周需求预测的例子,可能不止单单0或1两种选择,可能存在0到无穷大的范围,则此时S形函数不能适用,我们可以用不同的激活函数。在神经网络中,一个非常常见的激活函数是如下这个ReLU(Rectified Linear Unit,矫正线性)函数,当 时, ;当 时, :
以下是最常见的三个激活函数,线性激活函数、Sigmoid激活函数和ReLU激活函数:
2.2 激活函数的选择(Choosing Activation Functions)
在神经网络的输出层中:
- 当遇到二元分类问题时,Sigmoid激活函数无疑是最好的选择,用来分辨0或1;
- 如何试图预测明天的股价与今天的股价相比会有什么变化,这类回归问题可以使用线性激活函数;
- 如果是非负的回归问题,则ReLU激活函数最适用。
在神经网络的隐藏层中,由于ReLU激活函数比Sigmoid激活函数计算更快和梯度下降更快,所以ReLU激活函数是最常见的选择:
在TensorFlow代码实现如下:
from tf.keras.layers import Dense
model = sequential([
Dense(units = 25, activation = 'relu'),
Dense(units = 15, activation = 'relu'),
Dense(units = 1, activation = 'sigmoid')
])
每年都会有新的激活函数提出,例如Tenh激活函数、Leaky ReLU激活函数、Swish激活函数等。
2.3 激活函数的重要性
如果对需求预测的神经网络均使用线性激活函数,这个神经网络将变得与仅仅是线性回归没有什么不同,从而无法适用其他更复杂的模型。
如果像下图这样化成两层去线性回归,还不如直接用一个线性回归:
每层中都用线性回归,最后用sigmoid激活函数的话,结果出来跟逻辑回归没区别:
所以,不要在神经网络的隐藏层中使用线性激活函数,而应该使用ReLU激活函数。
3 实战:TensorFlow实现手写识别数字0和1
3.1 加载数据集
# 加载数据集
def load_data():
X = np.load("手写识别01/X.npy")
y = np.load("手写识别01/y.npy")
X = X[0:1000]
y = y[0:1000]
return X, y
# 加载数据集,查看数据集大小
X, y = load_data()
print('The shape of X is: ' + str(X.shape))
print('The shape of y is: ' + str(y.shape))
# 划分10%作为测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=23)
构造load_data函数,用于加载npy文件中的数据集。
train_test_split函数用于将数据分割成训练集和测试集。
可以看到有1000个数据集,每个输入是20*20=400大小的图片:
3.2 构建模型与训练
通过构造三层模型进行分类,隐藏层使用ReLU激活函数,输出层使用Sigmoid激活函数。
然后告诉模型需要什么样的损失函数,这里使用的损失函数是二元交叉熵(BinaryCrossentropy)损失函数。
最后开始调用fit函数进行循环训练。
# 构建模型,三层模型进行分类,第一层输入25个神经元...
model = Sequential([
tf.keras.Input(shape=(400,)), #指定输入大小
Dense(25, activation='relu'),
Dense(15, activation='relu'),
Dense(1, activation='sigmoid')
])
# 模型设定,因为是分类,使用BinaryCrossentropy损失函数
model.compile(
loss=tf.keras.losses.BinaryCrossentropy()
)
# 开始训练,训练循环10次
model.fit(
X_train,y_train,
epochs=10
)
Sequential模型是Keras中的线性堆栈模型,允许简单地堆叠多个网络层。
Dense层是神经网络中的全连接层,每个输入节点与输出节点都是连接的。
可以看到每一次训练所用时长和损失函数的值:
3.3 结果可视化与打印测试数据集准确度信息
原始的输入的数据集是400 * 1000的数组,共包含1000个手写数字的数据,其中400为20*20像素的图片,因此对每个400的数组进行reshape((20, 20))可以得到原始的图片进而绘图。
# 绘制测试集的预测结果,绘制25个
fig, axes = plt.subplots(5, 5, figsize=(5, 5)) # 画窗、坐标轴
fig.tight_layout(pad=0.1, rect=[0, 0.03, 1, 0.88]) # [left, bottom, right, top]
for i, ax in enumerate(axes.flat):
# 随机选取索引
random_index = np.random.randint(X_test.shape[0])
# 选择与随机索引对应的行并对图像进行重构
X_random_reshaped = X_test[random_index].reshape((20, 20)).T
# 显示图像
ax.imshow(X_random_reshaped, cmap='gray')
# 使用神经网络进行预测
prediction = model.predict(X_test[random_index].reshape(1, 400))
if prediction >= 0.5:
yhat = 1
else:
yhat = 0
# 在图像上方显示标签
ax.set_title(f"{y_test[random_index, 0]},{yhat}")
ax.set_axis_off()
fig.suptitle("真实标签, 预测的标签", fontsize=16)
plt.show()
# 给出预测的测试集误差
y_pred=model.predict(X_test)
print("测试数据集准确率为:", accuracy_score(y_test, np.round(y_pred)))
pyplot是Matplotlib中的一个模块,用于绘制各种图形和图像。
能看到神经网络对每个图片的预测时长:
3.4 运行结果
按照最初的划分,数据集包含1000个数据,划分10%为测试集,也就是100个数据。结果可视化随机选择其中的25个数据绘图,每个图像的上方标明了其真实标签和预测的结果:
最后可以看到测试数据集的准确率还是很高的:
4 多类分类(Multiclass Classification)
4.1 多类(Multiclass)
多类分类指的是分类问题,在这些问题中,可以有不止两个可能的输出标签。
对于手写识别来说,是区分0-9这十个数字。或者是试图分类病人是否可能患有几种不同的疾病中的一种或多种。
在多类问题中要算的是 或 或 或 :
需要学习一个决策边界,它可能看起来像列表,将空间分为四个类别。
4.2 Softmax
Softmax算法是逻辑回归的推广,它是一种针对多类分类上下文的二进制分类算法。
在逻辑回归中只有两种可能的输出值1或0,当 时,可以得到 。同样,当这个方法推广到SoftMax回归中也适用,假设存在四种可能的值(),若 、 、 ,则 。
由此可得,在一般情况下的Softmax回归有 种可能值(),有:
且: 。
当 时,Softmax回归最终计算的结果与逻辑回归的结果基本相同,只是参数可能会有点不同,但它最终可以简化为逻辑回归模型。所以Softmax回归模型是逻辑回归的推广。
关于如何指定Softmax回归的成本函数,依旧是参照逻辑回归中的成本函数,则可以得到:
即 。
在这个损失函数中,每个训练示例只能具有一个值,即计算出的 仅对应一个 。
4.3 神经网络的Softmax输出
如果想要对0-9这十个数字进行手写瘦瘪,则需要十个输出类,然后可以沿用在之前的手写识别0和1时用到的三层神经网络,第一层隐藏层依旧是25个神经元,第二层隐藏层是15个神经元,并且第一第二层使用之前的ReLU激活函数,第三层输出层则为10个激活值组成的向量 。这十个输出类的表达式为:
表明这是与这个神经网络第三层的第一个单元相关的参数。
有了上面这个表达式,Softmax就能输出这十个可能的输出标签中的任何一个了。
在之前的逻辑回归中的三种激活函数(Sigmoid、RuLU、Linear),一个 决定一个 。但是在Softmax激活函数中,每一个激活值 都依赖于所有的 ( 到 ),这与前面的几个激活函数都有所不同。
在tensorflow中代码表示如下:
import tensorflow as tf
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential([
Dense(25, activation='relu'),
Dense(15, activation='relu'),
Dense(10, activation='softmax')
])
from tensorflow.keras.losses import SparseCategoricalCrossentropy
model.compile(
loss = SparseCategoricalCrossentropy()
)
model.fit(
X,Y,
epochs=100
)
第三层模型为10个神经元,激活函数为Softmax。
损失函数为稀疏范畴交叉熵函数(SparseCategoricalCrossentropy),稀疏指的是 只能接受0-9这十个值中的一个,所以每个图像要么是0要么是1要么是9,不会看到同时是数字2和数字7的图片。
然后就是循环进行训练模型。
4.4 Softmax的改进实现
计算机只有在有限的内存来存储每个数字,因此会存在一些计算误差,例如下面这个例子:
为了在TensorFlow中进行精确的计算,则需要减少一定的误差。在逻辑回归中,如果要计算给定实例的损失函数 ,由于用到了中间项 则会产生如上述例子带来的误差,因此将表达式展开为 ,从而能够用一个更精确的数值方法来计算这个损失函数。
然后可以将代码改写为,在第三层输出层使用线性激活函数,将 置于这个交叉熵损失函数的说明中。
由此我们可以把这个想法应用到Softmax回归中,同样在第三层输出层使用线性激活函数只计算 到 ,然后将整个损失的计算都包含在这里的损失函数中,从而使计算更加精确了,如下:
# 构建模型
import tensorflow as tf
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential([
Dense(100, activation='relu'),
Dense(75, activation='relu'),
Dense(10, activation='linear'),
])
# 配置模型的训练参数
from tensorflow.keras.losses import SparseCategoricalCrossentropy
model.compile(
loss=SparseCategoricalCrossentropy(from_logits=True)
)
# 训练模型
model.fit(
X, Y,
epochs=100
)
# 模型预测
logit = model(X)
f_x = tf.nn.sigmoid(logit)
5 实战:TensorFlow实现手写识别数字0~9
5.1 加载数据集
# 加载数据集
def load_data():
X = np.load("手写识别09/X.npy")
y = np.load("手写识别09/y.npy")
return X, y
X, y = load_data()
# 查看数据集大小
print ('The shape of X is: ' + str(X.shape))
print ('The shape of y is: ' + str(y.shape))
#划分10%作为测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.1, random_state=23)
构造load_data函数,用于加载npy文件中的数据集。
train_test_split函数用于将数据分割成训练集和测试集。
可以看到有5000个数据集,每个输入是20*20=400大小的图片:
5.2 构建模型与训练
通过构造三层模型进行分类,隐藏层使用ReLU激活函数,为确保计算精准性,输出层使用线性激活函数。
然后告诉模型需要什么样的损失函数,这里使用的损失函数是稀疏分类交叉熵(Sparse Categorical Crossentropy)损失函数。
最后开始调用fit函数进行循环训练。
# 构建模型
tf.random.set_seed(23) # 设置随机种子以确保每次运行的结果是一致的
model = Sequential([
tf.keras.Input(shape=(400,)), # 输入层,输入数据的形状是400维
Dense(25, activation='relu'), # 全连接层,25个神经元,使用ReLU激活函数
Dense(15, activation='relu'), # 全连接层,15个神经元,使用ReLU激活函数
Dense(10, activation='linear'), # 输出层,10个神经元,使用线性激活函数
])
# 配置模型的训练参数
model.compile(
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
# 使用稀疏分类交叉熵作为损失函数,且输出是logits(即未经过softmax的原始输出)
)
# 训练模型
model.fit(
X_train, y_train, # 使用X_train作为输入数据,y_train作为目标数据
epochs=100 # 训练100轮
)
训练过程如下:
5.3 结果可视化与打印测试数据集结果信息
原始的输入的数据集是400 * 5000的数组,共包含5000个手写数字的数据,其中400为20*20像素的图片,因此对每个400的数组进行reshape((20, 20))可以得到原始的图片进而绘图。
# 绘制测试集的预测结果,绘制15*10=150个
fig, axes = plt.subplots(15, 10, figsize=(15, 10))
fig.tight_layout(pad=0.13, rect=[0, 0.03, 1, 0.91]) # [left, bottom, right, top]
for i, ax in enumerate(axes.flat):
# 随机选取索引
random_index = np.random.randint(X_test.shape[0])
# 选择与随机索引对应的行并对图像进行重构
X_random_reshaped = X_test[random_index].reshape((20, 20)).T
# 显示图像
ax.imshow(X_random_reshaped, cmap='gray')
# 使用神经网络进行预测
prediction = model.predict(X_test[random_index].reshape(1, 400))
prediction_p = tf.nn.softmax(prediction)
yhat = np.argmax(prediction_p)
# 在图像上方显示标签
if y_test[random_index, 0] == yhat:
ax.set_title(f"{y_test[random_index, 0]},{yhat}", fontsize=10)
ax.set_axis_off()
else:
ax.set_title(f"{y_test[random_index, 0]},{yhat}", fontsize=10, color='red')
ax.set_axis_off()
fig.suptitle("Label, yhat", fontsize=14)
plt.show()
# 给出预测的测试集误差
def evaluation(y_test, y_predict):
accuracy=classification_report(y_test, y_predict,output_dict=True)['accuracy']
s=classification_report(y_test, y_predict,output_dict=True)['weighted avg']
precision=s['precision']
recall=s['recall']
f1_score=s['f1-score']
#kappa=cohen_kappa_score(y_test, y_predict)
return accuracy,precision,recall,f1_score #, kappa
y_pred=model.predict(X_test)
prediction_p = tf.nn.softmax(y_pred)
yhat = np.argmax(prediction_p, axis=1)
accuracy,precision,recall,f1_score=evaluation(y_test,yhat)
print("测试数据集准确率为:", accuracy)
print("测试数据集精确率为:", precision)
print("测试数据集召回率为:", recall)
print("测试数据集F1_score为:", f1_score)
能看到神经网络对每个图片的预测时长:
5.4 运行结果
按照最初的划分,数据集包含5000个数据,划分10%为测试集,也就是500个数据。结果可视化随机选择其中的150个数据绘图,每个图像的上方标明了其真实标签(Label)和预测的结果(yhat):
最后可以看到测试数据集的准确率还是很高的:
总结
通过本周的学习,了解了TensorFlow训练神经网络的细节,首先需要构建模型:使用Keras.Sequential定义神经网络模型、层的选择、选择激活函数,然后编译模型以指定损失函数和优化器,最后设置训练参数使用训练数据训练模型。
然后知道了该如何选择激活函数(Linear、Sigmoid、ReLU),需要考虑到任务类型:分类、回归等不同任务可能需要不同的激活函数,网络结构深度:深层网络常用ReLU系列以避免梯度消失,训练速度与稳定性:某些激活函数可能导致训练不稳定。
还知道了什么是多类分类。多类分类是机器学习中的一种任务,旨在将输入数据分到多个类别中。与二分类问题不同,多类分类涉及三个或更多的类别。为确保计算精确度还要对Softmax回归进行一定代码改进。
最后通过实战TensorFolw实现手写数字识别0和1以及识别0到9,加强了对TensorFlow和神经网络训练过程的理解。