Python 的 Keras 库来学习手写数字分类,将手写数字的灰度图像(28 像素 ×28 像素)划分到 10 个类别
中(0~9)
神经网络的核心组件是层(layer),它是一种数据处理模块,它从输入数据中提取表示,紧接着的一个例子中,将含有两个Dense 层,它们是密集链接(也叫全链接)的神经层,最后是一个10路的softmax层,它将返回一个由 10 个几率值(总和为 1)组成的数组。每一个几率值表示当前数字图像属于 10 个数字类别中某一个的几率
损失函数(loss function):网络如何衡量在训练数据上的性能,即网络如何朝着正确的方向前进
优化器(optimizer):基于训练数据和损失函数来更新网络的机制html
from keras.datasets import mnist 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() print("训练图片个数与尺寸: ", train_images.shape, "标签数: ", len(train_labels)) print("测试图片数量与尺寸: ", test_images.shape, "标签数: ", len(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']) # 数据预处理,将其变换为网络要求的形状,并缩放到全部值都在 [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 # 对标签进行分类编码 train_labels = to_categorical(train_labels) test_labels = to_categorical(test_labels) # 训练模型,epochs表示训练遍数,batch_size表示每次喂给网络的数据数目 network.fit(train_images, train_labels, epochs=5, batch_size=128) # 检测在测试集上的正确率 test_loss, test_acc = network.evaluate(test_images, test_labels) print('正确率: ', test_acc)
张量是矩阵向任意维度的推广,仅包含一个数字的张量叫做标量,数字组成的数组叫做向量(vector)或一维张量(1D 张量)。一维张量只有一个轴
显示数字图片git
(train_images, train_labels), (test_images, test_labels) = mnist.load_data("/home/fan/dataset/mnist.npz") # 显示第0个数字 import matplotlib.pyplot as plt digit = train_images[0] plt.imshow(digit, cmap=plt.cm.binary) plt.show()
一些数据张量
向量数据: 2D 张量,形状为 (samples, features)
时间序列数据或序列数据: 3D 张量,形状为 (samples, timesteps, features)
图像: 4D 张量,形状为 (samples, height, width, channels) 或 (samples, channels, height, width)
视频: 5D 张量,形状为 (samples, frames, height, width, channels) 或 (samples, frames, channels, height, width)算法
当时间(或序列顺序)对于数据很重要时,应该将数据存储在带有时间轴的 3D 张量中
json
根据惯例,时间轴始终是第 2 个轴
图像一般具备三个维度: 高度、宽度和颜色深度
灰度图像只有一个颜色通道,所以能够保存在 2D 张量中
4D张量表示
数组
图像张量的形状有两种约定: 通道在后(channels-last)的约定(在 TensorFlow 中使用)和通道在前(channels-first)的约定(在 Theano 中使用)。TensorFlow 机器学习框架将颜色深度轴放在最后: (samples, height, width, color_depth),Theano将图像深度轴放在批量轴以后: (samples, color_depth, height, width),Keras 框架同时支持这两种格式
视频数据为 5D 张量,每一帧均可以保存在一个形状为 (height, width, color_depth) 的 3D 张量中,所以一系列帧能够保存在一个形状为 (frames, height, width, color_depth) 的 4D 张量中,而不一样视频组成的批量则能够保存在一个 5D 张量中,其形状为(samples, frames, height, width, color_depth)
一个以每秒 4 帧采样的 60 秒 YouTube 视频片断,视频尺寸为 144×256,这个视频共有 240 帧。4 个这样的视频片断组成的批量将保存在形状为 (4, 240, 144, 256, 3)的张量中安全
若是将两个形状不一样的张量相加,较小的张量会被广播(broadcast),以匹配较大张量的形状:网络
a = np.array([[2, 2], [1, 1]]) c = np.array([3, 3]) print(a + c)
结果为架构
[[5 5] [4 4]]
若是一个张量的形状是 (a, b, ... n, n+1, ... m) ,另外一个张量的形状是 (n, n+1, ... m) ,那么你一般能够利用广播对它们作两个张量之间的逐元素运算。广播操做会自动应用于从 a 到 n-1 的轴app
在 Numpy、Keras、Theano 和 TensorFlow 中,都是用 * 实现逐元素乘积,在 Numpy 和 Keras 中,都是用标准的 dot 运算符来实现点积框架
a = np.array([1, 2]) b = np.array([[5], [6]]) # 输出[17] print(a.dot(b))
张量变形是指改变张量的行和列,以获得想要的形状。变形后的张量的元素总个数与初始张量相同
a = np.array([[0, 1], [2, 3], [4, 5]]) print(a) print("after reshape: \n", a.reshape((2, 3)))
输出
[[0 1] [2 3] [4 5]] after reshape: [[0 1 2] [3 4 5]]
转置 np.transpose(x)
SGD(stochastic gradient descent) -- 随机梯度降低
不一样的张量格式与不一样的数据处理类型须要用到不一样的层,简单的向量数据保存在形状为 (samples, features) 的 2D 张量中,一般用密集链接层[densely connected layer,也叫全链接层(fully connected layer)或密集层(dense layer),对应于 Keras 的 Dense 类]来处理。序列数据保存在形状为 (samples, timesteps, features) 的 3D 张量中,一般用循环层(recurrent layer,好比 Keras 的 LSTM 层)来处理。图像数据保存在 4D 张量中,一般用二维卷积层(Keras 的 Conv2D )来处理
Keras框架具备层兼容性,具体指的是每一层只接受特定形状的输入张量,并返回特定形状的输出张量
layer = layers.Dense(32, input_shape=(784,))
建立了一个层,只接受第一个维度大小为 784 的 2D 张量做为输入。这个层将返回一个张量,第一个维度的大小变成了 32 所以,这个层后面只能链接一个接受 32 维向量做为输入的层,使用 Keras 时,你无须担忧兼容性,由于向模型中添加的层都会自动匹配输入层的形状,下一次层能够写为
model.add(layers.Dense(32))
它能够自动推导出输入形状等于上一层的输出形状
具备多个输出的神经网络可能具备多个损失函数(每一个输出对应一个损失函数)。可是,梯度降低过程必须基于单个标量损失值。所以,对于具备多个损失函数的网络,须要将全部损失函数取平均,变为一个标量值
一个 Keras 工做流程
定义模型有两种方法:
一种是使用 Sequential 类(仅用于层的线性堆叠,这是目前最多见的网络架构)
另外一种是函数式 API(functional API,用于层组成的有向无环图,让你能够构建任意形式的架构)
Sequential 类定义两层模型
model = models.Sequential() model.add(layers.Dense(32, activation='relu', input_shape=(784,))) model.add(layers.Dense(10, activation='softmax'))
函数式 API 定义的相同模型
input_tensor = layers.Input(shape=(784,)) x = layers.Dense(32, activation='relu')(input_tensor) output_tensor = layers.Dense(10, activation='softmax')(x) model = models.Model(inputs=input_tensor, outputs=output_tensor)
如下学习根据电影评论的文字内容将其划分为正面或负面
使用 IMDB 数据集,数据集被分为用于训练的 25 000 条评论与用于测试的 25 000 条评论,训练集和测试集都包含 50% 的正面评论和 50% 的负面评论
其中,数据集中的labels 都是 0 和 1 组成的列表,0表明负面(negative),1 表明正面(positive)
你不能将整数序列直接输入神经网络。你须要将列表转换为张量。转换方法有如下两种
训练代码
from keras.datasets import imdb import os import numpy as np from keras import models from keras import layers import matplotlib.pyplot as plt # 将整数序列编码为二进制矩阵 def vectorize_sequences(sequences, dimension=10000): results = np.zeros((len(sequences), dimension)) for i, sequence in enumerate(sequences): # results[i] 的指定索引设为 1 results[i, sequence] = 1 return results data_url_base = "/home/fan/dataset" # 下载数据且只保留出现频率最高的前10000个单词 (train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=10000, path=os.path.join(data_url_base, "imdb.npz")) # 将某条评论迅速解码为英文单词 # word_index 是一个将单词映射为整数索引的字典 word_index = imdb.get_word_index(path=os.path.join(data_url_base, "imdb_word_index.json")) # 将整数索引映射为单词 reverse_word_index = dict([(value, key) for (key, value) in word_index.items()]) # 索引减去了 3,由于 0、一、2是为“padding”(填充)、 # “start of sequence”(序列开始)、“unknown”(未知词)分别保留的索引 decoded_review = ' '.join([reverse_word_index.get(i - 3, '?') for i in train_data[0]]) print(decoded_review) # 将数据向量化 x_train = vectorize_sequences(train_data) x_test = vectorize_sequences(test_data) # 将标签向量化 y_train = np.asarray(train_labels).astype('float32') y_test = np.asarray(test_labels).astype('float32') # 设计网络 # 两个中间层,每层都有 16 个隐藏单元 # 第三层输出一个标量,预测当前评论的情感 # 中间层使用 relu 做为激活函数,最后一层使用 sigmoid 激活以输出一个 0~1 范围内的几率值 model = models.Sequential() model.add(layers.Dense(16, activation='relu', input_shape=(10000,))) model.add(layers.Dense(16, activation='relu')) model.add(layers.Dense(1, activation='sigmoid')) # 模型编译 # binary_crossentropy二元交叉熵 model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc']) # 留出验证集 x_val = x_train[:10000] partial_x_train = x_train[10000:] y_val = y_train[:10000] partial_y_train = y_train[10000:] history = model.fit(partial_x_train, partial_y_train, epochs=20, batch_size=512, validation_data=(x_val, y_val)) # 获得训练过程当中的全部数据 history_dict = history.history print(history_dict.keys()) # 绘制训练损失和验证损失 loss_values = history_dict['loss'] val_loss_values = history_dict['val_loss'] epochs = range(1, len(loss_values) + 1) # 'bo' 蓝色圆点 plt.plot(epochs, loss_values, 'bo', label='Training loss') # 'b' 蓝色实线 plt.plot(epochs, val_loss_values, 'b', label='Validation loss') plt.title("Training and Validation loss") plt.xlabel('Epochs') plt.legend() plt.show() # 绘制训练精度和验证精度 # plt.clf() 清空图像 acc = history_dict['acc'] val_acc = history_dict['val_acc'] plt.plot(epochs, acc, 'bo', label='Training acc') plt.plot(epochs, val_acc, 'b', label='Validation acc') plt.title('Training and validation accuracy') plt.xlabel('Epochs') plt.ylabel('Accuracy') plt.legend() plt.show()
结果以下
可见训练损失每轮都在下降,训练精度每轮都在提高,但验证损失和验证精度并不是如此,这是由于咱们遇到了过拟合的状况,能够采用多种方法防止过拟合,如增长数据样本,减小训练次数,减小网络参数等
使用训练好的网络对新数据进行预测
model.predict(x_test)
多分类问题 -- 新闻主题分类
若是每一个数据点只能划分到一个类别,那么这就是一个单标签、多分类问题,而若是每一个数据点能够划分到多个类别(主题),那它就是一个多标签、多分类问题,此处为单标签、多分类问题
将标签向量化有两种方法
将标签转换为整数张量
y_train = np.array(train_labels) y_test = np.array(test_labels)
对于此种编码方法,咱们选择的损失函数应该为sparse_categorical_crossentropy
,该编码方法适用于整数标签
新闻分类示例
from keras.datasets import reuters import numpy as np from keras.utils.np_utils import to_categorical from keras import models from keras import layers import matplotlib.pyplot as plt # 将整数序列编码为二进制矩阵 def vectorize_sequences(sequences, dimension=10000): results = np.zeros((len(sequences), dimension)) for i, sequence in enumerate(sequences): # results[i] 的指定索引设为 1 results[i, sequence] = 1 return results # 将数据限定为前10000个最常出现的单词 (train_data, train_labels), (test_data, test_labels) = reuters.load_data(num_words=10000, path="/home/fan/dataset/reuters/reuters.npz") # 新闻解析 word_index = reuters.get_word_index(path="/home/fan/dataset/reuters/reuters_word_index.json") reversed_word_index = dict([(value, key) for (key, value) in word_index.items()]) # 索引减去了3,由于 0、一、2 是为“padding”( 填 充 )、“start of # sequence”(序列开始)、“unknown”(未知词)分别保留的索引 decoded_newswire = ' '.join([reversed_word_index.get(i-3, '?') for i in train_data[0]]) print(decoded_newswire) # 标签的索引范围为0 - 45 print(np.amax(train_labels)) # 数据向量化 x_train = vectorize_sequences(train_data) x_test = vectorize_sequences(test_data) # 标签向量化 one_hot_train_labels = to_categorical(train_labels) one_hot_test_labels = to_categorical(test_labels) model = models.Sequential() model.add(layers.Dense(64, activation='relu', input_shape=(10000, ))) model.add(layers.Dense(64, activation='relu')) model.add(layers.Dense(46, activation='softmax')) model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy']) # 留出1000验证集 x_val = x_train[:1000] partial_x_train = x_train[1000:] y_val = one_hot_train_labels[:1000] partial_y_train = one_hot_train_labels[1000:] history = model.fit(partial_x_train, partial_y_train, epochs=20, batch_size=512, validation_data=(x_val, y_val)) loss = history.history['loss'] val_loss = history.history['val_loss'] epochs = range(1, len(loss) + 1) plt.plot(epochs, loss, 'bo', label='Training loss') plt.plot(epochs, val_loss, 'b', label='Validation loss') plt.title('Training and validation loss') plt.xlabel('Epochs') plt.ylabel('Loss') plt.legend() plt.show() acc = history.history['acc'] val_acc = history.history['val_acc'] plt.plot(epochs, acc, 'bo', label='Training acc') plt.plot(epochs, val_acc, 'b', label='Validation acc') plt.title('Training and validation accuracy') plt.xlabel('Epochs') plt.ylabel('Accuracy') plt.legend() plt.show()
实验结果
Loss
Accuracy
要点
回归问题 预测一个连续值而不是离散的标签
当咱们将取值范围差别很大的数据输入到神经网络中,网络可能会自动适应这种数据,可是学习确定是困难的。对于这种数据,广泛采用的最佳实践是对每一个特征作标准化,即对于输入数据的每一个特征(输入数据矩阵中的列),减去特征平均值,再除以标准差,这样获得的特征平均值为 0,标准差为 1
此处要注意,用于测试数据标准化的均值和标准差都是在训练数据上计算获得的。在工做流程中,你不能使用在测试数据上计算获得的任何结果,即便是像数据标准化这么简单的事情也不行
当样本数量不多,咱们应该使用一个很是小的网络,否则会出现严重的过拟合
当进行标量回归时,网络的最后一层只设置一个单元,不须要激活,是一个线性层,添加激活函数将会限制输出范围
当你的数据量较小时,没法给验证集分出较大的样本,这致使验证集的划分方式会形成验证分数上有很大的方差,而没法对模型进行有效的评估,这时咱们能够选用K折交叉验证
K折交叉验证
例子
from keras.datasets import boston_housing from keras import models from keras import layers import numpy as np import matplotlib.pyplot as plt def builde_model(): model = models.Sequential() model.add(layers.Dense(64, activation='relu', input_shape=(train_data.shape[1],))) model.add(layers.Dense(64, activation='relu')) model.add(layers.Dense(1)) model.compile(optimizer='rmsprop', loss='mse', metrics=['mae']) return model (train_data, train_targets), (test_data, test_targets) = boston_housing.load_data('/home/fan/dataset/boston_housing.npz') # 数据标准化 mean = train_data.mean(axis=0) train_data -= mean std = train_data.std(axis=0) train_data /= std test_data -= mean test_data /= std k = 4 num_val_samples = len(train_data) num_epochs = 500 all_mae_histories = [] for i in range(k): print('processing fold #', i) val_data = train_data[i * num_val_samples: (i + 1) * num_val_samples] val_targets = train_targets[i * num_val_samples: (i + 1) * num_val_samples] partial_train_data = np.concatenate([train_data[:i * num_val_samples], train_data[(i + 1) * num_val_samples:]], axis=0) partial_train_targets = np.concatenate([train_targets[:i * num_val_samples], train_targets[(i + 1) * num_val_samples:]], axis=0) model = builde_model() # 静默模式 verbose = 0 history = model.fit(partial_train_data, partial_train_targets, validation_data=(val_data, val_targets), epochs=num_epochs, batch_size=1, verbose=0) print(history.history.keys()) if 'mean_absolute_error' not in history.history.keys(): continue mae_history = history.history['mean_absolute_error'] all_mae_histories.append(mae_history) average_mae_history = [np.mean([x[i] for x in all_mae_histories]) for i in range(num_epochs)] def smooth_curve(points, factor=0.9): smoothed_points = [] for point in points: if smoothed_points: previous = smoothed_points[-1] smoothed_points.append(previous * factor + point * (1 - factor)) else: smoothed_points.append(point) return smoothed_points smooth_mae_history = smooth_curve(average_mae_history[10:]) plt.plot(range(1, len(smooth_mae_history) + 1), smooth_mae_history) plt.xlabel('Epochs') plt.ylabel('Validation mae') plt.show()
实验结果
每一个数据点为前面数据点的指数移动平均值
机器学习的四个分支
监督学习 给定一组样本(一般由人工标注),它能够学会将输入数据映射到已知目标,如
分类
回归
序列生成 给定一张图像,预测描述图像的文字
语法树预测 给定一个句子,预测其分解生成的语法树
目标检测 给定一张图像,在图中特定目标的周围画一个边界框
图像分割 给定一张图像,在特定物体上画一个像素级的掩模
无监督学习 在没有目标的状况下寻找输入数据的有趣变换,其目的在于数据可视化、数据压缩、数据去噪或更好地理解数据中的相关性,如
降维
聚类
自监督学习 是没有人工标注的标签的监督学习,标签仍然存在,但它们是从输入数据中生成的,一般是使用启发式算法生成的,如
自编码器其生成的目标就是未经修改的输入
给定视频中过去的帧来预测下一帧或者给定文本中前面的词来预测下一个词(用将来的输入数据做为监督)
强化学习
在强化学习中,智能体(agent)接收有关其环境的信息,并学会选择使某种奖励最大化的行动
机器学习的目的是获得能够泛化(generalize)的模型,即在前所未见的数据上表现很好的模型,而过拟合则是核心难点
评估模型的重点是将数据划分为三个集合: 训练集、验证集和测试集
划分为这三个集合的缘由是:
训练集用来训练网络中的参数,验证集用来调节网络超参数,测试集用来测试网络性能,须要注意的是咱们不该该使用模型读取任何测试集相关的信息而后依此来调节模型
若是可用的数据相对较少,而你又须要尽量精确地评估模型,那么能够选择带有打乱数据的重复 K 折验证(iterated K-fold validation with shuffling)
具体作法:屡次使用 K 折验证,在每次将数据划分为 K 个分区以前都先将数据打乱。最终分数是每次 K 折验证分数的平均值,这种方法一共要训练和评估 P×K 个模型,计算代价很大
选择模型评估方法时,须要注意如下几点:
数据表明性
训练的数据要可以表明总体,这时应该将数据打乱
时间箭头
当数据包含数据信息时,应该始终确保测试集中全部数据的时间都晚于训练集数据
数据冗余
当存在数据冗余时,打乱数据可能会形成训练集和验证集出现重复的数据,而咱们要确保训练集和验证集之间没有交集
将数据输入神经网络以前,通常咱们都须要进行数据预处理,以使其与咱们模型须要输入类型相匹配,包括
向量化
神经网络的全部输入和目标都必须是浮点数张量
值标准化
输入数据应该具备如下特征
取值较小: 大部分值都应该在 0~1 范围内
同质性(homogenous): 全部特征的取值都应该在大体相同的范围内
一种更严格的标准化为将: 每一个特征分别标准化,使其均值为 0,标准差为 1
处理缺失值
通常来讲,对于神经网络,将缺失值设置为 0 是安全的,只要 0 不是一个有意义的值。网络可以从数据中学到 0 意味着缺失数据,而且会忽略这个值
可是当网络在没有缺失值的状况下训练,这时候网络就不能学会忽略缺失值,这时咱们须要人为生成一些有缺失项的训练样本
特征工程(feature engineering)是指将数据输入模型以前,利用你本身关于数据和机器学习算法(这里指神经网络)的知识对数据进行硬编码的变换(不是模型学到的),以改善模型的效果
良好的特征可让你用更少的数据、更少的资源、更优雅地解决问题
优化(optimization)是指调节模型以在训练数据上获得最佳性能(即机器学习中的学习),而泛化(generalization)是指训练好的模型在前所未见的数据上的性能好坏。机器学习的目的固然是获得良好的泛化
训练开始时,优化和泛化是相关的: 训练数据上的损失越小,测试数据上的损失也越小。这时的模型是欠拟合(underfit)的,即仍有改进的空间,网络尚未对训练数据中全部相关模式建模;但在训练数据上迭代必定次数以后,泛化再也不提升,验证指标先是不变,而后开始变差,即模型开始过拟合。这时模型开始学习仅和训练数据有关的模式,但这种模式对新数据来讲是错误的或可有可无的
防止过拟合的方法:
减少网络大小
防止过拟合的最简单的方法就是减少模型大小,即减小模型中可学习参数的个数
要找到合适的模型大小,通常的工做流程是开始时选择相对较少的层和参数,而后逐渐增长层的大小或增长新层,直到这种增长对验证损失的影响变得很小
添加权重正则化
理论:简单模型比复杂模型更不容易过拟合
此处简单模型指参数值分布的熵更小的模型或参数更少的模型
方法:强制让模型权重只能取较小的值,从而限制模型的复杂度
如 Lp正则化
L1 正则化(L1 regularization):添加的成本与权重系数的绝对值成正比
L2 正则化(L2 regularization):添加的成本与权重系数的平方成正比
添加 dropout 正则化
训练过程当中随机将该层的一些输出特征舍弃
Keras添加正则化的方式
model.add(layers.Dense(16, kernel_regularizer=regularizers.l2(0.001), activation='relu', input_shape=(10000,)))
其中0.001为正则化损失系数
因为这个惩罚项只在训练时添加,因此这个网络的训练损失会比测试损失大不少
若是使用dropout正则化的话,dropout 比率(dropout rate)是被设为 0 的特征所占的比例,一般在 0.2~0.5范围内。测试时没有单元被舍弃,而该层的输出值须要按 dropout 比率缩小,由于这时比训练时有更多的单元被激活,须要加以平衡
在 Keras 中,你能够经过 Dropout 层向网络中引入 dropout,dropout 将被应用于前面一层的输出
model.add(layers.Dropout(0.5))
经常使用的由问题类型选择的最后一层激活和损失函数
问题类型 | 最后一层激活 | 损失函数 |
---|---|---|
二分类问题 | sigmoid | binary_crossentropy |
多分类、单标签问题 | softmax | categorical_crossentropy |
多分类、多标签问题 | sigmoid | binary_crossentropy |
回归到任意值 | 无 | mse |
回归到 0~1 范围内的值 | sigmoid | mse 或 binary_crossentropy |
在模型确认以后,咱们须要优化咱们的网络以达到最佳的性能,此时能够尝试如下几项: