2019年1月30日 星期三

深度學習 - 天氣預測練習 - 多種模型嘗試與優化

先上結論:以供後續方便複習
  1. 遇到新問題時,最好首先為你選擇的指標建立一個基於常識的基準(baseline)。藉由baseline 與手上訓練出來的模型比較,分辨模型建立的方向是否正確。
    ex.分辨MINST手寫數字的baseline=10%,分辨IMDb文章正反面的baseline=50%...等等
  2. 在嘗試計算代價較高的模型之前,先嘗試一些簡單的模型,以此證明增加計算代價是有意義的。有時簡單模型就是你的最佳選擇。
  3. 如果時間順序對數據很重要,那麼循環網絡是一種很適合的方法,先將時間數據展平的模型無法訓練出與時間順訊相關的特徵。
  4. 想要在循環網絡中使用dropout,應該使用一個不隨時間變化的dropout掩碼與循環dropout掩碼。
  5. 在處理語言翻譯等複雜問題時,與單個RNN層相比,堆疊RNN的表示能力更加強大。但它的計算代價也更高。堆疊RNN在較小,較簡單的問題上可能不一定有用。
  6. 雙向RNN從兩個方向查看一個序列,它對自然語言處理問題非常有用。但如果在序列數據中最近的數據比序列開頭包含更多的信息,那麼這種方法的效果就不明顯。

開始進入主題,來練習天氣預測的問題,先下載數據
 https://s3.amazonaws.com/kerasdatasets/jena_climate_2009_2016.csv.zip
解壓縮後得到一個.csv檔,打開後可以看到資料大概如下。

此資料檔是德國耶拿的馬克思• 普朗克生物地球化學研究所的氣象站紀錄,在這個數據集中,每10 分鐘記錄14 個不同的量(比如氣壓、溫度、濕度...等)
接著用編碼來練習讀取數據
import os
data_dir = 'C:\\Users\\lido_lee\\Downloads\\jena_climate'
fname = os.path.join(data_dir, 'jena_climate_2009_2016.csv')
f = open(fname)
data = f.read()
f.close()
lines = data.split('\n')
header = lines[0].split(',')
lines = lines[1:]
print(header)
print(len(lines))

['"Date Time"', '"p (mbar)"', '"T (degC)"', '"Tpot (K)"', '"Tdew (degC)"', '"rh (%)"', '"VPmax (mbar)"', '"VPact (mbar)"', '"VPdef (mbar)"', '"sh (g/kg)"', '"H2OC (mmol/mol)"', '"rho (g/m**3)"', '"wv (m/s)"', '"max. wv (m/s)"', '"wd (deg)"']
420551
總共有420551筆資料,420551*10/60/24/365 ~= 8 ,總共8年的data。
將溫度的data完整畫出
import matplotlib.pyplot as plt
temp = float_data[:, 1] # Temperature
plt.plot(range(len(temp)), temp,'g')
plt.xlabel('10 min /step')
plt.ylabel('Temperature')
plt.show()

我們的目的是要練習訓練模型可以用來預測溫度,"在得到10天的天氣變化資料,預測24小時候的溫度。"
  • 首先,先將數據標準化,因為要使用前200000個data做訓練集,故標準化前200000個data即可。
    標準化後的圖如下:
  • 我們需要一個生成器 (generator),可以用來產出訓練集、驗證集、測試集,有點類似先前練習的Imagedatagenerator ,只是這邊需要完整的練習寫一遍。

    這個生成器會有的參數:
    1.'data',輸入data 要分成[0:200000],[200001:300000],[300001:]分別是訓練集、驗證集、測試集。
    2.'min_index','max_index',呈上,選取資料範圍由'min_index','max_index'來定義。
    3.'delay',因為是預測問題,要預測24小時候的溫度,所以要delay=144個data。
    4.'lockback',得到10天的天氣變化的'lockback=10天的數據量',這邊lockback=1440。
    5.'samples',生成器的輸出,用來訓練模型的資料。
    6.'targets',生成器的輸出,用來訓練模型預測的溫度。
    7.'shuffle',是打亂樣本,還是按順序抽取樣本。
    8.'batch_size',每個批量的樣本數。
    9.'step',數據採樣的周期(單位:時間步),我們將其設為6,為的是每小時抽取一個數據點。

    def generator(data, 
                  lookback, 
                  delay, 
                  min_index, 
                  max_index, 
                  shuffle=False, 
                  batch_size=128, 
                  step=6):
        if max_index is None:
            max_index = len(data) - delay - 1 #考慮到最後一個data要給模型預測用
        i = min_index + lookback
        while 1:
            if shuffle:
                """給定一組數列,來決定之後的samples,shuffle=True則給定random數列。
                np.random.radint(1,10,5) => (1,4,5,4,8) 在1~10之間隨意抓取五次數值
                所以len(rows) 由最後的size決定,len(rows) = batch_size"""
                rows = np.random.randint( min_index + lookback, max_index, size=batch_size)
            else:
                if i + batch_size >= max_index:  #防止i+batch_size後停止while loop
                    i = min_index + lookback    #讓i回到原點
                rows = np.arange(i, min(i + batch_size, max_index))
                i += len(rows)
            samples = np.zeros((len(rows), lookback // step, data.shape[-1]))
            #產生訓練samples 形狀為(128,1440/6=240,14)
            targets = np.zeros((len(rows),))
            for j, row in enumerate(rows):
                indices = range(rows[j] - lookback, rows[j], step)
                samples[j] = data[indices]
                targets[j] = data[rows[j] + delay][1]
            yield samples, targets

    lookback = 1440
    step = 6
    delay = 144
    batch_size = 128
    train_gen = generator(float_data,
                          lookback=lookback,
                          delay=delay,
                          min_index=0,
                          max_index=200000,
                          shuffle=True,
                          step=step,
                          batch_size=batch_size)
    val_gen = generator(float_data,
                        lookback=lookback,
                        delay=delay,
                        min_index=200001,
                        max_index=300000,
                        step=step,
                        batch_size=batch_size)
    test_gen = generator(float_data,
                         lookback=lookback,
                         delay=delay,
                         min_index=300001,
                         max_index=None,
                         step=step,
                         batch_size=batch_size)

    來看看generator 的批量產出
    >>>next(train_gen)
    generator 每次產出128批資料,每一批含240個(10天的資料)數據list,每組資料共14個不同的量,也就是形狀(128,240,14)的numpy array。

    接著定義一下驗證&測試批量
    val_steps = (300000 - 200001 - lookback) //batch_size
    test_steps = (len(float_data) - 300001 - lookback) //batch_size
  • 在定義模型之前,先假設baseline,再定義模型看效能是否優於baseline。
    這邊假設,由於天氣變化的連續性,隔天的溫度近乎今天的溫度,所以我們可以將未來24小時候的溫度等於現在的溫度來做預測,我們使用平均絕對誤差(MAE)指標來評估這種方法,np.mean(np.abs(preds - targets))
    def evaluate_naive_method():
        batch_maes = []
        for step in range(val_steps):
            samples, targets = next(val_gen)
            preds = samples[:, -1, 1]
            mae = np.mean(np.abs(preds - targets))
            batch_maes.append(mae)
        print(np.mean(batch_maes))
    
    evaluate_naive_method()
    得到 0.2897359729905486
    轉換為攝氏  celsius_mae = 0.2897359729905486 * std[1] = 2.564887434980494
    誤差約為2.56度C。
  • 定義模型,先練習全連接層
    from keras.models import Sequential
    from keras import layers
    from keras.optimizers import RMSprop
    model = Sequential()
    model.add(layers.Flatten(input_shape=(lookback // step, float_data.shape[-1])))
    model.add(layers.Dense(32, activation='relu'))
    model.add(layers.Dense(1))
  • 選擇優化器、損失函數,丟進fit_gernerator訓練。
    model.compile(optimizer=RMSprop(), loss='mae')
    history = model.fit_generator(train_gen,
                                  steps_per_epoch=500,
                                  epochs=20,
                                  validation_data=val_gen,
                                  validation_steps=val_steps)
  • 畫出訓練過程
    import matplotlib.pyplot as plt
    loss = history.history['loss']
    val_loss = history.history['val_loss']
    epochs = range(1, len(loss) + 1)
    plt.figure()
    plt.plot(epochs, loss, 'go', label='Training loss')
    plt.plot(epochs, val_loss, 'g+', label='Validation loss')
    plt.title('Training and validation loss')
    plt.legend()
    plt.show()
    val_loss 大概在0.35左右,明顯得比 baseline 差許多,這是因為將一個有時間依賴性的序列用全連接層攤平訓練是一個不明智的做法,也許我們應該在這個問題上使用循環神經網絡來訓練模型。

  • 使用循環網絡之前,先嘗試使用運算代價較低的GRU而不是直接使用LSTM來定義模型,來看看初步的GRU是否有比baseline來的好。
    修改模型:
    from keras.models import Sequential
    from keras import layers
    from keras.optimizers import RMSprop
    model = Sequential()
    model.add(layers.GRU(32, input_shape=(None, float_data.shape[-1])))
    model.add(layers.Dense(1))
    利用GRU後,明顯的比使用全連接層來的好,MAE誤差最好有0.26左右,但在第五輪後開始出現過擬合現象,我們需要來嘗試降低過擬合。

  • 在循環層中加入Dropout ,Keras的每個循環層都有兩個與dropout 相關的參數,一個是dropout:是一個浮點數,指定該層輸入單元的dropout 比率;另一個是recurrent_dropout,指定循環單元的 dropout 比率。
    這邊要注意的是,在循環層單元中加入的dropout 是對每個時間步應該使用相同的dropout 掩碼(dropout mask,相同模式的捨棄單元),而不是讓dropout 掩碼隨著時間步的增加而隨機變化,因為每個時間步使用相同的dropout 掩碼,可以讓網絡沿著時間正確地傳播其學習誤差,而隨時間隨機變化的dropout 掩碼則會破壞這個誤差信號,並且不利於學習過程 Yarin Gal.al,"Uncertainty in deep learning"
    在Keras中簡單地就能加入循環層dropout:(1060的GPU跑了約1個小時...)
    from keras.models import Sequential
    from keras import layers
    from keras.optimizers import RMSprop
    model = Sequential()
    model.add(layers.GRU(32, dropout=0.2,
                         recurrent_dropout=0.2,
                         input_shape=(None, float_data.shape[-1])))
    model.add(layers.Dense(1))
    加入dropout後,成功降低過擬合的現象,MAE誤差也大概在0.26左右,接著嘗試使用其他優化方法。
  • 使用堆疊循環層,我們可以考慮增加網絡容量,只要過擬合的問題不是很嚴重的話,可以嘗試增加網絡容量來增加模型性能。
    在Keras中增加堆疊循環層時,所有中間層都應該返回完整的輸出序列(一個 3D 張量),而不是只返回最後一個時間步的輸出。這可以通過指定 return_sequences=True 來實現:
    from keras.models import Sequential
    from keras import layers
    from keras.optimizers import RMSprop
    model = Sequential()
    model.add(layers.GRU(32,dropout=0.1,
                         recurrent_dropout=0.5,
                         return_sequences=True,
                         input_shape=(None, float_data.shape[-1])))
    model.add(layers.GRU(64, activation='relu',
                         dropout=0.1,
                         recurrent_dropout=0.5))
    model.add(layers.Dense(1))
    添加一層後模型並沒有顯著改進,提高網絡能力的回報在逐漸減小。
  • 嘗試其它優化 ex.雙向RNN(GRU, LSTM...),這邊因為跑太久,直接給結論,在此例中因為最靠近預測溫度的時間的資料包含的訊息可能較為重要,所以在反向RNN的表現會非常的差()。
    在使用雙向RNN時,較多精準預測的權重偏向正向RNN,即反向RNN是沒什麼參考價值,甚至可以說是浪費運算資源。
    但是,在自然語言工程(NLP)使用RNN是非常有幫助的,就好像文順字序不響讀影閱一樣。














2019年1月28日 星期一

深度學習 - 理解 RNN & LSTM & GRU

循環神經網絡(Recurrent Neural Network)見wiki,相信在看完之後還是無法了解RNN到底在做什麼。
簡單來說,RNN就是將一組序列輸入到網絡中,隨時間將輸出再返回到輸入中去調整權重參數。......還是不懂對吧!!

用簡單程式碼來說明,務必要一行一行讀懂。

假設有一組序列input_sequence,輸入元素input_t,目前網絡的狀態state_t,一開始我們要初始化state_t ,在未訓練的情況下state_t = 0。可以簡單地表示RNN如下:
state_t = 0        #網絡初始狀態
for input_t in input_sequence:   #每個序列的元素都透過神經網絡處理
    output_t = f(input_t, state_t)    #output 會經由input_t & state_t 調整網絡的權重
    state_t = output_t     #再把output返回給state_t

此 function 為 activation function ,舉例如下
ouput_t = f(input_t, state_t) = np.tanh(np.dot(W, input_t) + np.dot(U, state_t) + b)
W,U,b 為權重參數,類似 Dense layer的概念,需要一個 activation 來訓練模型。

還是不太了解的話,參考大神GoKu 的教學

在Keras中,很簡單地就能將RNN加入模型中。
#載入IMDb 數據庫,做資料預處理
from keras.datasets import imdb
from keras.preprocessing import sequence
max_features = 10000
maxlen = 200
batch_size = 32
print('Loading data...')
(input_train, y_train), (input_test, y_test) = imdb.load_data(num_words=max_features)
print(len(input_train), 'train sequences')
print(len(input_test), 'test sequences')
print('Pad sequences (samples x time)')
input_train = sequence.pad_sequences(input_train, maxlen=maxlen)
input_test = sequence.pad_sequences(input_test, maxlen=maxlen)
print('input_train shape:', input_train.shape)
print('input_test shape:', input_test.shape)

在設定模型時,加入RNN層實際練習看看
#設定訓練模型,加入RNN層
from keras.layers import Dense , Embedding , SimpleRNN
from keras.models import Sequential
model = Sequential()
model.add(Embedding(max_features, 32))
model.add(SimpleRNN(32)) #加入RNN 層到模型中
model.add(Dense(32, activation='relu'))
model.add(Dense(1, activation='sigmoid'))

#選擇優化器、損失函數、指標
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])

#丟進fit訓練模型
history = model.fit(input_train, y_train,
                    epochs=10,
                    batch_size=128,
                    validation_split=0.2)

#將訓練過程畫出
import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(acc) + 1)
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()


訓練精度達到85%左右,但是在先前練習只有全連接層時有達到87.5%的精度,為何使用RNN精度還比較低!?

這原因是因為RNN在處理文本較長的序列時,每個時間點ht都要計算一次梯度權重,當權重W>1時,W^t會指數爆炸,而當W<1時,會很快地趨近於0,這就是造成權重指數級爆炸或消失的問題 (Exploding and vanishing gradients problem),會難以捕捉長期時間關聯。


這圖片舉例,當處理的文本越長,圖中等號右邊展開就會越長,導致運算代價過高且會造成權重指數級爆炸或消失的問題 ,所以通常會限定序列長度而不是全部的文本,導致訓練成果沒有全連接層來的好。

所以我們要使用RNN改良版,LSTM (Long Short-Term Memory)

Bengio et al, "Learning long-term dependencies with gradient descent is difficult", IEEE Transactions on Neural Network, 1994
Pascanu et al, "On the difficulty of training recurrent neural networks", ICML2013


長短期記憶(LSTM : long short-term memory) ,是具有處理長期依賴性的特殊類型的RNN。

LSTM還可以為消失/爆炸梯度問題提供解決方案。



一個簡單的LSTM單元是由4個gate組成:
分別是i, f, o, g :
  • i:input gate,判定是否寫入單元
  • f:forget gate,判定是否抹除單元
  • o:output gate,判定輸出多少個單元
  • g:gate gate,因為沒有更好的名稱,故稱之gate gate,判定寫入多少個單元
與RNN不同之處在於網絡中攜帶著跨越時間步的的信息。它在不同的時間步的的值叫作Ct,其中C表示 carry 的含意。

  • Forgot gate :在獲得先前狀態的輸出h(t-1)之後,forgot gate 幫助我們決定必須從h(t-1)狀態中移除什麼,從而僅保留有用的訊息。 Forgot gate 是一個sigmoid函數,會輸出介於[0,1]之間的狀態。 
  • Input gate + Gate gate: 決定將當前輸入中的新內容添加到我們當前的單元格狀態,並根據我們希望添加它們的大小進行縮放。
    sigmoid層決定要更新哪些值,並且tanh層為新候選者創建一個向量以添加到當前單元狀態。
  • Output gate : 最後將決定從我們的單元狀態輸出什麼,這將由sigmoid函數完成。
    我們將輸入與tanh相乘以壓縮(-1,1)之間的值,然後將其與sigmoid函數的輸出相乘,以便我們只輸出我們想要的值。
Gradient Flow 流程如下圖:

我將其作為動畫流程,可能比較好理解,如下 :
將C(t-1)和f相乘,是為了抹除攜帶數據流中的不相關信息。同時,i和g都提供關於當前的信息,可以用新信息來更新攜帶軌道C(t),其中C(t)=dot(f,C(t-1))+dot(i,g)。
但歸根結底,這些解釋並沒有多大意義,因為這些運算的實際效果是由參數化權重決定的,而權重是以端到端的方式進行學習,每次訓練都要從頭開始,不可能為某個運算賦予特定的目的。

就如Keras作者所言:
你只需要記住LSTM單元的作用:允許過去的信息稍後重新進入,從而解決梯度消失問題。

了解LSTM後,在Keras一樣輕易地就能將LSTM層加入模型當中,實際操作如下:
#將LSTM層加入模型當中
from keras.layers import Dense , Embedding , LSTM
from keras.models import Sequential
model = Sequential()
model.add(Embedding(max_features, 32))
model.add(LSTM(32))  #LSTM 層
model.add(Dense(1, activation='sigmoid'))

將訓練結果畫出:

訓練精度到達88%

調整maxlen = 500
訓練精度到達88.5%


門控循環單元(GRU,gated recurrent unit),類似LSTM且工作原理與LSTM相同,但它做了一些簡化,使其在較小的數據集上能表現出更好的性能(訓練速度較快,但準確度可能較LSTM差)。
簡而言之,就是在訓練模型時,可以先使用GRU訓練(速度較快)看到初步的結果,再使用LSTM來優化模型(較精準)。
#定義模型
from keras.models import Sequential
from keras.layers import Embedding, Dense , GRU
model = Sequential()
model.add(Embedding(max_features, 32))
model.add(GRU(32))  #加入GRU 層
model.add(Dense(1))








2019年1月26日 星期六

深度學習 - IMDb數據集 - 原始文本結合GloVe預訓練詞嵌入模型

在上一篇 深度學習 - IMDb數據集 - 文本與序列預處理 將處理好的文本,接續將詞嵌入Glove模型加入。

https://nlp.stanford.edu/projects/glove 下載文件名為 glove.6B.zip 的檔案(822MB),裡面包含400,000 個單詞(或非單詞的標記)的100 維嵌入向量

讀取該文件看看內容長甚麼樣子,比較好理解接下來要做什麼。
import os
glove_dir = 'C:\\Users\\Lido_Lee\\Downloads\\glove.6B'
#'cp950' error 需在open中加入(encoding = 'utf-8-sig')
f = open(os.path.join(glove_dir, 'glove.6B.100d.txt'), encoding = 'utf-8-sig')
i=0
word=[]
for line in f:
    values = line.split()
    print(values)
    word.append(values)
    i += 1
    if i >= 4:
        break
f.close()



可以看到Values[0] 都對應一個文字,分別是"the",",(逗號)",".(句號)","of"
看一下每個word含意:
>>>len(word[0])
101
這邊可以看出,每個字都是由100個tensor表示。
了解了之後,接著我們建構一個嵌入矩陣
#構建一個可以加載到Embedding 層中的嵌入矩陣
import numpy as np
import os
glove_dir = 'C:\\Users\\Lido_Lee\\Downloads\\glove.6B'
embeddings_index = {}
#'cp950' error 需在open中加入(encoding = 'utf-8-sig')
f = open(os.path.join(glove_dir, 'glove.6B.200d.txt'), encoding = 'utf-8-sig')
for line in f:
    values = line.split()
    word = values[0]
    coefs = np.asarray(values[1:], dtype='float32')
    embeddings_index[word] = coefs
f.close()
print('Found %s word vectors.' % len(embeddings_index))
Found 400000 word vectors.

#準備GloVe 詞嵌入矩陣
embedding_dim = 100
embedding_matrix = np.zeros((max_words, embedding_dim))
for word, i in word_index.items():
    if i < max_words:
        embedding_vector = embeddings_index.get(word)
        if embedding_vector is not None:
            embedding_matrix[i] = embedding_vector

#定義模型
from keras.models import Sequential
from keras.layers import Embedding, Flatten, Dense
model = Sequential()
model.add(Embedding(max_words, embedding_dim, input_length=maxlen))
model.add(Flatten())
model.add(Dense(32, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.summary()

#將預訓練的詞嵌入加載到Embedding 層中
model.layers[0].set_weights([embedding_matrix])
model.layers[0].trainable = False

#選擇優化器、損失函數、指標
model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['acc'])
#丟進fit訓練模型
history = model.fit(x_train, y_train,
                    epochs=15,
                    batch_size=32,
                    validation_data=(x_val, y_val))

#將訓練好的模型保存起來
model.save_weights('pre_trained_glove_model.h5')

#畫出訓練過程
import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(acc) + 1)
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()

plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()

 驗證精度大概在70%左右。

如果不使用Glove 預訓練的詞嵌入,將之mark起來。
"""
#將預訓練的詞嵌入加載到Embedding 層中
model.layers[0].set_weights([embedding_matrix])
model.layers[0].trainable = False
"""

 可以看到精度約為85%左右。


若使用Glove 預訓練的詞嵌入,且讓Glove的權重是可以跟隨著訓練過程來調整,會是怎樣的情況?
#將預訓練的詞嵌入加載到Embedding 層中
model.layers[0].set_weights([embedding_matrix])
model.layers[0].trainable = True
 其精度在84%左右。

這邊發生了什麼事情!? 為何預訓練的驗證精度比自己訓練的精度還要低?

這邊猜想是,預訓練的模型在處理少量資料的時候,可以藉由詞向量的特徵是可遷移的性質,在少量資料表現是比較好的,因為新的模型在少量資料時根本沒有提取到任何特徵。但是在處理大量資料(如IMDb)時,新的模型根據大量資料提取的特徵,會是處理這類問題的重要特徵,而不是預訓練模型是屬於較泛化的特徵。


2019年1月25日 星期五

深度學習 - IMDb數據集 - 文本與序列預處理

在先前IMDb練習中,都是使用Keras內建處理好的文本(已向量化了),可以直接丟進fit訓練模型,看似簡單,其實在實際拿到的data並非如此。
現在就來練習如何將拿到的原始文本data,轉化為向量吧(還是透過Keras工具,呵呵...)。
http://mng.bz/0tIo 可以下在IMDb原始文本,將下載好的data 解壓縮後,放在路徑'C:\\Users\\lido_lee\\Downloads\\aclImdb'

#將文本讀取存放並分類
import os
imdb_dir = 'C:\\Users\\lido_lee\\Downloads\\aclImdb'
train_dir = os.path.join(imdb_dir, 'train')
labels = [] #建立一個存放label 的list
texts = [] #建立一個存放文本 的list
for label_type in ['neg','pos']:
    dir_name = os.path.join(train_dir, label_type)
    for fname in os.listdir(dir_name):
        if fname[-4:] == '.txt':
            # 'cp950' error 需在open中加入(encoding = 'utf-8-sig')
            f = open(os.path.join(dir_name, fname), encoding = 'utf-8-sig')
            texts.append(f.read())
            f.close()
            if label_type == 'neg':
                labels.append(0)
            else:
                labels.append(1)

來看看存入的資料 & label
>>>texts[0]
"Story of a man who has unnatural feelings for a pig. Starts out with a opening scene that is a terrific example of absurd comedy. A formal orchestra audience is turned into an insane, violent mob by the crazy chantings of it's singers. Unfortunately it stays absurd the WHOLE time with no general narrative eventually making it just too off putting. Even those from the era should be turned off. The cryptic dialogue would make Shakespeare seem easy to a third grader. On a technical level it's better than you might think with some good cinematography by future great Vilmos Zsigmond. Future stars Sally Kirkland and Frederic Forrest can be seen briefly."
>>>labels[0]0 ==>表示'neg'

接著要來了解如何對文本資料做分詞(Tokenizer)&序列預處理pad_sequences ,我們會用到Keras的 Tokenizer ,見連結。
函數如下:
keras.preprocessing.text.Tokenizer(num_words=None,
                                                         filters='!"#$%&()*+,-./:;<=>?@[\]^_`{|}~ ', 
                                                         lower=True,
                                                         split=' ', 
                                                         char_level=False, 
                                                         oov_token=None, 
                                                         document_count=0)
其中參數表示:
  • num_words: 需要保留的最大詞數,基於詞頻。只有最常出現的 num_words 詞會被保留。
  • filters: 一個字符串,其中每個元素是一個將從文本中過濾掉的字符。默認值是所有標點符號,加上製表符和換行符,減去 ' 字符。
  • lower: 布爾值。是否將文本轉換為小寫。
  • split: 字符串。按該字符串切割文本。
  • char_level: 如果為 True,則每個字符都將被視為標記。
  • oov_token: 如果給出,它將被添加到 word_index 中,並用於在 text_to_sequence 調用期間替換詞彙表外的單詞。
    默認情況下,刪除所有標點符號,將文本轉換為空格分隔的單詞序列(單詞可能包含 ' 字符)。這些序列然後被分割成標記列表。然後它們將被索引或向量化。
Tokenizer 的成員函數:
  • fit_on_text(texts) 使用一系列文檔來生成token詞典,texts為list類,每個元素為一個文檔。
  • texts_to_sequences(texts) 將多個文檔轉換為word下標的向量形式,shape為[len(texts),len(text)] -- (文檔數,每條文檔的長度)
  • texts_to_matrix(texts) 將多個文檔轉換為矩陣表示,shape為[len(texts),num_words]
Tokenizer 的属性函數:
  • word_counts:字典,將單詞(字符串)映射為它們在訓練期間出現的次數。僅在調用fit_on_texts之後設置。
  • word_docs: 字典,將單詞(字符串)映射為它們在訓練期間所出現的文檔或文本的數量。僅在調用fit_on_texts之後設置。
  • word_index: 字典,將單詞(字符串)映射為它們的排名或者索引。僅在調用fit_on_texts之後設置。
  • document_count: 整數。分詞器被訓練的文檔(文本或者序列)數量。僅在調用fit_on_texts或fit_on_sequences之後設置。
序列預處理pad_sequences 將多個序列截斷或補齊為相同長度。

函數如下:
keras.preprocessing.sequence.pad_sequences(sequences,
                                                                         maxlen=None,
                                                                         dtype='int32',
                                                                         padding='pre',
                                                                         truncating='pre',
                                                                         value=0)

  • sequences:浮點數或整數構​​成的兩層嵌套列表。
  • maxlen:None或整數,為序列的最大長度。大於此長度的序列將被截短,小於此長度的序列將在後部填0。
  • dtype:返回的numpy array的數據類型。
  • padding:‘pre’或‘post’,確定當需要補0時,在序列的起始還是結尾補。
  • truncating:‘pre’或‘post’,確定當需要截斷序列時,從起始還是結尾截斷。
  • value:浮點數,此值將在填充時代替默認的填充值0。
該函數將一個 num_samples 的序列(整數列表)轉化為一個 2D Numpy 矩陣,其尺寸為 (num_samples, num_timesteps)。 num_timesteps 要么是給定的 maxlen 參數,要么是最長序列的長度。比 num_timesteps 短的序列將在末端以 value 值補齊。比 num_timesteps 長的序列將會被截斷以滿足所需要的長度。補齊或截斷發生的位置分別由參數 pading 和 truncating 決定。向前補齊為默認操作。

經過大概了解 Tokenizer & pad_sequences 怎麼使用之後,我們將texts list進行分詞處理存進sequences中,然後將處理後的分詞建立一個字典word_index,最後將sequences透過pad_sequences 處理轉化成長度為maxlen的data(np矩陣)。
#對數據進行分詞
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
import numpy as np
maxlen = 200
training_samples = 20000
validation_samples = 10000
max_words = 10000
#利用 Keras Tokenizer 過濾文本的單詞
tokenizer = Tokenizer(num_words=max_words)
#接續 Tokenizer之後使用.fit_on_texts#將過濾的文本存入list
tokenizer.fit_on_texts(texts)
sequences = tokenizer.texts_to_sequences(texts)
word_index = tokenizer.word_index
print('Found %s unique tokens.' % len(word_index))
data = pad_sequences(sequences, maxlen=maxlen)
labels = np.asarray(labels)
print('Shape of data tensor:', data.shape)
print('Shape of label tensor:', labels.shape)
都轉化為numpy.ndarray之後,print出來的結果:
Found 88582 unique tokens.
Shape of data tensor: (25000, 200)
Shape of label tensor: (25000,)

接著來到文本與序列預處理的最後階段,劃分訓練集&驗證集。
我們會需要將資料打亂,
將資料與標籤順序打亂後,後還是互相對應的。
利用在python中 a,b =b,a 的語法,直接做swap,像似 [a,b] = [b,a] = array[1,2] = array[2,1] 類似概念。
舉個例:
import numpy as np
a = [1,2,3,4,5,6,7,8,9,10]
b = ['a','b','c','d','e','f','g','h','i','j']
a = np.asarray(a)
b = np.asarray(b)
indices = np.arange(len(a))
np.random.shuffle(indices)
a = a[indices]
b = b[indices]
print(a ,"\n" ,b)
[ 5  6  3  9  7  8  1  2 10  4]
 ['e' 'f' 'c' 'i' 'g' 'h' 'a' 'b' 'j' 'd']
得到互相對應的array,就是我們要的。
最後將訓練集&驗證集打亂後分配好:
#將數據劃分為訓練集和驗證集
"""
首先要打亂數據,因為一開始數據中的樣本是排好序的
(所有負面評論都在前面,然後是所有正面​​評論)
"""
indices = np.arange(data.shape[0])
np.random.shuffle(indices)
data = data[indices]
labels = labels[indices]
x_train = data[:training_samples]
y_train = labels[:training_samples]
x_val = data[training_samples: training_samples + validation_samples]
y_val = labels[training_samples: training_samples + validation_samples]


2019年1月24日 星期四

Python 基礎練習

以下是我剛接觸python 時,練習寫的程式,有些是用螢幕擷取,現在因為在學習Deep-Learning,沒有時間整理之前的練習程式,就當做記錄我的學習過程,沒有參考價值。
之後有空會再回來整理的!!

Github for windows clone with Pycharm HackMD

#99乘法表練習

深度學習 - IMDb數據集 - Embedding層添加


詞嵌入是自然語言處理(NLP)中語言模型與表徵學習技術的統稱。概念上而言,它涉及從每個單詞一維的空間到具有更低維度的連續向量空間的數學嵌入,每個單詞或詞組被映射為實數域上的向量。與one-hot 編碼得到的詞向量不同,詞嵌入是從數據中學習得到的。常見的詞向量維度是256、512 或1024(處理非常大的詞表時)。與此相對,one-hot 編碼的詞向量維度通常為 20000 或更高(對應包含20000 個標記的詞表)。因此,詞向量可以將更多的信息塞入更低的維度中。
one-hot 編碼得到的向量是二進制的、稀疏的(絕大部分元素都是0)、維度很高的(維度大小等於詞表中的單詞個數),而詞嵌入是低維的浮點數向量(即密集向量,與稀疏向量相對)

其中理論的部分,有空再來詳讀,先來實際練習將embedding層加入模型中,
以IMDb實際操作如下。

#加載IMDb 數據,準備用於Embedding 層
from keras.datasets import imdb
from keras import preprocessing
max_features = 10000 #特徵單詞的個數
maxlen = 20 #文本只截取前20個字(後續再討論)
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features)
#將整數列表轉換成形狀為(samples,maxlen) 的二維整數張量
x_train = preprocessing.sequence.pad_sequences(x_train, maxlen=maxlen)
x_test = preprocessing.sequence.pad_sequences(x_test, maxlen=maxlen)

看一下轉換後的資料,只擷取文章的前20個字。
>>>x_train[0]
Out[3]:  array([  65,   16,   38, 1334,   88,   12,   16,  283,    5,   16, 4472,
        113,  103,   32,   15,   16, 5345,   19,  178,   32])

添加Embedding層,如要連接Dense層至少需要兩個參數:標記的個數(這裡是10000,即最
大單詞索引+1)和嵌入的維度(這裡是8),input_length取決於文本的長度(這裡給一個變數maxlen)
from keras.models import Sequential
from keras.layers import Flatten, Dense, Embedding
model = Sequential()
model.add(Embedding(10000, 8, input_length=maxlen))
model.add(Flatten())
model.add(Dense(1, activation='sigmoid'))

看看model結構
>>>model.summary()

#選擇優化器、損失函數、指標
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
#丟進fit訓練
history = model.fit(x_train, y_train,epochs=10,batch_size=32,validation_split=0.2)


#將訓練過程畫出
import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(acc) + 1)
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b+', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b+', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()

可以得到驗證精度在75%左右,大概在epochs = 5 左右開始overfit。

接著我們來看訓練過程與maxlen的關係,在正常情況下,maxlen 越大越好,表示文章的完整性越高,但是整篇文章都丟進來訓練會浪費計算資源,那要取多少才好呢?
修改一下編碼,讓maxlen 從50~500跑看看驗證精度的變化,雖然時間有點久...我先去買個飲料先。

可以看到,當文本的內容大約200個字左右,精度就差不多在87~88%左右,其實這也是大部分film review文章的長度。
藉由此可以得知之後在調整訓練模型的時候,maxlen取值約200就好,不需要在更大了。

完整程式碼如下:
from keras.layers import Embedding
import numpy as np
embedding_layer = Embedding(1000, 64)
x_len_of_film_review=[] #紀錄maxlen長度
y_film_review_acc=[]  #記錄隨maxlen 長度變化的精度
from keras.datasets import imdb
from keras import preprocessing
#讓maxlen 從50~500 總共訓練11次看精度變化
for i in range(50,501,50):
    max_features = 10000    maxlen = i
    x_len_of_film_review.append(maxlen)  #紀錄maxlen長度
    (x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features)

    x_train = preprocessing.sequence.pad_sequences(x_train, maxlen=maxlen)
    x_test = preprocessing.sequence.pad_sequences(x_test, maxlen=maxlen)

    from keras.models import Sequential
    from keras.layers import Flatten, Dense, Embedding
    model = Sequential()
    model.add(Embedding(10000, 8, input_length=maxlen))
    model.add(Flatten())
    model.add(Dense(1, activation='sigmoid'))
    model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
    model.summary()

    history = model.fit(x_train, y_train,epochs=5,batch_size=32,validation_split=0.2)
    val_acc = history.history['val_acc']
    val_acc_mean = np.mean(val_acc)
    # 記錄隨maxlen 長度變化的精度 (取每次訓練平均值)
    y_film_review_acc.append(val_acc_mean)

import matplotlib.pyplot as plt
plt.plot(x_len_of_film_review, y_film_review_acc, 'bo')
plt.title('maxlen of film review vs val_acc')
plt.xlabel('maxlen of film review')
plt.ylabel('val_acc_mean')
plt.legend()
plt.show()