(備忘録-python)自然言語処理超入門:CNNを理解し使うテキスト分類 (準初心者向け)

自然言語処理・画像解析

今回はCNNを使ったテキストを勉強しています。

自然言語処理に対して無知すぎた自分が、bertを学ぶ前に、ニューラルネットワーク系でテキスト分類をしたいとおもった際に学んだことをまとめた第3弾となります。前回までは事例列データ等に強いRNN、LSTMを自然言語処理に使っていましたが、今回はCNNという画像系で有名なもので自然言語処理タスクを行ってみようと思います。

CNN

必要なライブラリ

  • sklearn
  • numpy
  • pandas
  • BeautifulSoup
  • gensim
  • nltk
  • tensorflow==2.5.0

CNN(Convolutional Neural Network)

概要

  • 日本語では「畳み込みニューラルネットワーク」
  • 通常のNeural NetworkにConvolutional層を追加したもの
  • 主に画像認識の分野において代表的なNN

「畳み込み層」「プーリング層」といった特殊な機能を備えた層を積み重ねて構成されている

ざっくり歴史(G検定で勉強した知識)

CNNは、人間がもつ視覚野の神経細胞の2つの働きを模してみようという発想

  • 単純型細胞:画像の濃淡パターン(特徴)を検出
  • 複雑型細胞:特徴の位置が変動しても同一の特徴とする

ネオコグニトロン:この2つの細胞の働きを組み込んだモデル

ヤン・ルカンによってLeNetと呼ばれる有名なCNN のモデル
畳み込み層とプーリング層の2種類の層を複数組み合わせた構造

CNN

CNNの層について

簡潔な畳み込みニューラルネットワークの構成を以下に示す

  1. 入力層
  2. 畳み込み層(Conv)
  3. プーリング層(Pool)
  4. 全結合層
  • 畳み込み層

畳み込み(convolution)処理を行う、フィルタ(またはカーネル)を用いて画像から特徴を抽出する操作

畳み込み層はフィルタから構成

※フィルタ:数値の格納された行列

フィルタを用いて畳み込みを行う

※畳み込み:入力データに対応する要素×フィルタの要素

畳み込みを行う

出力サイズが小さくなる

小さくしたくない

パディング=畳み込みを行う前に入力データの周囲に0などのデータを埋め込む作業

畳み込み層によって、「位置のズレ」に強いモデル ができる

  • プーリング層

プーリング処理をおこない、画像サイズを決められたルールに従って小さくする

プーリング(Pooling)=入力サイズを小さくする演算=ある領域を1つの要素に縮約する演算

例)最大値プーリング(max pooling)

例)平均値プーリング(average pooling)

畳み込み処理同様、画像のズレに対する頑健性を持つことができる

CNNコード

コード データのロード 前処理等

#データセットのダウンロード
from tensorflow.keras.datasets import imdb

# imdb = imdb
(train_data, train_labels), (test_data, test_labels) = imdb.load_data()

#単語が整数にマッピングされた辞書を取得
word_index = imdb.get_word_index()

# 最初の要素を予約(単語を登録)
word_index = {k:(v+3) for k,v in word_index.items()} 
word_index["<PAD>"] = 0
word_index["<START>"] = 1
word_index["<UNK>"] = 2  # 不明な単語
word_index["<UNUSED>"] = 3

# 整数を単語にマッピングする辞書を作成
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])

def decode_review(text):
    return ' '.join([reverse_word_index.get(i, '?') for i in text])

import pandas as pd
train_df=pd.DataFrame(train_data)[0].map(decode_review).reset_index(drop=True)#データを軽くするため
test_df=pd.DataFrame(test_data)[0].map(decode_review).reset_index(drop=True)#データを軽くするため

データセットをダウンロードし、データフレームに成形(他でもよくデータフレームを用いるため)。

データセットのテキストに対して前処理を行う。

from bs4 import BeautifulSoup
import re
from nltk.stem import WordNetLemmatizer
lemmatizer = WordNetLemmatizer()
#nltk.download('wordnet')
#nltk.download('omw-1.4')

from nltk.stem.porter import PorterStemmer 
stemmer = PorterStemmer()

import nltk
from nltk.corpus import stopwords
# nltk.download('stopwords')

def clean_text(x):
    #ノイズ除去
    soup = BeautifulSoup(x, 'html.parser')
    text= soup.get_text()
    
    #アルファベット以外をスペースに置き換え
    text_ = re.sub(r'[^a-zA-Z]', ' ', text)
    
    #単語長が短いものものは削除(中身による)+その後の処理のために分割
    text_ = [word for word in text_.split() if len(word) > 2]
    
    #形態素=>動詞
    text_ = [lemmatizer.lemmatize(word.lower(), pos="v") for word in text_]
    
    #ステミング
#     text_ = [stemmer.stem(word) for word in text_]
    
    #stopword除去
    A = [word for word in text_ if word not in stopwords.words('english')]
    
    #単語同士をスペースでつなぎ, 文章に戻す
    #その後の処理で戻す必要ない場合はコメントアウト
    clean_text = ' '.join(A)
    return clean_text


clean_text_df=train_df.map(clean_text)
clean_text_df

clean_text_test_df=test_df.map(clean_text)
clean_text_test_df


#テストで実行するため5こと飛ばしデータを用いて軽量で実行する
diff=5

train_texts=clean_text_df.iloc[::diff]
train_label=train_labels[::diff]

test_texts=clean_text_test_df.iloc[::diff]
test_label=test_labels[::diff]

np.shape(train_texts),np.shape(train_label),np.shape(test_texts),np.shape(test_label)

CNNへの入力準備

配列のid化(トークン化)を行う。

from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences

all_text=pd.concat([train_texts,test_texts]).reset_index(drop=True)

sentences = []
for text in all_text:
    text_list = text.split(' ')
    sentences.append(text_list)

tokenizer = Tokenizer()
tokenizer.fit_on_texts(sentences)

#テキストデータを数値化
sequences_tk = tokenizer.texts_to_sequences(sentences)

MAX_SEQUENCE_LENGTH =int(pd.DataFrame(sequences_tk).shape[1])
MAX_SEQUENCE_LENGTH

#要素の合わない配列に対し、0で埋めるなどの配列のサイズを一致させる処理
X=pad_sequences(sequences_tk, maxlen=MAX_SEQUENCE_LENGTH, truncating='post')

X_train=X[:train_texts.shape[0]]
X_test=X[train_texts.shape[0]:]

CNNでの2値分類

CNNの構築と学習を行う。

from tensorflow.keras.models import Model
from tensorflow.keras.layers import Dense,Input,Embedding,LSTM, GlobalMaxPooling1D
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
from tensorflow.keras.models import load_model
from tensorflow.keras.preprocessing.sequence import pad_sequences
from sklearn.metrics import f1_score, precision_score, recall_score
from sklearn.model_selection import train_test_split

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.layers import Embedding
from tensorflow.keras.layers import InputLayer
from tensorflow.keras import optimizers
from tensorflow.keras import layers
from tensorflow import keras
from tensorflow.keras import regularizers


word_index = tokenizer.word_index
num_words = len(word_index)
print(num_words)

input_dim = num_words+1           # 入力データの次元数
emb_dim = 300
output_dim = 2               # 出力データの次元数:クラス分
batch_size = 128             # ミニバッチサイズ
epochs = 100 # 学習エポック数
learning_rate = 0.001        # 学習率

filters=250    #channel_size
kernel_size=3 #filter size



def CNN_model():
    model = Sequential()
    model.add(InputLayer(input_shape=(None,), name='input'))

    model.add(Embedding(
        input_dim=input_dim,                 # 入力として取り得るカテゴリ数(パディングの0を含む)
        output_dim=emb_dim,                # 出力ユニット数(本来の特徴量の次元数)
        trainable=True,                     # 埋め込み行列を固定(学習時に更新)
        mask_zero=True))                     # 0をパディング用に特別扱いする

    model.add(Conv1D(
        filters,
        kernel_size,
        padding="valid",
        activation="relu",
        strides=1
        ))

    model.add(GlobalMaxPooling1D())

    model.add(Dense(output_dim, activation="softmax"))

    adam=optimizers.Adam(learning_rate=0.001, beta_1=0.9, beta_2=0.999, epsilon=None, decay=0.0, amsgrad=False)
    model.compile(optimizer=adam,
                  loss='sparse_categorical_crossentropy',
                  metrics=['acc'])

    model.summary()
    return model



# Preparing callbacks.
model=CNN_model()

callbacks = [
    EarlyStopping(patience=3)
]

#seed_everything(42)

# Train the model.
history=model.fit(x=X_train,
                  y=train_label,
                  batch_size=batch_size,
                  epochs=epochs,
                  validation_split=0.2,
                  callbacks=callbacks,
                  shuffle=True)


# 可視化
hist_df = pd.DataFrame(history.history)
plt.figure()
hist_df[['acc', 'val_acc']].plot()
plt.ylabel('accuracy')
plt.xlabel('epoch')
plt.show()

plt.figure()
hist_df[['loss', 'val_loss']].plot()
plt.ylabel('loss')
plt.xlabel('epoch')
plt.show()

動かしたら学習工程までを出せると思います。単語ベクトルとしてword2vecの学習済みモデルである”GoogleNews-vectors-negative300”を用いることも可能です。(自作しても可)

前回同様、他の記事を参考にしてみてください。

も参考にしてみてください。embedding層に追加すればよいはずです。そしてその例も他の記事に載っていると思います。

是非見てみてください。

まとめ

やっとNN系の自然言語処理が終わりました(一応)。次からはattention機構などのより高度なものを学びたいと考えています。

もし、この記事を読んで参考になった・他の記事も読んでみたいと思った方、twitterのフォローボタンを押していただけるとモチベーション向上につながるので、よろしくお願いいたします。

コメント

タイトルとURLをコピーしました