Le résumé de code consiste à générer une description en langage naturel d’extraits de code. Parmi les tâches courantes, on peut citer l’exploration d’une nouvelle base de code, l’apprentissage d’un nouveau langage de programmation ou la génération de commentaires de code et d’explications sur les fonctions. Générer un résumé d’un extrait de code s’apparente à générer un résumé textuel d’un document en langage naturel. La différence réside dans le fait que le grand modèle de langage (LLM) chargé de générer le résumé doit comprendre le langage de programmation qu’il analyse, tout en identifiant la logique sous-jacente de ce qu’il cherche à accomplir.
Les résumés de code constituent un élément précieux du développement logiciel ; ils facilitent la maintenance des logiciels en effectuant un résumé automatique du code source, en créant des résumés en langage naturel pour la documentation ou en analysant des bases de code à grande échelle. Les modèles LLM plus récents, qui utilisent une approche basée sur les transformers, peuvent servir soit de modèles de résumé de code, soit à générer du code. Ces fonctions sont possibles car les modèles ont été entraînés sur de vastes jeux de données constitués à partir de sources telles que les référentiels GitHub, qui contiennent du code et des commentaires ainsi que la documentation relative à ce code.
Avant la généralisation des LLM, les approches nécessitaient d’analyser la sémantique du code et de générer un arbre syntaxique abstrait (AST) pour chaque identifiant du code, qui pouvait ensuite être utilisé pour générer de la documentation1,2. Avec l’avènement de l’apprentissage profond et des réseaux de neurones, les approches issues de l’informatique ont été abandonnées au profit d’approches s’inspirant davantage de la méthodologie de la traduction automatique neuronale3,4.
Pour les modèles de transformer, des fenêtres de contexte plus larges conduisent à de meilleurs résultats. Bon nombre des modèles Granite Code de pointe les plus récents, tels que Granite-8B-Code-Instruct-128K, disposent d’une fenêtre de contexte de 128k. Le modèle peut ainsi stocker davantage de texte dans sa mémoire de travail. Cela permet de suivre les moments clés et les détails lors d’une conversation prolongée, ou dans un document ou une base de code volumineux. Cette mémoire de travail permet à un chatbot basé sur un LLM de générer des réponses cohérentes tant dans l’instant présent que dans un contexte plus large, ce qui lui permet de surpasser les modèles dotés de fenêtres de contexte plus petites, aussi bien selon l’évaluation humaine que selon les indicateurs de performance5.
Des fenêtres de contexte plus larges permettent aux modèles de stocker davantage de texte dans leur mémoire de travail, facilitant ainsi le suivi des moments clés et des détails dans des conversations prolongées, des documents volumineux ou des bases de code.
Lors de son lancement, la fenêtre contextuelle de ChatGPT était de 4 000 tokens. Si votre conversation dépassait la limite de 3 000 mots imposée par l’interface de chat, le chatbot risquait d’halluciner et de s’écarter du sujet. Aujourd’hui, la norme est de 32 000 tokens, et le secteur s’oriente vers 128 000. Cela correspond à peu près à la longueur d’un livre de 250 pages. IBM dispose désormais de deux modèles Granite dotés d’une fenêtre de 128 000 tokens, et d’autres sont en cours de développement.
Au cours de cette étape, nous vous guiderons dans la création d’un compte IBM pour accéder à Jupyter Notebooks.
1. Connectez-vous à watsonx.ai à l’aide de votre compte IBM Cloud.
2. Cliquez sur + pour créer un nouveau projet.
a. Sélectionnez Créer un projet vide.
b. Saisissez un nom de projet dans le champ Nom.
c. Créez un espace de stockage IBM Cloud Object Storage pour stocker les actifs de votre projet s’il n’a pas encore été créé.
d. Sélectionnez Créer.
3. Créez un Jupyter Notebook.
a. Sélectionnez l’onglet Actifs dans l’environnement de votre projet.
b. Cliquez sur Nouvel actif.
c. Sélectionnez l’option Travailler avec des modèles dans le panneau de gauche.
d. Cliquez sur Travailler avec des données et des modèles à l’aide de notebooks Python et R.
e. Saisissez un nom pour votre notebook dans le champ Nom. Choisissez Runtime 23.1 sur Python (4 vCPU, 16 Go de RAM) pour définir la configuration.
f. Sélectionnez Créer.
4. Configurez une instance watsonx.ai Runtime et une clé d’API
a. Créez une instance du service watsonx.ai Runtime (sélectionnez la région appropriée et choisissez la formule Lite, qui est une instance gratuite).
b. Générez une clé d’API.
c. Associez l’instance du service watsonx.ai Runtime au projet que vous avez créé dans watsonx.ai.
Tout d’abord, nous allons installer la bibliothèque open source Hugging Face Hub pour télécharger les modèles :
Nous pouvons désormais télécharger Granite-8B-Code-Instruct-128K :
Nous pouvons désormais commencer à utiliser Granite Code Instruct.
Soumettons à notre modèle un appel de fonction assez complexe issu de la bibliothèque GluonTS, tiré de son référentiel GitHub. Comme il s’agit d’un long bloc de code à coller dans un prompt, nous allons le stocker dans une 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 # si les données observées sont toutes des zéros, la fonction nanmedian renvoie 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) # vérifier l’absence de valeurs nans dans les données normalisées, la position ou l’échelle 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"
Nous pouvons désormais demander à Granite Code de générer un résumé en tokenisant notre conversation, puis en invoquant callingmodel.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)
# demander à l’encodeur de transformer le texte en tokens de sortie
inputs = tokenizer(chat, return_tensors="pt").input_ids
start_index = inputs.shape[-1]
# générer les tokens de sortie
output = model.generate(inputs, max_new_tokens=1500)
# demander au décodeur de transformer les tokens de sortie en texte
output = tokenizer.batch_decode(output[start_index:])
# parcourir le lot pour l’afficher ; dans cet exemple, la taille du lot est de 1
print(output[0])
Vérifions le résumé généré :
Réponse : Cette fonction effectue une normalisation des données à l’aide de la méthode de l’écart interquartile (IQR). Elle prend deux arguments : data et weights. L’argument data est un tenseur de forme (batch_size, sequence_length, hidden_size), où batch_size correspond au nombre d’échantillons dans un lot, sequence_length à la longueur de la séquence d’entrée et hidden_size à la dimensionnalité de l’état caché. L’argument weights est un tenseur de forme (batch_size, sequence_length), où chaque élément vaut soit 0, soit 1, indiquant si l’élément correspondant dans le tenseur data est observé ou manquant. La fonction vérifie d’abord que les tenseurs data et weights ont la même forme. Elle calcule ensuite la médiane et l’IQR des points de données observés (ceux dont le poids est égal à 1) à l’aide des fonctions torch.nanmedian et torch.nanquantile. La fonction torch.nanmedian calcule la médiane le long de la dimension spécifiée (`self.dim`), tandis que la fonction torch.nanquantile calcule les 25e et 75e centiles. Ensuite, la fonction calcule les paramètres de position (`loc`) et d’échelle (`scale`) de la distribution normalisée. La position est définie sur la médiane des points de données observés, et l’échelle est définie sur l’IQR des points de données observés. Si les points de données observés sont tous des zéros, la fonction définit la position sur zéro. La fonction s’assure également que l’échelle est au moins égale à une valeur minimale (`self.minimum_scale`) afin d’éviter une division par zéro. Enfin, la fonction calcule les données normalisées (`scaled_data`) en soustrayant la position et en divisant par l’échelle. Si l’argument keepdim est défini sur False, la fonction supprime la dimension spécifiée des tenseurs de position et d’échelle. La fonction effectue également des vérifications pour s’assurer que les données normalisées, ainsi que les tenseurs de position et d’échelle, ne contiennent aucune valeur NaN ni aucun zéro dans l’échelle. La fonction renvoie un tuple contenant les tenseurs de données normalisées, de position et d’échelle.
La description est à la fois très détaillée et décrit avec précision la fonction que nous avons transmise ainsi que l’algorithme sous-jacent.
La génération de documentation constitue une application puissante du résumé de code, car elle permet non seulement d’expliquer le code à tout futur utilisateur qui le lira, mais aussi de faciliter la génération de documentation à l’aide d’un outil tel que pydoc.
Nous pouvons désormais demander à Granite Code de générer des chaînes de documentation (docstrings) pour une fonction. Grâce à la fenêtre de contexte plus large, nous pouvons transmettre une fonction entière accompagnée d’une demande de génération d’une chaîne de documentation.
Rédige une chaîne de documentation Python correctement formatée pour la fonction suivante :
Granite Code renvoie une excellente documentation au format Python :
Extrait un objet JSON de la chaîne HTML fournie à l’aide de la clé et du caractère d’arrêt spécifiés. Arguments : html (str) : la chaîne HTML d’entrée à partir de laquelle extraire l’objet JSON. key (str) : la clé utilisée pour localiser le début de l’objet JSON dans la chaîne HTML. num_chars (int, optional) : le nombre de caractères après la clé à inclure dans l’objet JSON extrait. La valeur par défaut est 2. stop (str, optional) : le caractère marquant la fin de l’objet JSON dans la chaîne HTML. La valeur par défaut est '"'. Renvoie : str : l’objet JSON extrait sous forme de chaîne de caractères.
Si vous le souhaitez, vous pouvez désormais réutiliser le prompt précédent, encore présent dans la grande fenêtre de contexte :
chat = "Réécris la fonction précédente pour en faire une méthode de classe et inclus les chaînes de documentation." # demander à l’encodeur de transformer le texte en tokens de sortie inputs = tokenizer(chat, return_tensors="pt").input_ids start_index = inputs.shape[-1] # générer les tokens de sortie output = model.generate(inputs, max_new_tokens=1500) # demander au décodeur de transformer les tokens de sortie en texte output = tokenizer.batch_decode(output[start_index:]) # parcourir le lot pour l’afficher ; dans cet exemple, la taille du lot est de 1 print(output[0])
Cela renvoie
class HtmlJsonExtractor: def init(self, html): """ Initialise l’objet HtmlJsonExtractor avec la chaîne HTML fournie. Arguments : html (str) : la chaîne HTML d’entrée à partir de laquelle extraire les objets JSON. """ self.html = html @classmethod def extract(cls, html, key, num_chars=2, stop='"'): """ Extrait un objet JSON de la chaîne HTML fournie à l’aide de la clé et du caractère d’arrêt spécifiés. Arguments : html (str) : la chaîne HTML d’entrée à partir de laquelle extraire l’objet JSON. key (str) : la clé utilisée pour localiser le début de l’objet JSON dans la chaîne HTML. num_chars (int, optional) : le nombre de caractères après la clé à inclure dans l’objet JSON extrait. La valeur par défaut est 2. stop (str, optional) : le caractère marquant la fin de l’objet JSON dans la chaîne HTML. La valeur par défaut est '"'. Renvoie : str : l’objet JSON extrait sous forme de chaîne de caractères. """ pos_begin = html.find(key) + len(key) + num_chars pos_end = html.find(stop, pos_begin) return html[pos_begin:pos_end]
Le modèle a désormais créé une classe portant un nom approprié, a transformé le code précédent en méthode de cette classe et a inséré nos chaînes de documentation.
Promptons maintenant le modèle pour qu’il nous aide à optimiser un extrait de code :
chat = """Optimise le code suivant : def fib(n): if (n < 2): return 1 else: return fib(n-1) + fib(n-2)"""
Granite Code renvoie :
La fonction Python fib(n) donnée calcule le n-ième nombre de Fibonacci à l’aide de la récursivité. Cependant, cette implémentation présente une complexité temporelle de O(2^n), ce qui est inefficace pour des entrées volumineuses. Nous pouvons
l’optimiser à l’aide de la programmation dynamique ou de la mémorisation afin de réduire la complexité temporelle à O(n). Voici une version optimisée de la fonction :
```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]
```
Cette version optimisée utilise un mémo de type dictionnaire pour stocker les nombres de Fibonacci précédemment calculés. Lors du calcul du n-ième nombre de Fibonacci, elle vérifie d’abord s’il a déjà été calculé et stocké dans le mémo. Si c’est le cas, elle renvoie la valeur stockée au lieu de la recalculer, ce qui permet de gagner du temps et d’améliorer les performances.
Ici, le modèle a non seulement expliqué ce qui ne va pas avec la fonction en matière d’efficacité et attribué à l’implémentation une complexité en Big-O, mais il a également proposé une meilleure implémentation. Fournir des analyses et des explications est un moyen de tirer parti de la fonction de résumé et, dans le cadre d’une génération de code plus générale, d’aider les développeurs à s’améliorer.
Dans ce tutoriel, vous avez découvert le résumé de code et utilisé le modèle Granite Code avec une fenêtre de contexte étendue à 128 000 tokens pour générer des explications sur du code Python. Nous avons également utilisé des prompts pour générer une nouvelle documentation, ajouter une classe autour d’un extrait de code et utiliser la fenêtre de contexte étendue pour ajouter cette documentation à une nouvelle fenêtre de code. Enfin, nous avons demandé à Granite Code d’analyser et de résumer un extrait de code, puis d’expliquer comment il pourrait être amélioré.
Accélérez la livraison de logiciels grâce à IBM Bob, votre partenaire d’IA pour un développement sécurisé et sensible aux intentions.
Développez, déployez et gérez plus rapidement des applications d’IA grâce à des outils adaptés aux entreprises.
Repensez vos systèmes hérités grâce à une modernisation intelligente basée sur l’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.