stMind

about Tech, Computer vision and Machine learning

Natural Language Processing Specialization

www.coursera.org

CourseraのNLP Specialization、完走しました!

NLP Specializationは全部で4つのコース、各コースはさらに4週間分のサブコースで構成されている専門講座になります。最初のコースの終了が7月だったので、全4コースを終了するのに4ヶ月かかったことになります。約4ヶ月のコースと記述されているので、ほぼ標準通りの進捗でした。

また、Certificationを最後にもらうためにSubscriptionに入っていたので、4ヶ月で合計約2万円の出費でした。

一つ一つのビデオは短く(2分から3分くらいが主)、疲れている日は一つだけちょっと見るか、みたいな感じで続けられるのが良かったかな。後は、どちらかというと理論的に踏み込んだ内容というよりは、概念や問題を理解するためのIntuition重視の説明で、またプログラミング課題も理論的に難しいところはスキップされていたりするので、これだけでエキスパートになれるというわけではないけれど、深層学習登場以前の古典的な機械学習アルゴリズムから、NNベースのアルゴリズムに至る過程を体系的に学べるので、自然言語処理技術や関連ワードを知っている状態からもう一歩進んだ状態になるには良いコースだと思いました。

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