網頁

2019年7月5日 星期五

Text Classification With Python and Keras

目錄
1.數據集
2.定義基線模型
3.深度神經網絡簡介
(i)介紹Keras
(ii)First Keras模型
4.什麼是單詞嵌入?
(i)One-Hot編碼
(ii)Word嵌入
(iii)Keras嵌入層
5.卷積神經網絡(CNN)
6.Hyperparameters優化

Importing Packages


import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
import matplotlib.pyplot as plt
plt.style.use('ggplot')

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder
from sklearn.model_selection import RandomizedSearchCV

from keras.models import Sequential
from keras import layers
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
from keras.wrappers.scikit_learn import KerasClassifier

import os
print(os.listdir("../input"))


將文件夾解壓縮到數據文件夾中,然後繼續使用Pandas加載數據:

import pandas as pd

filepath_dict = {'yelp':   '../input/sentiment labelled sentences/sentiment labelled sentences/yelp_labelled.txt',
                 'amazon': '../input/sentiment labelled sentences/sentiment labelled sentences/amazon_cells_labelled.txt',
                 'imdb':   '../input/sentiment labelled sentences/sentiment labelled sentences/imdb_labelled.txt'}

df_list = []
for source, filepath in filepath_dict.items():
    df = pd.read_csv(filepath, names=['sentence', 'label'], sep='\t')
    df['source'] = source  # Add another column filled with the source name
    df_list.append(df)
# df_list


df = pd.concat(df_list)
df.iloc[0]



df.head()




df.tail()


現在使用scikit-learn函式庫提供的CountVectorizer來向量化句子。 它採用每個句子的單字,並創建句子中所有單字的詞彙表。 然後可以使用該詞彙表創建單字計數的特徵向量,例如:

sentences = ['Rashmi likes ice cream', 'Rashmi hates chocolate.']

vectorizer = CountVectorizer(min_df=0, lowercase=False)
vectorizer.fit(sentences)
vectorizer.vocabulary_


vectorizer.transform(sentences).toarray()


CountVectorizer(input=’content’, encoding=’utf-8’, decode_error=’strict’, strip_accents=None, lowercase=True, preprocessor=None, tokenizer=None, stop_words=None, token_pattern=’(?u)\b\w\w+\b’, ngram_range=(1, 1), analyzer=’word’, max_df=1.0, min_df=1, max_features=None, vocabulary=None, binary=False, dtype=<class ‘numpy.int64’>)

lowercase : boolean, True by default Convert all characters to lowercase before tokenizing.

min_df : float in range [0.0, 1.0] or int, default=1 When building the vocabulary ignore terms that have a document frequency strictly lower than the given threshold. This value is also called cut-off in the literature. If float, the parameter represents a proportion of documents, integer absolute counts. This parameter is ignored if vocabulary is not None.



參考
https://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.CountVectorizer.html

Defining a Baseline Model
首先,您要將數據拆分為訓練和測試集,這樣您就可以評估準確性,看看您的模型是否能夠很好地歸納。 這意味著該模型是否能夠在以前沒有見過的數據上表現良好。 這是一種查看模型是否過度擬合的方法。

過度擬合是指模型在訓練數據上訓練得太好,這意味著模型大多只是記住了訓練數據,這將使訓練數據具有較高的準確度,但測試數據的準確度較低。

首先從數據集中提取Yelp數據集。這些數據集採用句子和標籤。

df_yelp = df[df['source'] == 'yelp']
sentences = df_yelp['sentence'].values
y = df_yelp['label'].values

sentences_train, sentences_test, y_train, y_test = train_test_split(sentences, y, test_size=0.25, random_state=1000)

print(type(sentences_train))
print(type(sentences_train[0]))
print(type(sentences_train[0][0]))
print(np.array(sentences_train).shape)
print(len(sentences_train[0]))
print(len(sentences_train[0][0]))

<class 'numpy.ndarray'>
<class 'str'>
<class 'str'>
(750,)
104
1

為訓練和測試集的每個句子創建特徵向量:

vectorizer = CountVectorizer()
vectorizer.fit(sentences_train)

X_train = vectorizer.transform(sentences_train)
X_test  = vectorizer.transform(sentences_test)

X_train


print(X_train.shape)
print(X_train.toarray())
print(type(X_train))
print(type(X_train[0]))
print(type(X_train[0][0]))

(750, 1714)
[[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]]
<class 'scipy.sparse.csr.csr_matrix'>
<class 'scipy.sparse.csr.csr_matrix'>
<class 'scipy.sparse.csr.csr_matrix'>

CountVectorizer執行標記化,將句子分成一組標記。 它還可以刪除標點符號和特殊字符,並可以對每個單字應用其他預處理。 如果需要,可以使用帶有CountVectorizer的NLTK庫中的自定義標記生成器,或使用您可以探索的任意數量的自定義來提高模型的性能。

我們將要使用的分類模型是邏輯回歸,它是一個簡單而強大的線性模型,在數學上講,實際上是基於輸入特徵向量的0到1之間的回歸形式。 藉由指定截斷值(默認為0.5),回歸模型用於分類。

classifier = LogisticRegression()
classifier.fit(X_train, y_train)
score = classifier.score(X_test, y_test)

print("Accuracy:", score)


您可以看到邏輯回歸達到79.6%,但讓我們看看這個模型如何對我們擁有的其他數據集執行。 在此腳本中,我們執行並評估我們每個數據集的整個過程:

for source in df['source'].unique():
    df_source = df[df['source'] == source]
    sentences = df_source['sentence'].values
    y = df_source['label'].values

    sentences_train, sentences_test, y_train, y_test = train_test_split(
        sentences, y, test_size=0.25, random_state=1000)

    vectorizer = CountVectorizer()
    vectorizer.fit(sentences_train)
    X_train = vectorizer.transform(sentences_train)
    X_test  = vectorizer.transform(sentences_test)

    classifier = LogisticRegression()
    classifier.fit(X_train, y_train)
    score = classifier.score(X_test, y_test)
    print('Accuracy for {} data: {:.4f}'.format(source, score))


你可以看到這個相當簡單的模型達到了相當好的準確性。

Introduction to Deep Neural Networks
神經網絡,或者有時稱為人工神經網絡(ANN)或前饋神經網絡(feedforward neural network),計算機網絡概念是受到人腦中神經網絡的啟發。 它們由神經元(也稱為節點)組成,它們如下圖所示連接。

首先,把特徵向量中輸入到input層,然後將值向前饋送到隱藏層。 在每個連接處,您向前提供值會乘以權重,並將偏差添加到該值。 這種情況發生在每個連接處,最後到達具有一個或多個輸出節點的output層。

如果要使用二元分類(binary classification),可以使用一個節點,但如果您有多個類別(multiple categories),則應為每個類別使用多個節點:


Introducing Keras

Keras是FrançoisChollet的深度學習和神經網絡API,能夠運行在Tensorflow(谷歌),Theano或CNTK(微軟)之上。引用FrançoisChollet的精彩書籍,Python深度學習:


Keras is a model-level library, providing high-level building blocks for developing deep-learning models. It doesn’t handle low-level operations such as tensor manipulation and differentiation. Instead, it relies on a specialized, well-optimized tensor library to do so, serving as the backend engine of Keras (Source)

這是開始嘗試神經網絡的好方法,無需自己實現每一層和一塊。例如,Tensorflow是一個很棒的機器學習庫,但是你必須實現很多樣板(boilerplate)代碼來運行模型。

First Keras Model
Keras支援兩種主要類型的模型。 您擁有Sequential模型API和功能API,可以執行Sequential模型的所有功能,但它也可用於具有複雜網絡結構的高級模型。

Sequential模型是一個線性堆疊(linear stack)層,您可以在Keras中使用各種各樣的可用層。 最常見的層是Dense層,它是具有全部權重和偏差密集連接的神經網絡層,。

在建構模型之前,我們需要知道特徵向量的輸入維度。 這僅在第一層中發生,因為其他層可以進行自動形狀推斷。 為了構建Sequential模型,您可以按順序逐層添加

input_dim = X_train.shape[1]  # Number of features

model = Sequential()
model.add(layers.Dense(10, input_dim=input_dim, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

model.compile(loss='binary_crossentropy', 
              optimizer='adam', 
              metrics=['accuracy'])
model.summary()



history = model.fit(X_train, y_train,
                    epochs=100,
                    verbose=True,
                    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))


def plot_history(history):
    acc = history.history['acc']
    val_acc = history.history['val_acc']
    loss = history.history['loss']
    val_loss = history.history['val_loss']
    x = range(1, len(acc) + 1)

    plt.figure(figsize=(12, 5))
    plt.subplot(1, 2, 1)
    plt.plot(x, acc, 'b', label='Training acc')
    plt.plot(x, val_acc, 'r', label='Validation acc')
    plt.title('Training and validation accuracy')
    plt.legend()
    plt.subplot(1, 2, 2)
    plt.plot(x, loss, 'b', label='Training loss')
    plt.plot(x, val_loss, 'r', label='Validation loss')
    plt.title('Training and validation loss')
    plt.legend()

plot_history(history)


What Is a Word Embedding?

文本被認為是一種序列數據形式,像是天氣數據或財務數據。現在,您將看到如何將每個單字表示為向量。有多種方法可以對文本進行向量化,例如:

(i)由每個單字表示的單字作為向量
(ii)由每個字元表示的字元作為向量
(iii)單字/字元的N-gram表示為向量(N-gram是文本中多個連續單字/字元的重疊組)

接下來將會看到如何處理將單字表示為向量,這是神經網絡使用文本的常用方式。將單字表示為向量有兩種可能的方式,分別為One-Hot Encoding和word embeddings。

One-Hot Encoding

第一種方法是產生所謂的One-Hot Encoding,取語料庫中每個單詞形成詞彙長度的向量。

在詞彙表中每個單詞的相對應位置設定為1,其他點設定為零。

cities = ['London', 'Berlin', 'Berlin', 'New York', 'London']
cities


LabelEncoder將城市列表編碼為分類整數值

LabelEncoder to encode the list of cities into categorical integer values

encoder = LabelEncoder()
city_labels = encoder.fit_transform(cities)
city_labels


OneHotEncoder期望每個分類值都在一個單獨的行中,因此您需要重新整形數組,然後您可以應用編碼器:

OneHotEncoder expects each categorical value to be in a separate row, so you’ll need to reshape the array, then you can apply the encoder:

encoder = OneHotEncoder(sparse=False)
city_labels = city_labels.reshape((5, 1))
encoder.fit_transform(city_labels)



Word Embeddings
該方法將單字表示為密集詞向量(也稱為詞嵌入),其被訓練而不像硬編碼(hardcoded)的one-hot encoding。這意味著詞嵌入將收集更多資訊到更少的維度中。

請注意,詞嵌入並不像人類那樣理解文本,而是映射語料庫中使用的語言統計結構。他們的目標是將語義意義映射到幾何空間。然後將該幾何空間稱為嵌入空間(embedding space)。

現在,您需要將數據標記為詞嵌入的使用格式。 Keras為文本預處理(text preprocessing)和序列預處理(sequence preprocessing)提供了幾種方便的方法,您可以使用這些方法來準備文本。

您可以從使用Tokenizer工具類別開始,將文本語料庫向量化為整數list。每個整數映射到dictionary中的一個值,該字典對整個語料庫進行編碼,dictionary中的keys是詞彙表本身。您可以添加參數num_words,它負責設置詞彙表的大小。然後將保留最常見的num_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)

vocab_size = len(tokenizer.word_index) + 1  # Adding 1 because of reserved 0 index

print(sentences_train[2])
print(X_train[2])


for word in ['the', 'all','fan']:
    print('{}: {}'.format(word, tokenizer.word_index[word]))


pad sequences with Keras

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, :])


Keras Embedding Layer
現在,您可以使用Keras的嵌入層,它採用先前計算的整數並將它們映射到嵌入的密集向量。 您將需要以下參數:

(i)input_dim:詞彙量的大小
(ii)output_dim:密集向量的大小
(iii)input_length:序列的長度

使用嵌入層,我們現在有幾個選項。 一種方法是獲取嵌入層的輸出並將其插入Dense層。 為了做到這一點,你必須在它們之間添加一個Flatten層來為Dense層準備好序列性input:

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


history = model.fit(X_train, y_train,
                    epochs=20,
                    verbose=True,
                    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)


Global max/average pooling採用所有特徵的最大值/平均值,而在另一種情況下,您必須定義pool大小:

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


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)


Convolutional Neural Networks (CNN)
卷積神經網絡或稱為卷積網(convnets)是近年來機器學習中最令人興奮的發展之一。

它們通過能夠從圖像中提取特徵並在神經網絡中使用它們,徹底改變了圖像分類和計算機視覺。 這個特性使它們在圖像處理中對於序列處理也很方便。 您可以將CNN想像成能夠檢測特定模式的專屬神經網絡。

CNN具有隱藏的層,稱為卷積層(convolutional layers)。 當您想到圖像時,計算機必須處理二維數字矩陣,因此您需要某種方法來檢測此矩陣中的特徵。 這些卷積層能夠檢測邊緣,角落和其他類型的紋理,這使它們成為一種特殊的工具。 卷積層由多個過濾器組成,這些過濾器在圖像上滑動並且能夠檢測特定特徵。

embedding_dim = 100

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.add(layers.Dense(10, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(optimizer='adam',
              loss='binary_crossentropy',
              metrics=['accuracy'])
model.summary()


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: {:.4f}".format(accuracy))
loss, accuracy = model.evaluate(X_test, y_test, verbose=False)
print("Testing Accuracy:  {:.4f}".format(accuracy))
plot_history(history)


Hyperparameters Optimization
深度學習和使用神經網絡的一個關鍵步驟是超參數優化。

到目前為止所使用的模型即使是更簡單的模型,您也有大量的參數可供調整和選擇。這些參數稱為超參數(hyperparameters)。這是機器學習中最耗時的部分,遺憾的是沒有一體適用(one-fits-all)的解決方案。

一種流行的超參數優化方法是網格搜索(grid search)。這個方法的作用是獲取參數list,並使用它可以找到的每個參數組合運行模型。這是最徹底的方式,但也是計算量最大的方法。另一種常見的方法,即隨機搜索(random search),您將在此處看到,只需採用隨機的參數組合。

要使用Keras應用隨機搜索,您需要使用KerasClassifier作為scikit-learn API的包裝器。使用此包裝器,您可以使用scikit提供的各種工具 - 像交叉驗證(cross-validation)一樣學習。您需要的類是RandomizedSearchCV,它使用交叉驗證實現隨機搜索。交叉驗證是一種驗證模型並獲取整個數據集並將其分成多個測試和訓練數據集的方法。

有各種類型的交叉驗證。一種是k折交叉驗證。在這種類型中,數據集被劃分為k個相等大小的集合,其中一個集合用於測試,其餘的分割用於訓練。這使您可以運行k個不同的運行,其中每個分割曾被用作測試集。因此,k越高,模型評估越準確,但每個測試集越小。

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

param_grid = dict(num_filters=[32, 64, 128],
                  kernel_size=[3, 5, 7],
                  vocab_size=[5000], 
                  embedding_dim=[50],
                  maxlen=[100])

# Main settings
epochs = 20
embedding_dim = 50
maxlen = 100
output_file = '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=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, 'w+') as f:
    s = ('Running {} data set\nBest Accuracy : '
             '{:.4f}\n{}\nTest 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)





參考
https://www.kaggle.com/sanikamal/text-classification-with-python-and-keras/data

沒有留言:

張貼留言