2019年2月25日 星期一

深度學習 - LSTM生成文本 - 唐詩 - 文本預處理

深度學習 - LSTM生成文本 - 唐詩 - 文本預處理

經過LSTM 生成文本練習過後,我就想來實踐一下李宏毅老師在上課時曾說過,有研究生演示過 LSTM 生成唐詩,還蠻有趣的!!
於是上網找了一下,已經有人發布至 Github 了,我們拿來練習一下吧!!
首先下載文本全唐诗.txt ,我個人不喜歡簡中,我寫了個翻譯程式(用Colab實現),把文本丟進根目錄後,就可以翻譯成繁體中文文檔,下載來用。
先來看一下文本樣貌:
其中包含了 主標題"全唐詩 捲一" 與 子標題"捲1_1【帝京篇十首】李世民"

目標 : 我們要將每首詩存成list

思路 : 將標題去掉,且每首詩皆被空行包覆,所以只要逐行讀取一首詩到空格時跳到下一首詩。

如何將標題去掉!? 以下代碼練習將過程紀錄:
先將文件逐行讀取。
with open(poetry_file, "r" ,encoding="utf-8") as f: for i in range(0, 3): line = f.readline() print(line,end="") print(len(line))
全唐詩 捲一
8

1
  捲1_1【帝京篇十首】李世民
17
第一行為何是8? google 了一下 “python3 編碼轉換” 使用 json 可以幫助我們了解,修改程式碼如下:
import json with open(poetry_file, "r" ,encoding="utf-8") as f: for i in range(0, 3): line = f.readline() u = json.dumps(line) print(u) print(line,end="") print(len(line))
"\ufeff\u5168\u5510\u8a69 \u6372\u4e00\n"
全唐詩 捲一
8
"\n"

1
"\u3000\u3000\u63721_1\u3010\u5e1d\u4eac\u7bc7\u5341\u9996\u3011\u674e\u4e16\u6c11\n"
  捲1_1【帝京篇十首】李世民
17
可以從輸出觀察到,空格" “& 換行”\n" 都算一個list的元素。
大概了解之後的第一件事,我們要去除主標題,但是主標題到底多長? 隨便找個最長的看看print出來看看
print(len("全唐詩 捲八百四十三"))
10
import json x= u'】' u= json.dumps(x) print(u)
"\u3011"
子標題呢!? 我們可以看到子標題皆含有"【","】" 特殊符號,從剛剛透過 json 印出來可以知道這兩個特殊符號是 “\u3010” & “\u3011”,所以在逐行讀取時,只要找到這兩個符號,就可以跳過。
利用正則表達示來尋找"\u3010" & “\u3011” ,用以下程式碼 test 看看,逐行讀取,如果有找到特殊符號就 print : “True”。
import re with open(poetry_file, "r" ,encoding="utf-8") as f: for i in range(0, 5): line = f.readline() if re.search(u'\u3010', line): print("True",line) else : print('Flase',line)
Flase 全唐詩 捲一
Flase 
True   捲1_1【帝京篇十首】李世民
Flase 
Flase 秦川雄帝宅,函榖壯皇居。綺殿韆尋起,離宮百雉?。
知道這些特殊符號,可以利用正則表達式 Regular expression operations來清理文本資料,也可以說利用正則表達式抓出我們要的資料,可參考regex教學
在了解文檔內容要如何清理後,我們將文本資料建立list。
以下是清理文本的代碼,也是參考原著再修改。
# *-* coding:utf-8 *-* poetry = [] #用來存詩的list old_line = '' #判斷該行是空行還是詩。 import re with open(poetry_file, "r" ,encoding="utf-8") as f: while 1: line = f.readline() #跳過主標題&子標題,遇到空行時重置old_line if len(line)<2 or"全唐詩"in line or":" in line or "【"in line: old_line = '' #逐行詩丟進list中 elif old_line == '': if "-" in line: #將有些詩後面會用 "--"備註詩人,將取其前面。 line = line.split("-")[0] if "□" in line: #將有□過濾掉,這是翻譯後產生的... line = re.sub('□',"",line) if "?" in line: line = line.replace("?","") #翻譯後有些"?",刪掉。 if re.search('[a-zA-Z0-9]',line): #有些詩的開頭會有英文!? 刪掉。 line = re.sub('[a-zA-Z0-9]',"",line) poetry.append(line[0:-1]) #將該行丟進list中 old_line = line #下一行還是同一首詩的line,加到同一個list中,表同一首詩。 else: if "-" in line: line = line.split("-")[0] if "□" in line: line = re.sub('□',"",line) if "?" in line: line = line.replace("?","") if re.search('[a-zA-Z0-9]',line): line = re.sub('[a-zA-Z0-9]',"",line) poetry[-1] = poetry[-1]+line[0:-1] old_line = line if not line: break #最後將文章內有()註解都刪掉 i=0 for poem in poetry: if "(" and ")" in poem: poem = poem.split("(")[0] + poem.split(")")[1] poetry[i] = poem i = i + 1 #有些遺留單個誇號註解(少量),整行刪掉 final_poetry = poetry.copy() j = 0 for poem in poetry: if "(" in poem or ")" in poem: final_poetry.pop(j) j = j-1 j = j+1
看看剩下多少詩可以訓練
print('poetry :',len(poetry)) print('final_poetry :',len(final_poetry))
poetry : 48209
final_poetry : 48125
還有剩下48125 首詩,夠我們訓練模型。
最後將 final_poetry 輸出存檔。
pre_process_poetry_file = os.path.join(data_dir, 'pre_process_poetry.txt') with open(pre_process_poetry_file, "w",encoding="utf-8") as w: for i in final_poetry : w.write(i+"\n")
呼…接下來就是將文字轉換成向量了。

2019年2月21日 星期四

深度學習 - 使用循環神經網絡(LSTM)生成文本

接下來,練習的東西越來越有趣了! 這也是為什麼我想學習 AI 的原因,現在越來越多聊天機器人、會下圍棋機器人(AlphaGo),還屌虐圍棋界高手,最近 DeepMind 又發表了 AlphaStar 玩星海爭霸II 戰勝職業選手。
想想自己未來也能建個 model 來訓練自己打傳說對決,輕鬆上S排,就迫不及待想成為AI 的專家(誤...)

使用循環神經網絡(LSTM)生成文本,要怎麼做到呢?

首先,要先了解生成文本是屬於哪一類的問題?

訓練生成文本的模型,基本上也是輸入一堆的文本資料,讓模型學到文字間的分佈關係,接著輸出是預測一段文字接著要放入哪個文字的機率,ex. 我今天很開__ ,__應該預測填入"心",這種給定前面的標記(token),能夠對下一個標記的概率進行建模的網絡叫作語言模型(language model)。語言模型能夠捕捉到語言的潛在空間(latent space),即語言的統計結構。

總之,就是一個機率的問題,也是多分類的問題,損失函數(loss function)使用categorical_crossentropy。如果模型的輸出是對所有可能的字符做 softmax,得到下一個字符的概率分佈。這個 LSTM 叫作字符級的神經語言模型(character-level neural language model),如下圖,如果是對單詞,則為單詞級的神經語言模型(word-level neural language model)。

在生成文本的時候,在預測生成下一個字符或單詞要如何選擇,有兩個方法:
  1. greedy sampling,貪婪採樣,在生成過程只選擇可能性最大的,意即只有某個特定字符的概率為 1,其他字符概率都為 0,這樣做會讓生成的文章更接近真實文章,同時也會讓文章重複率提高,使文章變得無趣。
  2. stochastic sampling,隨機採樣,在概率分佈中隨機選擇其中一個,我們無法控制生成的字符,會讓文章看起來有點無俚頭,但是這樣可以讓模型產生更有意思的句子,是我們無法想像的。
我們需要採取兩種採樣間的平衡,我們引入一個叫作 softmax 溫度(softmax temperature)的參數,用於表示採樣概率分佈的熵,即表示所選擇的下一個字符會有多麼出人意料或多麼可預測。給定一個temperature 值,將按照下列方法對原始概率分佈(即模型的softmax 輸出)進行重新加權,計算得到一個新的概率分佈。

來實際練習字符級的LSTM 文本生成,書中使用範例是尼采 Nietzsche 的文學作品
#下載並解析初始文本文件
import keras
import numpy as np
path = keras.utils.get_file('nietzsche.txt', origin='https://s3.amazonaws.com/text-datasets/nietzsche.txt')
text = open(path).read().lower()
print('Corpus length:', len(text))

#將字符序列向量化
maxlen = 60     #提取60 個字符組成的序列
step = 3        #每3個字符採樣一個新序列
sentences = []  #保存所提取的序列
next_chars = [] #保存目標(即下一個字符)
for i in range(0, len(text) - maxlen, step):
    sentences.append(text[i: i + maxlen])
    next_chars.append(text[i + maxlen])
print('Number of sequences:', len(sentences))
chars = sorted(list(set(text)))
print('Unique characters:', len(chars))
char_indices = dict((char, chars.index(char)) for char in chars)
print('Vectorization...')
x = np.zeros((len(sentences), maxlen, len(chars)), dtype=np.bool)
y = np.zeros((len(sentences), len(chars)), dtype=np.bool)
for i, sentence in enumerate(sentences):
    for t, char in enumerate(sentence):
        x[i, t, char_indices[char]] = 1
    y[i, char_indices[next_chars[i]]] = 1

#建構網絡模型
from keras import layers
model = keras.models.Sequential()
model.add(layers.LSTM(128, input_shape=(maxlen, len(chars))))
model.add(layers.Dense(len(chars), activation='softmax'))

#編譯器,優化器&損失函數
optimizer = keras.optimizers.RMSprop(lr=0.01)
model.compile(loss='categorical_crossentropy', optimizer=optimizer)

#給定模型預測,採樣下一個字符的函數
def sample(preds, temperature=1.0):
    preds = np.asarray(preds).astype('float64')
    preds = np.log(preds) / temperature
    exp_preds = np.exp(preds)
    preds = exp_preds / np.sum(exp_preds)
    probas = np.random.multinomial(1, preds, 1)
    return np.argmax(probas)

#生成文本循環
import random
import sys
for epoch in range(1, 60):
    print('epoch', epoch)
    model.fit(x, y, batch_size=128, epochs=1)
    start_index = random.randint(0, len(text) - maxlen - 1)
    generated_text = text[start_index: start_index + maxlen]
    print('--- Generating with seed: "' + generated_text + '"')
    model.save_weights('LSTM_gen_model.h5') #將model 保存起來
    for temperature in [0.2, 0.5, 1.0]:
        print('------ temperature:', temperature,"\n")
        sys.stdout.write(generated_text)
        for i in range(400):
            sampled = np.zeros((1, maxlen, len(chars)))
            for t, char in enumerate(generated_text):
                sampled[0, t, char_indices[char]] = 1.
            preds = model.predict(sampled, verbose=0)[0]
            next_index = sample(preds, temperature)
            next_char = chars[next_index]
            generated_text += next_char
            generated_text = generated_text[1:]
            sys.stdout.write(next_char)

可以看到訓練過程中,每個epochs 會隨機生成一段種子文本,然後經由模型預測生成後面的文章。隨著temperature的不同,會產生不一樣的文章。

舉例,在某一次生成的文本中
temperature=0.2 所產出的文章的其中一段"the mankind to the sense of the sense of the presence and the really the best the"

temperature=0.5 "the same noursely and the dease and person which be been an ential the great"

temperature=1.0 "may wild pain immanced to times the and turning way only his egolees lead one for bace regreelens"

可以看到隨著temperature 越大,生成的文章越有創造性,且重複性越低。一定要嘗試多種採樣策略!在學到的結構與隨機性之間,巧妙的平衡能夠讓生成的序列非常有趣。

2019年2月20日 星期三

深度學習 - 模型的高級架構

Deep Learning 基礎的模型都已經大概了解了。然而在業界與學界,都正在快速的發展奠基在基礎之上的高級模型,讓學習的效率更高對任務的處理更精準。這邊要練習幾個已經發展的高級模型:
  1. batch normalization (批標準化)
  2. depthwise separable convolution (深度可分離卷積)
  3. model ensembling (模型集成)
  • Batch normalization (批標準化)
    我們知道 normalization 對處理資料的重要性,見先前的練習,也可看CS231課程
    ,在梯度下降法的過程中,因為同時有多個參數一起在function 中做梯度下降,而參數間的差異過大會導致梯度下降的過程中不是最直接的朝最低點方向做梯度下降,所以我們做normalization 來讓資料在資料空間中的分佈是較接近的(如下圖表示)。

    而批標準化又是什麼呢!?

    標準化的過程是將數據減去其平均值使其中心為0,然後將數據除以其標準差使其標準差為1,這種做法可以讓數據分佈的中心為0,同時縮放到均方差為1。

    了解標準化用意之後,就可以理解為何需要批標準化。

    在訓練過程中,資料經過神經網絡輸出後,不會再保有中心是0、均方差為1的特性,所以在每一層layer之後再做一次標準化的動作,有助於梯度下降法尋找最小 loss。

    此做法有助於梯度傳播(這一點和殘差連接很像)。對於有些特別深的網絡,只有包含多個BatchNormalization 層時才能進行訓練。例如,BatchNormalization 廣泛用於 Keras 內置的許多高級卷積神經網絡架構,比如 ResNet50、Inception-V3 和 Xception。

    BatchNormalization 層通常在卷積層或密集連接層之後使用。
    用先前MNIST_CNN 的模型來練習加入BatchNormalization :
    #建構CNN網絡
    from keras import layers
    from keras import models
    model = models.Sequential()
    model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
    model.add(layers.BatchNormalization())  #加入BatchNormalization
    model.add(layers.MaxPooling2D((2, 2)))
    model.add(layers.Conv2D(64, (3, 3), activation='relu'))
    model.add(layers.BatchNormalization())  #加入BatchNormalization
    model.add(layers.MaxPooling2D((2, 2)))
    model.add(layers.Dropout(0.5))  #加入dropout層,預防過擬合
    model.add(layers.Conv2D(64, (3, 3), activation='relu'))
    model.add(layers.Flatten())
    model.add(layers.Dense(64, activation='relu'))
    model.add(layers.BatchNormalization())  #加入BatchNormalization
    model.add(layers.Dense(10, activation='softmax'))

    Sergey Ioffe, Christian Szegedy "Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift. " 2015 paper.


  • Depthwise separable convolution (深度可分離卷積)
    簡單來說,Depthwise separable convolution就是一個可以替代 Conv2D,還可以讓模型更加輕量(即更少的可訓練權重參數)、速度更快(即更少的浮點數運算),還可以讓任務性能提高幾個百分點. 見作者Francois Chollet 的 Paper.
    隨後這個架構又被使用到MobileNet上,因為其輕量的特性,可以使深度學習模型在行動裝置上訓練簡單的辨識模型,從而讓行動裝置加入AI的可行性又更進一步了。

    讓我又再次驚嘆!! Google 實在是太神啦啦啦啦啦!!

    另外,想了解更多細節,有 Blogger 有寫 paper 的中文心得,可以更好的理解。
    1.Tommy Huang
    2.Cheng-Shiang Li

    將 Depthwise separable convolution加入模型中訓練看看,一樣用MNIST_CNN 的模型 來練習。
    #建構CNN網絡
    from keras import layers
    from keras import models
    model = models.Sequential()
    #使用SeparableConv2D 建構模型
    model.add(layers.SeparableConv2D(32, 3,activation='relu',input_shape=(28, 28, 1)))
    model.add(layers.BatchNormalization())  #加入BatchNormalization
    model.add(layers.SeparableConv2D(64, 3, activation='relu'))
    model.add(layers.MaxPooling2D(2))
    model.add(layers.BatchNormalization())  #加入BatchNormalization
    model.add(layers.SeparableConv2D(64, 3, activation='relu'))
    model.add(layers.MaxPooling2D(2))
    model.add(layers.Dropout(0.5))
    model.add(layers.Flatten())
    model.add(layers.Dense(64, activation='relu'))
    model.add(layers.BatchNormalization())  #加入BatchNormalization
    model.add(layers.Dense(10, activation='softmax'))

  • model ensembling (模型集成)
    模型集成就是利用多個模型所預測或分類的結果,取其加權平均。以分類問題為例:
    preds_a = model_a.predict(x_val)
    preds_b = model_b.predict(x_val)
    preds_c = model_c.predict(x_val)
    preds_d = model_d.predict(x_val)
    final_preds = 0.5 * preds_a + 0.25 * preds_b + 0.1 * preds_c + 0.15 * preds_d

    就好比瞎子摸象,每個人(模型)摸到的地方不同,如果摸到鼻子,會以為是蛇,如果要知道大象的全部,就需要集合每個人摸到的結果。

    在做模型集成時,每個集成的模型要竟可能的好,且模型之間越不同越好,才能學習到不同的特徵。要盡量避免使用複製同一個模型,然後只使用不同的隨機初始值,或是改變數據的輸入順序,這樣做只會有些微的改變,因為模型的多樣性(diversity)太低了。

    作者Francois Chollet 和 Andrei Kolev曾經使用多種樹模型和深度神經網絡的集成,在Kaggle
    Higgs Boson Machine Learning Challenge 競賽中獲得第四名。

2019年2月17日 星期日

Tensorflow - Tensorboard Embedding Projector

在 Keras - callbacks 中見識到了 tensorboard 的厲害,接著來練習將先前練習過的數據利用 tensorboard 可視化,可以見tensorflow 官方範例
先練習用MNIST 手寫數字辨識 套用 tensorboard 可視化,如下
完整程式碼 : My Github
在 #Projector 可以看到 tensorboard 可以透過你選擇的降維算法自動將其降至二維或三維,可選的降維算法有主成分分析(PCA)和 t-分佈隨機近鄰嵌入(t-SNE)。

t-SNE 的 Embedding 都是藍點,蠻不直覺的。
還有方法可以將對應 label輸入 metadata,可以將每個點用圖片顯示,甚至可以對圖片做顏色區別。這真的是很酷的功能!!

完整程式碼 : My Github

以上皆參考 Pinch of Intelligence Blog , 
Simple Introduction to Tensorboard Embedding Visualisation


接著,還可以練習 Fashion-MNIST 的數據集,也是10大類分類問題。
首先,下載數據集,參考連結。fashion-mnist_test.csv & fashion-mnist_train.csv

完整程式碼 : My Github

2019年2月16日 星期六

深度學習 - 使用Keras callbacks and TensorBoard

每次在訓練模型時,都要try一下跑幾個epochs,並藉由 matplotlib 將訓練過程的'acc' & 'loss' 畫出來,看大概在什麼時候會發生過擬合,然後再用較少次的epochs重新訓練模型,這樣的過程是多麼的繁瑣與浪費時間。
現在練習使用 Keras 的 callbacks 函數,可以在訓練過程中,訪問模型狀態與性能,還能對模型採取行動,中斷、保存、改變模型狀態等。

這邊列出幾個 callbacks : 更多的callbacks 與其用法請參考連結。
  1. keras.callbacks.ModelCheckpoint
  2. keras.callbacks.EarlyStopping
  3. keras.callbacks.LearningRateScheduler
  4. keras.callbacks.ReduceLROnPlateau
  5. keras.callbacks.CSVLogger
  • ModelCheckpoint 與 EarlyStopping 練習
    使用先前 IMDb數據集 - 原始文本結合GloVe預訓練詞嵌入模型 的程式碼來練習,加入callbacks函數
    #加入callback
    import keras
    callbacks_list = [keras.callbacks.EarlyStopping(monitor='acc',
                                                    patience=1,),
                      keras.callbacks.ModelCheckpoint(filepath='my_model.h5',
                                                      monitor='val_loss',
                                                      save_best_only=True,)]
    EarlyStopping : 表示在 acc 被觀察再下1輪之後沒有改善則停止模型的訓練。
    ex. epoch1:'acc'=80 epoch2:'acc'=79 epoch3:'acc'=80 =>繼續訓練。
          epoch4:'acc'=80 epoch5:'acc'=79 epoch6:'acc'=78 =>停止訓練。
    ModelCheckpoint : 表示會在每個epochs 保存模型,而當 'val_loss' 沒有改善的情況,模型不會覆蓋上一次的權重。
    可以看到使用callbacks後,訓練提早到第10次就停止了(編碼epochs=15),並保存了模型權重,因此不需要再浪費時間重新訓練模型。

  • Tensorboard 練習
    Tensorboard 為 Tensorflow 的可視化框架,只有在 Keras 使用 Tensorflow 做後端運算時才能夠使用。TensorBoard 的主要用途是,在訓練過程中幫助你以可視化的方法監控模型內部發生的一切。如果你監控了除模型最終損失之外的更多信息,那麼可以更清楚地了解模型做了什麼,並且能夠更快地取得進展。
    在這邊也是先用IMDb 來練習,完整程式碼見最後。
    #建立一個資料夾存放log
    log_dir = 'C:\\Users\\Lido_Lee\\Downloads\\IMDb_callback'
    #加入tensorboard callback
    import keras
    callbacks = [keras.callbacks.TensorBoard(log_dir=log_dir,
                                             histogram_freq=1,
                                             embeddings_freq=0,
                                             embeddings_layer_names=None,
                                             ),]

    接著執行"Anaconda Prompt"
    在終端機激活編譯環境 "pypy" ("pypy" 使我aconda 下的python編譯環境),見先前文章 windows 10 安裝 anaconda tensorflow gpu CUDA & cudnn
     $ activate pypy
    啟動 tensorboard
    $ tensorboard --logdir="C:\Users\Lido_Lee\Downloads\IMDb_callback"

    在瀏覽器中輸入 http://localhost:6006/ 即可連結到 tensorboard 頁面,如下:
    可以看到訓練精度與損失圖,非常的方便。
    另外,也有模型架構圖,可以理解輸入資料到輸出資料的過程,這也是為何先選用Keras框架模型入門深度學習是非常容易的。
    看到模型架構之後...,如果我一開始是由 tensorflow 開始著手練習刻出模型的話,應該很快就放棄了吧。(汗顏...)

    有趣的是,還可以看到每層 layer 的激發圖,可以了解層的過濾器的狀況。
    由dense_2 層可以看到輸出符合我們的預期,大部分激活分散在兩端,在此為二分類問題,由圖就可以幫助我們了解層的運作過程。

    真心覺得 Google 真的非常的強大阿!!

    後續再由Fashion MNIST 的數據庫練習,來看看 tensorboard 的 embedding 超強視覺呈現吧!!

完整程式碼:
import os
imdb_dir = 'C:\\Users\\Lido_Lee\\Downloads\\aclImdb'
train_dir = os.path.join(imdb_dir, 'train')
labels = []
texts = []
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)

#對IMDB 原始數據的文本進行分詞
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
import numpy as np
maxlen = 200
training_samples = 20000
validation_samples = 5000
max_words = 10000
tokenizer = Tokenizer(num_words=max_words)
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)

#將數據劃分為訓練集和驗證集
"""
首先要打亂數據,因為一開始數據中的樣本是排好序的
(所有負面評論都在前面,然後是所有正面​​評論)
"""
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]

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

#建立一個資料夾存放log
log_dir = 'C:\\Users\\Lido_Lee\\Downloads\\mnist_callback'
#加入tensorboard callback
import keras
callbacks = [keras.callbacks.TensorBoard(log_dir=log_dir,
                                         histogram_freq=1,
                                         embeddings_freq=0,
                                         embeddings_layer_names=None,
                                         ),]

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

#丟進fit訓練模型
history = model.fit(x_train, y_train,
               batch_size = 32,
               epochs = 10,
               verbose = 1,
               callbacks=callbacks,
               validation_split=0.2)

#將訓練好的模型保存起來
model.save_weights('pre_trained_glove_model_callback_tensorboard.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()

2019年2月11日 星期一

深度學習 - Functional API - Inception & Residual connection

先前練習過多輸入&多輸出的 Functional API,接著來練習由層組成的有向無環的類圖形神經網絡。
  • Inception module :
    Google 的 Christian Szegedy 與其團隊所開發的網絡架構。Inception 模塊最基本的形式包含3~4 個分支,Inception 的進化可以參考博客Bharath Raj 的簡單介紹,下圖為 Inception V3示意圖。
    Christian Szegedy, Wei Liu,. et al. "Going Deeper with Convolutions"
    #分支a
    branch_a = layers.Conv2D(128, 1,activation='relu', strides=2)(x)

    #分支b
    branch_b = layers.Conv2D(128, 1, activation='relu')(x)
    branch_b = layers.Conv2D(128, 3, activation='relu', strides=2)(branch_b)

    #分支c
    branch_c = layers.AveragePooling2D(3, strides=2)(x)
    branch_c = layers.Conv2D(128, 3, activation='relu')(branch_c)

    #分支d
    branch_d = layers.Conv2D(128, 1, activation='relu')(x)
    branch_d = layers.Conv2D(128, 3, activation='relu')(branch_d)
    branch_d = layers.Conv2D(128, 3, activation='relu', strides=2)(branch_d)

    #將分支輸出連接在一起,得到模塊輸出
    output = layers.concatenate([branch_a, branch_b, branch_c, branch_d], axis=-1)


    使用Inception module 來測試在Kaggle - Cat & Dog 上的效能
    修改改程式碼:
    #建立模型網絡
    from keras.models import Model
    from keras import layers, Input
    x = Input( shape =(150,150,3),dtype='float32')
    #分支a
    branch_a = layers.Conv2D(32, 1,activation='relu', strides=2)(x)
    branch_a = layers.Flatten()(branch_a)
    branch_a = layers.Dense(256, activation='relu')(branch_a)
    #分支b
    branch_b = layers.Conv2D(32, 1, activation='relu')(x)
    branch_b = layers.Conv2D(32, 3, activation='relu', strides=2)(branch_b)
    branch_b = layers.Flatten()(branch_b)
    branch_b = layers.Dense(256, activation='relu')(branch_b)
    #分支c
    branch_c = layers.AveragePooling2D(3, strides=2)(x)
    branch_c = layers.Conv2D(32, 3, activation='relu')(branch_c)
    branch_c = layers.Flatten()(branch_c)
    branch_c = layers.Dense(256, activation='relu')(branch_c)
    #分支d
    branch_d = layers.Conv2D(32, 1, activation='relu')(x)
    branch_d = layers.Conv2D(32, 3, activation='relu')(branch_d)
    branch_d = layers.Conv2D(32, 3, activation='relu', strides=2)(branch_d)
    branch_d = layers.Flatten()(branch_d)
    branch_d = layers.Dense(256, activation='relu')(branch_d)
    #將分支輸出連接在一起,得到模塊輸出
    output = layers.concatenate([branch_a, branch_b, branch_c, branch_d],axis=-1)
    #將輸出做加入 sigmoid 分類
    output = layers.Dense(1, activation='sigmoid')(output)
    model = Model(x,output)

    非常遺憾,記憶體不夠,無法增加網絡的特徵維度,因此看不出效能。
    在此先做一個練習,知道如何使用就好...。


  • Residual Connection (殘差連接)
    為 Microsoft 的 Kaiming He 與其團隊在 ILSVRC2015 競賽中獲勝所使用的方法。
    在大規模深度學習的模型中(幾十層layer以上),通常會遇到兩個問題,梯度消失和表示瓶頸。
    梯度消失,在循環神經網絡(RNN)中已經提過,而LSTM則是解決此問題的優化模型,在大規模神經網絡,利用同一概念,允許過去的信息稍後重新進入,從而解決梯度消失問題。將一個純線性的信息攜帶軌道,與主要的層堆疊方向平行,有助於跨越任意深度的層來傳播梯度,解決梯度消失的問題。
    表示瓶頸,在大規模深度學習模型中,只要有其中一層的特徵維度過小,則會導致訊息將永遠被截掉(Loss),就好比類比訊號經過一層層的濾波器(filter 3MHz~1GHz),而其中一個濾波器頻寬只有3MHz~300MHz ,那在這之後的濾波器都只能看到3~300MHz 的訊號,而後面的訊號都流失掉了。藉由殘差連接,將過去的特徵再重新輸入到後面的層,以解決表示瓶頸的問題。
    HE K, ZHANG X, REN S,. et al. "Deep residual learning for image recognition"

    from keras import layers,Input
    #如果特徵圖的尺寸相同,使用恆等殘差連接(identityresidual connection)
    x = Input(shape=(None,),)
    y = layers.Conv2D(128, 3, activation='relu', padding='same')(x)
    y = layers.Conv2D(128, 3, activation='relu', padding='same')(y)
    y = layers.Conv2D(128, 3, activation='relu', padding='same')(y)
    y = layers.add([y, x])

    #如果特徵圖的尺寸不同,使用線性殘差連接(linear residual connection)
    x = Input(shape=(None,),)
    y = layers.Conv2D(128, 3, activation='relu', padding='same')(x)
    y = layers.Conv2D(128, 3, activation='relu', padding='same')(y)
    y = layers.MaxPooling2D(2, strides=2)(y)
    residual = layers.Conv2D(128, 1, strides=2, padding='same')(x)
    y = layers.add([y, residual])


    完整程式碼:
    #資料夾分類處理後的路徑
    original_dataset_dir ='C:\\Users\\Lido_Lee\\Downloads\\kaggle_original_data'
    base_dir = 'C:\\Users\\Lido_Lee\\Downloads\\cats_and_dogs_small'
    train_dir = 'C:\\Users\\Lido_Lee\\Downloads\\cats_and_dogs_small\\train'
    validation_dir = 'C:\\Users\\Lido_Lee\\Downloads\\cats_and_dogs_small\\validation'
    test_dir = 'C:\\Users\\Lido_Lee\\Downloads\\cats_and_dogs_small\\test'

    #使用ImageDataGenerator 數據增強
    from keras.preprocessing.image import ImageDataGenerator
    train_datagen = ImageDataGenerator(rescale=1./255,
                                       rotation_range=40,
                                       width_shift_range=0.2,
                                       height_shift_range=0.2,
                                       shear_range=0.2,
                                       zoom_range=0.2,
                                       horizontal_flip=True,
                                       fill_mode='nearest')

    #測試數據不可使用數據增強
    test_datagen = ImageDataGenerator(rescale=1./255)

    train_generator = train_datagen.flow_from_directory(train_dir,
                                                        target_size=(256, 256),
                                                        batch_size=20,
                                                        class_mode='binary')
    
    validation_generator = test_datagen.flow_from_directory(validation_dir,
                                                            target_size=(256, 256),
                                                            batch_size=20,
                                                            class_mode='binary')

    #建立模型網絡
    from keras.models import Model
    from keras import layers, Input
    x = Input( shape =(256,256,3),dtype='float32')
    #線性殘差連接
    y = layers.Conv2D(128, 3, activation='relu', padding='same')(x)
    y = layers.Conv2D(128, 3, activation='relu', padding='same')(y)
    y = layers.MaxPooling2D(2, strides=2)(y)
    residual = layers.Conv2D(128, 1, strides=2, padding='same')(x)
    y = layers.add([y, residual])
    z = layers.Conv2D(128, 3, activation='relu', padding='same')(y)
    z = layers.Conv2D(128, 3, activation='relu', padding='same')(z)
    z = layers.MaxPooling2D(2, strides=2)(z)
    w = layers.Conv2D(128, 3, activation='relu', padding='same')(z)
    w = layers.Conv2D(128, 3, activation='relu', padding='same')(w)
    w = layers.MaxPooling2D(2, strides=2)(w)
    residual_w = layers.Conv2D(128, 1, strides=2, padding='same')(z)
    w = layers.add([w, residual_w])

    #將輸出做加入 sigmoid 分類
    w = layers.Flatten()(w)
    output = layers.Dense(256, activation='relu')(w)
    output = layers.Dense(1, activation='sigmoid')(output)
    model = Model(x,output)

    #選擇優化器RMSprop、損失函數binary_crossentropy、指標acc
    from keras import optimizers
    model.compile(loss='binary_crossentropy',
                  optimizer=optimizers.RMSprop(lr=1e-5),
                  metrics=['acc'])

    #將訓練&驗證資料丟進fit_generator訓練
    history = model.fit_generator(train_generator,
                                  steps_per_epoch=100,
                                  epochs=20,
                                  validation_data=validation_generator,
                                  validation_steps=50)

    test_generator = test_datagen.flow_from_directory(test_dir,
                                                      target_size=(256, 256),
                                                      batch_size=20,
                                                      class_mode='binary')

    #保存訓練模型
    model.save('cats_and_dogs_residual_exercise.h5')

    #在測試數據集上驗證
    test_loss, test_acc = model.evaluate_generator(test_generator, steps=50)
    print('test acc:', test_acc)

    #將訓練過程畫出來
    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()

    由於跑太久...只跑了20個 epochs ,可以看到還尚未發生過擬合,有空再來跑個100次看看...