Categorías
Deep Learning

Construya un programador de IA usando una red neuronal recurrente (2)

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.

  Cree un programador de inteligencia artificial utilizando una red neuronal recurrente (3)
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).

  Cree un programador de inteligencia artificial utilizando una red neuronal recurrente (1)
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()
  ¿Cómo seleccionar la herramienta adecuada para el aprendizaje profundo?

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 por lazo. Si ajusta los parámetros (como NUM_INPUT_CHARS y STEP) y entrena más tiempo, puede obtener mejores resultados. Siéntete libre de probar. Nuevamente, ya conozco una mejor manera de hacer este trabajo, así que me detengo aquí y hago la mejora en la próxima publicación.

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

Por Programación.Click

Más de 20 años programando en diferentes lenguajes de programación. Apasionado del code clean y el terminar lo que se empieza. ¿Programamos de verdad?

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *