Embeddings con OpenAI

Embeddings

Como comentamos en un post anterior, los embeddings son una forma de representar la información de un texto en un espacio vectorial. El texto se convierte en un vector de números decimales.

Convertir textos con embeddings permite medir la proximidad o similitud de distintos textos y documentos en función de su significado, y esto facilita aplicaciones como búsqueda, comparación o clasificación. Los embeddings también pueden utilizarse en el contexto de entrenar un modelo de machine learning, si por ejemplo uno de los inputs que queremos utilizar para el modelo es un texto libre no estructurado; en este caso, la variable que se utiliza para entrenar el modelo no es el texto sino el resultado de aplicar el modelo de embeddings al texto.

API OpenAI

La API de OpenAI nos facilita mucho utilizar modelos de embeddings.

En el cuadro siguiente se muestra un fragmento de código en Python para aplicar uno de los modelos de embeddings de OpenAI (en este caso “text-embedding-ada-002”) a un texto muy sencillo:

import openai 
openai.api_key = [clave privada del usuario]   
input_text="Nuclear Fusion reactions power the Sun and other stars. In a fusion reaction, two light nuclei merge to form a single heavier nucleus."   
response = openai.Embedding.create(input=input_text, model="text-embedding-ada-002" )

El resultado que se obtiene con el código anterior es un objeto que incluye un vector de dimensión 1536, que es la representación vectorial del texto introducido en el modelo. En el cuadro siguiente se muestran unos fragmentos de este vector (los dos primeros y últimos elementos):

[0.015895795077085495, 0.0016827485524117947, … …, -0.015629597008228302, -0.008714799769222736]

La dimensión del vector está determinada por el modelo de embeddings; distintos modelos utilizan distintas dimensiones. Con el modelo anterior, cualquier texto queda mapeado al mismo espacio vectorial. En el ejemplo anterior se ha utilizado un texto de 23 palabras. Si introducimos un texto de 400 palabras en el mismo modelo, también obtendremos un vector de dimensión 1536.

Todos los vectores generados están normalizados para tener longitud 1, lo que facilita el cálculo de medidas de similitud de texto, como se comenta más abajo.

Search

Podríamos aplicar un modelo de embeddings a múltiples documentos internos que tenemos en nuestro repositorio interno y luego guardar el resultado (los embeddings de cada documento) junto con los documentos originales. Esto nos puede facilitar por ejemplo hacer búsquedas más adelante de los documentos que tratan sobre una temática específica.

Abajo se muestra un ejemplo sencillo de código en Python donde primero se generan los embeddings para dos fragmentos de texto y luego se busca el texto más próximo a un término de búsqueda (en este caso ‘nuclear fusion’).

import pandas as pd 
import openai from openai.embeddings_utils import get_embedding, cosine_similarity 
openai.api_key = [clave privada del usuario]   

# Create data frame to hold the contents of two documents 
df = pd.DataFrame([
"Nuclear Fusion reactions power the Sun and other stars. In a fusion reaction, two light nuclei merge to   form a single heavier nucleus.",
"Hydrogen is a clean fuel that, when consumed in a fuel cell, produces only water"     ], columns=['Input document'])   

# define function that calls embedding function to create embeddings of documents 
def get_embedding(text, model="text-embedding-ada-002"):    
    text = text.replace("\n", " ")    
    return openai.Embedding.create(input = [text], model=model)['data'][0]['embedding']   

df['Document embeddings'] = df['Input document'].apply(lambda x: get_embedding(x, model='text-embedding-ada-002'))   

# define function that will search for text in embeddings 
def search_text(df, input_text, n=1, pprint=True):    
    # first apply embeddings to the searched text    
    embedding = get_embedding(input_text, model='text-embedding-ada-002')    
    df['similarities'] =   df['Document embeddings'].apply(lambda x: cosine_similarity(x, embedding))
    res = df.sort_values('similarities', ascending=False).head(n)    
    return res   

res = search_text(df, 'nuclear fusion', n=1) print(res['Input document'])

El resultado que se obtiene con el código anterior es el texto que está más próximo al término de búsqueda: “Nuclear Fusion reactions power the Sun and other stars. In a fusion reaction, two light nuclei merge to form a single heavier nucleus.”

El ejemplo anterior es muy sencillo porque sólo considera dos textos muy cortos. Pero no sería muy complicado generalizarlo a utilizar muchos más documentos y más largos.

Para medir la proximidad de textos, las APIs de OpenAI utilizan la métrica de cosine similarity, que es una función del producto de dos vectores. Puede tomar un valor entre -1 (significado totalmente opuesto) y +1 (significado exactamente igual).

En el ejemplo anterior, cuando queremos hacer la búsqueda de un término: primero aplicamos el modelo de embeddings a este término, con lo que obtenemos un vector; luego se compara este vector (el que corresponde al término buscado) con cada uno de los vectores que tenemos almacenados (uno para cada texto o documento); asociamos a cada texto o documento de nuestro repositorio un valor de proximidad con el término buscado; esto nos permite finalmente ordenar los documentos en función de su métrica de proximidad y seleccionar el que tiene un valor más alto. En el ejemplo anterior, el valor de proximidad para el texto seleccionado es 0.864321. 

Lo interesante de estos ejemplos simplificados es que muestran la facilidad con la que es posible incorporar las capacidades de generar embeddings y de búsqueda sobre múltiples documentos, a partir de las APIs de OpenAI y pocas líneas de código.

Keep reading