Tensorflow2 RNN模型imdb電影評分預測和Character-level language model

import matplotlib as mpl
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
import pandas as pd
import os
import sys
import time
import sklearn
from tensorflow import keras

import tensorflow as tf
print(tf.__version__)
print(sys.version_info)
for module in mpl, np, pd, sklearn, tf, keras:
    print(module.__name__, module.__version__)

2.0.0
sys.version_info(major=3, minor=7, micro=3, releaselevel=‘final’, serial=0)
matplotlib 3.0.3
numpy 1.16.2
pandas 0.24.2
sklearn 0.20.3
tensorflow 2.0.0
tensorflow_core.keras 2.2.4-tf

7-3 電影評分數據

Embedding概論

$
在開始RNN前首先是需要將數據做Embedding的,做Embedding就是將每個單詞或者字符的one-hot編碼轉爲Dense編碼,在實際操作過程中的操作步驟如下
1. 構建語義庫
2. 將數據集中單詞或字符轉爲對應在語義庫中索引,vocab_size爲數據集索引的最大值
3. 手動確定需要變成的Dense集的維度embedding_dim
keras.layers.Embedding(vocab_size, embedding_dim, input_length = max_length),
$

Embedding有2種方式如下圖

在這裏插入圖片描述
在這裏插入圖片描述

imdb = keras.datasets.imdb
vocab_size = 10000
index_from = 3
(train_data, train_labels),(test_data, test_labels) = imdb.load_data(num_words=vocab_size, 
                                                                    index_from = index_from)
print(train_data.shape, train_labels.shape)
print(train_data[0], train_labels[0])
print(len(train_data[0]), len(train_data[1] ))
print(test_data.shape, test_labels.shape)                                                                   

在這裏插入圖片描述

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['END'] = 3
reverse_word_index = dict([(v, k) for k,v in word_index.items()])
def decode_review(text_ids):
    return " ".join([reverse_word_index.get(word_id, '<UNK>') for word_id in text_ids ])

decode_review(train_data[0])

在這裏插入圖片描述

#  數據補全 paddling
max_length = 500
train_data = keras.preprocessing.sequence.pad_sequences(train_data, 
                                                       value = word_index['<PAD>'],
                                                       padding = 'post', # post放在句子後面,pre:放在前面
                                                       maxlen = max_length)

test_data = keras.preprocessing.sequence.pad_sequences(test_data, 
                                                       value = word_index['<PAD>'],
                                                       padding = 'post', # post放在句子後面,pre:放在前面
                                                       maxlen = max_length)

在這裏插入圖片描述

# https://yq.aliyun.com/articles/221681  Embedding
embedding_dim = 16 # 每個word的向量長度16
batch_size = 128
model = keras.models.Sequential([
    # Embedding 層:1. define matrix: [vocab_size, embedding_dim] ; 
    # 2. 將一個樣本[vocab1,vocab2,...] 變成 max_length * embedding的矩陣
    # 3. 如果是批次讀取 則矩陣shape = batch_size * max_length * embedding
    keras.layers.Embedding(vocab_size, embedding_dim, input_length = max_length),
#     GlobalAveragePooling1D : 將上層的batch_size * max_length * embedding 變成 batch_size *  embedding
#     keras.layers.Flatten(),
    keras.layers.GlobalAveragePooling1D(), # 上面已經做了padding,這裏可以不用GlobalAveragePooling1D,可以直接展開 keras.layers.Flatten(),
    keras.layers.Dense(64, activation='relu'),
    keras.layers.Dense(1, activation='sigmoid')
])
model.summary()
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
history = model.fit(train_data, train_labels, epochs=20, batch_size = batch_size, validation_split=0.2)
# summary中 Output Shape 解讀 (None, 500, 16)  
# None:樣本數(batch_input_shape指定),
# 500:一條序列多少個詞  (input_length指定 )
# 16 :一個詞的Dense編碼維度爲多少 (embedding_dim指定)

在這裏插入圖片描述

plot_learning_curving(history) #  過擬合現象

在這裏插入圖片描述

上面model使用了合併與padding,缺點如下:所以需要使用循環神經網絡
  • 信息丟失
    • 多個embedding(詞嵌入)後使用了GlobalAveragePooling1D均值合併,可能序列裏有很多pad,
    • 這就是pad噪音,即便序列裏沒有pad,但是沒有體現出句子中詞語的主次,像這個數據集是電影
    • 評分的數據集,主謂語不那麼重要,表達情感的詞語更重要(pad噪音,無主次)
  • 無效計算太多,低效
    • 太多的padding
上面模型出現過擬合,可以使用單層RNN,雙層RNN

7-5 RNN

在這裏插入圖片描述

# numpy 實現 RNN前向傳播
timesteps = 100
input_features = 32
output_features = 64

# 輸入有100個時間點,每個時間點有32維的數據
inputs = np.random.random((timesteps,input_features)) # (100,32)
state_t = np.zeros((output_features,)) # (64,)

W = np.random.random((output_features,input_features)) # input的權重 (64, 32)
U = np.random.random((output_features,output_features)) # state的權重 (64,64)
b = np.random.random((output_features,)) # bias (64,)

successive_outputs = []
for input_t in inputs:
    # 按timesteps進行迭代
    # output_t是一個64維的向量
    output_t = np.tanh(np.dot(W,input_t)+np.dot(U,state_t)+b)  # (64,)
    # 將當前時刻的輸出保存到successive_outputs中
    successive_outputs.append(output_t)
    # 當前時刻的輸出作爲下一時刻的state
    state_t = output_t
    
final_output_sequence = np.concatenate(successive_outputs,axis=0)
final_output_sequence.shape

在這裏插入圖片描述

rnn 隱藏單元

在這裏插入圖片描述

單層RNN

# https://yq.aliyun.com/articles/221681  Embedding
embedding_dim = 16 # 每個word的向量長度16
batch_size = 128
single_rnn_model = keras.models.Sequential([
    keras.layers.Embedding(vocab_size, embedding_dim, input_length = max_length),
    # keras.layers.SimpleRNN
    keras.layers.SimpleRNN(units=64, return_sequences  = False), # 5184=64 * 16 + 64*64 +64, 這裏的64爲units=64,不是下面Dense的64
    keras.layers.Dense(64, activation='relu'),
    keras.layers.Dense(1, activation='sigmoid')
])
single_rnn_model.summary()
single_rnn_model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

在這裏插入圖片描述

雙向多層RNN

# 雙向多層RNN,改善驗證集精度  但是發現過擬合,接來下使用單層的雙向rnn並減少參數看下結果
embedding_dim = 16 
batch_size = 128
model = keras.models.Sequential([
    keras.layers.Embedding(vocab_size, embedding_dim, input_length = max_length),
    #  keras.layers.Bidirectional(keras.layers.LSTM
    keras.layers.Bidirectional(keras.layers.SimpleRNN(units=64, return_sequences  = True)), # 10368 = (16*64 + 64*64 + 64) * 2
    keras.layers.Bidirectional(keras.layers.SimpleRNN(units=64, return_sequences  = False)), # 24704 = (128*64 + 64*64 + 64)*2
    keras.layers.Dense(64, activation='relu'),
    keras.layers.Dense(1, activation='sigmoid')
])
model.summary()
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

單層的雙向rnn

# 單層的雙向rnn並減少參數看下結果,發現還是過擬合,比7-3中的普通神經網絡效果還差,說明RNN太過強大,容易出現過擬合,
# 可以用dropout或正則化處理
embedding_dim = 16 
batch_size = 512
model = keras.models.Sequential([
    keras.layers.Embedding(vocab_size, embedding_dim, input_length = max_length),
    keras.layers.Bidirectional(keras.layers.SimpleRNN(units=32, return_sequences  = False)), 
    keras.layers.Dense(32, activation='relu'),
    keras.layers.Dense(1, activation='sigmoid')
])
model.summary()
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

7-6 字符模型進行文本生成

在這裏插入圖片描述

# 使用莎士比亞數據集 https://storage.googleapis.com/download.tensorflow.org/data/shakespeare.txt
input_filepath = "./shakespeare.txt"
text = open(input_filepath).read()
print(len(text))
print(text[:30])

在這裏插入圖片描述

模型構建思路

$

模型構建思路
  1. generete vocab
  2. build mapping : char -> id ,{char:id} char與id的字典形式
  3. data -> id_data: 將文本data通過第二步的字典轉爲數字
  4. 定義輸入輸出 ,如 輸入爲 abcd -> 則輸出爲 bcdy, 通過切分id_data獲得
  5. 將輸入(訓練集)輸出(標籤)用構建的模型進行訓練參數並保存
  6. 加載保存的參數進行預測
    $
# 1. generate vocab
vocab = sorted(set(text))
print(len(vocab))
print(vocab)

在這裏插入圖片描述

# 2 . build mapping : char -> id
char2idx = {char:idx for idx,char in enumerate(vocab)}
print(char2idx)

在這裏插入圖片描述

idx2char = np.array(vocab)
# 3. data -> id_data
text_as_int =  np.array([char2idx[x] for x in text])
print(text_as_int[0:10])
print(text[0:10])

在這裏插入圖片描述

def split_input_target(id_text):
    # abcde -> abcd , bcde
    return id_text[0:-1] ,id_text[1:]

char_dataset = tf.data.Dataset.from_tensor_slices(text_as_int)
seq_length = 100
seq_dataset = char_dataset.batch(seq_length+1, drop_remainder=True) # drop_remainder: 如果最後一個batch不夠batch_size則捨棄
for seq in seq_dataset.take(2):
    print(seq)
    print(repr("".join(idx2char[seq.numpy()])))

在這裏插入圖片描述

seq_dataset = seq_dataset.map(lambda x: split_input_target(x))]
for item_input, item_output in seq_dataset.take(1):
    print(item_input.numpy(), item_input.shape)
    print(item_output.numpy())

在這裏插入圖片描述

batch_size = 64
seq_dataset = seq_dataset.shuffle(10000).batch(batch_size, drop_remainder=True)
vocab_size = len(vocab)
embedding_dim = 256
rnn_units = 1024

def build_model(vocab_size, embedding_dim, rnn_units, batch_size):
    model = keras.models.Sequential([  
        keras.layers.Embedding(vocab_size, embedding_dim, batch_input_shape = [batch_size, None]),
        keras.layers.SimpleRNN(units = rnn_units, return_sequences = True) ,# 每個時間戳都要輸出;添加這2個參數 # , statful=True, recurrent_initializer='glorot_uniform' 效果更好    
        keras.layers.Dense(vocab_size)
    ])
    return model

model =  build_model(vocab_size, embedding_dim, rnn_units, batch_size )
model.summary()
# summary中 Output Shape 解讀(64, None, 256) ,
# 64:樣本數(batch_input_shape指定),
# None:一條序列多少個詞  (input_length指定 )
# 256 :一個詞的Dense編碼維度爲多少 (embedding_dim指定)

在這裏插入圖片描述

for input_example_batch, target_example_batch in seq_dataset.take(1):
    example_batch_predictions = model(input_example_batch)
    print(example_batch_predictions.shape, target_example_batch.shape)

(64, 100, 65) (64, 100)

# 有隨機採樣:隨機從輸出結果中抽取n_samples個值 和貪心採樣:獲取值最大的那個值,
# 我們使用隨機採樣,
samples_indices = tf.random.categorical(logits=example_batch_predictions[0], num_samples=1) # (100,65) - > (100,1)
samples_indices = tf.squeeze(samples_indices, axis=-1)
samples_indices

在這裏插入圖片描述

# 查看下結果, predict 輸出的是亂七八糟的,因爲還沒有訓練
print("input: ", repr("".join( idx2char[input_example_batch[0]] )))
print("output: ", repr("".join( idx2char[target_example_batch[0]] )))
print("predice;" ,repr("".join( idx2char[samples_indices] ))) 

在這裏插入圖片描述

# 自定義損失函數
def loss(labels, logits):
    #from_logits 的default = False;爲False時會將已經經過sigmoid或softmax概率化的值重新返回爲logits,這裏不是概率化的值
    # 所以將from_logits設爲True,我不知道爲啥model最後輸出不用激活函數
    return keras.losses.sparse_categorical_crossentropy(labels, logits, from_logits=True) 

model.compile(optimizer='adam', loss=loss)
example_loss = loss(target_example_batch, example_batch_predictions)
# target_example_batch : (64,100) ; example_batch_predictions : (64, 100, 65) 
# 因爲loss使用的是sparse_categorical_crossentropy,則說明要求輸入的labels是數值型,然後會將數值型轉換爲One-hot
# 並與logits進行交叉熵計算獲得損失值
print(example_loss.shape)
print(example_loss.numpy().mean())
output_dir = "./text_generation_checkpoints"
if not os.path.exists(output_dir):
    os.mkdir(output_dir)
checkpoint_predix = os.path.join(output_dir, 'model_{epoch:02d}')
checkpoint_callbacks =[
    keras.callbacks.ModelCheckpoint(filepath = checkpoint_predix, save_weights_only= True)
]

epochs = 20
history = model.fit(seq_dataset, epochs = epochs,  callbacks=checkpoint_callbacks)
# 查看最新保存的模型
tf.train.latest_checkpoint(output_dir)

‘./text_generation_checkpoints/model_20’

#  從checkpoint載入模型
model2  = build_model(vocab_size, embedding_dim, rnn_units, batch_size=1) # batch_size:1,一次只輸出一個句子
model2.load_weights(tf.train.latest_checkpoint(output_dir)) 
model2.build(tf.TensorShape([1, None]))  # 1:一個樣本(因爲batch_size設爲1),None表示輸入可以是變長的序列,build函數時輸入接收什麼格式 參考:https://blog.csdn.net/qq_34964399/article/details/104084070
# 文本生成流程
# start ch Sequnce A # 一個字符A
# A -> model ->b  : 將A輸入model輸出一個字符b,輸出的字符b是需要採樣的,用tf.random.categorical進行採樣
# A.append(b) -> B :將字符b添加到A後面變成序列B ,此時B = Ab
# B -> model ->c
# B.append(c) -> C  # C = Abc
# ...  所以說輸入的序列是變長的,上面的build接收變長序列
model2.summary()

在這裏插入圖片描述

def generate_text(model, start_string, num_generate=1000 ):
    input_val = [char2idx[x] for x in start_string]
    input_val = tf.expand_dims(input_val, 0)
    text = []
    model.reset_states()
    temperature = 0.5 # if temperature>1,random  ;temperature<1,越傾向於greedy的算法,取最大值,可能是因爲
    # 當<1時,predictions / temperature可能分佈更陡峭,ramdom取值的時候回偏向取概率更大的值
    
    for _ in range(num_generate):
        predictions = model2(input_val) #  dim: (batch_size, len(start_string), 100)   batch_size = 1,  <==> model2.predict(input_val) 
        # 
        predictions = predictions / temperature
        predictions = tf.squeeze(predictions, axis=0) # 去除維度爲1的,爲了輸入下面的接口。現在dim: (len(start_string), 100) 
        predict_id = tf.random.categorical(predictions, num_samples=1)[-1][0].numpy() # 通過start_string預測出的字符
        text.append(predict_id)
        input_val = tf.expand_dims([predict_id], 0)
#     input_val = tf.concat([input_val, tf.expand_dims([predict_id],0) ], axis=1) # 預測的字符加入到原始字符進行下一步預測,這樣的效果會好           
    return start_string + "".join(idx2char[text])
print(generate_text(model2, 'All we', num_generate=500 ))

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章