《Deep Learning with Python》第三章 3.5 走進(jìn)神經(jīng)網(wǎng)絡(luò)之新聞多分類

3.5 新聞分類:多分類

在上一小節(jié),學(xué)習(xí)了如何使用全聯(lián)接神經(jīng)網(wǎng)絡(luò)將向量輸入分為二類。但是,當(dāng)需要多分類時(shí)該咋辦呢?

在本小節(jié),你將學(xué)習(xí)構(gòu)建神經(jīng)網(wǎng)絡(luò),把路透社新聞分為互不相交的46類主題。很明顯,這個(gè)問題是多分類問題,并且每個(gè)數(shù)據(jù)點(diǎn)都只歸為一類,那么該問題屬于單標(biāo)簽、多分類;如果每個(gè)數(shù)據(jù)點(diǎn)可以屬于多個(gè)分類,那么你面對(duì)的將是多標(biāo)簽、多分類問題。

3.5.1 路透社新聞數(shù)據(jù)集

路透社新聞數(shù)據(jù)集是由路透社1986年發(fā)布的短新聞和對(duì)應(yīng)主題的集合,它常被用作文本分類的練手?jǐn)?shù)據(jù)集。該數(shù)據(jù)集有46個(gè)不同的新聞主題,在訓(xùn)練集中每個(gè)主題包含至少10個(gè)新聞。

和IMDB和MNIST數(shù)據(jù)集一樣,路透社新聞數(shù)據(jù)集也打包作為Keras的一部分,下面簡(jiǎn)單看下:

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

設(shè)置參數(shù)num_words=10000,保留訓(xùn)練集中詞頻為top 10000的單詞。

你有8982條訓(xùn)練樣本數(shù)據(jù)和2246條測(cè)試樣本數(shù)據(jù):

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

從上述返回的結(jié)果看,每個(gè)樣本都是整數(shù)列表(詞索引):

>>> 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的整數(shù)(主題索引):

>>> train_labels[10]
3
3.5.2 準(zhǔn)備數(shù)據(jù)

使用和上一小節(jié)同樣的代碼進(jìn)行數(shù)據(jù)向量化。

#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列表轉(zhuǎn)成整數(shù)張量,另一種是,使用one-hot編碼。one-hot編碼廣泛適用于分類數(shù)據(jù),也稱為分類編碼。它的更詳細(xì)介紹在6.1小節(jié)。在本例中,label的one-hot編碼是將每個(gè)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中有內(nèi)建的函數(shù)實(shí)現(xiàn),這在MNIST的例子中已經(jīng)使用過。

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 構(gòu)建神經(jīng)網(wǎng)絡(luò)

這個(gè)主題分類問題和前一個(gè)影評(píng)分類類似:兩類問題都是將短文本分類。但是這里有個(gè)新的限制:輸出分類的數(shù)量由過去的2個(gè)變?yōu)?6個(gè)。所以輸出空間的維度更大。

使用一系列的Dense layer時(shí),每個(gè)layer只能訪問上一個(gè)layer的輸出信息。如果某一個(gè)layer丟失一些與分類相關(guān)的信息時(shí),接下來的layer不可能再恢復(fù)這些信息,所以每個(gè)layer都可能成為潛在的信息瓶頸。在前面的例子中,選用的16維中間layer,但是16維空間并不能學(xué)習(xí)到46個(gè)不同的分類。

考慮到上面的情況,這里使用更大的layer,隱藏單元設(shè)為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'))

上述代碼中的神經(jīng)網(wǎng)絡(luò)架構(gòu)需要注意兩個(gè)事情:

  • 最后一個(gè)Dense layer大小為46。這意味著每個(gè)輸入樣本,神經(jīng)網(wǎng)絡(luò)模型輸出一個(gè)46維向量。其中每個(gè)項(xiàng)代表不同的分類;
  • 最后一個(gè)layer使用softmax激活函數(shù)。這意味著神經(jīng)網(wǎng)絡(luò)模型輸出一個(gè)46維的概率分布。對(duì)于每個(gè)輸入樣本,模型將輸出一個(gè)46維的輸出向量,每個(gè)output[i]是樣本屬于類別 i 的概率,且46個(gè)分?jǐn)?shù)之和為1。

對(duì)于本例最適合的損失函數(shù)是categorical_crossentropy。該函數(shù)度量?jī)蓚€(gè)概率分布的距離,意即,模型輸出的概率分布與label真實(shí)分布之間的距離。為了最小化兩個(gè)分布的距離,訓(xùn)練模型使得其輸出更接近真實(shí)label。

#Listing 3.16 Compiling the model
model.compile(optimizer='rmsprop',
              loss='categorical_crossentropy',
              metrics=['accuracy'])
3.5.4 驗(yàn)證模型

下面從訓(xùn)練數(shù)據(jù)中分出1000個(gè)樣本作為驗(yàn)證集。

#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:]

接著訓(xùn)練神經(jīng)網(wǎng)絡(luò)模型20個(gè)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))

最后,顯示損失函數(shù)和準(zhǔn)確度的曲線,見圖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 訓(xùn)練集和驗(yàn)證集的損失曲線

image

圖3.10 訓(xùn)練集和驗(yàn)證集的準(zhǔn)確度曲線

從上面的圖可以看出,神經(jīng)網(wǎng)絡(luò)模型訓(xùn)練在第9個(gè)epoch開始過擬合。接著從頭開始訓(xùn)練9個(gè)epoch,然后在測(cè)試集賞進(jìn)行評(píng)估。

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

下面是最終訓(xùn)練結(jié)果:

>>> results
[0.9565213431445807, 0.79697239536954589]

上面的方法達(dá)到約80%的準(zhǔn)確度。在二分類問題中,純隨機(jī)分類的準(zhǔn)確度是50%。而在本例中,純隨機(jī)分類的準(zhǔn)確度將近19%,所以本例的模型結(jié)果還是不錯(cuò)的,至少超過隨機(jī)基準(zhǔn)線:

>>> 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 模型預(yù)測(cè)

你可以用模型實(shí)例的predict方法驗(yàn)證返回的46個(gè)主題分類的概率分布。下面對(duì)所有的測(cè)試集生成主題預(yù)測(cè)。

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

predictions的每項(xiàng)是一個(gè)長(zhǎng)度為64的向量:

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

這些向量的系數(shù)之和為1:

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

下面從預(yù)測(cè)分類中找出概率最大的項(xiàng):

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

前面提到過label編碼的兩外一種方法,將其轉(zhuǎn)化為整數(shù)張量,比如:

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

上述處理label的方法唯一需要改變的是損失函數(shù)。在listing 3.21代碼中使用的損失函數(shù),categorical_crossentropy,期望label是一個(gè)分類編碼。對(duì)于整數(shù)label,你應(yīng)該選用sparse_categorical_crossentropy損失函數(shù):

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

上面這個(gè)新的損失函數(shù)在數(shù)學(xué)上是和categorical_crossentropy相同的,不同之處在于接口不同。

3.5.7 中間layer的重要性

前面提過,因?yàn)樽詈蟮妮敵鍪?6維,你應(yīng)該避免中間layer小于46個(gè)hidden unit。下面為你展示中間layer小于46維導(dǎo)致的信息瓶頸問題,以4個(gè)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))

現(xiàn)在新的模型達(dá)到最大約71%的驗(yàn)證準(zhǔn)確度,丟失了8%。這種情況主要是壓縮許多信息到一個(gè)低維度的空間,導(dǎo)致沒有足夠多的信息可以恢復(fù)。

3.5.8 延伸實(shí)驗(yàn)
  • 嘗試使用更大或者更小的layer:32個(gè)unit、128個(gè)unit等等;
  • 本例使用兩個(gè)隱藏層??梢試L試一個(gè)或者三個(gè)隱藏層。
3.5.9 總結(jié)

從本例應(yīng)該學(xué)習(xí)到的知識(shí)點(diǎn):

  • 如果你想將數(shù)據(jù)分為N類,那神經(jīng)網(wǎng)絡(luò)模型最后一個(gè)Dense layer大小為N;
  • 在單標(biāo)簽、多分類的問題中,模型輸出應(yīng)該用softmax激活函數(shù),輸出N個(gè)分類的概率分布;
  • 分類交叉熵是分類問題合適的損失函數(shù)。它最小化模型輸出的概率分布和真實(shí)label的概率分布之間的距離;
  • 處理多分類中l(wèi)abel的兩種方法:
    • 通過one-hot編碼編碼label,并使用categorical_crossentropy作為損失函數(shù);
    • 通過整數(shù)張量編碼label,并使用sparse_categorical_crossentropy損失函數(shù)
  • 對(duì)于數(shù)據(jù)分類的類別較多的情況,應(yīng)該避免創(chuàng)建較小的中間layer,導(dǎo)致信息瓶頸。

未完待續(xù)。。。

Enjoy!

翻譯本書系列的初衷是,覺得其中把深度學(xué)習(xí)講解的通俗易懂。不光有實(shí)例,也包含作者多年實(shí)踐對(duì)深度學(xué)習(xí)概念、原理的深度理解。最后說不重要的一點(diǎn),F(xiàn)ran?ois Chollet是Keras作者。
聲明本資料僅供個(gè)人學(xué)習(xí)交流、研究,禁止用于其他目的。如果喜歡,請(qǐng)購買英文原版。


俠天,專注于大數(shù)據(jù)、機(jī)器學(xué)習(xí)和數(shù)學(xué)相關(guān)的內(nèi)容,并有個(gè)人公眾號(hào)分享相關(guān)技術(shù)文章。

若發(fā)現(xiàn)以上文章有任何不妥,請(qǐng)聯(lián)系我。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容