El resumen de código es el proceso de generar una descripción en lenguaje natural de fragmentos de código. Las tareas más comunes de resumen de código son explorar una nueva base de código, aprender un nuevo lenguaje de programación o generar comentarios de código y explicaciones de funciones. Generar un resumen de un fragmento de código es similar a generar un resumen de texto de un documento en lenguaje natural. Se diferencia porque el modelo de lenguaje de gran tamaño (LLM) que genera el resumen necesita entender el lenguaje de programación que está leyendo mientras identifica la lógica subyacente de lo que intenta lograr.
Los resúmenes de código son una parte valiosa del desarrollo de software, ayudando en el mantenimiento mediante la realización automática de resumen del código fuente, la creación de resúmenes en lenguaje natural para documentación o la elaboración de bases de código a gran escala. Los modelos de LLM más nuevos que utilizan un enfoque basado en transformadores pueden actuar como modelos de resumen de código o ejecutar también la generación de código. Estas funciones son posibles porque los modelos se han entrenado en grandes conjuntos de datos creados a partir de fuentes como los repositorios de Github que incluyen código y comentarios junto con documentación para ese código.
Antes de que se popularizaran los LLM, los enfoques del resumen del código requerían analizar la semántica del código y generar un árbol sintáctico abstracto (AST) de cada identificador a partir del código que pudiera utilizarse para generar la documentación.1,2 Con la llegada del deep learning y las redes neuronales, los enfoques basados en la informática fueron abandonados por enfoques que tomaban más prestado de la metodología de la traducción automática3,4.
Para los modelos de transformadores, las ventanas de contexto más grandes conducen a mejores resultados. Muchos de los modelos más recientes y avanzados de Granite Code, como el Granite-8B-Code-Instruct-128K, cuentan con una ventana de contexto de 128 k. Una ventana de contexto más grande permite que el modelo contenga más texto en una memoria de trabajo. Esto ayuda a monitorizar los momentos y detalles clave de una charla prolongada o de un documento o base de código extenso. Esta memoria de trabajo permite a un chatbot basado en LLM generar respuestas que tengan sentido en el momento inmediato y en un contexto más prolongado, lo que les ayuda a superar a los modelos con ventanas de contexto más pequeñas, tanto en términos de evaluación humana como de métricas.5
Las ventanas de contexto más amplias permiten a los modelos almacenar más texto en su memoria de trabajo, lo que les ayuda a no perder de vista los momentos y detalles clave en conversaciones prolongadas, documentos extensos o código fuente.
Cuando ChatGPT se introdujo por primera vez, su ventana de contexto era de 4000 tokens. Si su conversación superaba el límite de 3000 palabras de la interfaz de chat, era probable que el chatbot alucinara y se desviara del tema. Actualmente, el estándar es de 32 000 tokens, aunque los sectores están cambiando a 128 000 tokens. Eso es aproximadamente la longitud de un libro de 250 páginas. IBM tiene ahora dos modelos Granite con una ventana de 128 000 tokens, y hay más en camino.
En este paso, le guiaremos para que cree una cuenta de IBM y pueda acceder a Jupyter Notebooks.
1. Inicie sesión en watsonx.ai usando su cuenta de IBM Cloud.
2. Haga clic en + para crear un nuevo proyecto.
a. Seleccione "Crear un proyecto vacío".
b. Introduzca un nombre de proyecto en el campo "Nombre".
c. Cree un Cloud Object Storage para almacenar los activos de su proyecto si aún no se ha creado.
d. Seleccione "Crear".
3. Cree un Jupyter Notebook.
a. Seleccione la pestaña "Activos" en su entorno de proyecto.
b. Haga clic en "Nuevo activo".
c. Seleccione la opción "Trabajar con modelos" en el panel izquierdo.
d. Haga clic en "Trabajar con datos y modelos" mediante cuadernos de Python y R.
e. Introduzca un nombre para su cuaderno en el campo "Nombre". Seleccione Runtime 23.1 en Python (4 vCPU, 16 GB de RAM) para definir la configuración.
f. Seleccione "Crear".
4. Configure una instancia de tiempo de ejecución de watsonx.ai y una clave API
a. Cree una instancia de servicio de watsonx.ai Runtime (seleccione la región adecuada y elija el plan Lite, que es una instancia sin coste).
b. Genere una clave API.
c. Asocie la instancia del servicio watsonx.ai Runtime al proyecto que ha creado en watsonx.ai.
Primero, instalaremos la biblioteca Hugging Face Hub de código abierto para descargar modelos:
Ahora podemos descargar Granite-8B-Code-Instruct-128K:
Ahora podemos empezar a usar Granite Code Instruct.
Vamos a introducir en nuestro modelo una llamada a una función bastante compleja de la biblioteca GluonTS, tomada de su repositorio de GitHub. Se trata de un bloque de código bastante largo para pegarlo directamente en la instrucción, así que lo guardaremos en una variable:
ll_func_2 = """ def call( self, data: torch.Tensor, weights: torch.Tensor ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: assert ( data.shape == weights.shape ), "data and observed_indicator must have same shape" with torch.no_grad(): observed_data = torch.where(weights == 1, data, torch.nan) med = torch.nanmedian(observed_data, dim=self.dim, keepdim=True).values q1 = torch.nanquantile(observed_data, 0.25, dim=self.dim, keepdim=True) q3 = torch.nanquantile(observed_data, 0.75, dim=self.dim, keepdim=True) iqr = q3 - q1 # if observed data is all zeros, nanmedian returns nan loc = torch.where(torch.isnan(med), torch.zeros_like(med), med) scale = torch.where(torch.isnan(iqr), torch.ones_like(iqr), iqr) scale = torch.maximum(scale, torch.full_like(iqr, self.minimum_scale)) scaled_data = (data - loc) / scale if not self.keepdim: loc = torch.squeeze(loc, dim=self.dim) scale = torch.squeeze(scale, dim=self.dim) # assert no nans in scaled data, loc or scale assert not torch.any(torch.isnan(scaled_data)) assert not torch.any(torch.isnan(loc)) assert not torch.any(torch.isnan(scale)) assert not torch.any(scale == 0) return scaled_data, loc, scaledef call( self, data: torch.Tensor, weights: torch.Tensor ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: assert ( data.shape == weights.shape ), "data and observed_indicator must have same shape"
Ahora podemos pedirle a Granite Code un resumen tokenizando nuestro chat y, a continuación, llamando a model.generate()
chat = [
{ "role": "user", "content": "Explain what this function does" + ll_func_2 },
]
chat = tokenizer.apply_chat_template(chat, tokenize=False, add_generation_prompt=True)
# have the encoder turn text into output tokens
inputs = tokenizer(chat, return_tensors="pt").input_ids
start_index = inputs.shape[-1]
# generate output tokens
output = model.generate(inputs, max_new_tokens=1500)
# have the decoder turn output tokens into text
output = tokenizer.batch_decode(output[start_index:])
# loop over the batch to print, in this example the batch size is 1
print(output[0])
Vamos a revisar el resumen generado:
Respuesta: Esta función realiza la normalización de datos utilizando el método del rango intercuartil (IQR). Se necesitan dos argumentos: datos y pesos. El argumento "data" es un tensor de forma (batch_size, sequence_length, hidden_size), donde batch_size es el número de muestras de un lote, sequence_length es la longitud de la secuencia de entrada y hidden_size es la dimensionalidad del estado oculto. El argumento "weights" es un tensor de forma (batch_size, sequence_length), en el que cada elemento es 0 o 1, lo que indica si el elemento correspondiente del tensor de datos está presente o falta. La función primero comprueba que los tensores de datos y pesos tengan la misma forma. A continuación, calcula la mediana y el IQR de los puntos de datos observados (aquellos con un peso de 1) utilizando las funciones torch.nanmedian y torch.nanquantile. La función torch.nanmedian calcula la mediana a lo largo de la dimensión especificada (`self.dim`), mientras que la función torch.nanquantile calcula los percentiles 25 y 75. A continuación, la función calcula los parámetros de ubicación (`loc`) y escala (`scale`) de la distribución normalizada. La ubicación se establece en la mediana de los puntos de datos observados y la escala se establece en el IQR de los puntos de datos observados. Si todos los valores observados son ceros, la función establece la ubicación en cero. La función también garantiza que la escala sea al menos un valor mínimo (`self.minimum_scale`) para evitar la división por cero. Por último, la función calcula los datos normalizados (`scaled_data`) restando la ubicación y dividiendo por la escala. Si el argumento "keepdim" se establece en "False", la función elimina la dimensión especificada de los tensores de ubicación y escala. La función también realiza aserciones para garantizar que los tensores normalizados de datos, ubicación y escala no contengan ningún valor NaN o ceros en la escala. La función devuelve una tupla que contiene los tensores normalizados de datos, ubicación y escala.
La descripción es muy detallada y describe con precisión la función que le hemos pasado y el algoritmo subyacente.
La generación de documentación es una potente aplicación del resumen de código, ya que no solo explica el código a cualquier futuro usuario que lea el código, sino que también puede ayudar a generar documentación utilizando una herramienta como pydoc.
Ahora podemos dar la instrucción a Granite Code de que genere docstrings para una función. Debido a la ventana de contexto más amplia, podemos pasar una función entera junto con una petición para generar un docstring.
Escriba una cadena de documentos de Python con el formato correcto para la siguiente función:
Granite Code devuelve una excelente documentación en formato Python:
Extrae un objeto JSON de la cadena HTML dada utilizando la clave especificada y el carácter de parada. Args: html (str): La cadena HTML de entrada para extraer el objeto JSON. key (str): la clave utilizada para localizar el inicio del objeto JSON en la cadena HTML. num_chars (int, opcional): el número de caracteres después de la clave para incluir en el objeto JSON extraído. El valor predeterminado es 2. stop (str, opcional): el carácter que marca el final del objeto JSON en la cadena HTML. Por defecto es '"'. Devoluciones: str: el objeto JSON extraído como una cadena.
Ahora, si queremos, podemos reutilizar la instrucción anterior que todavía está presente en la ventana de contexto grande:
chat = "Reescribe la función anterior para que sea un método de clase e incluye las cadenas de documentación." # have the encoder turn text into output tokens inputs = tokenizer(chat, return_tensors="pt").input_ids start_index = inputs.shape[-1] # generate output tokens output = model.generate(inputs, max_new_tokens=1500) # have the decoder turn output tokens into text output = tokenizer.batch_decode(output[start_index:]) # loop over the batch to print, in this example the batch size is 1 print(output[0])
Esto devuelve
class HtmlJsonExtractor: def init(self, html): """ Inicializa el objeto HtmlJsonExtractor con la cadena HTML proporcionada. Args: html (str): La cadena HTML de entrada de la que se van a extraer los objetos JSON. """ self.html = html @classmethod def extract(cls, html, key, num_chars=2, stop='"'): """ Extrae un objeto JSON de la cadena HTML proporcionada utilizando la clave y el carácter de finalización especificados. Args: html (str): La cadena HTML de entrada para extraer el objeto JSON. key (str): la clave utilizada para localizar el inicio del objeto JSON en la cadena HTML. num_chars (int, opcional): el número de caracteres después de la clave para incluir en el objeto JSON extraído. El valor predeterminado es 2. stop (str, opcional): el carácter que marca el final del objeto JSON en la cadena HTML. Por defecto es '"'. Devoluciones: str: el objeto JSON extraído como una cadena. """ pos_begin = html.find(key) + len(key) + num_chars pos_end = html.find(stop, pos_begin) return html[pos_begin:pos_end]
El modelo ahora ha creado una clase con un nombre apropiado, ha convertido el código anterior en un método de esa clase e insertado nuestras cadenas de documentación.
Vamos ahora a dar la instrucción al modelo para que nos ayude a optimizar un fragmento de código:
chat = """Por favor, optimiza el siguiente código: def fib(n): if (n < 2): return 1 else: return fib(n-1) + fib(n-2)"""
El código Granite devuelve:
La función fib(n) de Python dada calcula el enésimo número de Fibonacci mediante recursión. Sin embargo, esta implementación tiene una complejidad temporal de O(2^n), que es ineficiente para entradas grandes. Podemos
optimizarlo utilizando programación dinámica o memorización para reducir la complejidad temporal a O(n). Aquí tienes una versión optimizada de la función:
```python
def fib_optimized(n, memo={}):
if n < 2:
return 1
elif n not in memo:
memo[n] = fib_optimized(n-1, memo) + fib_optimized(n-2, memo)
return memo[n]
```
Esta versión optimizada utiliza un memorando de diccionario para almacenar números de Fibonacci previamente calculados. Al calcular el enésimo número de Fibonacci, primero comprueba si ya ha sido calculado y almacenado en un memorando. Si es así, devuelve el valor almacenado en lugar de volver a calcularlo, lo que ahorra tiempo y mejora el rendimiento.
Aquí, el modelo no solo ha explicado lo que falla en la función en términos de eficiencia y ha dado a la implementación una complejidad de Big-O, sino que también ha proporcionado una mejor implementación. Proporcionar reseñas y explicaciones es una forma de aprovechar el resumen y forma parte de una generación de código más general para ayudar a los desarrolladores a mejorar.
En este tutorial ha aprendido qué es la síntesis de código y ha utilizado el modelo Granite Code con una ventana de contexto ampliada de 128 000 tokens para generar explicaciones sobre código Python. También utilizamos instrucciones para generar nueva documentación, añadir una clase en torno a un fragmento de código y utilizar la ventana de contexto ampliada para añadir esa documentación a una nueva ventana de código. Por último, hicimos que Granite Code analizara y resumiera un fragmento de código y explicara cómo podría mejorarse.
Acelere la entrega de software con IBM Bob™, su socio de IA para un desarrollo seguro y orientado a la intención.
Desarrolle, implemente y gestione aplicaciones de IA más rápido con herramientas preparadas para la empresa.
Reinvente los sistemas heredados con una modernización inteligente de la IA.
1 Sonia Haiduc, Jairo Aponte, Andrian Marcus. “Supporting program comprehension with source code summarization”. ICSE ‘10: Proceedings of the 32nd ACM/IEEE International Conference on Software Engineering https://doi.org/10.1145/3377811.3380383.
2 Paul W. McBurney, Collin McMillan. “Automatic Source Code Summarization of Context for Java Methods”. https://ieeexplore.ieee.org/document/7181703.
3 Chen Lin, Zhichao Ouyang, Junqing Zhuang, Jianqiang Chen, Hui Li, Rongxin Wu, “Improving Code Summarization with Block-wise Abstract Syntax Tree Splitting” IEEE/ACM. International Conference on Program Comprehension (ICPC 2021) https://arxiv.org/abs/2103.07845.
4 Jian Zhang, Xu Wang, Hongyu Zhang, Hailong Sun, Xudong Liu, “Retrieval-based neural source code summarization”. ICSE ‘10: Proceedings of the 32nd ACM/IEEE International Conference on Software Engineering. https://doi.org/10.1145/1810295.1810335.
5 Xinyi Hou, Yanjie Zhao, Yue Huang, Zhou Yang, Kailong Wang, Li Li, Xiapu Luo, David Jin, John Grundy, Haoyu Wang. “Large Language Models for Software Engineering: A Systematic Literature Review”. https://arxiv.org/abs/2308.10620.