Las redes neuronales recurrentes (RNN) están ganando mucha atención en los últimos años porque se han mostrado muy prometedoras en muchas tareas de procesamiento del lenguaje natural. A pesar de su popularidad, hay un número limitado de tutoriales que explican cómo implementar una aplicación sencilla e interesante utilizando herramientas de última generación. En esta serie, utilizaremos una red neuronal recurrente para entrenar a un programador de IA, que puede escribir código Java como un programador real (con suerte). Se cubrirá lo siguiente:
1. Construyendo un programador de IA simple
2. Mejora del programador de IA: uso de tokens (esta publicación)
3. Mejora del programador de IA: uso de diferentes estructuras de red
En la publicación anterior, creamos un programador de IA básico utilizando una red neuronal LSTM simple de 1 capa. El código que genera el programador de IA no tiene mucho sentido. En esta publicación, usaremos tokens en lugar de secuencias de caracteres individuales para entrenar el modelo.
1. Obtener los datos brutos de entrenamiento
Estoy usando el mismo código fuente que en la publicación anterior. Está disponible aquí: https://github.com/frohoff/jdk8u-jdk. Esta vez, se escanea cada archivo .java, tokenizadoy luego se agrega en un archivo llamado «jdk-tokens.txt». Los saltos de línea no están reservados. No es necesario descargar el código fuente de JDK. Para su conveniencia, he incluido el archivo agregado en el repositorio de GitHub de este proyecto. Puede encontrar el enlace al final de esta publicación.
El siguiente código lee tokens del archivo jdk-tokens.txt y lo corta para que se ajuste a la capacidad de hardware de mi escritorio. En mi caso, solo usé el 20% del código como se muestra en el código.
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. Índice de construcción para abordar los tokens
Las entradas LSTM solo comprenden números. Una forma de convertir tokens en números es asignar un número entero único a cada token. Por ejemplo, si hay 1000 tokens únicos en el código, podemos asignar un número único a cada uno de los 1000 tokens. El siguiente código crea un diccionario con entradas como [ “public” : 0 ] [ “static” : 1 ], …]. El diccionario inverso también se genera para decodificar la salida de 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. Preparar las secuencias de entrenamiento con etiquetas
Aquí cortamos el texto en secuencias semi-redundantes de 10 tokens. Cada secuencia es una muestra de entrenamiento y la etiqueta de cada secuencia de token es el siguiente token.
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. Vectorización de datos de entrenamiento
Primero creamos dos matrices y luego le asignamos valores a cada una de ellas. Uno para las características y otro para la etiqueta. len (secuencias) es el número total de muestras de entrenamiento.
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. Construcción de un modelo LSTM de una sola capa
Construimos una red como la siguiente:
Además, es bastante sencillo apilar dos capas LSTM como también se muestra en el código comentado a continuación.
El siguiente código define la estructura de la red neuronal. La red contiene una capa de LSTM con 128 unidades ocultas. El parámetro input_shape especifica la longitud de la secuencia de entrada y la dimensión de la entrada en cada momento. Implementos densos () salida = activación (punto (entrada, núcleo) + sesgo) . La entrada aquí es la salida de la capa LSTM. La función de activación se especifica mediante la línea Activación (‘softmax’). Optimizer es la función de optimización. Es posible que esté familiarizado con el que se usa comúnmente en regresión logística, que es el descenso de gradiente estocástico. La última línea especifica la función de costo. En este caso, usamos ‘categorical_crossentropy’. Puede echar un vistazo esta bonita publicación para entender por qué la entropía cruzada es mejor que el error cuadrático medio (MSE).
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()) |
Arriba, también incluyo el código para apilar otra capa de LSTM y convertirlo en un LSTM RNN de 2 capas.
6. Modelo de formación y generación de código Java
La función de muestra se utiliza para muestrear un índice de una matriz de probabilidad. Por ejemplo, dados preds =[0.5,0.2,0.3] y una temperatura predeterminada, el índice de retorno de la función 0 con probabilidad de 0,5, 1 con probabilidad de 0,2 o 2 con probabilidad de 0,3. Se usa para evitar generar la misma oración una y otra vez. Queremos ver una secuencia de código diferente que el programador AI pueda codificar.
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. Resultados
Se necesitan algunas horas para entrenar al modelo. Me detuve en la 40a iteración y el código generado se parece a lo siguiente:
----- 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 ++ ] ;
El código generado se ve mucho mejor que el código generado por el enfoque anterior basado en caracteres. Tenga en cuenta que agregué saltos de línea para facilitar la lectura. Podemos ver que LSTM captura los bucles y las condiciones bastante bien, el código comienza a tener más sentido. Por ejemplo, «for (int i = 0; i
También puede echar un vistazo al código generado en las iteraciones anteriores. Tienen menos sentido.
8. ¿Qué sigue?
En esta publicación, utilicé secuencias de tokens como entrada para entrenar el modelo y el modelo predice secuencias de tokens. Si todo funciona correctamente, debería funcionar mejor que el enfoque basado en personajes. Además, también podemos utilizar diferentes estructuras de red. Los exploraremos en la próxima publicación.
Código fuente
1) El código fuente de esta publicación es lstm_ai_coder_tokens.py que se encuentra en https://github.com/ryanlr/RNN-AI-Programmer