[TensorFlow Certification Day14] TensorFlow Embedded文本相關model


Posted by Kled on 2020-09-17

接續著上一篇的, 繼續努力
把這邊再練習熟悉就可以去考試了

先再複習上次的imdb

#load tfds data
imdb, info = tfds.load("imdb_reviews", with_info=True, as_supervised=True)
train_data, test_data = imdb['train'], imdb['test']

training_sentences = []
training_labels = []

testing_sentences = []
testing_labels = []

#s,l都是儲存成tf.Tensor
#透過.numpy()轉換成numpy
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_final = np.array(training_labels)
testing_labels_final = np.array(testing_labels)

要把文字送進去model訓練有兩個問題

  1. 要把文字數字化
  2. input要相同長度

接下來就是今天的重頭戲, 首先是Tokenizer

sentences = [
    'I love my dog',
    'I love my cat'
]
#將文字變成數字, token化
#加標點符號不影響, 不會因為dog!, 就增加一個index
#透過空格或, 分割語句
#不分大小寫 I = i
#tokenizer會依照出現的頻率來排序, 越前面的出現頻率越高
tokenizer = Tokenizer(num_words = 100)
#根據fit_on_texts自動去tokenize, 會自動定義出token需要的長度有多少
tokenizer.fit_on_texts(sentences)
#word_index 是詞對照到數字的dict
word_index = tokenizer.word_index
print(word_index)
#{'i': 1, 'my' : 3, 'dog' : 4, 'cat' : 5, 'love' : 2}

#建立好token後, 對想轉換的句子做texts_to_sequences
sentences = [
    'I love my dog',
    'I love my cat'
    'You love my dog!',
    'Do you think my dog is amazing?'
]

sequences = tokenizer.texts_to_sequences(sentences)
print(sequences)
#沒看過的詞不會出現
#[[4, 2, 1, 3], [4, 2, 1, 6], [5, 2, 1, 3], [7, 5, 8, 1, 3, 9, 10]]

上面看到遇到沒看過的字就麻煩了, 有兩個方法可以解決這個問題

  1. database夠大
  2. 給予不知道的詞一個特殊的值

另外透過pad_sequences補充到相同長度

#要特別注意<OOV>不能跟真實數據的詞一樣, 否則會混淆
#這邊會自動把沒看過的東西補<OOV>, 然後再轉成數字
tokenizer = Tokenizer(num_words = 100, oov_token="<OOV>")
from tensorflow.keras.preprocessing.sequence import pad_sequences
#會把每一段padding補0到相同長度, 會自動找語句最長的當maxlen
padded = pad_sequences(sequences)
#限制最長的語句長度, post是指把pad補0補在後面, 預設是'pre'
padded = pad_sequences(sequences, padding='post', maxlen=5)
#建立model
#vocab_size是word_index size
#embedding_dim是希望詞向量的維度
#max_length是想要把多少個詞切成一個input
model = tf.keras.Sequential([
    tf.keras.layers.Embedding(vocab_size, embedding_dim, input_length=max_length),
    tf.keras.layers.Flatten(), #或使用tf.keras.layers.GlobalAveragePooling1D(),
    #Global出來的Cell數會比較少, 所以會比較快
    tf.keras.layers.Dense(6, activation='relu'),
    tf.keras.layers.Dense(1, activation='sigmoid')
])

model.fit(padded,
           training_labels_final,
           epochs = num_epochs,
           validation_data = (testing_padded, testing_labels_final))


e = model.layers[0]
weights = e.get_weights()[0]
print(weights.shape) #10000*16
#原本的word_index的key是word
#現在想把key改成數字 value為word
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])

import io
out_v = io.open('vecs.tsv', 'w', encoding='utf-8')
out_m = io.open('meta.tsv', 'w', encoding='utf-8')

#weights等於是words在這些項量上的投影

for word_num in range(1, vocab_size):
    word = reverse_word_index[word_num]
    embeddings = weights[word_num]
    out_m.write(word + "\n")
    out_v.write('\t'.join([str(x) for x in embeddings]) + "\n")

out_v.close()
out_m.close()

#https://projector.tensorflow.org/  可以看project後的分類效果

改成用subword, subword介於詞與字符之間, 能夠比較好的平衡OOV問題
subword有點像我們看英文的字首字尾

  1. 準備足夠大的訓練database
  2. 確定期望的subword詞表大小
  3. 將單詞拆分為字符序並在結尾添加"</w>", 統計單詞頻率
  4. 統計每一個連續字節對的出現頻率, 選擇最高頻率者合併成新的subword
  5. 重複第四步, 直到達到第二步設定的subword詞表大小, 或下一個最高頻率的字節對出現頻率為1

因為feature很多, 很難flatten(), 建議是使用GlobalAveragePooling1D
但是subword通常沒有意義, 所以訓練出來也會GG
需要透過RNN, 有Sequence的組起來這些subword才有意義
看起來不用會也沒有關係~~

把重點回到token來看, 這邊要做的是預測下一個字是甚麼, 自動產生文本

tokenizer = Tokenizer()
data = 'In the town of Athy one Jeremy Lanigan \n Battered away ... ...'
#corpus 這邊是把字體都變小寫, 並且每一句拆開
corpus = data.lower().split("\n")

tokenizer.fit_on_texts(corpus)
total_words = len(tokenizer.word_index) + 1 #+1個不在訓練的詞彙

input_sequences = []
#這邊做的動作是把每一句拆成從不同長度
#例如把一整句[4, 2, 66, 8, 67, 68, 69, 70]
#變成 注意下面至少是len >= 2
#[4, 2]
#[4, 2, 66]
#[4, 2, 66, 8]
#...
#[4, 2, 66, 8, 67, 68, 69, 70]
for line in corpus:
    token_list = tokenizer.texts_to_sequences([line])[0]
    for i in range(1, len(token_list)):
        n_gram_sequence = token_list[:i+1]
        input_sequences.append(n_gram_sequence)

#找出最長的sub line長度
max_sequence_len = max([len(x) for x in input_sequences]) 
#把前面都補0, 並轉換成nparray
input_sequences = np.array(pad_sequences(input_sequences, maxlen=max_sequence_len, padding='pre'))

#把每一句subline的最後一個拿去當label, 前面的全部都拿去當input data
xs = input_sequences[:,:-1]
labels = input_sequences[:,-1]
#把label換成one hot encode
ys = tf.keras.utils.to_categorical(labels, num_classes=total_words)

#Bidirectional是雙向, 可以前往後, 也可以前往後互相影響
model = Sequential()
model.add(Embedding(total_words, 64, input_length=max_sequence_len-1))
model.add(Bidirectional(LSTM(20)))
model.add(Dense(total_words, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
history = model.fit(xs, ys, epochs=500, verbose=1)

#想要從這句話產生100個詞
seed_text = 'Lauraence went to dublin'
next_words = 100
for _ in range(next_words):
    token_list = tokenizer.texts_to_sequences([seed_text])[0]
    token_list = pad_sequences([token_list], maxlen=max_sequence_len-1, padding='pre')
    predicted = model.predict_classes(token_list, verbose=0)

    output_word = ''
    #下面是把predicted轉換成詞
    #看哪個打中了index跟predicted一樣, 這個詞就是predict的詞
    for word, index in tokenizer.word_index.items():
        if index == predicted:
            output_word = word
            break
    seed_text += ' ' + output_word
print(seed_text)

這篇先到這邊, 原本想一篇做個結尾的, 但內容有點太多,
後續還有time series model留到下一篇講解


#AI #人工智慧 #機器學習 #machine learning #TensorFlow #tensorFlow Certification #Deep Learning #深度學習







Related Posts

Python Decorator 入門教學

Python Decorator 入門教學

綜合能力測驗 - 破關紀錄

綜合能力測驗 - 破關紀錄

簡明 Linux Shell Script 入門教學

簡明 Linux Shell Script 入門教學


Comments