stMind

about Tech, Computer vision and Machine learning

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')
    ])

CourseraのNLP in tensorflowを見てSentiment Analysisが容易に出来ると知る

www.coursera.org

tensorflow_datasetに用意されているimdb_reviewsデータセットを取得して、kerasのTokenizerを使って前処理して、Modelを書いて、学習。

これをテンプレートとして使えば、EmbeddingはBERTに変えたり、imdb_reviewsを自分で作ったデータセットにしたりして、拡張していけそう。

import tensorflow_datasets as tfds
import numpy as np

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

if __name__ == "__main__":
    # 1. Load imdb dataset
    imdb, info = tfds.load('imdb_reviews', with_info=True, as_supervised=True)

    # 2. Split imdb dataset into train / test data
    train_data, test_data = imdb['train'], imdb['test']

    # 3. Prepare sentences and labels
    training_sentences = []
    training_labels = []

    testing_sentences = []
    testing_labels = []

    for s, l in train_data:
        training_sentences.append(str(s.numpy()))
        training_labels.append(l.numpy())

    for s, l in test_data:
        testing_sentences.append(str(s.numpy()))
        testing_labels.append(l.numpy())

    training_labels = np.array(training_labels)
    testing_labels = np.array(testing_labels)

    # 4. Hyperparameters
    vocab_size = 10000
    embedding_dim = 16
    max_length = 120
    trunc_type = 'post'
    oov_token = '<OOV>'

    # 5. Tokenize
    tokenizer = Tokenizer(num_words=vocab_size, oov_token=oov_token)
    tokenizer.fit_on_texts(training_sentences)
    word_index = tokenizer.word_index
    sequences = tokenizer.texts_to_sequences(training_sentences)
    padded = pad_sequences(sequences, maxlen=max_length, truncating=trunc_type)

    testing_sequences = tokenizer.texts_to_sequences(testing_sentences)
    testing_padded = pad_sequences(testing_sequences, maxlen=max_length, truncating=trunc_type)

    # 6. Defining model
    model = tf.keras.Sequential([
        tf.keras.layers.Embedding(vocab_size, embedding_dim, input_length=max_length),
        tf.keras.layers.Flatten(),
        tf.keras.layers.Dense(6, activation='relu'),
        tf.keras.layers.Dense(1, activation='sigmoid')
    ])

    # 7. Compile and training
    model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
    model.summary()

    num_epochs = 10
    model.fit(padded, training_labels, epochs=num_epochs, validation_data=(testing_padded, testing_labels))
Model: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
embedding_1 (Embedding)      (None, 120, 16)           160000    
_________________________________________________________________
flatten_1 (Flatten)          (None, 1920)              0         
_________________________________________________________________
dense_2 (Dense)              (None, 6)                 11526     
_________________________________________________________________
dense_3 (Dense)              (None, 1)                 7         
=================================================================
Total params: 171,533
Trainable params: 171,533
Non-trainable params: 0
_________________________________________________________________
Epoch 1/10
782/782 [==============================] - 4s 5ms/step - loss: 0.5723 - accuracy: 0.7062 - val_loss: 0.4707 - val_accuracy: 0.8163
Epoch 2/10
782/782 [==============================] - 4s 5ms/step - loss: 0.3454 - accuracy: 0.8918 - val_loss: 0.4481 - val_accuracy: 0.8147
Epoch 3/10
782/782 [==============================] - 4s 5ms/step - loss: 0.1946 - accuracy: 0.9558 - val_loss: 0.5217 - val_accuracy: 0.8035
Epoch 4/10
782/782 [==============================] - 4s 5ms/step - loss: 0.1172 - accuracy: 0.9798 - val_loss: 0.5584 - val_accuracy: 0.8052
Epoch 5/10
782/782 [==============================] - 4s 5ms/step - loss: 0.0851 - accuracy: 0.9852 - val_loss: 0.6381 - val_accuracy: 0.8047
Epoch 6/10
782/782 [==============================] - 4s 5ms/step - loss: 0.0707 - accuracy: 0.9869 - val_loss: 0.6734 - val_accuracy: 0.8055
Epoch 7/10
782/782 [==============================] - 4s 5ms/step - loss: 0.0643 - accuracy: 0.9872 - val_loss: 0.7113 - val_accuracy: 0.8036
Epoch 8/10
782/782 [==============================] - 4s 5ms/step - loss: 0.0598 - accuracy: 0.9879 - val_loss: 0.7544 - val_accuracy: 0.8028
Epoch 9/10
782/782 [==============================] - 4s 5ms/step - loss: 0.0568 - accuracy: 0.9883 - val_loss: 0.7722 - val_accuracy: 0.8052
Epoch 10/10
782/782 [==============================] - 4s 5ms/step - loss: 0.0558 - accuracy: 0.9884 - val_loss: 0.8033 - val_accuracy: 0.8050

github.com

Tensorflowレポジトリのコミット数ランキングを表示するワンライナー

  1. git logコマンドのfomatオプションでauthor nameを出力
  2. sort
  3. uniq -cで連続する行数をカウント、重複行は削除
  4. sort -nrで降順にソート
  5. headで先頭から10件表示
$ git log --format='%an' | sort | uniq -c | sort -nr | head 
21995 A. Unique TensorFlower
3476 TensorFlower Gardener
1229 Yong Tang
1155 Shanqing Cai
1154 Derek Murray
1055 Gunhan Gulsoy
1046 Benoit Steiner
1036 Vijay Vasudevan
 865 River Riddle
 794 Martin Wicke

tensorflowのレポジトリをローカルに持ってない場合は、Github APIを使ってコミット履歴を取ってくる。(Pageを解釈するのはちょっと難しかったので置いといて...)

  1. wgetでcommitsをJSONで受け取る
  2. jqでjsonのauthor nameを抽出
  3. 上で実行した2以降の処理をする
$ wget -O - -q https://api.github.com/repos/tensorflow/tensorflow/commits?per_page=100 | jq '.[] | .commit.author.name' | sort | uniq -c | sort -nr | head
  16 "A. Unique TensorFlower"
   8 "TensorFlower Gardener"
   5 "Gunhan Gulsoy"
   4 "Yujing Zhang"
   3 "Zhenyu Tan"
   3 "Peter Hawkins"
   3 "Andy Ly"
   2 "tg-at-google"
   2 "Thomas O'Malley"
   2 "T.J. Alumbaugh"

以下のブログにインスパイアされてやってみました。

prithu.xyz

画像からの料理名推定

blog.stratospark.com

2017年なので少し前になりますが、画像から料理名を推定するプロジェクト。

InceptionV3のImageNet pre-trainedのモデルをFine tuningして、Food 101データセットで82.03% @ Top-1の精度が出たようです。

Food 101は、名前の通り101クラスで、各クラス1000画像含まれているので、メモリ上に展開すると80GB程度になるらしい。96GBのメモリを積んだマシンを使ったらしいが、これは富豪アプローチですね...

Data augmentationは、KerasのImageDataGeneratorで、 rotation_range、width_shift_range、height_shift_range、horizontal_flip、zoom_range、channel_shift_rangeあたりが使われてます。

学習は初期トライアルではAdamやAdaGradで試してたが、最終的にはSGDで学習。

ここから精度を上げるのは反復トライアルが必要な難しいタスクになりそうですが、スタート地点のBaselineとして使える気がします。

Baidu researchが作ったテキストからビデオを作成する技術

blog.deeplearning.ai

deeplearning.aiが発行しているnewsletterにあったVidPress。

URLを入力とすると、最初にWebページの内容を解析して、関連する記事も収集(解析には、Ernieというモデルが使われているよう)。 その後、テキストサマリを作成、サマリに関する合成音声を作成するのと、サマリに合う映像クリップを収集して、最後に映像と音声をアラインメントして出力する。

65%のユーザは、VidPressが作成したビデオを最後まで視聴したが、人が作成したビデオは途中で視聴をやめることが多かったという結果が得られたらしい。