我們都知道,神經網路下圍棋能贏柯潔、讀X光照片好過醫生、就連文字翻譯上也快超過人類了……其實在寫程式碼方面,神經網路也絲毫不落下風……用Linux原始碼訓練2小時,一個遞迴神經網路就能重寫好它自己的程式碼,這是不是比程式設計師學得還快?
為了幫大家一窺究竟,AI100(rgznai100)編譯了開發者Thibault Neveu的這篇文章,手把手教你做一個這樣的神經網路。
作者 | Thibault Neveu
我認這很瘋狂。開發者讓神經網路學會了自己程式設計來重寫它自己程式碼!好吧,我們們也試。
預備條件
Tensorflow + 基本的深度學習技能
該專案的github程式碼庫 - https://github.com/thibo73800/deep_generation/tree/master/c_code
我會在本文中快速回顧一下遞迴神經網路。但是,如果你對這個課題不甚瞭解,我相信以下兩個資源能讓你弄懂遞迴神經網路:
文章 - http://colah.github.io/posts/2015-08-Understanding-LSTMs/
我不會在本文中詳解本專案的所有環節。但我會仔細闡述其中的基本要點來讓你理解整個專案。花點時間,親手執行下文中給出的每一段程式碼,理解其中的邏輯。這很重要,畢竟,實踐出真知。
接下來是正題,讓我們開始吧!
資料庫
程式碼地址-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