ANNs: artificial neural networks,人工神经网络,受人类大脑生物神经元的启发。
人工神经网络是深度学习的核心,其应用广泛、强大并且扩展性好。深度学习在很多IT公司都有布局,例如Google Images、Apple Siri、YouTube视频推荐、DeepMind AlphaGo等等。
0. 导入所需的库
import tensorflow as tf
import matplotlib as mpl
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import os
for i in (tf, mpl, np, pd):
print(i.__name__,": ",i.__version__,sep="")
输出:
tensorflow: 2.2.0
matplotlib: 3.1.2
numpy: 1.17.4
pandas: 0.25.3
1. 从生物神经元到人工神经元
1.1 概述
人工神经网络首次由神经生物学家Warren McCulloch和数学家Walter Pitts于1943年提出。
人工神经网络有很大的发展前景,主要原因有:
- 当今世界是大数据的时代,有大量的数据可供神经网络使用,并且ANNs强大的解决问题的能力。
- 计算力的巨大提升,根据摩尔定律每2年翻一倍,同时也是由于游戏行业的发展,带动了GPU等的生产和进步,同时云计算也起到很大的作用。
- 在实践中,ANNs的一些理论局限性被证明是良性的。
1.2 生物神经元
生物神经元由具有许多分支的树突和很长的轴突组成,轴突的长度是细胞体的几倍到几万倍不等。
1.3 神经元的逻辑运算
McCulloch和Pitts提出了一种非常简单的生物神经元模型,即人工神经元:有一个或多个二进制的输入,最终得出一个二进制的输出。
1.4 感知机(Perceptron)
感知机是一种最简单的人工神经网络,由Frank Rosenblatt于1957年发明。
感知机基于叫作阈值逻辑单元(TLU)的人工神经元,输入和输出都是数字,每个输入都有一个权重。TLU计算输入的加权和,最后经过阶跃函数得到输出。用公式表示如下:
阶跃函数通常是Heaviside阶跃函数(也叫单位阶跃函数),或是sign函数。
Heaviside阶跃函数:大于等于0返回1,否则返回0
sign函数:大于0返回1,小于0返回-1,等于0返回0
如果当前层的所有神经元与前一层每个神经元都有连接,则称该层为全连接层(fully connected layer,也叫dense layer)。全连接层计算公式如下:
其中:
- X:表示输入特征,每行一个样本,每列一特征
- W:表示权重矩阵
- b:表示偏置向量
- 𝜙:表示激活函数,如果是人工神经元就是阶跃函数
感知机权重更新公式如下:
其中:
- 𝜂:表示学习率
sklearn中提供了Perceptron类:
from sklearn.datasets import load_iris
from sklearn.linear_model import Perceptron
iris = load_iris()
X = iris.data[:,(2,3)]
y = (iris.target==0).astype(np.int)
per_clf = Perceptron(max_iter=1000, tol=1e-3, random_state=42)
per_clf.fit(X, y)
输出:
Perceptron(alpha=0.0001, class_weight=None, early_stopping=False, eta0=1.0,
fit_intercept=True, max_iter=1000, n_iter_no_change=5, n_jobs=None,
penalty=None, random_state=42, shuffle=True, tol=0.001,
validation_fraction=0.1, verbose=0, warm_start=False)
sklearn中Perceptron类使用梯度下降算法,因此该类也等同于设置:loss="perceptron", learning_rate="constant", eta0=1, penalty=None时的SGDClassifier类。
y_pred = per_clf.predict([[2, 0.5]])
y_pred
输出:
array([1])
如上输出所示,Perceptron类给出的类别值,而不是属于每个类别的概率。因此,在实际应用中更常用逻辑回归而不是感知机。
a = - per_clf.coef_[0][0] / per_clf.coef_[0][1]
b = - per_clf.intercept_ / per_clf.coef_[0][1]
axes = [0, 5, 0, 2]
x0, x1 = np.meshgrid(
np.linspace(axes[0], axes[1], 500).reshape(-1, 1),
np.linspace(axes[2], axes[3], 200).reshape(-1, 1),
)
X_new = np.c_[x0.ravel(), x1.ravel()]
y_predict = per_clf.predict(X_new)
zz = y_predict.reshape(x0.shape)
plt.figure(figsize=(12, 5))
plt.plot(X[y==0, 0], X[y==0, 1], "bs", label="Not Iris-Setosa")
plt.plot(X[y==1, 0], X[y==1, 1], "yo", label="Iris-Setosa")
plt.plot([axes[0], axes[1]], [a * axes[0] + b, a * axes[1] + b], "k-", linewidth=3)
from matplotlib.colors import ListedColormap
custom_cmap = ListedColormap(['#9898ff', '#fafab0'])
plt.contourf(x0, x1, zz, cmap=custom_cmap)
plt.xlabel("Petal length", fontsize=14)
plt.ylabel("Petal width", fontsize=14)
plt.legend(loc="lower right", fontsize=14)
plt.axis(axes)
plt.show()
输出:
如上输出所示为感知机的决策边界。
感知机有一个致命的缺点,就是无法解决机器学习中很简单的异或问题。也就是因为研究者发现感知机连这么简单的异或问题都无法解决,有大部分研究者开始放弃对神经网络的研究,纷纷将目光转向其它研究领域。
1.5 多层感知机和反向传播算法
以上提到的感知机无法解决异或问题是针对单层感知机。而多层感知机可以解决异或问题。
多层感知机由输入层、多个隐藏层和输出层组成,除了输出层,其它层神经元都有一个偏置,并与下一层是全连接的。由于信息的流动是单向的,即从输入层流向输出层,因此这种多层感知机也叫作前馈神经网络。
深度神经网络:具有大量隐藏层的人工神经网络。
多年来,人们一直在研究如何训练多层感知机。直到1986年,由David Rumelhart、Geoffrey Hinton和Ronald Williams一起发表了著名的具有突破性的关于反向传播算法的文章。
梯度下降算法:神经网络中有两个通路,加权求和的信息向前传播,预测值与真实值之间的误差(损失函数)的梯度反向(向后)传播,然后根据梯度更新参数,不断重复前向传播和反向传播,直到网络收敛。
一般情况下,权重参数W进行随机初始化,偏置b一般进行0初始化。
由于阶跃函数在反向传播过程中无法求导,因此常常被替换成更常用的激活函数。
为什么要用激活函数?增加更多非线性。对于多层全连接神经网络,如果每层都不使用激活函数,则输出y=(((XW1)W2)...Wn,即相当于y=XW1W2...Wn,令W=W1W2...Wn,则该网络就相当于单层网络。因此,如果不使用激活函数,无论多少层的神经网络还是相当于单层神经网络。反过来说,任何足够大的具有非线性激活的神经网络理论上能够拟合任何连接函数。
常用的激活函数如下:
- Sigmoid函数:f(x) = 1/(1+exp(-x)),函数是S形曲线,在实数定义域内连续且可导,值域(0,1)。因为函数输出分布在0和1之间,所以输出可以被看作是概率。
- 双曲正切函数:tanh(x) = (exp(x) - exp(-x)) / ( exp(x) + exp(-x),函数是S形曲线,在实数定义域内连续且可导,值域(-1,1)。因此,在训练刚开始时输出更可能在0附近,有利于模型的收敛。
- ReLU函数:relu(x) = max(0,x),实数定义域内连续,但在0处不可微,但在实际应用中如果碰到0处求导的情况,默认直接让其导数等于0,这样就解决了0处不可导的问题。ReLU用于激活函数的优点是计算导数简单,如果X大于0,导数为1,如果X小于0,导数为0。
- softplus:ReLU函数的变体,softplus(x)=log(1+exp(x))。
- softmax:常常用于多类别分类任务
更多的激活函数请参考:https://mp.csdn.net/console/editor/html/107147043
mpl.rcParams['font.sans-serif'] = ['SimHei']
mpl.rcParams['font.serif'] = ['SimHei']
mpl.rcParams['axes.unicode_minus'] = False
def sigmoid(x):
return 1/(1+np.exp(-x))
def relu(x):
return np.maximum(0,x)
def derivative(f, x, eps=0.000001):
return (f(x + eps) - f(x - eps))/(2 * eps)
z = np.linspace(-5, 5, 200)
plt.figure(figsize=(12,5))
plt.subplot(121)
plt.plot(z, np.sign(z), "r-", linewidth=1, label="Step") # 阶跃函数
plt.plot(z, sigmoid(z), "g--", linewidth=2, label="Sigmoid") # Sigmoid函数
plt.plot(z, np.tanh(z), "b-", linewidth=2, label="Tanh") # tanh函数
plt.plot(z, relu(z), "m-.", linewidth=2, label="ReLU") # ReLU函数
plt.grid(True)
plt.legend(loc="center right", fontsize=14)
plt.title("激活函数", fontsize=14)
plt.axis([-5, 5, -1.2, 1.2])
plt.subplot(122)
plt.plot(z, derivative(np.sign, z), "r-", linewidth=1, label="Step")
plt.plot(0, 0, "ro", markersize=5)
plt.plot(0, 0, "rx", markersize=10)
plt.plot(z, derivative(sigmoid, z), "g--", linewidth=2, label="Sigmoid")
plt.plot(z, derivative(np.tanh, z), "b-", linewidth=2, label="Tanh")
plt.plot(z, derivative(relu, z), "m-.", linewidth=2, label="ReLU")
plt.grid(True)
plt.legend(fontsize=14)
plt.title("Derivatives", fontsize=14)
plt.title(r"导数", fontsize=14)
plt.axis([-5, 5, -0.2, 1.2])
plt.tight_layout()
plt.show()
输出:
1.6 多层感知机回归(Regression MLPs)
Huber Loss:当误差小于阈值delta(通常为1)时求平方,否则求绝对值。Huber损失函数其实是圴方误差MSE和绝对平均误差MAE的综合。
1.7 多层感知机分类(Classification MLPs)
softmax激活函数常常用于多类别分类任务。
2. 利用Keras实现多层感知机
2.1 Keras
Keras是一个用于创建、训练、评估和运行各类神经网络的高级API。官网:https://keras.io/
Keras由François Chollet于2015年作为开源项目发布,作为高级API,目前后端支持调用TensorFlow、Microsoft Cognitive Toolkit(CNTK)和Theano。
在TensorFlow2中也对Keras进行了实现,即tf.keras,同时加入了一些原生Keras没有的特性,例如tf.keras支持TensorFlowr的Data API,因此tf.keras后端只支持TensorFlow。
除了Keras、TensorFlow等深度学习库外,PyTorch也是非常流行,其源于Facebook,PyTorch的API非常的类似于Keras。而这些API都受到了Python库sklearn和Chainer的启发。
TensorFlow 1.x存在许多缺点,很难入门,易用性不好,导致2018年PyTorch大火。TensorFlow2放弃对1.x的延续或继承而进行了重写,进行了重大的调整,并且采用Keras为官方高级API,并精简和清理了大量冗余的API。同样PyTorch1.0解决了PyTorch的主要不足。
2.2 TensorFlow2安装
安装CPU版本:
pip install tensorflow -i https://pypi.douban.com/simple
其中:-i参数指定安装下载源。直接使用pip install tensorflow安装时由于服务器在国外,导致下载速度很慢很慢很慢,可能会报超时错误。因此可以指定国内镜像源从而加快下载速度。常用的国内镜像源如下:
- 清华:https://pypi.tuna.tsinghua.edu.cn/simple
- 阿里云:http://mirrors.aliyun.com/pypi/simple/
- 中国科技大学 https://pypi.mirrors.ustc.edu.cn/simple/
- 华中理工大学:http://pypi.hustunique.com/
- 山东理工大学:http://pypi.sdutlinux.org/
- 豆瓣:http://pypi.douban.com/simple/
TensorFlow2 GPU版本安装请参考:https://blog.csdn.net/Jwenxue/article/details/89300028
import tensorflow as tf
print(tf.__version__)
print(tf.keras.__version__)
输出:
2.2.0
2.3.0-tf
2.3 利用连续API构建图像分类器
Fashion MNIST数据集:与MNIST类似,共有7万张灰度图像,每张图像大小28*28,总共10个类别,每张图像代表不同的时尚用品。因此Fashion MNIST数据集相比于MNIST比较复杂,利用普通的线性模型在MNIST数据集上能达到92%的准确率,而在Fashion MNIST上只能达到83%。
fashion_mnist = tf.keras.datasets.fashion_mnist
(X_train_full, y_train_full),(X_test, y_test) = fashion_mnist.load_data()
for i in (X_train_full, y_train_full, X_test, y_test):
print(i.shape, i.dtype)
输出:
(60000, 28, 28) uint8
(60000,) uint8
(10000, 28, 28) uint8
(10000,) uint8
如上输出所示,sklearn中加载mnist数据集后每个样本为784的向量,向量中每个值的范围是0.0到255.0的浮点数;而用tf.keras加载的MNIST、Fashion MNIST数据集每个样本是28*28的矩阵,每个值的范围是0到255的整数。
X_valid, X_train = X_train_full[:5000] / 255.0, X_train_full[5000:] / 255.0 # 将数据归一化到0.0到1.0之间
y_valid, y_train = y_train_full[:5000], y_train_full[5000:]
X_test = X_test / 255.0
for i in (X_train, y_train, X_valid, y_valid, X_test, y_test):
print(i.shape, i.dtype)
输出:
(55000, 28, 28) float64
(55000,) uint8
(5000, 28, 28) float64
(5000,) uint8
(10000, 28, 28) float64
(10000,) uint8
Fashion MNIST有10个类别的时尚用品:
class_names = ["T-shirt/top", "Trouser", "Pullover", "Dress", "Coat",
"Sandal", "Shirt", "Sneaker", "Bag", "Ankle boot"]
# 查看前25张样本图像
plt.figure(figsize=(12,5))
for i in range(10):
plt.subplot(2,5,i+1)
plt.imshow(X_train[i], cmap="binary")
plt.axis('off')
plt.title(str(y_train[i])+": "+class_names[y_train[i]])
plt.tight_layout()
plt.show()
输出:
如上输出所示,上图所示为训练集中前10张的图像,每张图像的标签是0-9之间的整数。
model = tf.keras.models.Sequential()
model.add(tf.keras.layers.Flatten(input_shape=[28, 28])) # 对输入数据进行X.reshape(-1,1)操作
model.add(tf.keras.layers.Dense(300, activation="relu")) # "relu"等价于tf.keras.activations.relu
model.add(tf.keras.layers.Dense(100, activation="relu"))
model.add(tf.keras.layers.Dense(10, activation="softmax"))
tf.keras.backend.clear_session()
np.random.seed(42)
tf.random.set_seed(42)
也可以使用如下的方法将所有层以list的形式写在Sequential()中:
model = tf.keras.models.Sequential([
tf.keras.layers.Flatten(input_shape=[28, 28]),
tf.keras.layers.Dense(300, activation="relu"),
tf.keras.layers.Dense(100, activation="relu"),
tf.keras.layers.Dense(10, activation="softmax")
])
model.layers
输出:
[<tensorflow.python.keras.layers.core.Flatten at 0x2af3e588>,
<tensorflow.python.keras.layers.core.Dense at 0x24b49358>,
<tensorflow.python.keras.layers.core.Dense at 0x24dd25f8>,
<tensorflow.python.keras.layers.core.Dense at 0x2465a6d8>]
model.summary()
输出:
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
flatten (Flatten) (None, 784) 0
_________________________________________________________________
dense (Dense) (None, 300) 235500
_________________________________________________________________
dense_1 (Dense) (None, 100) 30100
_________________________________________________________________
dense_2 (Dense) (None, 10) 1010
=================================================================
Total params: 266,610
Trainable params: 266,610
Non-trainable params: 0
_________________________________________________________________
如上输出所示,layer的名字由TensorFlow自动生成,也可以自定义;None代表batch-size,可以是任意大小。观察可以发现全连接有大量的参数。
tf.keras.utils.plot_model(model, "my_mnist_model.png",show_shapes=True)
输出:
hidden1 = model.layers[1]
hidden1.name
输出:
'dense'
model.get_layer(hidden1.name) is hidden1
输出:
True
weights, biases = hidden1.get_weights()
print(weights.shape)
print(weights)
输出:
(784, 300)
[[ 0.01613547 0.03505557 -0.03335547 ... 0.04602281 0.05135202
-0.07207453]
[-0.05218205 0.01972669 -0.01451721 ... 0.01652082 -0.05650191
-0.04502674]
[ 0.04127794 0.06563474 -0.00815298 ... -0.07359543 -0.02140902
-0.07401732]
...
[ 0.06348912 -0.0053833 -0.03209457 ... -0.02296805 -0.05252977
0.0024286 ]
[-0.01588191 -0.02908474 0.05714309 ... 0.06348763 0.05528355
0.04353456]
[-0.03586832 -0.03909247 0.0099389 ... -0.02353308 -0.00736362
-0.03326848]]
print(biases.shape)
print(biases)
输出:
(300,)
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
如上输出所示,权重W进行了随机初始化,而偏置b进行了全0初始化。如果想指定初始化方法,可以使用超参数kernel_initializer和bias_initializer进行指定。
model.compile(loss="sparse_categorical_crossentropy",optimizer="sgd",metrics=["accuracy"])
如上过程对模型进行编译,主要指定所使用的损失函数,优化器和衡量指标。
loss使用sparse_categorical_crossentropy是因为标签是稀疏的,每个样本有0-9之间的一个数字作为标签;如果one_hot格式的标签,则需要使用categorical_crossentropy。可以使用tf.keras.utils.to_categorical()函数转换成one_hot编码。
优化器使用sgd,即随机梯度下降,默认学习率为0.01
history = model.fit(X_train, y_train, epochs=30, validation_data=(X_valid, y_valid))
输出:
Epoch 1/30
1719/1719 [==============================] - 4s 2ms/step - loss: 0.7146 - accuracy: 0.7649 - val_loss: 0.5156 - val_accuracy: 0.8232
Epoch 2/30
1719/1719 [==============================] - 4s 2ms/step - loss: 0.4915 - accuracy: 0.8295 - val_loss: 0.4821 - val_accuracy: 0.8302
Epoch 3/30
1719/1719 [==============================] - 4s 2ms/step - loss: 0.4455 - accuracy: 0.8452 - val_loss: 0.4759 - val_accuracy: 0.8238
Epoch 4/30
1719/1719 [==============================] - 4s 2ms/step - loss: 0.4165 - accuracy: 0.8552 - val_loss: 0.4037 - val_accuracy: 0.8604
Epoch 5/30
1719/1719 [==============================] - 4s 2ms/step - loss: 0.3971 - accuracy: 0.8602 - val_loss: 0.3886 - val_accuracy: 0.8684
Epoch 6/30
1719/1719 [==============================] - 4s 2ms/step - loss: 0.3806 - accuracy: 0.8660 - val_loss: 0.3813 - val_accuracy: 0.8696
Epoch 7/30
1719/1719 [==============================] - 4s 2ms/step - loss: 0.3664 - accuracy: 0.8710 - val_loss: 0.3918 - val_accuracy: 0.8668
Epoch 8/30
1719/1719 [==============================] - 4s 2ms/step - loss: 0.3547 - accuracy: 0.8739 - val_loss: 0.3637 - val_accuracy: 0.8728
Epoch 9/30
1719/1719 [==============================] - 4s 2ms/step - loss: 0.3445 - accuracy: 0.8785 - val_loss: 0.3517 - val_accuracy: 0.8770
Epoch 10/30
1719/1719 [==============================] - 4s 2ms/step - loss: 0.3358 - accuracy: 0.8812 - val_loss: 0.3424 - val_accuracy: 0.8778
Epoch 11/30
1719/1719 [==============================] - 4s 2ms/step - loss: 0.3274 - accuracy: 0.883