摘要: 本文是使用kreas處理文本分析的入門教程(下),介紹文本處理的兩種方法——獨熱編碼和詞嵌入。
在上一節Keras文本分類實戰(上),講述了關於NLP的基本知識。這部分,將學會以不一樣方式將單詞表示爲向量。html
文本也被視爲一種序列化的數據形式,相似於天氣數據或財務數據中的時間序列數據。在以前的BOW模型中,瞭解瞭如何將整個單詞序列表示爲單個特徵向量。下面將看到如何將每一個單詞表示爲向量。這裏有多種方法能夠對文本進行向量化,好比:api
在本教程中,將使用單熱編碼和單詞嵌入將單詞表示爲向量,這是在神經網絡中處理文本的經常使用方法。數組
將單詞表示爲向量的第一種方式是建立獨熱碼,這是經過將詞彙長度的向量與語料庫中的每一個單詞的條目組合一塊兒來完成。
經過這種方式,對於每一個單詞,只要它在詞彙表中存在,就會將該單詞在相應的位置設置爲1,而向量中其它的位置設置爲0。但這種方式可能爲每一個單詞建立至關大的向量,且不會提供任何其餘信息,例如單詞之間的關係。
假設有一個城市列表,以下例所示:服務器
>>> cities = ['London', 'Berlin', 'Berlin', 'New York', 'London'] >>> cities ['London', 'Berlin', 'Berlin', 'New York', 'London']
可使用scikit-learn包中LabelEncoder
對城市列表進行分類:網絡
>>> from sklearn.preprocessing import LabelEncoder >>> encoder = LabelEncoder() >>> city_labels = encoder.fit_transform(cities) >>> city_labels array([1, 0, 0, 2, 1])
以後就可使用scikit-learn包中OneHotEncoder
對城市列表進行編碼:app
>>> from sklearn.preprocessing import OneHotEncoder >>> encoder = OneHotEncoder(sparse=False) >>> city_labels = city_labels.reshape((5, 1)) >>> encoder.fit_transform(city_labels) array([[0., 1., 0.], [1., 0., 0.], [1., 0., 0.], [0., 0., 1.], [0., 1., 0.]])
使用這種表示,能夠看到分類整數值表示數組的位置,1表示出現,0表示不出現。這種編碼經常使用於分類之中,這些類別能夠是例如城市、部門或其餘類別。dom
該方法將字表示爲密集字向量(也稱爲字嵌入),其訓練方式不像獨熱碼那樣,這意味着詞嵌入將更多的信息收集到更少的維度中。
嵌入詞並不像人類那樣理解文本,而是映射語料庫中使用的語言的統計結構,其目標是將語義意義映射到幾何空間,該幾何空間也被稱爲嵌入空間(embedding space)。這個研究領域的一個著名例子是可以映射King - Man + Woman = Queen
。
怎麼能得到這樣的詞嵌入呢?這裏有兩種方法,其中一種是在訓練神經網絡時訓練詞嵌入(word embeddings )層。另外一種方法是使用預訓練好的詞嵌入。
如今,須要將數據標記爲能夠由詞嵌入使用的格式。Keras爲文本預處理和序列預處理提供了幾種便捷方法,咱們可使用這些方法來處理文本。
首先,能夠從使用Tokenizer
類開始,該類能夠將文本語料庫向量化爲整數列表。每一個整數映射到字典中的一個值,該字典對整個語料庫進行編碼,字典中的鍵是詞彙表自己。此外,能夠添加參數num_words
,該參數負責設置詞彙表的大小。num_words
保留最多見的單詞。對前面的例子準備測試和訓練數據:機器學習
>>> from keras.preprocessing.text import Tokenizer >>> tokenizer = Tokenizer(num_words=5000) >>> tokenizer.fit_on_texts(sentences_train) >>> X_train = tokenizer.texts_to_sequences(sentences_train) >>> X_test = tokenizer.texts_to_sequences(sentences_test) >>> vocab_size = len(tokenizer.word_index) + 1 # Adding 1 because of reserved 0 index >>> print(sentences_train[2]) >>> print(X_train[2]) Of all the dishes, the salmon was the best, but all were great. [11, 43, 1, 171, 1, 283, 3, 1, 47, 26, 43, 24, 22]
索引是按文本中最經常使用的單詞排序,這裏索引0是保留的,並不會分配給任何單詞,0索引用於填。
未知單詞(不在詞彙表中的單詞)在Keras中用word_count + 1
表示,由於它們也能夠保存一些信息。函數
>>> for word in ['the', 'all', 'happy', 'sad']: ... print('{}: {}'.format(word, tokenizer.word_index[word])) the: 1 all: 43 happy: 320 sad: 450
注意:密切注意這種技術與scikit-learn的
CountVectorizer
產生的X_train兩者之間的區別。
使用CountVectorizer
,每一個向量的長度相同(總語料庫的大小)。使用Tokenizer
,每一個向量等於每一個文本的長度,其數值並不表示計數,而是對應於字典tokenizer.word_index
中的單詞值。工具
爲了解決每一個文本序列在大多數狀況下具備不一樣長度的單詞的問題,可使用pad_sequence()
簡單地用零填充單詞序列。此外,還須要添加maxlen
參數來指定序列的長度。如下代碼展現如何使用Keras填充序列:
>>> from keras.preprocessing.sequence import pad_sequences >>> maxlen = 100 >>> X_train = pad_sequences(X_train, padding='post', maxlen=maxlen) >>> X_test = pad_sequences(X_test, padding='post', maxlen=maxlen) >>> print(X_train[0, :]) [ 1 10 3 282 739 25 8 208 30 64 459 230 13 1 124 5 231 8 58 5 67 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
第一個值表示從前面的示例中學到的詞彙表中的索引,能夠看到生成的特徵向量主要包含0值元素,這是由於句子比較短。
此時,採用的數據仍然是硬編碼(hardcode),且沒有告訴Keras經過後續任務學習新的嵌入空間。這種狀況下,就可使用Keras 的嵌入層,它採用先前計算的整數並將它們映射到嵌入的密集向量,須要設定如下參數:
使用該嵌入層有兩種方法,一種方法是獲取嵌入層的輸出並將其插入一個全鏈接層(dense layer)。爲此,必須在其中間添加一個flatten layer
:
from keras.models import Sequential from keras import layers embedding_dim = 50 model = Sequential() model.add(layers.Embedding(input_dim=vocab_size, output_dim=embedding_dim, input_length=maxlen)) model.add(layers.Flatten()) model.add(layers.Dense(10, activation='relu')) model.add(layers.Dense(1, activation='sigmoid')) model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy']) model.summary()
結果以下
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= embedding_8 (Embedding) (None, 100, 50) 87350 _________________________________________________________________ flatten_3 (Flatten) (None, 5000) 0 _________________________________________________________________ dense_13 (Dense) (None, 10) 50010 _________________________________________________________________ dense_14 (Dense) (None, 1) 11 ================================================================= Total params: 137,371 Trainable params: 137,371 Non-trainable params: 0 _________________________________________________________________
能夠看到,有87350個新參數須要訓練,嵌入層的這些權重初始化使用隨機權重初始化,並在訓練期間經過反向傳播進行調整,該模型將單詞按照句子的順序做爲輸入向量。可使用如下方法進行訓練:
history = model.fit(X_train, y_train, epochs=20, verbose=False, validation_data=(X_test, y_test), batch_size=10) loss, accuracy = model.evaluate(X_train, y_train, verbose=False) print("Training Accuracy: {:.4f}".format(accuracy)) loss, accuracy = model.evaluate(X_test, y_test, verbose=False) print("Testing Accuracy: {:.4f}".format(accuracy)) plot_history(history)
結果以下
Training Accuracy: 0.5100 Testing Accuracy: 0.4600
從圖中能夠看到,這用來處理順序數據時一般是一種不太可靠的方法。當處理順序數據時,但願關注查看本地和順序信息的方法,而不是絕對的位置信息。
使用嵌入的另外一種方法是在嵌入後使用MaxPooling1D/AveragePooling1D
或GlobalMaxPooling1D/ GlobalAveragePooling1D
層。在最大池化的狀況下,能夠爲每一個要素維度獲取池中全部要素的最大值。在平均池化的狀況下取得平均值。通常在神經網絡中,最大池化更經常使用,且效果要優於平均池化。
使用Keras能夠在順序模型中添加各種池化層:
from keras.models import Sequential from keras import layers embedding_dim = 50 model = Sequential() model.add(layers.Embedding(input_dim=vocab_size, output_dim=embedding_dim, input_length=maxlen)) model.add(layers.GlobalMaxPool1D()) model.add(layers.Dense(10, activation='relu')) model.add(layers.Dense(1, activation='sigmoid')) model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy']) model.summary()
結果以下
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= embedding_9 (Embedding) (None, 100, 50) 87350 _________________________________________________________________ global_max_pooling1d_5 (Glob (None, 50) 0 _________________________________________________________________ dense_15 (Dense) (None, 10) 510 _________________________________________________________________ dense_16 (Dense) (None, 1) 11 ================================================================= Total params: 87,871 Trainable params: 87,871 Non-trainable params: 0 _________________________________________________________________
訓練步驟不變:
history format(accuracy)) loss, accuracy = model.evaluate(X_test, y_test, verbose=False) print("Testing Accuracy: {:.4f}".format(accuracy)) plot_history(history)
結果以下
Training Accuracy: 1.0000 Testing Accuracy: 0.8050
能夠看到,模型有一些改進。接下來,將學習如何使用預訓練的詞嵌入,以及是否對咱們的模型有所幫助。
對於機器學習而言,遷移學習比較火熱。在NLP中,也可使用預先計算好的嵌入空間,且該嵌入空間可使用更大的語料庫。最流行的方法是由谷歌開發的Word2Vec和由斯坦福NLP組開發的Glove,其中Word2Vec是經過神經網絡來實現,而GloVe經過共生矩陣和使用矩陣分解來實現。在這兩種狀況下,都是進行降維處理。但比較而言,Word2Vec更準確,GloVe的計算速度更快。
下面將瞭解如何使用斯坦福NLP組的GloVe詞嵌入,從這裏下載6B大小的詞嵌入(822 MB),還能夠在GloVe主頁面上找到其餘的詞嵌入,另外預訓練好的Word2Vec的嵌入詞能夠在此下載。若是你想訓練本身的詞嵌入,也可使Python的gensim包有效地完成,更多實現內容能夠在此查看。
下面將使用一個示例展現如何加載嵌入矩陣。示例中的文件的每一行都以單詞開頭,後面跟着特定單詞的嵌入向量。該文件包含400000行,每行表明一個單詞,後跟其向量做爲浮點數流。例如,如下是第一行的前50個字符:
$ head -n 1 data/glove_word_embeddings/glove.6B.50d.txt | cut -c-50 the 0.418 0.24968 -0.41242 0.1217 0.34527 -0.04445
因爲不須要涵蓋全部的單詞,只需關注詞彙中的單詞。因爲詞彙量有限,所以能夠在預訓練詞嵌入時略過絕大部分單詞:
import numpy as np def create_embedding_matrix(filepath, word_index, embedding_dim): vocab_size = len(word_index) + 1 # Adding again 1 because of reserved 0 index embedding_matrix = np.zeros((vocab_size, embedding_dim)) with open(filepath) as f: for line in f: word, *vector = return embedding_matrix
可使用下面函數檢索嵌入矩陣:
>>> embedding_dim = 50 >>> embedding_matrix = create_embedding_matrix( ... 'data/glove_word_embeddings/glove.6B.50d.txt', ... tokenizer.word_index, embedding_dim)
下面將在訓練中使用嵌入矩陣,當使用預訓練詞嵌入時,咱們能夠選擇在訓練期間對嵌入進行更新,或者只按照原樣使用這兩種方式。
首先,快速查看有多少嵌入向量是非零的:
>>> nonzero_elements = np.count_nonzero(np.count_nonzero(embedding_matrix, axis=1)) >>> nonzero_elements / vocab_size 0.9507727532913566
從上能夠看到,95.1%的詞彙被預先訓練的模型所覆蓋,這是一個很好的詞彙覆蓋範圍。下面看看使用全局池化層時的性能:
model = Sequential() model.add(layers.Embedding(vocab_size, embedding_dim, weights=[ 'relu'))
結果以下:
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= embedding_10 (Embedding) (None, 100, 50) 87350 _________________________________________________________________ global_max_pooling1d_6 (Glob (None, 50) 0 _________________________________________________________________ dense_17 (Dense) (None, 10) 510 _________________________________________________________________ dense_18 (Dense) (None, 1) 11 ================================================================= Total params: 87,871 Trainable params: 521 Non-trainable params: 87,350 _________________________________________________________________
結果以下:
Training Accuracy: 0.7500 Testing Accuracy: 0.6950
因爲詞嵌入沒有通過額外訓練,所以預計精度會下降。可是,若是經過使用trainable=True
這種方式訓練詞嵌入,其執行效果又會是怎樣呢?
model = Sequential() model.add(layers.Embedding(vocab_size, embedding_dim, weights=[embedding_matrix], input_length=maxlen, trainable=True)) 'relu')) model.add(layers.Dense(1, activation='sigmoid')) model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy']) model.summary()
結果以下
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= embedding_11 (Embedding) (None, 100, 50) 87350 _________________________________________________________________ global_max_pooling1d_7 (Glob (None, 50) 0 _________________________________________________________________ dense_19 (Dense) (None, 10) 510 _________________________________________________________________ dense_20 (Dense) (None, 1) 11 ================================================================= Total params: 87,871 Trainable params: 87,871 Non-trainable params: 0 _________________________________________________________________
history = model.fit(X_train, y_train, epochs=50, verbose=False, validation_data=(X_test, y_test), batch_size=10) loss, accuracy = model.evaluate(X_train, y_train, verbose=False) print("Training Accuracy: {:.4f}".format(accuracy)) loss, accuracy = model.evaluate(X_test, y_test, verbose=False) print("Testing Accuracy: {:.4f}".format(accuracy)) plot_history(history)
結果以下
Training Accuracy: 1.0000 Testing Accuracy: 0.8250
從上能夠看到,使用預訓練詞嵌入是最有效的。在處理大型訓練集時,能夠加快訓練過程。
下面,是時候關注更先進的神經網絡模型,看看是否有可能提高模型及其性能優點。
卷積神經網絡或是近年來機器學習領域中最使人振奮的發展成果之一,尤爲是在計算機視覺領域裏表現優異。關於CNN詳細介紹能夠看這篇文章《一文入門卷積神經網絡:CNN通俗解析》,這裏只作簡單介紹。
在下圖中,能夠看到卷積是如何工做的。它首先是從一個具備過濾器內核大小的輸入特徵開始的,且一維卷積對於平移是不變的,這意味着能夠在不一樣位置識別某些序列,這對文本中的某些模式是頗有幫助:
下面演示如何在Keras中使用這個網絡,Keras提供了各類卷積層:
embedding_dim model = Sequential() model.add(layers.Embedding(vocab_size, embedding_dim, input_length=maxlen)) model.add(layers.Conv1D(128, 5, activation='relu')) model.add(layers.GlobalMaxPooling1D()) model.Dense(10,
結果以下
_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= embedding_13 (Embedding) (None, 100, 100) 174700 _________________________________________________________________ conv1d_2 (Conv1D) (None, 96, 128) 64128 _________________________________________________________________ global_max_pooling1d_9 (Glob (None, 128) 0 _________________________________________________________________ dense_23 (Dense) (None, 10) 1290 _________________________________________________________________ Total params: 240,129 Trainable params: 240,129 Non-trainable params: 0
history = model.fit(X_train, y_train, epochs=10, verbose=False, validation_data=(X_test, y_test), batch_size=10) loss, accuracy = model.evaluate(X_train, y_train, verbose=False) print("Training Accuracy: format(accuracy)) loss, accuracy = model.evaluate(X_test, y_test, verbose=False) print("Testing Accuracy: {:.4f}".format(accuracy)) plot_history(history)
結果以下
Training Accuracy: 1.0000 Testing Accuracy: 0.7700
從上能夠看到,其準確率最高爲80%,表現並非很理想,形成這樣的緣由多是:
CNN網絡通常適合在大型訓練集上使用,在這些訓練集中,CNN可以找到像邏輯迴歸這樣的簡單模型沒法實現的歸納。
深度學習和使用神經網絡的一個關鍵步驟是超參數優化。
正如在目前使用的模型中看到的那樣,即便是更簡單的模型,也有大量的參數可供調整和選擇,這些參數被稱爲超參數,也是機器學習中最爲耗時的部分,依賴於實驗和我的經驗。
Kaggle上的比賽經常使用的方法有:一種流行的超參數優化方法是網格搜索(grid search)。這個方法的做用是獲取參數列表,並使用它找到每一個參數組合運行模型。簡單粗暴,但計算量最大;另外一種常見的方法是隨機搜索(random search),只需採用隨機的參數組合。
爲了使用Keras應用隨機搜索,須要使用KerasClassifier做爲scikit-learn API的包裝器。使用這個包裝器,可使用scikit提供的各類工具——像交叉驗證同樣學習。須要的類是RandomizedSearchCV,使用交叉驗證明現隨機搜索。交叉驗證是一種驗證模型並獲取整個數據集並將其分紅多個測試和訓練數據集的方法。
經常使用的方法有k折交叉驗證(k-fold cross-validation)和嵌套交叉驗證( nested cross-validation ),這裏實現k折交叉驗證法。在該方法中,數據集被劃分爲k個相等大小的集合,其中一個集合用於測試,其他的分區用於訓練。這使得咱們能夠運行k個不一樣的運行仿真,其中每一個分區都曾被用做測試集。所以,k越高,模型評估越準確,而每一個測試集也越小。
第一步KerasClassifier建立一個建立Keras模型的函數:
def create_model(num_filters, kernel_size, vocab_size, embedding_dim, maxlen): model = Sequential() model.add(layers.Embedding(vocab_size, embedding_dim, input_length=maxlen)) model.add(layers.Conv1D(num_filters, kernel_size, activation=)) model.add(layers.GlobalMaxPooling1D()) model.add(layers.Dense(10, activation='relu')) model.add(layers.Dense(1, activation='sigmoid')) model.compile(optimizer='adam', loss='binary_crossentropy', metrics'accuracy']) return model
接下來,定義在訓練中使用的參數。它由一個字典組成,每一個參數都在上一個函數中命名。網格上的空格數是3 * 3 * 1 * 1 * 1
,其中每一個數字是給定參數的不一樣選擇的數量。
使用如下字典初始化參數網格:
param_grid = dict(num_filters=[32, 64, 128], kernel_size=[3, 5, 7], vocab_size=[5000], embedding_dim=[50], maxlen=[100])
接下來運行隨機搜索:
from keras.wrappers.scikit_learn import KerasClassifier from sklearn.model_selection import RandomizedSearchCV # Main settings epochs = 20 embedding_dim = 50 maxlen = 100 output_file = 'data/output.txt' # Run grid search for each source (yelp, amazon, imdb) for source, frame in df.groupby('source'): print('Running grid search for data set :', source) sentences = df['sentence'].values y = df['label'].values # Train-test split sentences_train, sentences_test, y_train, y_test = train_test_split( sentences, y, test_size=0.25, random_state=1000) # Tokenize words tokenizer = Tokenizer(num_words=5000) tokenizer.fit_on_texts(sentences_train) X_train = tokenizer.texts_to_sequences(sentences_train) X_test = tokenizer.texts_to_sequences(sentences_test) # Adding 1 because of reserved 0 index vocab_size = len(tokenizer.word_index) + 1 # Pad sequences with zeros X_train = pad_sequences(X_train, padding='post', maxlen=maxlen) X_test = pad_sequences(X_test, padding='post', maxlen=maxlen) # Parameter grid for grid search param_grid = dict(num_filters=[32, 64, 128], kernel_size=[3, 5, 7], vocab_size=[vocab_size], embedding_dim=[embedding_dim], maxlen=[maxlen]) model = KerasClassifier(build_fn=create_model, epochs=, batch_size=10, verbose=False) grid = RandomizedSearchCV(estimator=model, param_distributions=param_grid, cv=4, verbose=1, n_iter=5) grid_result = grid.fit(X_train, y_train) # Evaluate testing set test_accuracy = grid.score(X_test, y_test) # Save and evaluate results prompt = input(f'finished {source}; write to file and proceed? [y/n]') if prompt.lower() not in {'y', 'true', 'yes'}: break with open(output_file, 'a') as f: s = ('Running {} data set\nBest Accuracy : ' '{:.4f}\n{}Test Accuracy : {:.4f}\n\n') output_string = s.format( source, grid_result.best_score_, grid_result.best_params_, test_accuracy) print(output_string) f.write(output_string)
運行須要一段時間,最後結果輸出以下:
Running amazon data set Best Accuracy : 0.8122 {'vocab_size': 4603, 'num_filters': 64, 'maxlen': 100, 'kernel_size': 5, 'embedding_dim': 50} Test Accuracy : 0.8457 Running imdb data set Best Accuracy : 0.8161 {'vocab_size': 4603, 'num_filters': 128, 'maxlen': 100, 'kernel_size': 5, 'embedding_dim': 50} Test Accuracy : 0.8210 Running yelp data set Best Accuracy : 0.8127 {'vocab_size': 4603, 'num_filters': 64, 'maxlen': 100, 'kernel_size': 7, 'embedding_dim': 50} Test Accuracy : 0.8384
因爲某種緣由,測試精度高於訓練精度,這多是由於在交叉驗證期間得分存在很大差別。能夠看到,使用卷積神經網絡表現最佳。
本文講述如何使用Keras進行文本分類,從一個使用邏輯迴歸的詞袋模型變成了愈來愈先進的卷積神經網絡方法。本文沒有涉及的另外一個重要主題是循環神經網絡RNN,更具體地說是LSTM和GRU。這些是處理文本或時間序列等順序數據的強大且流行的工具。當了解上述內容後,就能夠將其用於各類文本分類中,例如:電子郵件中的垃圾郵件檢測、自動標記文本或使用預約義主題對新聞文章進行分類等,快動手嘗試吧。
雲服務器99元拼團購!拉新還可贏現金紅包!300萬等你瓜分!
立刻一鍵開團贏紅包: http://click.aliyun.com/m/100...
本文爲雲棲社區原創內容,未經容許不得轉載。