要想入门以及往下理解深度学习,其中一些概念多是没法避免地须要你理解一番,好比:html
在开始以前但愿你有一点机器学习方面的知识,解决问题的前提是提出问题,咱们提出这样一个问题,对MNIST数据集
进行分析,而后在解决问题的过程当中一步一步地来捋清楚其中涉及到的概念python
MNIST数据集
是一份手写字训练集,出自MNIST
,相信你对它不会陌生,它是机器学习领域的一个经典数据集,感受任意一个教程都拿它来讲事,不过这也侧面证实了这个数据集的经典,这里简单介绍一下:git
压缩包内容以下:github
上图:算法
图片生成代码以下:apache
%matplotlib inline import matplotlib import matplotlib.pyplot as plt import numpy as np from keras.datasets import mnist (train_images, train_labels), (test_images, test_labels) = mnist.load_data() def plot_digits(instances, images_per_row=10, **options): size = 28 images_per_row = min(len(instances), images_per_row) images = instances n_rows = (len(instances) - 1) // images_per_row + 1 row_images = [] n_empty = n_rows * images_per_row - len(instances) images.append(np.zeros((size, size * n_empty))) for row in range(n_rows): rimages = images[row * images_per_row : (row + 1) * images_per_row] row_images.append(np.concatenate(rimages, axis=1)) image = np.concatenate(row_images, axis=0) plt.imshow(image, cmap = matplotlib.cm.binary, **options) plt.axis("off") plt.figure(figsize=(9,9)) plot_digits(train_images[:100], images_per_row=10) plt.show()
不过你不用急着尝试,接下来咱们能够一步一步慢慢来分析手写字训练集数组
看这一行代码:网络
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
MNIST数据集
经过keras.datasets
加载,其中train_images
和train_labels
构成了训练集,另外两个则是测试集:app
咱们要作的事情很简单,将训练集丢到神经网络里面去,训练后生成了咱们指望的神经网络模型,而后模型再对测试集进行预测,咱们只须要判断预测的数字是否是正确的便可机器学习
在用代码构建一个神经网络以前,我先简单介绍一下到底什么是神经网络,让咱们从感知器开始
感知器是Frank Rosenblatt提出的一个由两层神经元组成的人工神经网络,它的出如今当时但是引发了轰动,由于感知器是首个能够学习的神经网络
感知器的工做方式以下所示:
左侧三个变量分别表示三个不一样的二进制输入,output则是一个二进制输出,对于多种输入,可能有的输入成立有的不成立,在这么多输入的影响下,该如何判断输出output呢?Rosenblatt引入了权重来表示相应输入的重要性
此时,output能够表示为:
上面右侧的式子是一个阶跃函数,就是和Sigmoid、Relu同样做用的激活函数,而后咱们就能够本身实现一个感知器:
import numpy as np class Perceptron: """ 代码实现 Frank Rosenblatt 提出的感知器的与非门,加深对感知器的理解 blog: https://www.howie6879.cn/post/33/ """ def __init__(self, act_func, input_nums=2): """ 实例化一些基本参数 :param act_func: 激活函数 """ # 激活函数 self.act_func = act_func # 权重 已经肯定只会有两个二进制输入 self.w = np.zeros(input_nums) # 偏置项 self.b = 0.0 def fit(self, input_vectors, labels, learn_nums=10, rate=0.1): """ 训练出合适的 w 和 b :param input_vectors: 样本训练数据集 :param labels: 标记值 :param learn_nums: 学习多少次 :param rate: 学习率 """ for i in range(learn_nums): for index, input_vector in enumerate(input_vectors): label = labels[index] output = self.predict(input_vector) delta = label - output self.w += input_vector * rate * delta self.b += rate * delta print("此时感知器权重为{0},偏置项为{1}".format(self.w, self.b)) return self def predict(self, input_vector): if isinstance(input_vector, list): input_vector = np.array(input_vector) return self.act_func(sum(self.w * input_vector) + self.b) def f(z): """ 激活函数 :param z: (w1*x1+w2*x2+...+wj*xj) + b :return: 1 or 0 """ return 1 if z > 0 else 0 def get_and_gate_training_data(): ''' AND 训练数据集 ''' input_vectors = np.array([[1, 1], [1, 0], [0, 1], [0, 0]]) labels = np.array([1, 0, 0, 0]) return input_vectors, labels if __name__ == '__main__': """ 输出以下: 此时感知器权重为[ 0.1 0.2],偏置项为-0.2 与门 1 and 1 = 1 1 and 0 = 0 0 and 1 = 0 0 and 0 = 0 """ # 获取样本数据 and_input_vectors, and_labels = get_and_gate_training_data() # 实例化感知器模型 p = Perceptron(f) # 开始学习 AND p_and = p.fit(and_input_vectors, and_labels) # 开始预测 AND print('1 and 1 = %d' % p_and.predict([1, 1])) print('1 and 0 = %d' % p_and.predict([1, 0])) print('0 and 1 = %d' % p_and.predict([0, 1])) print('0 and 0 = %d' % p_and.predict([0, 0]))
神经元和感知器本质上是同样的,他们的区别在于激活函数不一样,好比跃迁函数改成Sigmoid函数
神经网络能够经过样本的学习来调整人工神经元的权重和偏置,从而使输出的结果更加准确,那么怎样给⼀个神经⽹络设计这样的算法呢?
以数字识别为例,假设⽹络错误地把⼀个9的图像分类为8,咱们可让权重和偏置作些⼩的改动,从而达到咱们须要的结果9,这就是学习。对于感知器,咱们知道,其返还的结果不是0就是1,极可能出现这样一个状况,咱们好不容易将一个目标,好比把9的图像分类为8调整回原来正确的分类,可此时的阈值和偏置会形成其余样本的判断失误,这样的调整不是一个好的方案
因此,咱们须要S型神经元,由于S型神经元返回的是[0,1]之间的任何实数,这样的话权重和偏置的微⼩改动只会引发输出的微⼩变化,此时的output能够表示为σ(w⋅x+b),而σ就是S型函数,S型函数中S指的是Sigmoid函数,定义以下:
神经网络其实就是按照必定规则链接起来的多个神经元,一个神经网络由如下组件构成:
从输入层传入手写字训练集
,而后经过隐藏层向前传递训练集数据,最后输出层会输出10个几率值,总和为1。如今,咱们能够看看Keras
代码:
第一步,对数据进行预处理,咱们知道,本来数据形状是(60000, 28, 28)
,取值区间为[0, 255]
,如今改成[0, 1]
:
train_images = train_images.reshape((60000, 28 * 28)) train_images = train_images.astype('float32') / 255 test_images = test_images.reshape((10000, 28 * 28)) test_images = test_images.astype('float32') / 255
而后对标签进行分类编码:
from keras.utils import to_categorical train_labels = to_categorical(train_labels) test_labels = to_categorical(test_labels)
第二步,编写模型:
from keras import models from keras import layers network = models.Sequential() network.add(layers.Dense(512, activation='relu', input_shape=(28 * 28,))) network.add(layers.Dense(10, activation='softmax') network.compile(optimizer='rmsprop',loss='categorical_crossentropy', metrics=['accuracy']) network.fit(train_images, train_labels, epochs=5, batch_size=128)
一个隐藏层,激活函数选用relu
,输出层使用softmax
返回一个由10个几率值(总和为 1)组成的数组
训练过程当中显示了两个数字:一个是网络在训练数据上的损失loss
,另外一个是网络在 训练数据上的精度acc
很简单,咱们构建和训练一个神经网络,就这么几行代码,之因此写的这么剪短,是由于keras
接接口封装地比较好用,可是里面的理论知识咱们仍是须要好好研究下
TensorFlow
里面的Tensor
是张量的意思,上面例子里面存储在多维Numpy数组中的数据就是张量:张量是数据容器,矩阵就是二维张量,张量是矩阵向任意维度的推广,张量的维度称为轴
包含一个数字的张量叫作标量(0D张量),以下:
x = np.array(12) print(x, x.ndim) # 12, 0
张量轴的个数也叫作阶(rank)
数字组成的数组叫作向量(1D张量),以下:
x = np.array([12, 3, 6, 14, 7]) print(x, x.ndim) # [12 3 6 14 7] 1
向量组成的数组叫作矩阵(2D张量),以下:
x = np.array([[5, 78, 2, 34, 0], [6, 79, 3, 35, 1], [7, 80, 4, 36, 2]]) print(x, x.ndim) # [[ 5 78 2 34 0] # [ 6 79 3 35 1] # [ 7 80 4 36 2]] 2
将多个矩阵组合成一个新的数组就是一个3D张量,以下:
x = np.array([[[5, 78, 2, 34, 0], [6, 79, 3, 35, 1]], [[5, 78, 2, 34, 0], [6, 79, 3, 35, 1]], [[5, 78, 2, 34, 0], [6, 79, 3, 35, 1]]]) print(x, x.ndim) # (array([[[ 5, 78, 2, 34, 0], # [ 6, 79, 3, 35, 1]], # # [[ 5, 78, 2, 34, 0], # [ 6, 79, 3, 35, 1]], # # [[ 5, 78, 2, 34, 0], # [ 6, 79, 3, 35, 1]]]), 3)
将多个3D张量组合成一个数组,能够建立一个4D张量
张量是由如下三个关键属性来定义:
之前面加载的train_images
为:
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
好比进行切片选择10~100
个数字:
train_images[10:100].shape # (90, 28, 28)
深度学习模型会将数据集随机分割成小批量进行处理,好比:
batch = train_images[:128] batch.shape # (128, 28, 28)
下面将介绍下现实世界中数据的形状:
相似于计算机程序的计算能够转化为二进制计算,深度学习计算能够转化为数值数据张量上的一些张量运算(tensor operation)
上面模型的隐藏层代码以下:
keras.layers.Dense(512, activation='relu')
这一层能够理解为一个函数,输入一个2D张量,输出一个2D张量,就如同上面感知机那一节最后输出的计算函数:
output = relu(dot(W, input) + b)
Relu 和加法运算都是逐元素的运算,好比:
# 输入示例 input_x = np.array([[2], [3], [1]]) # 权重 W = np.array([[5, 6, 1], [7, 8, 1]]) # 计算输出 z z = np.dot(W, input_x) # 实现激活函数 def naive_relu(x): assert len(x.shape) == 2 x = x.copy() for i in range(x.shape[0]): for j in range(x.shape[1]): x[i, j] = max(x[i, j], 0) return x # 激活函数对应的输出 output = naive_relu(z) output
张量运算那节中,有这样一段代码:
output = relu(dot(W, input) + b)
dot(W, input)
是2D张量,b
是向量,两个形状不一样的张量相加,会发生什么?
若是没有歧义的话,较小的张量会被广播,用来匹配较大张量的形状:
input_x = np.array([[1], [3]]) # 权重 W = np.array([[5, 6], [7, 8]]) b = np.array([1]) # 计算输出 z z = np.dot(W, input_x) + b # array([[24], # [32]])
点积运算,也叫张量积,如:
import numpy as np # 输入示例 input_x = np.array([[2], [3], [1]]) # 权重 W = np.array([[5, 6, 1], [7, 8, 1]]) np.dot(W, input_x)
两个向量之间的点积是一个标量:
def naive_vector_dot(x, y): assert len(x.shape) == 1 assert len(y.shape) == 1 assert x.shape[0] == y.shape[0] z = 0. for i in range(x.shape[0]): z += x[i] * y[i] return z x = np.array([1,2]) y = np.array([1,2]) naive_vector_dot(x, y) # 5.0
矩阵和向量点积后是一个向量:
np.dot(W, [1, 2, 3]) # array([20, 26])
前面对数据进行预处理的时候:
train_images = train_images.reshape((60000, 28 * 28)) train_images = train_images.astype('float32') / 255
上面的例子将输入数据的shape变成了(60000, 784),张量变形指的就是改变张量的行和列,获得想要的形状,先后数据集个数不变,常常遇到一个特殊的张量变形是转置(transposition),以下:
x = np.zeros((300, 20)) x = np.transpose(x) x.shape # (20, 300)
针对每一个输入,神经网络都会经过下面的函数对输入数据进行变换:
output = relu(dot(W, input_x) + b)
其中:
如今咱们须要一个算法来让咱们找到权重和偏置,从而使得y=y(x)能够拟合样本输入的x
感知器学习的过程就是其中权重和偏置不断调优更新的过程,其中的偏置能够理解成输入为1的权重值,那么权重是怎么更新的呢?
首先,介绍一个概念,损失函数,引用李航老师统计学习方法书中的一个解释:
监督学习问题是在假设空间中选取模型f做为决策函数,对于给定的输入X,由f(X)给出相应的输出Y,这个输出的预测值f(X)与真实值Y可能一致也可能不一致,用一个损失函数(loss function)或代价函数(cost function)来度量预测错误的程度,损失函数是f(X)和Y的非负实值函数,记做L(Y,f(X))
其中模型f(X)关于训练数据集的平均损失,咱们称之为:经验风险(empirical risk),上述的权重调整,就是在不断地让经验风险最小,求出最好的模型f(X),咱们暂时不考虑正则化,此时咱们经验风险的最优化的目标函数就是:
求解出此目标函数最小时对应的权重值,就是咱们感知器里面对应的权重值,在推导以前,咱们还得明白两个概念:
假设有一个连续的光滑函数f(x) = y
,什么是函数连续性?指的是x的微小变化只能致使y的微小变化。
假设f(x)上的两点a,b
足够接近,那么a,b
能够近似为一个线性函数,此时他们斜率为k
,那么能够说斜率k是f在b点的导数
总之,导数描述了改变x后f(x)会如何变化,若是你但愿减少f(x)的值,只须要将x沿着导数的反方向移动一小步便可,反之亦然
梯度是张量运算的导数,是导数这一律念向多元函数导数的推广,它指向函数值上升最快的方向,函数值降低最快的方向天然就是梯度的反方向
推导过程以下:
感知器代码里面的这段:
self.w += input_vector * rate * delta
就对应上面式子里面推导出来的规则
再来看看所有的手写字识别模型代码:
from keras import models from keras import layers from keras.utils import to_categorical (train_images, train_labels), (test_images, test_labels) = mnist.load_data() train_images = train_images.reshape((60000, 28 * 28)) train_images = train_images.astype('float32') / 255 test_images = test_images.reshape((10000, 28 * 28)) test_images = test_images.astype('float32') / 255 train_labels = to_categorical(train_labels) test_labels = to_categorical(test_labels) network = models.Sequential() network.add(layers.Dense(512, activation='relu', input_shape=(28 * 28,))) network.add(layers.Dense(10, activation='softmax')) network.compile(optimizer='rmsprop',loss='categorical_crossentropy', metrics=['accuracy']) network.fit(train_images, train_labels, epochs=5, batch_size=128) test_loss, test_acc = network.evaluate(test_images, test_labels) print('test_acc:', test_acc)
float32
格式的Numpy
张量中,形状分别是(60000, 784)和(10000, 784)对本文有影响的书籍文章以下,感谢他们的付出: