影评分类:二分类问题示例
加载 imdb 数据集
ini
from tensorflow.keras.datasets import imdb
if __name__ == '__main__':
# data 其实就是 每条评论中 出现的单词,只不过这里不是实际的单词 而是 这个单词在字典中索引的位置
# labels 就是代表这条评论是 负面的还是正面的 0代表负面 1代表正面
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=1000)
print(train_data[0])
print(train_labels[0])
# 这段代码其实就是把data 给解码成实际的 单词了,
word_index = imdb.get_word_index()
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])
decoder = " ".join([reverse_word_index.get(i - 3, "") for i in train_data[0]])
print("decoder: ", decoder)
可以看下运行效果
准备数据
这里会碰到一个问题,因为每个评论的 单词数量都不一样,但是神经网络需要处理的是 大小相同的数据批量 ,你需要将 列表转换为 大小一致的张量
简称 数据向量化
我们可以定义一个10000 维的向量,假设一个评论的序列是 [8,5] 那么这个10000维的向量 8和5的位置 就是1,其他位置默认都是0
我们可以修改一下上面的代码
python
from tensorflow.keras.datasets import imdb
import numpy as np
def vectorize_sequences(sequences, dimension=10000):
results = np.zeros((len(sequences), dimension))
print(f"vectorize_sequences results.shape:{results.shape}")
for i, sequence in enumerate(sequences):
for j in sequence:
results[i, j] = 1.
return results
if __name__ == '__main__':
# data 其实就是 每条评论中 出现的单词,只不过这里不是实际的单词 而是 这个单词在字典中索引的位置
# labels 就是代表这条评论是 负面的还是正面的 0代表负面 1代表正面
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(num_words=1000)
# 从结果来看 都是25000条数据
print(f"train_data.shape:{train_data.shape}")
print(f"test_data.shape:{test_data.shape}")
print(f"train_data[0]:{train_data[0]}")
print(f"train_labels[0]:{train_labels[0]}")
print(f"train_labels.shape:{train_labels.shape}")
print(f"test_labels.shape{test_labels.shape}")
print(f"test_labels{test_labels}")
print(f"test_labels.type{type(test_labels)}")
# 这段代码其实就是把data 给解码成实际的 单词了,
word_index = imdb.get_word_index()
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])
decoder = " ".join([reverse_word_index.get(i - 3, "") for i in train_data[0]])
print("decoder: ", decoder)
# 向量化
x_train = vectorize_sequences(train_data)
x_test = vectorize_sequences(test_data)
# 标签不要忘记向量化
# 这里有人会觉得奇怪 labels无非就是int,这里为啥要转成float类型呢?
# 其实是因为深度学习中 我们的权重往往都是浮点数,如果你的数据不是浮点数的化 容易造成精度损失
# 所以我们切记一定要
y_train = np.array(train_labels).astype('float32')
print(f"y_train{y_train}")
print(f"y_train.type{type(y_train)}")
y_test = np.array(test_labels).astype('float32')
这次我们打印了更多的信息 方便你体会一下 数据处理的流程
模型的构建
python
# 开始构建模型
# 输入数据是向量, 标签是标量(1,0) 这是最简单的一类问题
# 大家只要记得这种问题 就用relu激活函数 的dense 简单堆叠起来就可以了
# 别问为什么,反正我们不会高等数学, 就记住类似的问题 这么套用就可以
model = keras.Sequential([
layers.Dense(16, activation="relu"),
layers.Dense(16, activation="relu"),
layers.Dense(1, activation="sigmoid")
])
# 二分类的问题 模型输出是一个概率值,也是损失函数 最佳就用binary_crossentropy
# 优化器 一般默认就用rmsprop
# 训练过程中要监控精度
model.compile(loss='binary_crossentropy', optimizer='rmsprop', metrics=['accuracy'])
留出验证集
python
# 留出验证集,我们要在训练数据中 留一部分数据专门用来测试精准度,而不能把全部训练数据用来训练然后评估
# 否则不准。
x_val = x_train[:10000]
partial_x_train = x_train[10000:]
y_val = y_train[:10000]
partial_y_train = y_train[10000:]
训练模型
python
# 开始训练模型 训练20轮,一次512条数据
history = model.fit(partial_x_train, partial_y_train, epochs=20, batch_size=512, validation_data=(x_val, y_val))
# fit方法返回的是History对象,包含训练过程的全部数据
history_dict = history.history
print(f"history.type{type(history)} history_dict.type{type(history_dict)}")
# dict_keys(['loss', 'accuracy', 'val_loss', 'val_accuracy']) 分别对应着训练和验证过程中监控的指标
print(history_dict.keys())
绘制 loss 的曲线变化
我们可以看下训练集 和 验证集 的loss 曲线变化
ini
# 绘制训练损失 和 验证集的损失
loss = history_dict['loss']
val_loss = history_dict['val_loss']
epochs = range(1, len(loss) + 1)
# bo表示蓝色原点
plt.plot(epochs, loss, "bo", label="Training loss")
# b表示蓝色实线
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()
绘制精度变化
python
# 绘制精度变化
plt.clf()
acc= history.history['accuracy']
val_acc = history.history['val_accuracy']
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()
我们仔细看下 acc和loss的这2个图,你会发现一个趋势,就是 对于训练数据来说,总是越到后面效果最好 比如acc这个图,你会发现他的高点几乎就是最后几个点了
但是你看验证数据,最后的批次 往往不是效果最好的批次
这就是为什么 我们要把训练数据 截取一部分用来做验证数据的原因。
简单来说:
模型在训练数据上的表现越来越好,但在前所未见的数据不一定表现的越来越好,这种现象叫做过拟合
用模型来预测
既然前面我们发现了过拟合的现象,我们就不用训练那么多批次了,假设我们是训练到第五次,验证数据的效果就达到最好了
python
history = model.fit(partial_x_train, partial_y_train, epochs=5, batch_size=512, validation_data=(x_val, y_val))
# 检验一下效果
results = model.evaluate(x_test, y_test)
array = model.predict(x_test)
print(f"Test loss: {results}")
print(f"array:{array}")
可以看下效果:
总结
- 单词序列可以被编码为二进制向量
- 对于2个输出类别的2分类问题,模型的最后一层 应该是 只有1个单元的dense层,并使用sigmoid激活函数,模型输出应该是一个0到1的标量,表示概率值
- 二分类 要用binary_crossentropy 损失函数
- 优化器就用rmsprop
- 一定要监控模型在训练集之外数据上的性能,防止过拟合
新闻分类:多分类问题
有一堆新闻,每个新闻都对应着 46个主题中的1个, 这些主题之前是互斥的 属于典型的单标签,多分类问题
python
from matplotlib import pyplot as plt
from tensorflow.keras.datasets import reuters
import numpy as np
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.utils import to_categorical
def vectorize_sequences(sequences, dimension=10000):
results = np.zeros((len(sequences), dimension))
print(f"vectorize_sequences results.shape:{results.shape}")
for i, sequence in enumerate(sequences):
for j in sequence:
results[i, j] = 1.
return results
if __name__ == '__main__':
(train_data, train_labels), (test_data, test_labels) = reuters.load_data(num_words=1000)
print(f"train data shape: {train_data.shape}")
print(f"test data shape: {test_data.shape}")
print(f"train_labels shape: {train_labels.shape}")
print(f"test_labels shape: {test_labels.shape}")
# test_labels: [ 3 10 1 ... 3 3 24] type: <class 'numpy.ndarray'> 46 46种不同的标签
print(f"test_labels: {test_labels} type: {type(test_labels)} {len(np.unique(test_labels))}")
# 将数据向量化
x_train = vectorize_sequences(train_data)
x_test = vectorize_sequences(test_data)
print(f"x_train shape: {x_train.shape}")
print(f"x_test shape: {x_test.shape}")
# one hot 编码
# 将每个标签表示为全0向量,只有标签索引对应的元素为1
y_train = to_categorical(train_labels)
y_test = to_categorical(test_labels)
# 因为前面验证过 只有46种不同的标签,所以这里的shapre 才是(8892,46)
print(f"y_train shape: {y_train.shape}")
print(f"y_test shape: {y_test.shape}")
print(y_train)
可以看下输出,对我们理解这个业务数据的向量化过程很有帮助
模型生成到训练
python
# 模型定义
# 模型的最后一层用的激活函数是softmax,对于每个样本输入,都会输出一个46维的向量
# 其中output[i] 是样本属于 第i个类别的概率, 加起来为1
model = keras.Sequential([
layers.Dense(64, activation="relu"),
layers.Dense(64, activation="relu"),
layers.Dense(46, activation="softmax")
])
# 损失函数categorical_crossentropy 衡量的是2个概率分布之间的距离,分别是模型输出的概率分布和标签的真实分布
model.compile(optimizer="rmsprop", loss="categorical_crossentropy", metrics=["accuracy"])
# 留出验证集
x_val = x_train[:1000]
partial_x_train = x_train[1000:]
y_val = y_train[:1000]
partial_y_train = y_train[1000:]
# 开始训练
history = model.fit(partial_x_train, partial_y_train, epochs=20, batch_size=512, validation_data=(x_val, y_val))
绘制 loss和 acc
python
history_dict = history.history
loss = history_dict['loss']
val_loss = history_dict['val_loss']
epochs = range(1, len(loss) + 1)
# bo表示蓝色原点
plt.plot(epochs, loss, "bo", label="Training loss")
# b表示蓝色实线
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()
ini
plt.clf()
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']
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()
差不多就是在第9轮 出现过拟合的现象
从头开始训练一个模型
ini
# 开始训练 这次就训练9轮
history = model.fit(partial_x_train, partial_y_train, epochs=9, batch_size=512, validation_data=(x_val, y_val))
results = model.evaluate(x_test, y_test)
print(f"results: {results}")
看上去有75%的准确度 还是不错的
预测数据
python
pred= model.predict(x_test)
print(f"pred[0] sum: {np.sum(pred[0])} pred.shape: {pred[0].shape}")
print(f"pred {pred[0]}")
# 向量预测最大的类别,概率最高的类别
print(f"max {np.argmax(pred[0])}")
print(f"{test_labels[0]}")
总结
- 对n个类别的数据点进行分类,模型的最后一层应该是大小为n的 dense层
- 单标签,多分类问题,最后一层应该用softmax 激活函数
- 处理多分类问题的标签 一般都是 ont-hot编码,搭配 categorical_crossentropy损失函数 如果标签编码为整数,那么要用sparse_categorical_crossentropy 损失函数
波士顿房价预测
这个就是给你一堆数据, 每个样本 有13个参数,分别是 犯罪率啊,收入啊 之类的,然后labels是对应的房价
其实挺好理解的
准备数据
python
from matplotlib import pyplot as plt
from tensorflow.keras.datasets import boston_housing
import numpy as np
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.utils import to_categorical
if __name__ == '__main__':
# 404个训练样本 102个测试样本
# 梅个养呗
(train_data, train_labels), (test_data, test_labels) =(boston_housing.load_data())
print(f"Train data shape: {train_data.shape}")
print(f"Test data shape: {test_data.shape}")
print(f"train_labels shape: {train_labels.shape}")
print(f"test_labels shape: {test_labels.shape}")
print(f"test_data {test_data}")
数据标准化
前面我们看到这13个参数中,数值范围波动是很大的,有的值 0到100 有的0到1 碰到这种情况 我们要对数据进行处理
ini
# 数据标准化 目的是均值为0,标准差为1
# axis =0 对每列进行计算
# 每个元素的值 是该列上的均值
mean = train_data.mean(axis=0)
print(f"mean: {mean}")
train_data -= mean
# 计算标准差,使得所有特征的重要性都相同
std = train_data.std(axis=0)
train_data /= std
test_data -= mean
test_data /= std
构建模型
ini
# 构建模型
# 注意最后一层只有1个单元,而且没有配置激活函数
# 因为添加了激活函数 会限制 模型预测的值的大小,比如你用了sigmoid 那么只能预测0~1的值了
# 最后一层是纯线性的 所以模型可以学会预测任意范围的值
model = keras.Sequential([
layers.Dense(64, activation="relu"),
layers.Dense(64, activation="relu"),
layers.Dense(1)
])
# 损失函数用的是mse,均方误差, 预测值与目标值之差的平方,回归问题常用的损失函数
# 监控指标是mae 预测值与目标值之差的绝对值 如果mae=0.5 则表示房价预测与实际价格差了500
model.compile(optimizer="rmsprop", loss="mse", metrics=["mae"])
训练模型
python
# k 折交叉验证
# 我们的训练样本太少了,如果在这个里面取一部分 作为验证样本 那么精度是不准的,上下浮动的可能性太大
# 对于这种样本少的情况,我们可以采用k折交叉验证
# 简单来说就是将 训练的数据 分成 k 组, 假设k=4 那就是4组数据
# 我们分别让第一组数据 为验证组,234 是训练组,
# 第二组 为验证组,134 是训练组。。。
# 以此类推,
# 这样模型的验证分数 就等于这k个验证分数的平均值
k = 4
# 整除运算符
num_val_samples = len(train_data) // k
print(f"num_val_samples: {num_val_samples}")
num_epochs = 100
all_scores = []
for i in range(k):
print(f"processing fold #{i} ")
# 先准备好 第k个分区的数据 这一组数据 用来验证效果
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]
# 准备好训练样本
# concatenate 把数组拼接在一起 形成新的训练集
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 = build_model()
model.fit(partial_train_data, partial_train_targets, epochs=num_epochs, batch_size=16, verbose=0)
#在验证数据上评估模型
val_mse, val_mae = model.evaluate(val_data, val_targets, verbose=0)
all_scores.append(val_mae)
print(f"all scores: {all_scores}")
print(f"all scores: {np.mean(all_scores)}")
可以看下效果
可以看到每次的波动变化还是有点大的,所以我们一般用mean函数 来取一个平均值
初次之外我们还可以 保存每折的验证分数
ini
# k 折交叉验证
# 我们的训练样本太少了,如果在这个里面取一部分 作为验证样本 那么精度是不准的,上下浮动的可能性太大
# 对于这种样本少的情况,我们可以采用k折交叉验证
# 简单来说就是将 训练的数据 分成 k 组, 假设k=4 那就是4组数据
# 我们分别让第一组数据 为验证组,234 是训练组,
# 第二组 为验证组,134 是训练组。。。
# 以此类推,
# 这样模型的验证分数 就等于这k个验证分数的平均值
k = 4
# 整除运算符
num_val_samples = len(train_data) // k
print(f"num_val_samples: {num_val_samples}")
num_epochs = 500
all_scores = []
all_mae_history = []
for i in range(k):
print(f"processing fold #{i} ")
# 先准备好 第k个分区的数据 这一组数据 用来验证效果
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]
# 准备好训练样本
# concatenate 把数组拼接在一起 形成新的训练集
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 = build_model()
history = model.fit(partial_train_data, partial_train_targets, epochs=num_epochs, batch_size=16, verbose=0)
print(f"history: {history.history.keys()}")
mae_history = history.history["mae"]
all_mae_history.append(mae_history)
# 计算每轮mae的平均值
avarage_mae_history = [
np.mean([x[i] for x in all_mae_history]) for i in range(num_epochs)
]
plt.plot(range(1, len(avarage_mae_history) + 1),avarage_mae_history)
plt.xlabel("Epochs")
plt.ylabel("validation mae")
plt.show()
可以看一下这条曲线, 可以看看在过拟合的状态
不想优化的话其实这里就可以直接使用模型了
python
predictions = model.predict(test_data)
print(f"predictions: {predictions[0]}")
总结
- 回归问题的损失函数和分类问题不同,回归的损失函数是mse
- 回归问题的回归指标一般是mae
- 输入数据的取值范围不一样,那就需要预处理 对每个特性进行缩放
- 如果可用的数据少,那么要用k折交叉验证,并且中间层要小 避免过拟合