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