NLP-使用CNN進行文字分類

spring_willow發表於2018-04-20
  • CNN最初用於處理影像問題,但是在自然語言處理中,使用CNN進行文字分類也可以取得不錯的效果。
  • 在文字中,每個詞都可以用一個行向量表示,一句話就可以用一個矩陣來表示,那麼處理文字就與處理影像是類似的了。

目錄


一、卷積神經網路CNN

這裡寫圖片描述
圖片來源:Convolutional Neural Networks for Sentence Classification

1.模型說明

  • 在影像處理中使用不同的濾鏡可以使影像凸顯出不同的特徵,我們把新的影像稱為卷積後的特徵圖(第二層)。
  • 將特徵圖通過pooling層(第三層),這與人眼在看東西的時候會首先看到耀眼的內容一樣,通常選取最大值或使用平均值來得到更小的特徵圖,這有助於計算。
  • 加上任意分類器,如SVM,LSTM等。

2.卷積核

  • 上述所說的濾鏡(filter)就是我們的CNN模型中的卷積核,通過使用卷積核可以將我們的原始矩陣(CNN的第一層)變成CNN的第二層。
  • 卷積核是通過學習得到的,不能人工設定,這是很重要的一點,而剛開始的時候卷積核的設定是隨機的。

3.CNN4Text

上述模型中是將每個單詞作為一個特徵向量,使用了二維和三維的卷積核進行Filter,我們也可以使用一整個句子作為特徵向量,用一維的Filter進行掃描(1xN),如下圖所示:
這裡寫圖片描述
圖片來源:七月線上視訊課件(包括下圖,原始來源我母雞的了)

4.兩種引數調整問題

  • 邊界處理:Narrow 和Wide

這裡寫圖片描述

  • Stride size:步伐大小
    這裡寫圖片描述

二、使用例項:word2vec+CNN進行文字分類

1.題目

用每日新聞預測金融市場變化

題目來源:Kaggle競賽
程式碼作者:加號


2.資料說明

  • Combined_News_DJIA.csv: 作者將資料combine成27列,第一列是日期,第二列是標籤,其他25列是每日的前25條新聞,通過熱門程度進行排序得來。
  • 這是一個二分類問題,‘1’表示這一天的股票值上升或保持不變;‘0’表示下降。
  • 訓練集和測試集的比例是8:2

3.資料預處理

①匯入所需要的庫

import pandas as pd
import numpy as np
from sklearn.metrics import roc_auc_score
from datetime import date

②讀入資料

data = pd.read_csv('./Combined_News_DJIA.csv')

可以通過data.head()檢視資料的長相
③分割資料集

train = data[data['Date'] < '2015-01-01']
test = data[data['Date'] > '2014-12-31']

④處理資料集

X_train = train[train.columns[2:]]
#去掉日期和標籤,把每條新聞做成單獨的句子,集合在一起
corpus = X_train.values.flatten().astype(str)
#獲取語料庫
X_train = X_train.values.astype(str)
X_train = np.array([' '.join(x) for x in X_train])

X_test = test[test.columns[2:]]
X_test = X_test.values.astype(str)
X_test = np.array([' '.join(x) for x in X_test])

y_train = train['Label'].values
y_test = test['Label'].values
  • 通過.columns[]獲取特定列;
  • flatten()函式將一個巢狀多層的陣列array轉化成只有一層的陣列;
  • numpy array儲存單一資料型別的多維陣列;

    說明:
    corpus是全部我們『可見』的文字資料。假設每條新聞就是一句話,把他們全部flatten()就會得到list of sentences。
    X_train和X_test不能隨便flatten,他們需要與y_train和y_test對應。

⑤分詞

from nltk.tokenize import word_tokenize

corpus = [word_tokenize(x) for x in corpus]
X_train = [word_tokenize(x) for x in X_train]
X_test = [word_tokenize(x) for x in X_test]
  • corpus的分詞結果裡,第二維資料是一個個的句子,而X_train的第二維資料是一天中整個新聞的合集,對應每個label。
  • 可以通過X_train[:2]和corpus[:2]檢視資料的變化情況。

⑥預處理

  • 小寫化
  • 刪除停止詞
  • 刪除數字和符號
  • lemma詞性還原

    把這些步驟統一合成一個func

# 停止詞
from nltk.corpus import stopwords
stop = stopwords.words('english')

# 數字
import re
def hasNumbers(inputString):
    return bool(re.search(r'\d', inputString))
# 正規表示式,出現數字的時候返回true

# 特殊符號
def isSymbol(inputString):
    return bool(re.match(r'[^\w]', inputString))
"""
正規表示式,\w表示單詞字元[A-Za-z0-9_]
[^\w]表示取反==\W非單詞字元
全是特殊符號時返回true
"""

# lemma
from nltk.stem import WordNetLemmatizer
wordnet_lemmatizer = WordNetLemmatizer()

def check(word):
    """
    如果需要這個單詞,則True
    如果應該去除,則False
    """
    word= word.lower()
    if word in stop:
        return False
    elif hasNumbers(word) or isSymbol(word):
        return False
    else:
        return True

# 把上面的方法綜合起來
def preprocessing(sen):
    res = []
    for word in sen:
        if check(word):
            # 這一段的用處僅僅是去除python裡面byte存str時候留下的標識。。之前資料沒處理好,其他case裡不會有這個情況
            word = word.lower().replace("b'", '').replace('b"', '').replace('"', '').replace("'", '')
            res.append(wordnet_lemmatizer.lemmatize(word))
    return res

把三組資料集通過上面的函式進行預處理:

corpus = [preprocessing(x) for x in corpus]
X_train = [preprocessing(x) for x in X_train]
X_test = [preprocessing(x) for x in X_test]

4.訓練NLP模型

使用最簡單的word2vec

from gensim.models.word2vec import Word2Vec
model = Word2Vec(corpus, size=128, window=5, min_count=5, workers=4)

Word2Vec:word to vector ,將單詞轉化為向量,這時候每個單詞都可以像查字典一樣讀取座標。
例如,使用model['white']就可以獲得單詞‘white’的w2v座標。

5.使用CNN

①獲取矩陣資訊

  • 用vector表示出一個大matrix,使用CNN做‘降維+注意力’
  • 通過調大引數可以增加複雜度和準確度
  • 確定padding size,使生成的matrix是一樣的size
  • 下面程式碼可以直接呼叫keras的sequence方法
# 說明,對於每天的新聞,我們會考慮前256個單詞。不夠的我們用[000000]補上
# vec_size 指的是我們本身vector的size
def transform_to_matrix(x, padding_size=256, vec_size=128):
    res = []
    for sen in x:
        matrix = []
        for i in range(padding_size):
            try:
                matrix.append(model[sen[i]].tolist())
            except:
                # 這裡有兩種except情況,
                # 1. 這個單詞找不到
                # 2. sen沒那麼長
                # 不管哪種情況,我們直接貼上全是0的vec
                matrix.append([0] * vec_size)
        res.append(matrix)
    return res

處理訓練集/測試集

X_train = transform_to_matrix(X_train)
X_test = transform_to_matrix(X_test)

# print(X_train[1])
# 檢視資料的長相

最後,我們得到的就是一個大大的Matrix,它的size是 128 * 256。每一個matrix對應一個資料點(標籤)。

②reshape輸入集

在進行下一步之前,需要將input 資料reshape一下,原因是要使每一個matrix的外部包裹一層維度,來告訴我們的CNN model,每個資料點都是獨立的,沒有前後關係。
(對於股票來說,存在前後關係,可以使用CNN+LSTM模型實現記憶功能)

# 搞成np的陣列,便於處理
X_train = np.array(X_train)
X_test = np.array(X_test)

# 看看陣列的大小
print(X_train.shape)
print(X_test.shape)

結果:
(1611, 256, 128)
(378, 256, 128)

X_train = X_train.reshape(X_train.shape[0], 1, X_train.shape[1], X_train.shape[2])
X_test = X_test.reshape(X_test.shape[0], 1, X_test.shape[1], X_test.shape[2])
#通過print(X_test)觀察與前者的區別,就是多了一個括號
print(X_train.shape)
print(X_test.shape)

結果:
(1611, 1, 256, 128)
(378, 1, 256, 128)

③定義CNN模型

from keras.preprocessing import sequence
from keras.models import Sequential
from keras.layers import Convolution2D, MaxPooling2D
from keras.layers.core import Dense, Dropout, Activation, Flatten

# set parameters:
batch_size = 32
n_filter = 16
filter_length = 4
nb_epoch = 5
n_pool = 2

# 新建一個sequential的模型
model = Sequential()
model.add(Convolution2D(n_filter,filter_length,filter_length,
                        input_shape=(1, 256, 128)))
model.add(Activation('relu'))
model.add(Convolution2D(n_filter,filter_length,filter_length))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(n_pool, n_pool)))
model.add(Dropout(0.25))
model.add(Flatten())
# 後面接上一個ANN
model.add(Dense(128))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(1))
model.add(Activation('softmax'))
# compile模型
model.compile(loss='mse',
              optimizer='adadelta',
              metrics=['accuracy'])

④獲取結果

model.fit(X_train, y_train, batch_size=batch_size, nb_epoch=nb_epoch,
          verbose=0)
score = model.evaluate(X_test, y_test, verbose=0)
print('Test score:', score[0])
print('Test accuracy:', score[1])

三、擴充套件

①對於CNN而言,無論input什麼內容,只要符合規矩,都可以process,所以可以不使用word2vec。甚至可以使用ASCII碼(0,256)來表達每個位置上的字元,然後組成一個大大的matrix。每個字元都可以被表示出來,而且使有意義的。
②分類器的使用也是多種多樣的,可以用LSTM或者RNN等接在CNN的那句Flatten語句後,來提高預測結果。

相關文章