近年來,循環(huán)神經(jīng)網(wǎng)絡(luò) (RNN) 受到了廣泛關(guān)注,因為它在許多自然語言處理任務(wù)中顯示出了巨大的前景。 盡管它們很受歡迎,但解釋如何使用最先進的工具實現(xiàn)簡單而有趣的應用程序的教程數(shù)量有限。在本系列中,我們將使用循環(huán)神經(jīng)網(wǎng)絡(luò)來訓練 AI 程序員,該程序員可以像真正的程序員一樣編寫 Java 代碼(希望如此)。將涵蓋以下內(nèi)容:
2. 改進 AI 程序員 - 使用令牌(這篇文章)
3. 改進 AI 程序員 - 使用不同的網(wǎng)絡(luò)結(jié)構(gòu)
在上一篇文章中,我們使用簡單的 1 層 LSTM 神經(jīng)網(wǎng)絡(luò)構(gòu)建了一個基本的 AI 程序員。AI 程序員生成的代碼沒有多大意義。在這篇文章中,我們將使用標記而不是單個字符序列來訓練模型。
1. 獲取訓練原始數(shù)據(jù)
我使用與上一篇文章相同的源代碼。它可以在這里找到:https://github.com/frohoff/jdk8u-jdk。這一次,每個 .java 文件都被掃描、標記化,然后聚合到一個名為“?jdk-tokens.txt
?”的文件中。不保留換行符。你不需要下載 JDK 源代碼。為方便起見,我已將聚合文件包含在該項目的 GitHub 存儲庫中。你可以在這篇文章的末尾找到鏈接。
以下代碼從 ?jdk-tokens.txt
? 文件中讀取令牌并將其切片以適合我的桌面的硬件功能。就我而言,我只使用了代碼中顯示的代碼的 20%。
path = "./jdk-tokens.txt"
filetext = open(path).read().lower()
# slice the whole string to overcome memory limitation
slice = len(filetext)/5
slice = int (slice)
filetext = filetext[:slice]
tokenized = filetext.split()
print('# of tokens:', len(tokenized))
2. 建立索引來定位令牌
LSTM 輸入只理解數(shù)字。將標記轉(zhuǎn)換為數(shù)字的一種方法是為每個標記分配一個唯一的整數(shù)。例如,如果代碼中有 ?1000
?個唯一令牌,我們可以為 ?1000
?個令牌中的每一個分配一個唯一編號。下面的代碼構(gòu)建了一個包含? [“public” : 0 ] [ “static” : 1 ], ... ]
? 等條目的字典。還生成反向字典用于解碼 LSTM 的輸出。
uniqueTokens = sorted(list(set(tokenized)))
print('total # of unique tokens:', len(uniqueTokens))
token_indices = dict((c, i) for i, c in enumerate(uniqueTokens))
indices_token = dict((i, c) for i, c in enumerate(uniqueTokens))
3. 準備帶標簽的訓練序列
在這里,我們以 ?10
?個標記的半冗余序列剪切文本。每個序列是一個訓練樣本,每個令牌序列的標簽是下一個令牌。
NUM_INPUT_TOKENS = 10
step = 3
sequences = []
next_token = []
for i in range(0, len(tokenized) - NUM_INPUT_TOKENS, step):
sequences.append(tokenized[i: i + NUM_INPUT_TOKENS])
next_token.append(tokenized[i + NUM_INPUT_TOKENS])
print('nb sequences:', len(sequences))
4. 向量化訓練數(shù)據(jù)
我們首先創(chuàng)建兩個矩陣,然后為每個矩陣賦值。一種用于特征,一種用于標簽。?len(sequences)
? 是訓練樣本的總數(shù)。
X = np.zeros((len(sequences), NUM_INPUT_TOKENS, len(uniqueTokens)), \
dtype=np.bool)
y = np.zeros((len(sequences), len(uniqueTokens)), dtype=np.bool)
for i, sentence in enumerate(sequences):
for t, char in enumerate(sentence):
X[i, t, token_indices[char]] = 1
y[i, token_indices[next_token[i]]] = 1
5. 構(gòu)建單層 LSTM 模型
我們正在構(gòu)建一個如下所示的網(wǎng)絡(luò):
此外,堆疊兩個 LSTM 層非常簡單,如下面的注釋代碼所示。
下面的代碼定義了神經(jīng)網(wǎng)絡(luò)的結(jié)構(gòu)。該網(wǎng)絡(luò)包含一層具有 128 個隱藏單元的 LSTM。?input_shape
?參數(shù)指定輸入序列的長度和每次輸入的維度。?Dense()
? 實現(xiàn)?output = activation(dot(input, kernel) + bias)
?。這里的輸入是 LSTM 層的輸出。激活函數(shù)由行 ?Activation('softmax')
? 指定。?Optimizer
?是優(yōu)化函數(shù)。您可能熟悉邏輯回歸中常用的一種,即隨機梯度下降。最后一行指定了成本函數(shù)。在這種情況下,我們使用“?categorical_crossentropy
?”。
model = Sequential()
# 1-layer LSTM
#model.add(LSTM(128, input_shape=(NUM_INPUT_TOKENS, len(uniqueTokens))))
# 2-layer LSTM
model.add(LSTM(128,return_sequences=True, \
input_shape=(NUM_INPUT_TOKENS, len(uniqueTokens))))
model.add(LSTM(128))
model.add(Dense(len(uniqueTokens)))
model.add(Activation('softmax'))
optimizer = RMSprop(lr=0.01)
model.compile(loss='categorical_crossentropy', optimizer=optimizer)
print(model.summary())
6.訓練模型和生成Java代碼
上面,我還包含了堆疊另一層 LSTM 并使其成為 2 層 LSTM RNN 的代碼。
?sample
?函數(shù)用于從概率數(shù)組中采樣一個索引。例如,給定? preds=[0.5,0.2,0.3]
?和默認溫度,函數(shù)返回索引 ?0
? 的概率為 ?0.5
?、?1
? 的概率為 ?0.2
?或 ?2
?的概率為 ?0.3
?。它用于避免一遍又一遍地生成相同的句子。我們希望看到 AI 程序員可以編寫的一些不同的代碼序列。
def sample(preds, temperature=1.0):
# helper function to sample an index from a probability array
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)
# train the model, output generated code after each iteration
for iteration in range(1, 60):
print()
print('-' * 50)
print('Iteration', iteration)
model.fit(X, y, batch_size=128, epochs=1)
start_index = random.randint(0, len(tokenized) - NUM_INPUT_TOKENS - 1)
for diversity in [0.2, 0.5, 1.0, 1.2]:
print()
print('----- diversity:', diversity)
generated = [] #''
sequence = tokenized[start_index: start_index + NUM_INPUT_TOKENS]
generated=list(sequence)
print('----- Generating with seed: "' + ' '.join(sequence) + '"-------')
sys.stdout.write(' '.join(generated))
for i in range(100):
x = np.zeros((1, NUM_INPUT_TOKENS, len(uniqueTokens)))
for t, char in enumerate(sequence):
x[0, t, token_indices[char]] = 1.
preds = model.predict(x, verbose=0)[0]
next_index = sample(preds, diversity)
next_pred_token = indices_token[next_index]
generated.append(next_pred_token)
sequence = sequence[1:]
sequence.append(next_pred_token)
sys.stdout.write(next_pred_token+" ")
sys.stdout.flush()
print()
7. 結(jié)果
訓練模型需要幾個小時。我在第 ?40
?次迭代時停止,生成的代碼如下所示:
----- Generating with seed: "true ) ; } else { boolean result = definesequals"------- true ) ; } else { boolean result = definesequals ( ) . substring ( 1 , gradients . get ( p ) ; } if ( val . null || ( npoints == null ) ? new void . bitlength ( ) + prefixlength ) ; for ( int i = 0 ; i < num ; i ++ ) } break ; } if ( radix result = != other . off ) ; int endoff = b . append ( buf , 0 , len + 1 ) ; digits ++ ] ;
代碼生成看起來比以前基于字符的方法生成的代碼要好得多。請注意,為了便于閱讀,我添加了換行符。我們可以看到 LSTM 很好地捕獲了循環(huán)和條件,代碼開始變得更有意義。例如,“?for ( int i = 0 ; i < num ; i ++ )
?” 是一個完美的 Java for循環(huán)。如果調(diào)整參數(shù)(如 ?NUM_INPUT_CHARS
?和 ?STEP
?)并訓練更長時間,可能會得到更好的結(jié)果。隨意嘗試。同樣,我已經(jīng)知道完成這項工作的更好方法,所以我停在這里并在下一篇文章中進行改進。
你還可以查看早期迭代中生成的代碼。他們的意義不大。
8. 下一步是什么?
在這篇文章中,我使用令牌序列作為輸入來訓練模型,模型預測令牌序列。如果一切正常,它應該比基于字符的方法更有效。此外,我們還可以使用不同的網(wǎng)絡(luò)結(jié)構(gòu)。我們將在下一篇文章中探討這些。