深度学习是机器学习领域中一个新的研究方向,它被引入机器学习使其更接近于人工智能。
原文:【科普&实践】超详细!一文带你玩转深度学习 - 飞桨AI Studio星河社区
人工智能是一个最宽泛的概念,是一个研究领域,同时也是一个实现目标,而机器学习则是实现这一目标的一类方法。深度学习只是机器学习这一类方法中的一种。
早期的浅层结构(如支持向量机、逻辑回归等)在涉及到一些复杂的问题,如语音、图像、视觉等问题时,会造成维度灾难。
以深度学习为基础的人工智能技术,在升级改造众多的传统行业领域,存在极其广阔的应用场景。
深度学习改变了很多领域算法的实现模式。
深度学习还推动人工智能进入了工业大生产阶段,算法的通用性促使标准化、自动化和模块化的框架产生。
除了应用广泛的特点外,深度学习还推动人工智能进入了工业大生产阶段,算法的通用性促使标准化、自动化和模块化的框架产生。 在深度学习出现之前,不同流派的机器学习算法理论和实现有所不同,这就导致每个算法均要独立实现,如随机森林和支撑向量机(SVM)。但在深度学习框架下,不同模型的算法结构具有较大的通用性,如常用于计算机视觉的卷积神经网络模型(CNN)和常用于自然语言处理的长期短期记忆模型(LSTM),都可以分为组网模块、梯度下降的优化模块和预测模块等。 这就使得抽象出统一的框架成为了可能,就能大大降低编写建模代码的成本。因此,一些相对通用的模块,如网络基础算子的实现、各种优化算法等都可以由框架实现。建模者只需要关注数据处理,配置组网的方式,并能够用少量代码串起训练和预测的流程即可。
在深度学习框架出现之前,机器学习工程师处于“手工作坊”生产的时代。为了完成建模,工程师需要储备大量的数学知识,并为特征工程工作积累大量行业知识。每个模型是极其个性化的,建模者如同手工业者一样,将自己的积累形成模型的“个性化签名”。而今,“深度学习工程师”进入了工业化大生产时代,只要掌握深度学习必要但少量的理论知识,掌握Python编程,即可在深度学习框架上实现非常有效的模型,甚至与该领域最领先的模型不相上下。建模领域的技术壁垒面临着颠覆,这同时也是新入行者的机遇。
神经网络能够反映人类大脑的行为,允许计算机程序识别模式,以及解决人工智能、机器学习和深度学习领域的常见问题。
在生物神经网络中,每个神经元与其他神经元通过突触进行连接。
神经元之间的信息传递,属于化学物质的传递。
M-P神经元模型实际上是对单个神经元的一种建模。
以经典乳腺癌为例
In [18]
#导入需要的包
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import sklearn.datasets
from sklearn.metrics import accuracy_score #以准确率为评价指标
from sklearn.model_selection import train_test_split #用来分割数据集
#导入数据集
from sklearn.datasets import load_breast_cancer
#分离特征和标签
# 1表示良性,0表示恶性
breast_cancer = sklearn.datasets.load_breast_cancer()
data = pd.DataFrame(breast_cancer.data,columns=breast_cancer.feature_names)
data['class'] = breast_cancer.target
data['class'].value_counts()
X = data.drop('class',axis=1)
y = data['class']
#数据集划分
#划分数据集和测试集,测试集的大小为总体数据的15%。设置stratify=y
#按照数据集中y的比例分配给train和test,使得train和test中各类别数据的比例与原数据集的比例一致。通常在数据集的分类分布不平衡的情况下会用到stratify。
X_train, X_test, y_train, y_test = train_test_split(X,y, test_size=0.15, stratify=y, random_state=0)
#M—P神经元模型仅能把0或1作为输入,所以我们要把数据进行处理,划分为0和1两类。
X_binarise_train =X_train.apply(pd.cut, bins=2, labels=[1,0])
X_binarise_test = X_test.apply(pd.cut, bins=2, labels=[1,0])
#获取value,用数组进行计算
X_binarise_train = X_binarise_train.values
X_binarise_test = X_binarise_test.values
#构建M-P神经元类
class MPNeuron:
def __init__(self):
self.b = None
def model(self,x):
return (sum(x) >= self.b)
def predict(self,X):
y = []
for x in X:
y.append(self.model(x))
return np.array(y)
def fit(self,X,y):
accuracy = {}
for b in range(X.shape[1] + 1):
self.b = b
y_pred = self.predict(X)
accuracy[b] = accuracy_score(y_pred,y)
best_b = max(accuracy, key = accuracy.get)
self.b = best_b
#打印最佳b值和最高准确率
print('best_b:', best_b)
print('best_accuracy:', accuracy[best_b])
#用M-P神经元训练,机器学习叫作fit,深度学习叫作train
mp_neuron = MPNeuron()
mp_neuron.fit(X_binarise_train,y_train)
#打印accuracy_score
w = mp_neuron.predict(X_binarise_test)
accuracy_score(w,y_test)
阶跃函数 — 可以将神经元输入值与阈值的差值映射为输出值1或0.若差值大于等于零则输出1,对应兴奋;若差值小于零则输出0,对应抑制。
S型函数(sigmoid函数)— 无论输入值的范围有多大,这个函数都可以将输出挤压在范围(0,1)之内。
阶跃函数
In [2]
import numpy as np
import matplotlib.pyplot as plt
def step_function(x):
return np.where(x >= 0, 1, 0) # 如果输入值大于等于0,则输出1,否则输出0
x = np.linspace(-2, 2, 1000) # 创建一个从-2到2的等差数列,包含1000个元素
y = step_function(x)
plt.figure(figsize=(8, 6)) # 创建一个8x6大小的新图形
plt.plot(x, y)
plt.title('Step Function')
plt.xlabel('x')
plt.ylabel('y')
plt.grid(True) # 添加网格线
plt.axis('on') #显示坐标轴
plt.show()
S型函数
In [16]
# 导入numpy库,用于进行数学计算
import numpy as np
# 导入matplotlib库,用于绘制图形
import matplotlib.pyplot as plt
# 定义sigmoid函数,接受一个参数x
def sigmoid(x):
# 返回1 / (1 + np.exp(-x))的结果,np.exp(-x)计算e的-x次方
return 1 / (1 + np.exp(-x))
# 使用numpy的linspace函数在-10和10之间创建一个等间距的点集,结果存储在x中
x = np.linspace(-10, 10, 1000)
# 计算每个点的sigmoid值并存储在y中
y = sigmoid(x)
# 使用matplotlib的plot函数将这个函数绘制出来
plt.plot(x, y)
# 设置图形的标题为"Sigmoid Function"
plt.title("Sigmoid Function")
# 设置x轴的标签为"x"
plt.xlabel("x")
# 设置y轴的标签为"y"
plt.ylabel("y")
# 添加网格线
plt.grid(True)
# 显示图像
plt.show()
感知机是一种判别模型,其目标是求得一个能够将数据集中的正实例点和负实例点完全分开的分离超平面。
基本结构 — 由两层神经元构成的网络结构。
上面所讲到的M-P神经元模型其实就是对单个神经元的一种建模,需要注意的一点是,M-P模型的权重和阈值都是人为给定的,所以对这一类模型不存在“学习”的说法。其实,这也是M-P模型与单层感知机最大的区别,感知机中引入了学习的概念,权重和阈值是通过学习得来的。
单层感知机模型是由美国科学家Frank Rosenblatt(罗森布拉特)在1957年提出的,它的基本结构如图所示,简单来说,感知机(Perceptron)就是一个由两层神经元构成的网络结构:输入层接收外界的输入信号,通过激活函数(阈值)变换,把信号传送至输出层,因此它也被称为“阈值逻辑单元”;输出层(也被称为是感知机的功能层)就是M-P神经元。
输出的数学表达式如图所示。可以看到,大于阈值的时候输出为1,小于等于阈值的时候输出为0。
通过区分香蕉和西瓜的经典案例来看看感知机是如何工作的。
这样一来,可以很容易根据感知机输出 数学表达式,如下式所示,对西瓜和香蕉做出鉴定: 西瓜:=(_1 _1+_2 _2−)=(1×1+1×1−0)=(2)=1 香蕉:=(_1 _1+_2 _2−)=(1×(−1)+1×(−1)−0)=(−2)=0 这里,我们使用了最简单的阶跃函数作为激活函数。在阶跃函数中,输出规则非常简单:当 中 时, 输出为1,否则输出为0。通过激活函数的“润滑”之后,结果就变成我们想要的样子,这样就实现了西瓜和香蕉的判定。 这里需要说明的是,对象的不同特征(比如水果的颜色或形状等),只要用不同数值区分表示开来即可,具体用什么样的值,其实并没有关系。但你们或许还会疑惑,这里的阈值(threshold) 和两个连接权值 和 ,为啥就这么巧分别就是0、1、1呢?如果取其它数值,会有不同的判定结果吗? 接下来我们假定 还是等于1,而 等于-1,阈值 还是等于0。然后我们对于西瓜的特征:绿色圆形通过加权求和再经过阶跃激活函数后输出如下式所示: 西瓜:=(_1 _1+_2 _2−)=(1×1+(−1)×1−0)=(0)=0
输出为0,而我们假设的输出值0为香蕉,显然判断错了,对于香蕉的特征我们加权求和经过阶跃激活函数得到输出值为0,对应香蕉,判断正确。由此观之,我们判断正确错误和我们的权值 、 和阈值相关。那么怎么选择权值和阈值呢?事实上,我们并不能一开始就知道这几个参数的取值,而是一点点地“折腾试错”(Try-Error),而这里的“折腾试错”其实就是感知机的学习过程。
神经网络的学习规则 — 调整神经元之间的连接权值和神经元内部阈值的规则。
感知机的学习过程分为四个阶段。
单个特征的感知机 — 在红点和蓝点交界的区间内直接任取一点都可以将这两部分样本分开。
二维特征的感知机 — 可以找到一条直线将两类样本点分开。
找到两类样本之间最近的那一部分点;
找到一条直线使得最近的那些样本点到此直线的距离相等且尽量最大,此处可以用“点到直线的距离”来解决,并且由于要把两类样本分开,所以必然一部分样本点在直线上方,一部分在直线下方,点的位置由两个输入特征横坐标和纵坐标来决定。这个例子就类似于我们一开始权重 、 都等于1,阈值等于0的情况。
三维特征的感知机 —用一个面将红点和蓝点分离开,这样平面的位置就由三个输入特征决定。
由上述内容,我们总结得到:
布尔函数 — 输入输出都是布尔值的一种函数,主要有“与”门、“与非”门、“或”门、“异或”门。
“与”门 — 当两个输入均为1时输出为1,其他时候输出为0。
“与非”门 — 当输入全1的时候输出为0,当输入有0则输出为1。
“或”门——只要有一个输入信号为1,输出就为1的逻辑电路。
“异或”门——当_1和 _2相同时输出为0,_1和_2不同时则输出为1。
深度神经网络是一种使用数学模型处理图像以及其他数据的多层系统,而且目前已经发展为人工智能的重要基石。
单层的感知机不能解决“异或”问题。
按照这个思路,我们可以考虑在输入层和输出层之间添加一层神经元,将其称之为隐藏层(Hidden Layer,又称“隐含层”或“隐层”)。
神经网络模型
深度神经网络(Deep Neural Network,DNN)— 将若干个单层神经网络级联在一起,前一层的输出作为后一层的输入。
深度神经网络模型解决回归问题。
前向传播 — 从输入层出发,逐层推进,将上一层的输出与权重结合后,作为下一层的输入,并计算下一层的输出,如此进行,直到运算到输出层为止。
(0.2×(−0.43)+0.5×(−0.37))=0.43
(0.2×0.04+0.5×0.40)=0.55
(0.2×0.04+0.5×0.40)=0.55
(0."43"×(−0."51")+0."55"×(−0.35)+"0.48"×"0.26"))=0.43
(0."43"×(−0."34")+0."55"×(−0."44")+"0.48"×"0.28")=0.44
(0."43"×"0.55"+0."55"×(−0.31)+"0.48"×"0.06")=0."53"
(0."43"×"0.36"+0."44"×(−0.30)+"0.53"×"0.24")=0."54"
(0."43"×"0.27"+0."44"×"0.35"+"0.53"×(−"0.31"))=0."53"
计算损失 — 利用损失函数来调节网络中的权重,进而减小损失函数,即使得输入经过权重能准确的预测出实际值。
计算损失,在机器学习中的“有监督学习”算法里,在假设空间中,构造一个决策函数, 对于给定的输入,由()给出相应的输出 ,这个实际输出值¯和原先预期值可能不一致。于是,需要定义了一个损失函数(Loss Function),也有人称之为代价函数(Cost Function)来度量两者之间的“落差”程度。这个损失函数通常记作(, ̄)=(,()),为了方便起见,这个函数的值为非负数。损失函数值越小,说明实际输出¯和预期输出之间的差值就越小,也就说明构建的模型越好。因此神经网络学习的本质,其实就是利用损失函数来调节网络中的权重,进而减小损失函数,即使得输入经过权重能准确的预测出实际值。
对于不同类型的问题,通常会使用不同的损失函数。比如对于回归问题,一般会使用均方差损失MSE,或平均绝对误差损失MAE。均方差损失和平均绝对误差损失的计算公式如下方所示,_为维度的真值, ̂_为预测值,也就是输出。MSE计算的是每一项误差的平方,而MAE计算的是每一项误差的绝对值。=1/ ∑2_(=1)^▒(_− ̂_ )^2 、=1/ ∑_(=1)^▒|_− ̂_ |
对于分类问题,MSE和MAE也是可用的,但更多情况下,使用的是交叉熵损失函数。确切地说,是将Softmax激活函数和交叉熵损失CrossEntropy搭配使用,交叉熵函数表达式如图中"CrossEntropy"所示。这一点,我们在后续会进行讲解。
此处,使用MSE来作为损失函数来计算损失。在前向传播中通过给定的权重计算出的实际输出为0.54和0.53,因此总的损失应该是(0.3-0.54)的平方加上(0.8-0.53)的平方,再除以2,计算的到的结果是 0.066,即为我们在这一次前向传播过程中预测值与真值的偏差。
反向传播——沿着模型相反的方向,从后向前地逐层计算每个神经元的损失,并更新参数。
反向传播,所谓反向传播,就是沿着模型相反的方向,从后向前地逐层计算每个神经元的损失,并更新参数。以神经网络为例,从输出层出发,首先被更新的,应该是输出层与第二个隐藏层之间的权重矩阵 ,第二被更新的,是两个隐藏层之间的权重矩阵^′,最后被更新的是输入层和第一个隐藏层的权重矩阵,示意图如图所示。
在训练中,反向传播需要搭配优化算法才能实现参数的更新。接下来就来介绍一个经典优化策略——梯度下降。
我们训练的最终目标就是让损失函数的值最小,因为当损失函数值最小的时候,预测值最接近真值。当然最理想的情况是损失为零,此时预测值与真值完全相符。由于模型复杂,所以无法知道损失函数的全貌,那我们要怎么决定向哪个方向改变呢?
就像我们下山一样,如果遇到了雾,无法看见山路的全部走向,只能看见周围的一小片区域。那么就只能基于能看见的部分做一个决策。(忽略主观因素)如果想尽快下山的话,那么我们应该找最陡峭的方向,因为陡峭的路在海拔上变化更快。大方向确定了,那么现在可以在二维平面考虑这个问题。如果发现自己目前是在向上爬坡的,比如左图那样,说明我身后的海拔比我此时的海拔更低。因此,为了降低海拔,那么应当掉头向反方向走,这样更容易走到山脚,如图右边。
反之,如果此时发现自己是在向下走的,那么我应该继续前进,因为我前方的海拔比我此时的海拔低,我更容易走到山脚。在这个场景下,“上”坡和“下”坡,如果换用数学语言来描述,其实就是在坐标系下,这个山的斜率(梯度)符号。上坡就是斜率大于0,下坡就是斜率小于0。那上坡时,我们要选择的是 减小的方向,下坡时选择的是 增大的方向。也就是说我们总是沿着斜率的反方向来走的。
反向传播与更新参数——沿着模型相反的方向,从后向前地逐层计算每个神经元的损失,并更新参数。
参数更新前权重矩阵和计算结果。
一次反向传播后权重矩阵和计算结果。
分类问题
In [34]
import numpy
def sigmoid (x): #激活函数
return 1/(1+numpy.exp(-x))
def der_sigmoid(x): #激活函数的导数
return sigmoid(x)*(1-sigmoid(x))
def mse_loss(y_tr,y_pre): #均方误差损失函数
return((y_tr - y_pre)**2).mean()
class nerualnetwo():
def __init__(self): #感知神经元的权值属性定义,初始化随机值,会导致模型以及预测结果每次都不同
self.w1 = numpy.random.normal()
self.w2 = numpy.random.normal()
self.w3 = numpy.random.normal()
self.w4 = numpy.random.normal()
self.w5 = numpy.random.normal()
self.w6 = numpy.random.normal()
self.b1 = numpy.random.normal()
self.b2 = numpy.random.normal()
self.b3 = numpy.random.normal()
def feedforward(self,x): #前向计算方法,#返回所有神经元值
h1 = x[0]*self.w1+x[1]*self.w2+self.b1
h1f = sigmoid(h1)
h2 = x[0]*self.w3+x[1]*self.w4+self.b2
h2f = sigmoid(h2)
o1 = h1f*self.w5+h2f*self.w6+self.b3
of = sigmoid(o1)
return h1,h1f,h2,h2f,o1,of
def simulate (self,x): #前向计算方法,返回预测值
h1 = x[0]*self.w1+x[1]*self.w2+self.b1
h1f = sigmoid(h1)
h2 = x[0]*self.w3+x[1]*self.w4+self.b2
h2f = sigmoid(h2)
o1 = h1f*self.w5+h2f*self.w6+self.b3
of = sigmoid(o1)
return of
def train(self,data,all_y_tr):
epochs = 1000 #迭代次数
learn_rate = 0.1 #学习率
#print(self.w1)
for i in range(epochs):
for x , y_tr in zip(data,all_y_tr):
valcell = self.feedforward(x) #使用当前权值前向计算所有神经元值
#h1-cell[0],h1f-cell[1],h2-cell[2],h2f-cell[3],o1-cell[4],of-cell[5]
y_pre = valcell[5] #当前预测结果
#反向传播求导计算,每个节点或权值仅仅需要求导一次,即可覆盖下方所有节点
der_L_y_pre = -2*(y_tr-y_pre) #损失函数L对y_pre求导
#y_pre对h1f求偏导,y_pre就是of, of=sg(o1),o1= h1f*self.w5+h2f*self.w6+self.b3(w5后面做常数处理)
#用下一层的输出对上一层的输出逐层求偏导, 目的是求出对权值的偏导
#(权值是刻画数据的元数据,机器学习的目的就是以数据为依据求出刻画自身的元数据)
der_y_pre_h1f = der_sigmoid(valcell[4])*self.w5
der_y_pre_h2f = der_sigmoid(valcell[4])*self.w6
#print(valcell,der_y_pre_h2f)
#h1f对w1,w2求偏导
der_h1f_w1 = der_sigmoid(valcell[0])*x[0]
der_h1f_w2 = der_sigmoid(valcell[0])*x[1]
#h2f对w3,w4求偏导
der_h2f_w3 = der_sigmoid(valcell[2])*x[0]
der_h2f_w4 = der_sigmoid(valcell[2])*x[1]
#y_pre对w5w6b3求偏导
der_y_pre_w5 = der_sigmoid(valcell[4])*valcell[1]
der_y_pre_w6 = der_sigmoid(valcell[4])*valcell[3]
der_y_pre_b3 = der_sigmoid(valcell[4])
#h1f对b1求偏导
der_h1f_b1 = der_sigmoid(valcell[0])
#h2f对b2求偏导
der_h2f_b2 = der_sigmoid(valcell[2])
#反向传播,以损失函数为起点反向传播(通过求导链式法则)到各个权值参数
#梯度下降,调整权值,按照学习速率逐渐下降,学习率与梯度的乘积为下降步长。
#即权重和偏置每向前一步,就需要走学习率和当前梯度的乘积这么远
self.w1 -= learn_rate * der_L_y_pre * der_y_pre_h1f * der_h1f_w1
self.w2 -= learn_rate * der_L_y_pre * der_y_pre_h1f * der_h1f_w2
self.w3 -= learn_rate * der_L_y_pre * der_y_pre_h2f * der_h2f_w3
self.w4 -= learn_rate * der_L_y_pre * der_y_pre_h2f * der_h2f_w4
self.w5 -= learn_rate * der_L_y_pre * der_y_pre_w5
self.w6 -= learn_rate * der_L_y_pre * der_y_pre_w6
self.b1 -= learn_rate * der_L_y_pre * der_y_pre_h1f * der_h1f_b1
self.b2 -= learn_rate * der_L_y_pre * der_y_pre_h2f * der_h2f_b2
self.b3 -= learn_rate * der_L_y_pre *der_y_pre_b3
if i % 10 ==0 :
#对训练数据数组作为simulate函数的参数依次进行处理得到当前预测值
y_pred = numpy.apply_along_axis(self.simulate,1,data)
#对真实值与预测值两个数组使用损失函数求均方误差作为参数的评价标准
loss = mse_loss (all_y_tr , y_pred)
print(i,loss)
梯度消失——反向传播的过程,会有若干个小数,且是小于0.25的数相乘的情况。
_0=_0 =0.06;_1=_1 (_0 )=0.515;_1=_1 _1=0.103_2=_2 (_1 )=0.525;_2=_2 _2=0.315;_3=_3 (_2 )=0.578
(_3)/(_0 )=(_3)/(_2 )×(_2)/(_2 )×(_2)/(_1 )×(_1)/(_1 )×(_1)/(_0 )×(_0)/(_0 )=_3^′×_2×_2^′×_1×_1^′× =0.000036
Relu函数
梯度爆炸 — 当初始的权重过大(如大于10)时,在反向传播的过程中会造成梯度呈指数增长,在靠近输入层的位置,由于梯度过大,导致权重有非常大的更新。