手把手教你自制程式設計AI:訓練2小時,RNN就能寫自己的程式碼

AI100發表於2017-06-12

手把手教你自制程式設計AI:訓練2小時,RNN就能寫自己的程式碼

我們都知道,神經網路下圍棋能贏柯潔、讀X光照片好過醫生、就連文字翻譯上也快超過人類了……其實在寫程式碼方面,神經網路也絲毫不落下風……用Linux原始碼訓練2小時,一個遞迴神經網路就能重寫好它自己的程式碼,這是不是比程式設計師學得還快?

為了幫大家一窺究竟,AI100(rgznai100)編譯了開發者Thibault Neveu的這篇文章,手把手教你做一個這樣的神經網路。

作者 | Thibault Neveu

我認這很瘋狂。開發者讓神經網路學會了自己程式設計來重寫它自己程式碼!好吧,我們們也試。

預備條件

  1. Tensorflow + 基本的深度學習技能

  2. 該專案的github程式碼庫 - https://github.com/thibo73800/deep_generation/tree/master/c_code

  3. 我會在本文中快速回顧一下遞迴神經網路。但是,如果你對這個課題不甚瞭解,我相信以下兩個資源能讓你弄懂遞迴神經網路:

視訊 - https://www.youtube.com/watch?v=iX5V1WpxxkY&t=2652s

文章 - http://colah.github.io/posts/2015-08-Understanding-LSTMs/

我不會在本文中詳解本專案的所有環節。但我會仔細闡述其中的基本要點來讓你理解整個專案。花點時間,親手執行下文中給出的每一段程式碼,理解其中的邏輯。這很重要,畢竟,實踐出真知。

接下來是正題,讓我們開始吧!

資料庫

跟其他監督訓練一樣,我們需要為神經網路提供一個資料集。這裡我們使用C語言(如果用太簡單的語言,就不好玩了)。我們直接用Linux github程式碼庫中的c語言指令碼作為訓練資料。我已經把我們會用到的.c程式碼提取到本專案中。

程式碼地址-https://github.com/thibo73800/deep_generation/tree/master/c_code/dataset

首要問題:如何表示資料?

神經網路只能用於處理數字。對於其他形式的資料,它就無能為力了。因此,資料集中的每個字元都需要被翻譯成這種形式(每個數字對應一個字元)。

示例:把字元轉換為整數(int)

舉例來說,這裡用數字7表示字元“=”。為了在反向傳播期間獲得更好的收斂性,我們稍後會在獨熱編碼(One-Hot Encoding)編碼中表示每個數字。

# List all file in the dataset directory

all_file = os.listdir("dataset")

# Filter : Select only c file

all_file_name = np.array([f for f in all_file if f.find(".c") != -1])


content = ""

for name in all_file_name:

      with open(os.path.join("dataset", name), "r") as f:

            content += f.read() + "\n"


# Convert the string into a list of interger

vocab = set(content)

vocab_to_int = {c: i for i, c in enumerate(vocab)}

int_to_vocab = dict(enumerate(vocab))

encoded = np.array([vocab_to_int[c] for c in content], dtype=np.int32)

這裡,需要記住的三個重要變數是:vocab_to_int、int_to_vocab和encoded。前兩個變數是讓我們能夠字元和整數間隨意轉換。最後的變數是用編碼器的形式來表示所有資料。(均已轉換為數字)

第一個批函式

首先建立一個簡單的批處理:由兩個輸入序列構成,每個序列10個數字。這一批處理將作為下文字元處理的一個示例。

batch = {

    "x" : [

        encoded[:10],

         encoded[20:30]

],

"y" : [

      encoded[1:11],

      encoded[21:31]

   ]

      }

Batch Inputs :

[20 6 58 27 6 27 97 86 56 49]

[ 36 32 32 37 27 12 94 60 89 101]

Batch Targets :

[ 6 58 27 6 27 97 86 56 49 57]

[ 32 32 37 27 12 94 60 89 101 77]

這就是批函式所處理的內容,翻譯成字元如下:

['/', '*', '\n', ' ', '*', ' ', 'C', 'o', 'p', 'y']

['2', '0', '0', '4', ' ', 'E', 'v', 'g', 'e', 'n']

現在,我們需要來處理一些數值。我們希望神經網路能夠在上一個字元"n"已知的條件下預測出下一個字元。而且,不只是上一個字元。如果我告訴神經網路上一個字元是“e” ,下一個字元的可能性空間會非常大。但如果我能告訴神經網路前幾個字元分別是 “w” 、“h”、 “i” 、“l” 和 “e” ,下一個要輸入的字元很顯然就是“(“。

因此,我們必須構建一個能夠考慮字元時間間隔的神經網路。這就是遞迴神經網路。

遞迴神經網路?

為說明上述例項,我們用一個典型的分類器(上圖左側)來處理上一個字元;它被傳遞出藍色的隱含層後,分類器就能推斷出結果。遞迴神經網路在結構上則不同。每個紅色的隱含層“細胞”不僅與輸入相連,還與前一個“細胞”(instant t-1)相連。為了解決這裡的問題,我們的“細胞”內部使用長短期記憶(LSTM)網路。

請花點時間來理解遞迴神經網路的原理,這樣才能充分理解接下來的程式碼。

構建模型!

Tensorboard圖

接下來的內容,我們將詳述這一神經網路的5大部分。佔位符在這裡用作模型的一個入口。LSTM神經元初始化後用於生成遞迴神經網路。

輸出層各自相連,用於估量模型的誤差。最後,我們會定義訓練內容。

1)圖形輸入

with tf.name_scope("graph_inputs"):

     inputs = tf.placeholder(tf.int32, [2, 10], name='placeholder_inputs')

     targets = tf.placeholder(tf.int32, [2, 10], name='placeholder_targets')

     keep_prob = tf.placeholder(tf.float32, name='placeholder_keep_prob')

這個批處理由兩個大小為10的輸入序列構成,因此輸入的預期特徵是[2, 10],批處理的每個入口都與單一輸出相關聯,目標的特徵定義與此相同。最後,我們定義了一個用作概率值的佔位符,用以表示後面的退出率(dropout)。

2)LSTM

with tf.name_scope("LSTM"):


     def create_cell():

         lstm = tf.contrib.rnn.BasicLSTMCell(4)

         drop = tf.contrib.rnn.DropoutWrapper(lstm, output_keep_prob=keep_prob)

         return drop


cell = tf.contrib.rnn.MultiRNNCell([create_cell() for _ in range(3)])

initial_state = cell.zero_state(2, tf.float32)


io_size = len(vocab)

x_one_hot = tf.one_hot(inputs, io_size)

cell_outputs, final_state = tf.nn.dynamic_rnn(cell, x_one_hot, initial_state=initial_state)

讓我們來學習這份程式碼的每一部分:

  • create_cell() 用於生成由4個隱神經元所構成的LSTM神經元。在返回結果前,該函式還在cell輸出中新增了一個退出項(dropout)。

  • tf.contrib.rnn.MultiRNNCell用於例項化遞迴神經網路。我們把給出的create_cell()陣列作為引數,是因為我們希望得到由多層網路構成的遞迴神經網路。本例為三層。

  • initial_state:已知遞迴神經網路的每個神經元都依賴於先前的狀態,因此我們必須例項化一個全是零的初始狀態,它將作為批處理首批入口的輸入。

  • x_one_hot將batch轉化為獨熱編碼。

  • cell_outputs給出遞迴神經網路每個細胞的輸出。在本例中,每個輸出由4個數值(隱神經元個數)構成。

  • final_state返回最後一個細胞的狀態,在訓練期間可用作下一批處理的最新初始狀態(假設下一個批處理是上一個批處理的邏輯延續)。

3)圖形輸出

with tf.name_scope("graph_outputs"):

     seq_output_reshape = tf.reshape(cell_outputs, [-1, 4], name="reshape_x") 


     with tf.name_scope('output_layer'):

           w = tf.Variable(tf.truncated_normal((4, io_size), stddev=0.1), name="weights")

           b = tf.Variable(tf.zeros(io_size), name="bias")


logits = tf.add(tf.matmul(seq_output_reshape , w), b, name= "logits")

softmax = tf.nn.softmax(logits, name='predictions')

細胞的輸出值被儲存在一個三維特徵表內[序列數,序列大小,神經元數],或為 [2, 10, 4]。我們無需按序列來分離輸出。然後,改變輸出值的維度以儲存在seq_out_reshape的陣列[20, 4]內。

最後,使用一個簡單的線性運算:tf.matmul (..) + b。最後以softmax結尾,為的是用概率形式來表示輸出。

4)損失

with tf.name_scope("Loss"):

        y_one_hot = tf.one_hot(targets, io_size, name="y_to_one_hot")

        y_reshaped = tf.reshape(y_one_hot, logits.get_shape(), name="reshape_one_hot")


loss = tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=y_reshaped)

loss = tf.reduce_mean(loss)

為進行誤差運算,我們的批處理目標必須要表示成跟模型輸出值相同的方法和維度。使用與輸入相同的編碼方式,我們用tf.one_hot來表示輸出值。然後將陣列tf.reshape ()的維度重寫為與tf.matmul (..) + b的線性輸出一樣。而後,我們就可以用該函式來計算模型的誤差。

5)訓練

with tf.name_scope("train"):

    adam = tf.train.AdamOptimizer(0.0001)

    optimizer = adam.minimize(loss)

我們簡單用AdamOptimize來最小化誤差。

結果!

這是最值得慶祝的環節:訓練結果。我所用到的引數如下:

  • 序列大小:100

  • 批處理大小:200

  • 每個細胞的神經元數: 512

  • 遞迴神經網路深度:2

  • 學習速度:0.0005

  • Dropout:0.5

在我的GPU(GeForce GTX 1060)上訓練大約兩小時後,所得結果如下圖所示:

我們先來觀察誤差的變化:

最後,我們來閱讀模型所生成的程式碼:

static int super_fold(struct mddev *mddev, void __user * *rd)

{

     struct md_rdev *rdev;


     if (!tryet & gcov_ntreef(*stint)) {

          if (gc_th->max_sectors)

          if (task)

         goto next_start;

    if (!list_empty(&mddev->disks) {

        if (mddev->dev_sectors == 0 ||

          mddev->chunk_sectors == 0 && mddev->minor_version !=

           mddev->max_disks && mddev->dev_sectors

         rdev2->rescan_recovnr != 0)

rdev->recovery_offset = mddev->curr_resync_completed;

           }

     }

}

從結果上看,模型清楚地理解了該程式的一般結構,非常酷:一個函式、引數、變數初始化……條件,等等。

我們注意到,所用資料集中絕對沒有那個名為“super_fold”的函式。因此,我很難理解這個函式的具體作用,一個較為合理的解釋,是該模型要比我聰明……天哪!!

原文地址:

https://becominghuman.ai/how-to-train-a-neural-network-to-code-by-itself-a432e8a120df


相關文章