stMind

about Tech, Computer vision and Machine learning

T-shaped knowledge base

Machine Learning Engineerとして応募するのであれば、幅広い分野の知識を持つと同時に、一つの領域に関して深く専門的になろうという話。

自分はComputer Visionを専門にしていますが、特に自然言語処理で使われている技術は画像認識でも適用されたり、またその逆も然りで、両方の分野の知識があることは、明確なプラス作用があると思いますし、違った分野の知識を複合して持っていると、問題を違った側面から見ることができるのかなと思います。(単純に、NLPとCVの融合領域は面白いなぁと思ったり)

あとは、そういった知識をもとに、どの問題に対してどのようなアプローチを取るのか、といった経験が必要なのだと思いますが、そのあたりはこの図でいうと、縦軸に関する取り組みをやってみることで身についていくものなのかなと感じます。

Work From Home with Radiko

Work From Homeでのお供、Radikoでラジオ。

大体、こんなところを聞いてる。

あと金曜日は、はなさんのLovely Day - Fm yokohama 84.7も聞いている。

ラジオをここまで聞くのは人生で初めてだけど、面白い。ラジオ初心者に良い番組があれば、他にも何か知りたいところ。

imdb_reviews datasetのレビューに含まれるunique wordの上位N件の分布

レビューのテキストに含まれる語には、どういったものがあるか。特に、上位に含まれる語について、分布を見てみます。

scikit-learnのCountVectorizerを使って実装します。

def get_top_n_words(corpus, n=20, is_stop_words=False):
    vectorizer = CountVectorizer(stop_words='english') if is_stop_words else CountVectorizer()
    X = vectorizer.fit_transform(corpus)
    X_sum = X.sum(axis=0)
    words_freq = [(word, X_sum[0, idx]) for word, idx in vectorizer.vocabulary_.items()]
    words_freq = sorted(words_freq, key=lambda x: x[1], reverse=True)
    return words_freq[:n]

fit_transformで、corpusに含まれるunique wordのcountを含むarrayが得られるので、sumでword毎のcountを計算しています。

公式の例から引用。

>>> from sklearn.feature_extraction.text import CountVectorizer
>>> corpus = [
...     'This is the first document.',
...     'This document is the second document.',
...     'And this is the third one.',
...     'Is this the first document?',
... ]
>>> vectorizer = CountVectorizer()
>>> X = vectorizer.fit_transform(corpus)
>>> print(vectorizer.get_feature_names())
['and', 'document', 'first', 'is', 'one', 'second', 'the', 'third', 'this']
>>> print(X.toarray())
[[0 1 1 1 0 0 1 0 1]
 [0 2 0 1 0 1 1 0 1]
 [1 0 0 1 1 0 1 1 1]
 [0 1 1 1 0 0 1 0 1]]

このsumを計算すると以下のようにword毎のカウントが得られます。

>>> X.sum(axis=0)                                                                                             
matrix([[1, 4, 2, 4, 1, 1, 4, 1, 4]], dtype=int64)

では、分布を見てみます。上段のグラフがStop wordsを含むもの、下段がStop wordsを除いたものです。

f:id:satojkovic:20200704171438p:plain

上位の語のリストで見ると、結構似ています。goodやbadのような感情分析に有効であると思われる語だけでなく、movieやfilmのようなニュートラルな語も含まれています。positiveなテキストにおいて、goodはbadより多く含まれていますが、negativeなテキストではbadはgoodより多いものの、同じくらい多く含まれていることも分かりました。

imdb_reviews datasetのレビュー長のヒストグラム

データセット自体を理解し、モデル改善に役立てていきます。

Positiveなレビュー、Negativeなレビュー、それぞれについてレビュー長(ワード数)をカウントし、ヒストグラムで可視化します。

    # 4. Count the length of sentences
    all_sentences = training_sentences + testing_sentences
    all_labels = training_labels + testing_labels
    positive_sentence_length = [len(sentence.split(' ')) for sentence, label in zip(all_sentences, all_labels) if label == 1]
    negative_sentence_length = [len(sentence.split(' ')) for sentence, label in zip(all_sentences, all_labels) if label == 0]
    max_length = max(max(positive_sentence_length), max(negative_sentence_length))

    # 5. Plot the distribution
    plt.hist(positive_sentence_length, bins=max_length, alpha=0.5, rwidth=2, label='positive')
    plt.hist(negative_sentence_length, bins=max_length, alpha=0.5, rwidth=2, label='negative')
    plt.xlabel('Review length')
    plt.ylabel('Frequency')
    plt.legend()
    plt.show()

f:id:satojkovic:20200629210500p:plain

100〜200ワード前後が最も多いですね。

LSTMでSentiment Analysis

これまでは、語単位での特徴量化でレビューの感情分析を行いましたが、文章のコンテキストを考慮するともっとうまくPositive/Negativeの判定が出来そうです。 (単純にgoodやbadのような語が含まれているかどうかだけでなく、「最初はgoodだけど、最後にはdisappointedだった」みたいなレビューであれば、文脈上disappointedがより重要な語になるし、goodは重要度が低くなるみたいな)

LSTM層の導入も非常に簡単です。

    model = tf.keras.Sequential([
        tf.keras.layers.Embedding(vocab_size, embedding_dim, input_length=max_length),
        tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(32, return_sequences=True)),
        tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(16)),
        tf.keras.layers.Dense(6, activation='relu'),
        tf.keras.layers.Dense(1, activation='sigmoid')
    ])

ユニット数を指定してLSTM層を追加しています。ここでは、双方向のLSTMにするためにBidirectionalとし(順方向にも逆方向にも文脈を考慮)、双方向のLSTMを2層使いました。

f:id:satojkovic:20200622204815p:plain

LSTMを使わない場合もval_accuracyが80%程度だったので、あまり結果は変わらず(残念)。

入力次元数やユニット数などのハイパーパラメータのチューニングも含め、過学習を抑える必要がありそうです。

Sentiment Analysisの学習履歴をプロット

学習時のlossとaccuracyを確認するのも容易。model.fitの戻り値をhistoryとして受け取って、横軸にepoch数、縦軸にlossやaccuracyの値をとって表示するだけ。

    history = model.fit(padded, training_labels, epochs=num_epochs, validation_data=(testing_padded, testing_labels))
    acc = history.history['accuracy']
    loss = history.history['loss']
    val_acc = history.history['val_accuracy']
    val_loss = history.history['val_loss']
    epochs = range(len(acc))
    plt.plot(epochs, acc, 'r', label='acc')
    plt.plot(epochs, val_acc, 'b', label='val_acc')
    plt.plot(epochs, loss, 'r', linestyle='dashed', label='loss')
    plt.plot(epochs, val_loss, 'b', linestyle='dashed', label='val_loss')
    plt.legend()
    plt.show()

historyから取得可能な要素のリストは、keys()で確認出来る。

print(history.history.keys())

# this might produces the following
# dict_keys(['loss', 'accuracy', 'val_loss', 'val_accuracy'])

f:id:satojkovic:20200620125738p:plain

Sentiment AnalysisをGloveで行う

前回の続き。

前回はEmbedding layerも含めて学習したが、学習済みのGloveを使う。

最初に、glove.6B.100d.txt(wordとembeddingを行単位で記述)をロードして、embedding_indexを作る。次に、imdb_reviewsデータセットのwordに対して、対応するgloveのembeddingを格納したembedding_matrixを作成。後は、modelの記述において、Embedding layerのweightsにembedding_matrixを与えて、学習しないようにTrainable=FalseとすればOK。

    embedding_index = {}
    with open('glove.6B.100d.txt') as f:
        for line in f:
            values = line.split()
            word = values[0]
            coefs = np.asarray(values[1:], dtype='float32')
            embedding_index[word] = coefs

    embedding_matrix = np.zeros((vocab_size + 1, embedding_dim))
    for word, i in word_index.items():
        embedding_vector = embedding_index.get(word)
        if embedding_vector is not None:
            embedding_matrix[i] = embedding_vector

    # Glove is used for embedding layer.
    model = tf.keras.Sequential([
        tf.keras.layers.Embedding(vocab_size + 1, embedding_dim, input_length=max_length, weights=[embedding_matrix], trainable=False),
        tf.keras.layers.Flatten(),
        tf.keras.layers.Dense(6, activation='relu'),
        tf.keras.layers.Dense(1, activation='sigmoid')
    ])