机器学习和深度学习的通用工作流程

1 定义问题,收集数据集

首先定义所面对的问题:

  • 输入输出:输入数据是什么?需要预测什么?

  • 问题类型:面对的是什么类型的问题?比如二分类、多分类、标量回归、向量回归、等等,确定问题类型有助于选择模型架构、损失函数等

注意这一阶段的工作基于基本假设:

  • 假设输出是可以根据输入进行预测的

  • 假设可用数据包含足够多的信息,足以学习输入和输出之间的关系

机器学习只能用来记忆训练数据中存在的模式,即只能识别出曾经见过的东西,这里存在的假设就是未来的规律与过去相同,但事实往往并非如此

2 选择衡量成功的指标

比如精度、准确率、召回率等等,衡量成功的指标将指引你选择损失函数,即模型要优化什么

3 确定评估方法

即如何衡量当前的进展,也就是验证集的确定方式,通常有三种方法:

  • 直接留出验证集:数据量很大时可以采用这种方法
1
2
3
4
5
6
7
8
9
# 留出验证集
num_validation_samples = 10000

# 打乱数据
np.random.shuffle(data)

# 划分数据集
validation_data = data[:num_validation_samples]
train_data = data[num_validation_samples:]
  • K折交叉验证:如果验证集太小,无法保证可靠性,可以选择这种方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# K折交叉验证
k = 4
num_validation_samples = len(data) // k

# 打乱数据
np.random.shuffle(data)

validation_scores = []

for fold in range(k):
# 划分数据集
validation_data = data[num_validation_samples * fold:num_validation_samples * (fold + 1)]
train_data = data[:num_validation_samples * fold] + data[num_validation_samples * (fold + 1):]

model = get_model()
model.train(train_data)
validation_score = model.evaluate(validation_data)
validation_scores.append(validation_score)

validation_score = np.average(validation_scores)
  • 重复的K折验证:如果可用的数据很少,为了保证评估的准确性,应该采用此法(具体做法:多次使用K折验证,每次划分数据之前都先将数据打乱,最终分数是每次K折验证分数的平均值)

注意:

  • 将数据划分为训练集和测试集之前,通常应该随机打乱数据,以保证数据的代表性

  • 如果是要根据过去预测未来,即数据带有时间属性,那么在划分数据前不应该随机打乱数据,这种情况下应确保测试集中所有数据的时间都晚于训练集

  • 确保训练集和验证集之间没有交集,避免出现数据冗余,即避免在部分训练数据上评估模型

4 准备数据

  • 向量化:如果模型是深度神经网络,需要先将数据格式化为张量,神经网络的所有输入和目标都必须是浮点数张量(特定情况下可以是整数张量),张量的取值通常应该缩放为较小的值,比如[-1,1]或者[0,1]区间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# one-hot编码实现数据向量化,将整数序列变换为二进制矩阵
def vectorize_sequences(sequences, dimension=10000):

# 创建一个形状为(len(sequences), dimension)的零矩阵
results = np.zeros((len(sequences), dimension))

# 将results[i]的指定索引的数字设为1
for i, sequence in enumerate(sequences):
results[i, sequence] = 1

return results

# 数据向量化,转换成0和1组成的向量
x_train = vectorize_sequences(train_data)
x_test = vectorize_sequences(test_data)

# one-hot编码将标签向量化
def to_one_hot(labels, dimension=46):
results = np.zeros((len(labels), dimension))
for i, label in enumerate(labels):
results[i, label] = 1.
return results

one_hot_train_labels = to_one_hot(train_labels)
one_hot_test_labels = to_one_hot(test_labels)

# Keras内置方法可以实现这个操作
from keras.utils.np_utils import to_categorical

one_hot_train_labels = to_categorical(train_labels)
one_hot_test_labels = to_categorical(test_labels)
  • 标准化:如果不同的特征具有不同的取值范围(异质数据),应该对每个特征做数据标准化,即减去特征平均值,再除以标准差,这样得到的特征平均值为0,标准差为1(注意,用于测试集标准化的均值和标准差都是在训练集上计算得到的,不能使用测试集的数据计算得到任何结果)
1
2
3
4
5
6
7
8
9
# 数据标准化
# axis=0表示纵轴方向,axis=1表示横轴方向,不填表示全部元素
mean = train_data.mean(axis=0)
train_data -= mean
std = train_data.std(axis=0)
train_data /= std

test_data -= mean
test_data /= std
1
2
3
4
5
6
7
8
train_stats = train_dataset.describe()

# 定义一个函数来专门实现标准化,这样比较方便
def norm(x):
return (x - train_stats['mean']) / train_stats['std']

normed_train_data = norm(train_dataset)
normed_test_data = norm(test_dataset) # 将测试集放入与训练集相同的分布中
  • 处理缺失值:一般来说,对于神经网络,将缺失值设置为0是安全的,只要0不是一个有意义的值;注意,如果测试集中有缺失值,训练集中没有,网络无法学会忽略缺失值,这种情况下应该人为生成一些有缺失项的训练数据

  • 特征工程:本质是用更简单的方式表述问题,使问题变得更容易;虽然神经网络能够从原始数据中自动提取有用的特征,但这并不意味着不需要特征工程,因为良好的特征仍然可以让你用更少的资源、更少的数据解决问题,对于小数据集更加重要

5 开发比基准更好的模型

这一阶段的目标是获得统计功效,即开发一个小型模型,它能够打败纯随机的基准

三个关键参数:

  • 最后一层的激活函数:对网络输出进行有效的限制

  • 损失函数:相当于目标函数,在训练过程中需要将其最小化

  • 优化器:决定如何基于损失函数对网络进行更新

问题类型 最后一层激活 损失函数
二分类 sigmoid binary_crossentropy
多分类、单标签 softmax categorical_crossentropy
多分类、多标签 sigmoid binary_crossentropy
回归到任意值 mse
回归到0~1范围内的值 sigmoid mse或binary_crossentropy
1
2
3
4
5
6
7
8
9
10
11
# 以回归问题为例演示建模过程
from tensorflow.keras import models
from tensorflow.keras import layers

def build_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

6 扩大模型规模,开发过拟合的模型

机器学习中无处不在的对立是优化和泛化的对立,理想的模型是刚好在欠拟合和过拟合的界线上,在容量不足和容量过大的界线上,为了找到这条界线,必须穿过它,也就是必须开发一个过拟合的模型,主要方法:

  • 添加更多的层

  • 让每一层变得更大

  • 训练更多的轮次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 训练模型,并在history中记录训练和验证的准确性,compile中的metrics内容将被记录
history = model.fit(partial_x_train,
partial_y_train,
epochs=20,
batch_size=512,
validation_data=(x_val, y_val))

# 将history中的数据可视化
def plot_history(history):
hist = pd.DataFrame(history.history)
hist['epoch'] = history.epoch

plt.figure()
plt.xlabel('Epoch')
plt.ylabel('Mean Abs Error [MPG]')
plt.plot(hist['epoch'], hist['mae'], label='Train Error')
plt.plot(hist['epoch'], hist['val_mae'], label = 'Val Error')
plt.ylim([0,5])
plt.legend()

plt.figure()
plt.xlabel('Epoch')
plt.ylabel('Mean Square Error [$MPG^2$]')
plt.plot(hist['epoch'], hist['mse'], label='Train Error')
plt.plot(hist['epoch'], hist['val_mse'], label = 'Val Error')
plt.ylim([0,20])
plt.legend()
plt.show()

plot_history(history)

# 可视化的另一种操作
history_dict = history.history
loss_values = history_dict['loss']
val_loss_values = history_dict['val_loss']

epochs = range(1, len(loss_values)+1)

plt.plot(epochs, loss_values, 'bo', label='Training loss')
plt.plot(epochs, val_loss_values, 'b', label='Validation loss')
plt.title('Training and Validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()

plt.show()

7 模型正则化与调节超参数

不断地调节模型、训练、在验证集上评估,并重复这一过程,主要尝试手段:

  • 添加dropout:神经网络最有效也最常用的正则化方法之一,核心思想是在层的输出值中引入噪声,打破不显著的偶然模式,如果没有噪声的话,网络会记住这些偶然模式;具体效果是在训练过程中随机将该层的一些输出特征舍弃(置为0)
1
2
# 引入dropout,应用于前面一层的输出
model.add(layers.Dropout(0.5))
  • 尝试不同的架构:增加或减少层数

  • 添加L1/L2正则化

1
2
3
4
5
6
7
# 向模型添加L2权重正则化
from keras import regularizers

model = models.Sequential()
model.add(layers.Dense(16, kernel_regularizer=regularizers.l2(0.001),activation='relu', input_shape=(10000,)))
model.add(layers.Dense(16, kernel_regularizer=regularizers.l2(0.001),activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
  • 尝试不同的超参数

  • 反复做特征工程,添加新特征或删除没有信息量的旧特征