这是我的第一周的机器学习入门的笔记,这一周主要去学习了神经网络相关的知识,包括神经网络的训练,神经网络的激活函数和高级的优化方法等,也是吴恩达教授所讲的2022版机器学习的一些笔记。
目录
神经网络
神经网络的概念
在神经网络中,参数可被称为权重,神经网络模型是许多逻辑单元按照不同层级组织起来的网络,每一层的输出变量都是下一层的输入变量,第一层为输入层,最后一层为输出层,中间的为隐藏层,其中每一层会有多个神经元,每一个神经元都是在实现一个小逻辑回归单元或者是逻辑回归函数。
每一层的输入都是前一层的输出,每一层的输出都是一个列向量,其中列向量中的每一个元素是由每一个的神经元通过sigmoid函数(也称激活函数)算出来的值,最终通过输出层判断其中的概率。
神经网络的前向传播
比如手写识别是0还是1这个例子,这种类型的神经网络架构最初会有更多的隐藏单元,随着离输出层越来越近,隐藏单元的数量会减少,比如隐藏层的第一层有25个神经元,而第二层有15个神经元,输出层有1个神经元,每一层的神经元数量逐渐缩减,为神经网络的前向传播。
在这里借助了吴恩达教授举的例子,咖啡的制作是否成功,如果温度太低,时间太短,会导致不足,如果温度太高,时间太长,也会过度,只有适当的温度和时间才会成功。
通过给出的温度和时间,可以预测最后的咖啡是否成功,如果值大于0.5,我们可以说是成功的,若是小于0.5,我么可以说是失败的。
简单的代码如下:
import numpy as np
from tensorflow.keras.layers import Dense
x = np.array([[200.0,17.0]])
layer_1 = Dense(units=3,activation="sigmoid")
a1=layer_1(x)
layer_2 = Dense(units=1,activation="sigmoid")
a2=layer_2(a1)
最后看a2的值,同样判断数字为0或者1仍是利用此方法,只不过最开始的输入层的数据的变化,以及隐藏层的神经元数量的变化等。
而在这里给出的TensorFlow中的数据格式使用矩阵来表示数据的,并非只是一维的向量。
搭建一个神经网络
如果还是咖啡的例子,应该有个标准化的方式去得到数据。例如利用Sequential的方式,使每一层放到一个一维向量中。
并且在之后进行编译和进行拟合,最后得出概率。
自己构建TensorFlow的Dense函数和sequential函数
Dense的作用是输入上一层的激活并给定当前层的参数返回下一层的激活,代码如下:
W = np.array([[1,-3,5],[2,-4,6]])
b = np.array([1,-1,2])
a_in = np.array([-2,4])
def g(x): #sigmoid
return 1/(1+np.exp(-x))
def dense(a_in,W,b,g):
units = W.shape[1] #表示有几列,若shape[0]表示行数
a_out = np.zeros(units)#生成全部包含0的矩阵
for i in range(units):
w = W[:,i] #取矩阵的第j列的所有元素
z = np.dot(w,a_in)+b[i] #利用公式计算
a_out[i] = g(z)#sigmoid函数计算值并赋给a_out矩阵
return a_out
a_out = dense(a_in,W,b,g)
print(a_out)
一般大写字母指矩阵,小写字母指向量或者标量
sequentials函数实现如下:
def sequential(x):
a1=dense(x,W1,b1)
a2=dense(a1,W2,b2)
a3=dense(a2,W3,b3)
a4=dense(a3,W4,b4)
f_x=a4
return f_x
最后算出每一层的值最后返回输出层的值。
我们可以使用矢量化,可以让神经网络运行的更快,更简洁:
其中dot是向量的点积也是内积,是向量的矢量化,而matmul是矩阵的矢量化,代码更加简洁,并且运行速度会更快。
matmul是用于实现矩阵乘法的矩阵矢量化。
神经网络训练
比如一个识别数字是0还是1的例子,其中隐藏层的第一层为25个神经元,第二层为15个神经元,第三层为输出层的前向传播,训练这个神经网络在tensorflow的代码如下:
import tensorflow as tf
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense
model = Sequential([ #指定模型,告诉tensorflow如何计算推理
Dense(units=25,activation='sigmoid'),
Dense(units=15,activation='sigmoid'),
Dense(units=1,activation='sigmoid')
])
from tensorflow.keras.losses import BinaryCrossentropy
model.compile(loss=BinaryCrossentropy()) #使用特定的损失函数编译模型
model.fit(X,Y,epochs=100)#训练模型
其中模型训练的细节如下:
第一步是指定如何给定输入x和参数的情况下计算输出
第二步是指定损失和成本
第三步是最小化我们训练逻辑回归的成本函数
如果当前为逻辑回归,也就是分类算法时,在进行编译时应用BinaryCrossentropy,如果为线性回归,也就是回归算法时,在进行编译时应用MeanSquaredError
逻辑回归的损失函数也为交叉熵损失函数
sigmoid激活函数替代方案
如之前学过的sigmoid函数,输出在0到1,如果想有更大的正值,可以换成不同的激活函数,其中有整流线性单元,输出也是非负值,在自变量大于等于0时,输出为自变量,在自变量小于0时,输出为0;当然还有线性激活函数,输出值为自变量的值,当然存在负值,具体图像如下:
那么既然有了这么多我们可以选择的激活函数,我们应该怎么选择呢?
对于输出层来说,
如果处理y为0或者1的二元分类问题时,则sigmoid函数是一个很好的选择,神经网络就会学习预测y的概率
如果处理回归的问题,线性激活函数是一个很好的选择,因为y可以是正数或者是负数
如果y只能取非负值且处理回归问题,永远不会为负时,那么ReLu激活函数是一个好的选择。
对于隐藏层来说:
对于隐藏层的激活函数,ReLu激活函数是最常见的选择,因为ReLu速度会更快一些,而sigmoid要取幂,速度会慢一些。
如果对所有的隐藏层和输出层使用线性激活函数,模型计算结果完全等同于线性回归的输出;若输出层是sigmoid激活函数,那么结果等同于逻辑回归的输出。
因此,不要在神经网络隐藏层中使用线性激活函数,要使用ReLu激活函数。
多分类任务
如果有两个以上可能输出的标签的话,不仅仅是0或者1,就是指分类问题,比如之前识别数字是0或者1,现在可以识别多个可能的数字。当然y只能取少量的离散类别而不是任何数字,取的不仅仅是2个可能的值,多分类依然是监督学习,但是类别多了。
对于解决多分类问题,softmax算法是逻辑回归的推广,是对多分类的二元分类算法。
其中逻辑回归是一个简单的二元分类任务,y为0的概率加上y为1的概率和为1,对于softmax回归,它是有N个可能的输出,且N个事件发生的概率和为1.
如下图所示,为softmax回归的概率公式。其中里面的a_j是给定输入特征x时模型对y=j的估计,如果N=2的softmax回归,只有两种可能,softmax结果与逻辑回归的结果相同,softmax是逻辑回归的推广。
那么对于softmax的代价函数为:
神经网络的softmax输出
如果将输出层改为10个神经元,这个新的输出层是一个softmax输出层,这个时候,softmax输出层是可以估计y是这10个可能输出标签中的任何一个的可能性,也称为softmax激活函数,对于softmax激活函数,其概率是z1到z10的函数,激活值每一个都可取决于z的所有值,对于TensorFlow,损失函数可以计为SparseCategorialCrossentropy(),也被称为稀疏分类交叉熵。
对于softmax的编译,我们仍然还有更好的改进代码,我们可以对数值进行四舍五入的减少误差。其中sigmoid激活函数去算,容易丢失概率,没有那么精确,传统的编译为
model.compile(loss=BinaryCrossEntropy())
改进之后,将输出层激活函数改为linear线性,将sigmoid融合到损失函数当中
改进之后的代码为:
model.compile(loss=BinaryCrossEntropy(from-logits=True))
对于softmax回归的改进代码为:
model.compile(loss=SparseCategoricalCrossentropy(from_logits=True))
输出层改为linear线性。
preidct为最终预测的概率,之前是直接输出概率,现在是先输出值,在带入激活函数算出概率,以减少误差。
高级优化方法(Adam algorithm)
我么之前学到过寻找w和b的梯度下降方法,可能会出现其中学习率过大或者过小的问题,过大容易永远找不到合适的值,太小容易导致寻找的速度太慢,导致迭代递归多次。
现在学习到Adam算法,可以自动的执行降低或者增加学习率的大小,甚至比梯度下降方法更好,使学习率更加的稳健,应用在代码中,在编译时,代码如下:
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=1e-3))
其它网络层类型
我们之前的神经网络层都是密集层类型,该层中每个神经元都是从前一层获得所有激活的输入,另一种层的类型是卷积层,它的特点是只查看部分的像素,优点是可以更快的计算,并且不需要过多的训练数据,可以防止过拟合现象的发生,如下图所示:
这种每个神经元只查看输入图像区域的层类型称为卷积层(有限的输入窗口)。
模型评估
对于我们对于模型评估,一般情况是选百分之70的训练集,百分之三十的测试集,大部分的数据会加入训练集,小部分的数据加入测试集。
对于我们之前学习过的线性回归,它的代价函数,以及测试集,训练集的代价为:
其中里面的内容是预测值减去真实值的评分,平方误差函数。
那么,对于我么之前学习过的分类问题的话,也就是二元分类问题的话,它的代价函数,以及测试集,训练集的代价为:
相比之前我们计算的测试集和训练集的代价,我们会更常用分类错误的次数占预测总次数的比例来表示误差,可以衡量在测试集和训练集上的表现。
那么,我们如何为机器学习算法去选择一个好的模型呢,测试集可以评估模型的好坏,但并不是模型选择的标准,我们可以改进一下,将之前的两个集合,测试集和训练集分为三个不同的子集,训练集、交叉验证集、测试集,比如我们可以把百分之60的数据放入训练集,百分之二十的数据放入交叉验证集,再将百分之二十的数据放入测试集,通常训练集合称为training set,交叉验证集合称为validation set或者dev set,测试集合称为test set。
交叉验证集合用来检查不同模型的有效性和准确性。
计算误差如下所示:
并非在测试集合上测试,而是在交叉验证集合上测试,看哪个模型交叉验证的误差最低,在训练集合上训练一次项到n次项,在交叉验证集合上根据我们算出来的误差选择最小的那个模型,再测试集合考察它的泛化能力,交叉验证集合用于判断项数,测试集合用于判断误差。
接下来,我们通过偏差、方差进行判断,
如果是欠拟合,也被称为高偏差的话,训练集合的代价和交叉验证集合的代价很高,误差很大,训练集合的代价和交叉验证集合的代价差距不会太大。
如果是过拟合,也被称为高方差的话,训练集合的代价很低,而交叉验证集合的代价却很高,两者相差很大。
如果是正好的模型,那么训练集合的代价和交叉验证集合的代价都很低,误差很小。
如下图所示:
其中图中J_train指的是训练集合的代价,J_cv指的是交叉验证集合的代价,高偏差指的是拟合能力,高方差指的是泛化能力。
对于线性回归的正则化是这样的,如果正则参数lambda很大的话,那么参数w会很小,导致出现高偏差,会出现欠拟合的现象;如果正则参数lambda很小的话,比如lambda为0,会出现高方差,也就是过拟合的现象,如下图所示:
当然,对于正则参数lambda,lambda越大,越容易出现欠结合,导致训练集合的代价变大,正则参数lambda的的变化与项数次方的变化正好呈镜像变化,如下图所示:
如上方图所示,lambda的中间值会使算法表现最佳。
我么需要制定一个用于性能评估的基准,用于判断此模型是否为最佳,什么情况可以使误差最小,最优,一般会有基础人为的表现、测试集合的代价、交叉验证集合的代价这三个比较的数据。
我们有可以帮助理解学习算法如何作为它拥有经验量的函数的方法。
对于高偏差的模型,获得更多的训练数据没有太大的帮助,因为他们离人为的表现依旧差的太多,如下图所示:
那么对于高方差的模型,增加训练集合的大小很有帮助,如下图所示:
总结:欠拟合,也就是高偏差对新数据不够敏感;过拟合,也就是高方差对新数据很敏感。
我们即便有1000个数据,也可以仅仅训练100个数据,查看训练误差和交叉验证误差,然后再200个数据上训练,保留800个数据暂时不使用,通过训练误差和交叉验证误差了解学习曲线,查看学习曲线是高偏差还是高方差。
那么我们下一步应该做什么呢?
如果我们获得更多的训练数据,那么这对于高偏差,也就是欠拟合不会有太大的帮助,对于高方差,也就是过拟合会有很大帮助。
如果尝试获得较少的特征,那么这个很适合高方差,也就是过拟合。
如果尝试获得较多的特征,那么这个很适合高偏差,也就是欠拟合。
如果增加多项式的特征,比如x的平方等等,那么这个很适合高偏差,也就是欠拟合。
如果增大正则参数lambda,那么这也很适合高偏差,也就是欠拟合。
如果减小正则参数lambda,那么这很适合高方差,也就是过拟合。
当然,我么不要减少训练集合的大小,会认为这样对高偏差有帮助,这回恶化交叉验证集合的性能,因此,我们不要随意丢弃训练样本来修复高偏差,欠拟合。
我们可以举一个小例子,来理解偏差和方差,偏差可以看作是你平时刷题的学习能力,方差可以看作是你考试时候的成绩,偏差和方差可能是不一致的。
对于神经网络,我么应该怎样去训练一个最优的模型呢?
大型的神经网络是低偏差机器,如果神经网络足够大,几乎可以很好的适应训练集合,如下图所示:
如图所示,如果该神经网络有很低的训练误差,可以查看是否有低低交叉验证集合的误差,如果训练误差很大,我么可以选择Bigger network,就是去增加更多的隐藏层和神经元,如果我么的交叉验证误差很大,证明我们遇到了高方差,也就是过拟合,这个时候我们可以选择增加训练数据来使模型更优。
具有精心选择的正则化的大型神经网络与较小的神经网络一样好或者更好,如果正则化是正确的,扩大神经网络也无妨,不要认为扩大神经网络会导致过拟合,只要正则化正确,丝毫不影响。
正则化的代码如下:
该图片中,上方代码是未正则化的,下方是已经正则化的代码,其中kernel_regularizer后方的L2的意思是参数平方,L1、L2、L3是正则化的三种方法,其中L1代表参数绝对值,L2代表参数平方,L3代表两者相加。
机器学习的开发迭代循环
机器学习的开发迭代循环如下图所示:
首先选择结构,是否扩大神经网络,或者更改Lambda正则化参数,之后进行模型的训练,进行诊断是否存在高方差,也就是过拟合或者高偏差,也就是欠拟合,再次进行结构的选择的机器学习开发迭代循环。
我么可以举一个构建分类器来识别垃圾邮件和非垃圾邮件的例子:
方法1,我们可以训练一个监督学习算法,输入特征x,也就是电子邮件,进行输出y,二元分类,0或者1,取决于是否是垃圾邮件还是非垃圾邮件,可以去根据邮件某几个词语或者单词来判断是否是垃圾邮件还是非垃圾邮件。
方法2,我们不用二元分类,0或者1来判断垃圾邮件还是非垃圾邮件,我们利用敏感的一些单词或者词语在邮件中出现的次数来对邮件进行判断,训练分类算法,逻辑回归或者神经网络,给定这些输入特征,进行输出y,也就是对邮件种类的预测。