Traducir una memoria: un viaje técnico | de Valeria Cortez | diciembre de 2024

Aprovechando GPT-3.5 y API no estructuradas para traducciones

Esta publicación de blog detalla cómo utilicé GPT para traducir las memorias personales de un amigo de la familia, haciéndolas accesibles a una audiencia más amplia. Específicamente, empleé GPT-3.5 para la traducción y las API de Unstructured para una extracción y formato de contenido eficientes.

Las memorias, un relato sincero de mi amiga de la familia Carmen Rosa, narra su crianza en Bolivia y su viaje romántico a París con un hombre iraní durante la vibrante década de 1970. Escrito originalmente en español, nuestro objetivo era preservar la esencia de su narrativa y al mismo tiempo ampliar su alcance a lectores de habla inglesa mediante la aplicación de tecnologías LLM.

Imagen de portada de “Un Destino Sorprendente”, usada con autorización de la autora Carmen Rosa Wichtendahl.
Imagen de portada de “Un Destino Sorprendente”, usada con autorización de la autora Carmen Rosa Wichtendahl.

A continuación puede leer el proceso de traducción con más detalle o puede acceda aquí al Cuaderno de Colab.

Seguí los siguientes pasos para la traducción del libro:

  1. Importar datos del libro: Importé el libro desde un documento Docx usando la API no estructurada y lo dividí en capítulos y párrafos.
  2. Técnica de traducción: Traduje cada capítulo usando GPT-3.5. Para cada párrafo, proporcioné las tres últimas oraciones traducidas (si están disponibles) del mismo capítulo. Este enfoque sirvió para dos propósitos:
  • Consistencia de estilo: Mantener un estilo consistente en toda la traducción proporcionando contexto de traducciones anteriores.
  • Límite de tokens: Limitar la cantidad de tokens procesados ​​a la vez para evitar exceder el límite de contexto del modelo.

3. Exportar traducción como Docx: Utilicé la API de Unstructured una vez más para guardar el contenido traducido en formato Docx.

1. Bibliotecas

Comenzaremos con la instalación e importación de las bibliotecas necesarias.

pip install --upgrade openai 
pip install python-dotenv
pip install unstructured
pip install python-docx
import openai

# Unstructured
from unstructured.partition.docx import partition_docx
from unstructured.cleaners.core import group_broken_paragraphs

# Data and other libraries
import pandas as pd
import re
from typing import List, Dict
import os
from dotenv import load_dotenv

2. Conexión a la API de OpenAI

El siguiente código configura la clave API de OpenAI para su uso en un proyecto de Python. Debe guardar su clave API en un .env archivo.

import openai

# Specify the path to the .env file
dotenv_path = '/content/.env'

_ = load_dotenv(dotenv_path) # read local .env file
openai.api_key = os.environ['OPENAI_API_KEY']

3. Cargando el libro

El código nos permite importar el libro en formato Docx y dividirlo en párrafos individuales.

elements = partition_docx(
filename="/content/libro.docx",
paragraph_grouper=group_broken_paragraphs
)

El siguiente código devuelve el párrafo del décimo índice de elements.

print(elements[10])

# Returns: Destino sorprendente, es el título que la autora le puso ...

4. Agrupar el libro en títulos y capítulos.

El siguiente paso consiste en crear una lista de capítulos. Cada capítulo se representará como un diccionario que contiene un título y una lista de párrafos. Esta estructura simplifica el proceso de traducción de cada capítulo y párrafo individualmente. A continuación se muestra un ejemplo de este formato:

[
{"title": title 1, "content": [paragraph 1, paragraph 2, ..., paragraph n]},
{"title": title 2, "content": [paragraph 1, paragraph 2, ..., paragraph n]},
...
{"title": title n, "content": [paragraph 1, paragraph 2, ..., paragraph n]},
]

Para lograr esto, crearemos una función llamada group_by_chapter. Estos son los pasos clave involucrados:

  1. Extraer información relevante: Podemos obtener cada texto narrativo y título llamando element.category. Esas son las únicas categorías que estamos interesados ​​en traducir en este momento.
  2. Identificar títulos narrativos: Reconocemos que algunos títulos deben formar parte del texto narrativo. Para tener en cuenta esto, asumimos que los títulos en cursiva pertenecen al párrafo narrativo.
def group_by_chapter(elements: List) -> List[Dict]:
chapters = []
current_title = None

for element in elements:

text_style = element.metadata.emphasized_text_tags # checks if it is 'b' or 'i' and returns list
unique_text_style = list(set(text_style)) if text_style is not None else None

# we consider an element a title if it is a title category and the style is bold
is_title = (element.category == "Title") & (unique_text_style == ['b'])

# we consider an element a narrative content if it is a narrative text category or
# if it is a title category, but it is italic or italic and bold
is_narrative = (element.category == "NarrativeText") | (
((element.category == "Title") & (unique_text_style is None)) |
((element.category == "Title") & (unique_text_style == ['i'])) |
((element.category == "Title") & (unique_text_style == ['b', 'i']))
)

# for new titles
if is_title:
print(f"Adding title {element.text}")

# Add previous chapter when a new one comes in, unless current title is None
if current_title is not None:
chapters.append(current_chapter)

current_title = element.text
current_chapter = {"title": current_title, "content": []}

elif is_narrative:
print(f"Adding Narrative {element.text}")
current_chapter["content"].append(element.text)

else:
print(f'### No need to convert. Element type: {element.category}')

return chapters

En el siguiente ejemplo, podemos ver un ejemplo:

book_chapters[2] 

# Returns
{'title': 'Proemio',
'content': [
'La autobiografía es considerada ...',
'Dentro de las artes literarias, ...',
'Se encuentra más próxima a los, ...',
]
}

5. Traducción de libros

Para traducir el libro, seguimos estos pasos:

  1. Traducir títulos de capítulos: Traducimos el título de cada capítulo.
  2. Traducir párrafos: Traducimos cada párrafo, proporcionando al modelo las tres últimas oraciones traducidas como contexto.
  3. Guardar traducciones: Guardamos tanto los títulos traducidos como el contenido.

La siguiente función automatiza este proceso.

def translate_book(book_chapters: List[Dict]) -> Dict:
translated_book = []
for chapter in book_chapters:
print(f"Translating following chapter: {chapter['title']}.")
translated_title = translate_title(chapter['title'])
translated_chapter_content = translate_chapter(chapter['content'])
translated_book.append({
"title": translated_title,
"content": translated_chapter_content
})
return translated_book

Para el título, le pedimos a GPT una traducción simple de la siguiente manera:

def translate_title(title: str) -> str:
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages= [{
"role": "system",
"content": f"Translate the following book title into English:\n{title}"
}]
)
return response.choices[0].message.content

Para traducir un solo capítulo, proporcionamos el modelo con los párrafos correspondientes. Instruimos al modelo de la siguiente manera:

  1. Identificar el rol: Le informamos al modelo que es un traductor útil para un libro.
  2. Proporcionar contexto: Compartimos las últimas tres frases traducidas del capítulo.
  3. Solicitar traducción: Le pedimos al modelo que traduzca el siguiente párrafo.

Durante este proceso, la función combina todos los párrafos traducidos en una sola cadena.

# Function to translate a chapter using OpenAI API
def translate_chapter(chapter_paragraphs: List[str]) -> str:
translated_content = ""

for i, paragraph in enumerate(chapter_paragraphs):

print(f"Translating paragraph {i + 1} out of {len(chapter_paragraphs)}")

# Builds the message dynamically based on whether there is previous translated content
messages = [{
"role": "system",
"content": "You are a helpful translator for a book."
}]

if translated_content:
latest_content = get_last_three_sentences(translated_content)
messages.append(
{
"role": "system",
"content": f"This is the latest text from the book that you've translated from Spanish into English:\n{latest_content}"
}
)

# Adds the user message for the current paragraph
messages.append(
{
"role": "user",
"content": f"Translate the following text from the book into English:\n{paragraph}"
}
)

# Calls the API
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=messages
)

# Extracts the translated content and appends it
paragraph_translation = response.choices[0].message.content
translated_content += paragraph_translation + '\n\n'

return translated_content

Finalmente, a continuación podemos ver la función de soporte para obtener las últimas tres oraciones.

def get_last_three_sentences(paragraph: str) -> str:
# Use regex to split the text into sentences
sentences = re.split(r'(?<!\w\.\w.)(?<![A-Z][a-z]\.)(?<=\.|\?)\s', paragraph)

# Get the last three sentences (or fewer if the paragraph has less than 3 sentences)
last_three = sentences[-3:]

# Join the sentences into a single string
return ' '.join(last_three)

6. Exportación de libros

Finalmente, pasamos el diccionario de capítulos a una función que agrega cada título como encabezado y cada contenido como párrafo. Después de cada párrafo, se agrega un salto de página para separar los capítulos. Luego, el documento resultante se guarda localmente como un archivo Docx.

from docx import Document

def create_docx_from_chapters(chapters: Dict, output_filename: str) -> None:
doc = Document()

for chapter in chapters:
# Add chapter title as Heading 1
doc.add_heading(chapter['title'], level=1)

# Add chapter content as normal text
doc.add_paragraph(chapter['content'])

# Add a page break after each chapter
doc.add_page_break()

# Save the document
doc.save(output_filename)

Si bien el uso de GPT y API para la traducción es rápido y eficiente, existen limitaciones clave en comparación con la traducción humana:

  • Errores de pronombres y referencias: GPT malinterpretó pronombres o referencias en algunos casos, atribuyendo potencialmente acciones o declaraciones a la persona equivocada en la narrativa. Un traductor humano puede resolver mejor estas ambigüedades.
  • Contexto cultural: GPT pasó por alto sutiles referencias culturales y modismos que un traductor humano podría interpretar con mayor precisión. En este caso, varios términos de jerga exclusivos de Santa Cruz, Bolivia, se conservaron en el idioma original sin contexto ni explicación adicional.

Combinar la IA con la revisión humana puede equilibrar la velocidad y la calidad, garantizando que las traducciones sean precisas y auténticas.

Este proyecto demuestra un enfoque para traducir un libro utilizando una combinación de GPT-3 y API no estructuradas. Al automatizar el proceso de traducción, reducimos significativamente el esfuerzo manual requerido. Si bien el resultado de la traducción inicial puede requerir algunas revisiones humanas menores para refinar los matices y garantizar la más alta calidad, este enfoque sirve como una base sólida para una traducción de libros eficiente y efectiva.

Si tiene algún comentario o sugerencia sobre cómo mejorar este proceso o la calidad de las traducciones, no dude en compartirlo en los comentarios a continuación.