《Deep Learning with Python》第三章 3.5 走进神经网络之新闻多分类

3.5 新闻分类:多分类

在上一小节,学习了如何使用全联接神经网络将向量输入分为二类。但是,当需要多分类时该咋办呢?

在本小节,你将学习构建神经网络,把路透社新闻分为互不相交的46类主题。很明显,这个问题是多分类问题,并且每个数据点都只归为一类,那么该问题属于单标签、多分类;如果每个数据点可以属于多个分类,那么你面对的将是多标签、多分类问题。

3.5.1 路透社新闻数据集

路透社新闻数据集是由路透社1986年发布的短新闻和对应主题的集合,它常被用作文本分类的练手数据集。该数据集有46个不同的新闻主题,在训练集中每个主题包含至少10个新闻。

和IMDB和MNIST数据集一样,路透社新闻数据集也打包作为Keras的一部分,下面简单看下:

#Listing 3.12 Loading the Reuters dataset
from keras.datasets import reuters
(train_data, train_labels), (test_data, test_labels) = reuters.load_data(
    num_words=10000)

设置参数num_words=10000,保留训练集中词频为top 10000的单词。

你有8982条训练样本数据和2246条测试样本数据:

>>> len(train_data)
8982
>>> len(test_data)
2246

从上述返回的结果看,每个样本都是整数列表(词索引):

>>> train_data[10]
[1, 245, 273, 207, 156, 53, 74, 160, 26, 14, 46, 296, 26, 39, 74, 2979,
3554, 14, 46, 4689, 4329, 86, 61, 3499, 4795, 14, 61, 451, 4329, 17, 12]

下面代码可以把词索引解码成词:

#Listing 3.13 Decoding newswires back to text
word_index = reuters.get_word_index()
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])
#Note that the indices are offset by 3 because 0, 1, and 2 are reserved indices for “padding,” “start of sequence,” and “unknown.”
decoded_newswire = ' '.join([reverse_word_index.get(i - 3, '?') for i in train_data[0]])

样本的label是0到45的整数(主题索引):

>>> train_labels[10]
3
3.5.2 准备数据

使用和上一小节同样的代码进行数据向量化。

#Listing 3.14 Encoding the data
import numpy as np

def vectorize_sequences(sequences, dimension=10000):
    results = np.zeros((len(sequences), dimension))
    for i, sequence in enumerate(sequences):
        results[i, sequence] = 1.
    return results

#Vectorized training data
x_train = vectorize_sequences(train_data)
#Vectorized test data
x_test = vectorize_sequences(test_data)

向量化label有两种方法:一种是,将label列表转成整数张量,另一种是,使用one-hot编码。one-hot编码广泛适用于分类数据,也称为分类编码。它的更详细介绍在6.1小节。在本例中,label的one-hot编码是将每个label映射到label索引位置为1的值。

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
#Vectorized training labels
one_hot_train_labels = to_one_hot(train_labels)
#Vectorized test labels
one_hot_test_labels = to_one_hot(test_labels)

上述label向量化的方式在Keras中有内建的函数实现,这在MNIST的例子中已经使用过。

from keras.utils.np_utils import to_categorical
one_hot_train_labels = to_categorical(train_labels) one_hot_test_labels = to_categorical(test_labels)
3.5.3 构建神经网络

这个主题分类问题和前一个影评分类类似:两类问题都是将短文本分类。但是这里有个新的限制:输出分类的数量由过去的2个变为46个。所以输出空间的维度更大。

使用一系列的Dense layer时,每个layer只能访问上一个layer的输出信息。如果某一个layer丢失一些与分类相关的信息时,接下来的layer不可能再恢复这些信息,所以每个layer都可能成为潜在的信息瓶颈。在前面的例子中,选用的16维中间layer,但是16维空间并不能学习到46个不同的分类。

考虑到上面的情况,这里使用更大的layer,隐藏单元设为64。

#Listing 3.15 Model definition
from keras import models
from keras import layers

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'))

上述代码中的神经网络架构需要注意两个事情:

  • 最后一个Dense layer大小为46。这意味着每个输入样本,神经网络模型输出一个46维向量。其中每个项代表不同的分类;
  • 最后一个layer使用softmax激活函数。这意味着神经网络模型输出一个46维的概率分布。对于每个输入样本,模型将输出一个46维的输出向量,每个output[i]是样本属于类别 i 的概率,且46个分数之和为1。

对于本例最适合的损失函数是categorical_crossentropy。该函数度量两个概率分布的距离,意即,模型输出的概率分布与label真实分布之间的距离。为了最小化两个分布的距离,训练模型使得其输出更接近真实label。

#Listing 3.16 Compiling the model
model.compile(optimizer='rmsprop',
              loss='categorical_crossentropy',
              metrics=['accuracy'])
3.5.4 验证模型

下面从训练数据中分出1000个样本作为验证集。

#Listing 3.17 Setting aside a validation set
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:]

接着训练神经网络模型20个epoch。

#Listing 3.18 Training the model
history = model.fit(partial_x_train,
                    partial_y_train,
                    epochs=20,
                    batch_size=512,
                    validation_data=(x_val, y_val))

最后,显示损失函数和准确度的曲线,见图3.9和3.10。

#Listing 3.19 Plotting the training and validation loss
import matplotlib.pyplot as pet

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()
#Listing 3.20 Plotting the training and validation accuracy
#Clears the figure
plt.clf()

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('Loss')
plt.legend()

plt.show()
image

图3.9 训练集和验证集的损失曲线

image

图3.10 训练集和验证集的准确度曲线

从上面的图可以看出,神经网络模型训练在第9个epoch开始过拟合。接着从头开始训练9个epoch,然后在测试集赏进行评估。

#Listing 3.21 Retraining a model from scratch
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'])
model.fit(partial_x_train,
          partial_y_train,
          epochs=9,
          batch_size=512,
          validation_data=(x_val, y_val))
results = model.evaluate(x_test, one_hot_test_labels)

下面是最终训练结果:

>>> results
[0.9565213431445807, 0.79697239536954589]

上面的方法达到约80%的准确度。在二分类问题中,纯随机分类的准确度是50%。而在本例中,纯随机分类的准确度将近19%,所以本例的模型结果还是不错的,至少超过随机基准线:

>>> import copy
>>> test_labels_copy = copy.copy(test_labels)
>>> np.random.shuffle(test_labels_copy)
>>> hits_array = np.array(test_labels) == np.array(test_labels_copy)
>>> float(np.sum(hits_array)) / len(test_labels)
0.18655387355298308
3.5.5 模型预测

你可以用模型实例的predict方法验证返回的46个主题分类的概率分布。下面对所有的测试集生成主题预测。

#Listing 3.22 Generating predictions for new data
predictions = model.predict(x_test)

predictions的每项是一个长度为64的向量:

>>> predictions[0].shape
(46,)

这些向量的系数之和为1:

>>> np.sum(predictions[0])
1.0

下面从预测分类中找出概率最大的项:

>>> np.argmax(predictions[0])
4
3.5.6 处理label和loss的不同方法

前面提到过label编码的两外一种方法,将其转化为整数张量,比如:

y_train = np.array(train_labels)
y_test = np.array(test_labels)

上述处理label的方法唯一需要改变的是损失函数。在listing 3.21代码中使用的损失函数,categorical_crossentropy,期望label是一个分类编码。对于整数label,你应该选用sparse_categorical_crossentropy损失函数:

model.compile(optimizer='rmsprop',
              loss='sparse_categorical_crossentropy',
              metrics=['acc'])

上面这个新的损失函数在数学上是和categorical_crossentropy相同的,不同之处在于接口不同。

3.5.7 中间layer的重要性

前面提过,因为最后的输出是46维,你应该避免中间layer小于46个hidden unit。下面为你展示中间layer小于46维导致的信息瓶颈问题,以4个hidden unit为例。

#Listing 3.23 A model with an information bottleneck
model = models.Sequential()
model.add(layers.Dense(64, activation='relu', input_shape=(10000,)))
model.add(layers.Dense(4, activation='relu')) model.add(layers.Dense(46, activation='softmax'))

model.compile(optimizer='rmsprop',
              loss='categorical_crossentropy',
              metrics=['accuracy'])
model.fit(partial_x_train,
          partial_y_train,
          epochs=20,
          batch_size=128,
          validation_data=(x_val, y_val))

现在新的模型达到最大约71%的验证准确度,丢失了8%。这种情况主要是压缩许多信息到一个低维度的空间,导致没有足够多的信息可以恢复。

3.5.8 延伸实验
  • 尝试使用更大或者更小的layer:32个unit、128个unit等等;
  • 本例使用两个隐藏层。可以尝试一个或者三个隐藏层。
3.5.9 总结

从本例应该学习到的知识点:

  • 如果你想将数据分为N类,那神经网络模型最后一个Dense layer大小为N;
  • 在单标签、多分类的问题中,模型输出应该用softmax激活函数,输出N个分类的概率分布;
  • 分类交叉熵是分类问题合适的损失函数。它最小化模型输出的概率分布和真实label的概率分布之间的距离;
  • 处理多分类中label的两种方法:
    • 通过one-hot编码编码label,并使用categorical_crossentropy作为损失函数;
    • 通过整数张量编码label,并使用sparse_categorical_crossentropy损失函数
  • 对于数据分类的类别较多的情况,应该避免创建较小的中间layer,导致信息瓶颈。

未完待续。。。

Enjoy!

翻译本书系列的初衷是,觉得其中把深度学习讲解的通俗易懂。不光有实例,也包含作者多年实践对深度学习概念、原理的深度理解。最后说不重要的一点,François Chollet是Keras作者。
声明本资料仅供个人学习交流、研究,禁止用于其他目的。如果喜欢,请购买英文原版。


侠天,专注于大数据、机器学习和数学相关的内容,并有个人公众号分享相关技术文章。

若发现以上文章有任何不妥,请联系我。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容