基础知识介绍
深度学习旨在建立模拟大脑工作思考的模型,目前科学认为人脑是通过神经元传递信号工作的,故深度学习主要有神经网络和决策树这两种形式,大致是一侧传入数据,经过多层网络处理最后判断结果,大致流程如下图:
即输入层---隐藏层(可有多层)---输出层,分别对应输入,计算,和输出,多个特征可对应多个隐藏层中的不同神经元,并传给另一个神经元。
该方法需要大量数据,大规模网络的支持才能获取较好效果,随着最近大数据的发展和GPU算力提高,才给神经网络的大规模发展奠定了基础,在计算机视觉中应用尤其广泛,举一个人脸识别的简单例子:
输入一张图像的矩阵信息,隐藏层第一层识别图像边缘和,第二层组合形成面部特征,输出层比对脸型等特征信息后输出身份,这个过程是无标签规定的,神经网络自行学习不同特征并进行归类。
另外关于仿生学,目前关于人脑的研究还没有突破性进展,神经网络更是远远不如神经元复杂,所以目前担心AI失控产生意识的都是淡操心。
神经网络
感知机
每个隐藏层内部的神经元称为感知机,也就是组成神经网络的最小单元,每个单元负责处理判断其接收的特征向量,感知机输出为f(z)=f(w·x+b)
,比如处理一张面部图像的眉毛形状,输出柳叶眉等特征信息,称为激活值,传给下一隐藏层或输出层。
隐藏层
隐藏层是一层感知机的集合,负责对输入分部处理,一个神经网络至少有一个隐藏层,也称为单层感知机,另外还有多层感知机(MLP),这种也是我们最常用的神经网络结构,在MLP中有特例为神经卷积网络(CNN),就是通过卷积操作提取特征的局部特征,该算法在图像处理、语音识别和自然语言处理等领域应用广泛。
每层感知机负责处理的一个步骤,那是否说层数越多处理的越准确越细致呢?
其实不然;
1,层数增多会导致参数和获得的激活值及可能提取判定的因素指数增长,一方面增加运算时间,另外有可能产生过拟合;
2,具体问题比如面部识别,识别五官等特征信息后以及可以判断身份,此时再增加几个层对项目无益。
代码示例
x=np.array([]) # 输入x
layer_1=Dense(units=神经元个数,activation=激活函数,activity_regularizer=输出正则化函数) # 构建隐藏层
layer_2=Dense(......)
a_1=layer_1(x) # 获取激活值
手动构建的一次隐藏层运算如上,优化后的完整项目如下:
x=np.array([]) # 输入x
y=np.array([]) # 输出y
model=Sequential(Dense(),Dense()) # 简化layer直接融入模型
model.compile(loss=损失函数) # 使用损失函数编译模型,找到最小化损失函数的参数`w`和`b`
model.fit(x,y,epochs=100) # 用 x,y的数据训练之,
# epochs代表梯度下降的步长,返回History的对象,记录了loss和其他指标的数值随epoch变化的情况
model.predict(x_new) #预测新的y
model.save(模块名, save_format='tf') #以原格式保存训练好的模型
load_model = tf.saved_model.load(模块名)# 加载原有模型
自行构建
从零开始实现其实也不算难事,规定w
和b
的向量后,输入先调用点积运算z=np.dot(w,x)+b
计算输入,然后使用激活函数a=sigmoid(z)
,就完成了一个神经元的计算过程,将多个神经元的激活值组合成矩阵就获得了向下一层传播的输入,工程中应该没人会写了,所以作大概了解即可。
一般在工程中的实现过程如下:
def dense(a_in,w,b,g): # 构建隐藏层,输入前一层的激活值,返回往下一层的激活值
units=w.shape[1] # 找神经元个数
a_out=np.zeros(units) # 初始化输出
for g in range (units):
w=w[:,j] # 选出j列
z=np.dot(w,a_in)+b[j]# 计算输出
a_out[g]=z
retrun a_out
def sequential(x): # 多层计算
a1=dense(x,w1,b1)
a2=dense(a1,w2,b2)
f_x=a2
return f_x
神经网络的矢量化使用z=np.matul(A_in,w)+b
,可用GPU并行计算。
激活函数
sigmoid函数
只有两种可能输出时使用,其实就是逻辑回归。
RELU函数
机器学习最常用的激活函数,没有0以下的实质内容,大于0的直为线性回归,适用于非负值的回归问题,函数图像如下:
线性激活函数
或称未使用激活函数,纯线性回归问题可用。
激活函数的作用
激活函数的输入是wx+b
,激活的目的是获得非线性的结果,而非线性结果的目的则是:
1,为了模拟神经元电信号的传递,需要将线性数值转换为0、1信号
2,限制层间传递的值范围,输入由于参数不同,差值可能很大,激活函数可以基本实现正则化的功能
多分类问题---softmax算法
分类不止有0、1两种时的分类时,比如对人年龄身份的分类有儿童、青年、壮年、老年,解决方法为softmax
算法,即logistic
的推广,多种可能输出有z1=w1·x+b1
、z2=w2·x+b2
等时,第一个概率的估计为:
其中a1+a2+...+an=1
,可用于验证计算结果。
该方法的成本函数J为J=-logaj y=j
,与logstic
相同,接近1时损失小。
用神经网络实现softmax
时,应设置在输出层的激活函数,activation='softmax'
,编译的损失函数也应设置为model.compile(loss='categorical_crossentropy')
,为了减少数字舍入误差,原理和计算机内存位数啥的可能有关系,计算2/10000
和(1+1/10000)-(1-1/10000)
后者更精准,故语句可以改为model.compile(loss='categorical_crossentropy'(from_logits=True))
.
多标签分类
该分类输出一组分类结果,而非在多个选项中选出一个,比如[1,0,1]
,解决方案有两个思路:
1,看作三个独立问题,分别使用logistic分类
2,用神经网络对多个结果进行处理,同时对应多个输出元,每个输出层的感知机使用激活函数决定
优化方法
如果梯度下降能自动实现调整学习率参数a
,控制步幅,即可实现快速的梯度下降,该功能可在模型编译环境中添加参数实现,如odel.compile(optimizer=Adam(learning_rate=1e-3, loss=损失函数)
。
神经网络的优化
已有模型建立后,针对模型训练结果的改进大概有如下做法:
1,增加或减少训练特征
2,增大训练集
3,更改拟合曲线多项式
4,调整lamda
正则化
在以上任意方向,可能都需要几周甚至几个月的工作才能完成,如果结果不好则前功尽弃,所以如何找准方向很关键,其重要程度可以单独作一章。
在有监督学习基础中我们学到,可以采取绘制学习曲线类似的方法,用图像趋势观察训练结果,但在多特征等条件下,无法绘制出曲线时我们又该如何检验呢?
拆分子集
将已有训练集拆分为两个子集,数量大概为7:3,一个用于训练,一个用于测试训练结果,用测试的平均误差作为评估模型的标准。
交叉验证
拆分子集的局限性很大,每次训练数据多,时间长,改进版的交叉验证就是用于解决该问题的,该方案同时可以实现对模型的自动选择。
拟合多个不同次数的多项式如f1=wx+b
、f2=wx+wx方+b
等,类似枚举法,逐个尝试,选出误差最小的方案,基于这种方式的测试集应该分为三步,比例大概为6:2:2,分别为训练集,交叉验证集和测试集。
训练集顾名思义,用于训练模型,交叉验证集则用于选择模型,并不参与模型参数的制定,测试集仅使用一次,用于检验最终模型的训练结果。
偏差和方差
Jtrian
训练集成本函数高,Jtest
测试集成本函数也高,说明曲线未拟合数据,称为偏差;
Jtrian
训练集成本函数低,而Jtest
测试集成本函数高,说明曲线过拟合数据,无法适应新数据,称为方差
理解未是模型对数据的欠拟合和过拟合,二者也可能同时出现,训练集成本函数高,而测试集成本函数更高,说明训练结果很差,部分未拟合,部分过拟合数据。
解决偏差方差问题的首要是建立评价基准,即确定何种情况是可接受的,这很难有统一的正确标注,但通常情况下我们是和人的正确率作比较,也可以通过学习曲线,如下图的位置通常是比较好的情况:
随着数据增多,我们不可能拟合完美的曲线,因为曲线不可能覆盖每一个数据,但方差和偏差最终都会平缓。
针对高方差主要的解决思路有以下几种:
1,增加数据
2,减少特征
3,减小lambda
解决高偏差主要有以下几种方法:
1,增加特征
2,增加多项式次数
3,增大lambda
听起来很简单,用一些手段减少欠拟合或过拟合的程度,但这种方法快掌握,慢熟练,需要不断在应用中思考尝试才能真正理解。
机器学习周期
整体的设计流程为训练---调整---改进---训练的循环,需要多次迭代才能得到理想的模型,改进过程可展开如下:
整个机器学习的完整周期如下图:
迁移学习
很多情况我们无法获得更大的训练集,针对这种情况的高方差问题我们是否束手无策了呢?也有方法如下:
1,对已有数据进行失真或变换,这种情况在模型基本成熟后交为有效,一定程度还可以增强模型识别的鲁棒性
2,迁移学习,在已有模型的基础上修改输出层,只训练最后输出层的w
和b
,或者整个模型都重新训练,但所有感知层的参数初始化为原来的值。
这种方案的好处在于无需大数据训练,已有数据少时可以采用,但输入类型要相同,必须同是针对图像,音频等相同内容的网络,该方案可行的原理在于,隐藏层的初层都是执行类似功能,如检查边缘,拼合图像获取局部特征等操作,已有数据训练出来的参数大多可移植到新模型中。
决策树:
结构像树一样的决策算法,更准确来说是二叉树,根节点出发,分支为决策节点,底部的叶节点是做出的预测,每次分类旨在将更相似的个体放在同一分支,该算法的重点在于决策节点特征的选择,和停止分裂的判断。
测量纯度---熵
熵是数据杂质多少的度量,记为H(p)
,当信息比例均等时最大,H(0.5)=1
,两端依次减小,函数图像如下:
详细计算方法为H(p1)=-p1log(p1)-p2log(p2)=-p1log(p1)-(1-p1)log(1-p1)
,规定0log0=0
,该式作为划分特征用于决策的主要依据。
选择特征并拆分决策后,计算左右两端熵的加权平均,权为节点包含的分类数量,取H(0.5)
减去该值,作为信息增益,选择大的分类方案,越大说明分类后的结果越纯。
整体流程
1,信息增益最大的特征作为根节点,
2,计算根据不同特征拆分数据的信息增益
3,将数据拆分成两个子集,分别放到根节点的左右分支
4,重复拆分过程,直到满足停止标准,标准可以是一个节点的熵为零,也可以是达到最大深度或者节点内的数量最小
该过程递归实现,从图的角度来看类似深度优先,没产生叶节点之前会一直向下,再回头处理相邻节点。
多分枝决策树---热编码
基本的决策树只能处理有两个结果的分类情况,若分类结果更多,考虑向该结构转换,将k个特征用k个二进制替换,如选择编程语言时有java
、C
、python
和go
,我们对每一个特征都用二进制表示是否选择,将一次判断作为多个不同特征分别分支。
决策树处理连续值
决策树只能处理离散数据,若要处理连续值也需离散化,我们可以采用类似二分法的方式实现,学习时根据连续值计算信息增益,将信息增益最大的离散点作为特征放入决策树的决策分支中,
回归树
实现预测连续数字,而非只分类的情况,完成回归预测的功能。建立回归树的过程如下:
1,根据不同特征拆分数据,并分别计算两端的加权方差
2,选择拆分后加权方差最小的特征作为决策节点,数据放到节点两端
3,重复过程直到分配所有数据
输出本质仍是离散的,只是对输入范围的输出作了区域划分,仅在输出上实现了连续值的预测。
使用多个决策树
单个决策树对数据敏感,偶尔有误差的数据会极大影响决策树的分类效果,所以工程中通常使用多个决策树,多个决策树通过投票确定最终的输出结果,使用该方法可以降低个别误差数据对决策树训练的影响。
随机森林
因为数据有限,我们在训练多个决策树通常采取有放回地从训练集中取数据,比如从1,2,3,4,5,6,7,8,9
中取5个数为1,1,3,5,6
,使用传统构建决策树的方法可能导致有类似的结构,如根节点相同,为了增大随机性,我们限制从k
个特征中随机选出n
个(n<k)
特征构建决策树,使用这种方案可以对训练集异常数据产生的变化进行平均。
增强决策树XGBOOST
数学原理很复杂,直接忽略,表现为根据不同的概率在训练集中进行取样,优先选择在已构建其他树中还未能正确预测的数据训练新树,在森林中不断增强树的预测能力,另外XGBOOST还更快速有效,也能防止过拟合。
使用代码示例如下:
from xgboost import XGBClassifier
model=XGBClassifier() # 若要预测回归值仅需改为model=XGBRegressor()
model.fit(x_train,y_train)
y_pred=model.predict(x_test)
神经网络与决策树的比较
在表格和结构化的数据中,决策树表现更好,得益于其本身的结构,决策树训练更快,也更容易为人所理解。
神经网络更通用,对所有数据实用,尤其图像和音频,是首选算法,但训练慢,不过因为迁移学习技术,避免重复造轮子,更容易继承和实现多个网络串联。
总结
本章学习了深度学习的两大分支,神经网络和决策树,神经网络依靠其隐藏层对数据进行特征提取和变换处理,最后在输出层输出结果,根据对输出要求的不同可以使用不同的激活函数,作为普通开发者我们应该更关注的是神经网络的优化方向,增加学习数据,调整正则化参数,以及交叉验证方法和迁移学习;决策树对人来说更容易理解,使用类似二叉树的数据结构进行分类判断,为了减少异常数据对树的影响,我们使用多个决策树票选结果,为了减少特征对树的影响,我们又引入了随机森林,最后介绍了目前效果很好的决策树方案------XGBOOST。