Como se obtuvieron los datos?
Las compras del Municipio
La Municipalidad de Moron posee un portal online para proveedores que esta abierto al publico general en su sección de "Transparencia".
En la sección "Qué compra el municipio" subsección "Compras Concluidas" se puede acceder a un buscador para consultar las compras ya adjudicadas y ejecutadas por el municipio. Dicho portal puede visitarse acá
Este buscador no permite buscar por todos los trimestres y todos los rubros a la vez, por lo que obtener todas las compras de todos los trimestres de todos los rubros de un año específico en una sola consulta debería ser imposible.
Sin embargo, el filtro por Trimestre no funciona, por lo que establecer "Trimestre 1" es equivalente a establecer todos los trimestres.
A partir de este punto, se pueden consultar detalles de cada Solicitud de Cotización individualmente y las Ordenes de Compra asociadas a dicha Solicitud.
El formato de la página con los detalles de la Solicitud de Cotización es muy fácil de visualizar e incluso parsear
Similarmente, podemos ir al listado de Ordenes de Compra que surgieron a partir de esta Solicitud
Un problema que se puede notar inmediatamente en esta ultima imagen son los formatos numéricos. Notar que la orden de compra 170 indica en la tabla el importe "4.325.680,00" y la 171 "273000", cada entrada esta usando un formato numérico distinto. Esto se vuelve mucho peor en algunos listados, véase el siguiente ejemplo de una cotización distinta
En esta tabla encontramos
- El punto usado como separador de mil y la coma como separador decimal, con dos decimales de precision
- El punto usado como separador decimal, sin separador de mil, con dos decimales de precision
- Sin separador de mil, sin decimales de precision
En general, podríamos aplicar la siguiente regla
- Buscar separador decimal (primer '.' o ',' de derecha a izquierda con dos números detrás)
- Remover todos los puntos y comas
- Si no había separador decimal, multiplicar por 100
De esta manera podemos guardar todos los importes en centavos. Esto asume que siempre que hay decimales presentes son exactamente 2, lo cual por desgracia tampoco se cumple. Esto muestra solo una de las dificultades de tratar con estos datos publicados. Por suerte, el Detalle de la Orden de Compra sí es consistente
Si hacemos click en "ver detalle" podemos ver el Detalle de Orden de Compra
Esta tabla tiene los números respetando un mismo formato en todos los casos: punto como separador de mil, coma como separador decimal con dos decimales de precisión. El importe total de la orden de compra es la sumatoria de sus items, en todos los casos. Esto nos permite no depender de la columna "Importe O.C" que no usa ningún formato claro.
Con esta información publicada y accesible se puede proceder a su recolección automática con técnicas de parsing y scrapping
La informatizacion
Para una solicitud de cotización cualquiera se cumple que esta compuesta por las siguientes partes: numero, año y llamado, cada parte separada por un guión. El listado de ordenes de compras está en una pagina que responde a la siguiente forma
https://apps.moron.gob.ar/ext/rafam_portal/compras/resumen.php?trimestre=3&rubro=-1&nro=${numero}&anio=${año}&llamado=${llamado}
Por ejemplo, para obtener el listado de ordenes de compras de la solicitud de cotizacion 2007-0001-1, se deberia acceder a
https://apps.moron.gob.ar/ext/rafam_portal/compras/resumen.php?trimestre=3&rubro=-1&nro=0001&anio=2007&llamado=1
El código HTML de la pagina que se devuelve al hacer una GET request al endpoint contiene el siguiente fragmento de código
... código anterior ....
<table style="text-align:left;">
<tr style='background-color:#B9B6BF;font-size:10px; text-align:center;'>
<th>Nro. O. de Compra</th>
<th>Fecha O.C.</th>
<th>Importe O.C</th>
<th>Razón social</th>
<th>Acción</th>
</tr>
<!-- sacar esto despues -->
<!--<tr style='background-color:#E6E6E6;font-size:10px;'>
<td >1</td>
<td>1</td>
<td>Caño de plastico - Material PVC - Diametro 300MM - Espesor 6.2 MM - Cabeza y enchufe con aro de goma - Longitud x 6 MT.</td>
<td>2599</td>
<td><p class="linkdetalleprov" id="conTooltip">Norte María Laura</p></td>
<td>9,00</td>
<td>8,00</td>
<td>Normal</td>
<td>750,00</td>
</tr>
<tr style='background-color:#F2F2F2;font-size:10px;'>
<td >2</td>
<td>0</td>
<td>Caño de plastico - Material PVC - Color blanco - Diametro 300MM - Espesor 3.2 MM - Longitud x 6 MT.</td>
<td>2598</td>
<td><p class="linkdetalleprov" id="conTooltip2">Arquilad S.R.L</p></td>
<td>6,00</td>
<td>6,00</td>
<td>Normal</td>
<td>50,25</td>
</tr>-->
<tr style='background-color:#E6E6E6;font-size:10px;'><td style='padding-left:10px;'>705</td><td style='padding-left:10px;'>16/03/2007</td><td style='padding-left:10px;'>569,80</td><td id='conTooltip0' style='padding-left:10px;color:blue;'>CERAMICA SANTA MARTA S.R.L.</td><td style='padding-left:10px;'><a href='./detalleOC.php?trimestre=1&anio=2007&rubro=-1&nro= 0001&OC=705&llamado=1'>Ver detalle</a></td></tr>
<tr style='background-color:#F2F2F2; padding:10px;font-size:10px;'><td style='padding-left:10px;'>704</td><td style='padding-left:10px;'>16/03/2007</td><td style='padding-left:10px;'>124471</td><td id='conTooltip1' style='padding-left:10px;color:blue;'>FERDOM S.R.L.</td><td style='padding-left:10px;'><a href='./detalleOC.php?trimestre=1&anio=2007&rubro=-1&nro= 0001&OC=704&llamado=1'>Ver detalle</a></td></tr>
</table>
<br /><br />
<div class="menu_intro_volver">
<a style="margin-left:0px;" href="./concluidas.php?trimestre=1&orden=A&crit=N&rubro=-1&anio=2007"></a>
</div>
</div>
<script type='text/javascript'>
/*new Tip('conTooltip', '<h2>Razón social: </h2> Norte María Laura<br/><h2>Nombre de fantasía: </h2>Norte María Laura <br/><h2>Fecha de alta: </h2>10/09/2006 <br/><h2>Tipo de proveedor: </h2>Normal', { className: 'detalles',hook:{target: 'TopLeft', tip: 'bottomRight'},title: 'Detalles del proveedor: '});
new Tip('conTooltip2', '<h2>Razón social: </h2> Arquicad S.R.L<br/><h2>Nombre de fantasía: </h2>Arquicad S.R.L <br/><h2>Fecha de alta: </h2>10/05/2006 <br/><h2>Tipo de proveedor: </h2>Normal', { className: 'detalles',hook:{target: 'TopLeft', tip: 'bottomRight'},title: 'Detalles del proveedor: '});*/
</script>
... código despues ...
Sí, el "sacar esto después" es parte de la respuesta en producción.
Lo importante esta en las <tr> tags que contienen la información de la tabla y en el código javascript embebido y comentado al final del html. Este código se "descomenta" al pasar con el mouse sobre la Razón Social de la empresa de forma que se muestra un tooltip con la información de la misma.
Parsear la tabla html con una librería como HtmlAgility es trivial puesto que esta bien formada.
Parsear el código javascript comentado es también simple gracias a las <h2> tags dentro del <script> tag. En este caso se pueden usar expresiones Regex. Si bien usar Regex para el parseo de codigo html o javascript es una practica horrible (véase esta respuesta en SO), la realidad es que no es esperable que el codigo de la pagina cambie en la brevedad puesto que lleva igual desde 2007 y una vez obtenido nuestro dataset nunca tendríamos que volver a parsear esto
Sabiendo como formar la URL y qué partes son de importancia, los pasos para estructurar la información y obtenerla son simples
- Crear un Querier capaz de armar la query contra la URL y devolver el contenido
- Crear un Parser que dado el contenido devuelva una instancia del modelo a obtener.
Notar que por la disposición de la información en la página del municipio, tenemos que una Solicitud de Cotizacion tiene Ordenes de Compra las cuales tienen un Detalle de Orden De Compra. Cada uno de estos datos esta en una página distinta con una url que se puede formar a partir de la anterior, de forma que se podría intentar hacer un scrapper con la siguiente estructura
HtmlParserCompraConcluida parser = new();
HtmlParserSolicitudCotizacion parserSolicitud = new();
HtmlParserOrdenDeCompra parserOC = new();
HtmlParserDetalleOrdenDeCompra parserDOC = new();
SolicitudCotizacionHttpQuerier solicitudQuerier = new();
OrdenDeCompraHttpQuerier ocQuerier = new();
DetalleOrdenDeCompraHttpQuerier detalleOcQuerier = new();
Donde cada querier devuelve un documento cuyo parser correspondiente puede parsear y modelar. Esta es sólo una de las muchas formas de automatizar la recolección de información.
Las URL para Solicitud de Cotización y Detalle de Orden de Compra siguen estructuras similares y la estructura de su HTML casi idéntica.
Véase para la URL de Detalle de Orden de Compra que se usa trimestre, año, rubro, numero de la solicitud de cotizacion y numero de la orden de compra. Los primeros tres parámetros no se usan para nada, son solo para facilitar la navegación de los botones "volver"
https://apps.moron.gob.ar/ext/rafam_portal/compras/detalleOC.php?trimestre=2&anio=2024&rubro=-1&nro=%200099&OC=170&llamado=1
Para una URL de Solicitud de Cotización, se usan casi los mismos parametros de trimestre, rubro, año, numero de solicitud de cotizacion, llamado y tipo.
https://apps.moron.gob.ar/ext/rafam_portal/compras/detalle.php?trimestre=2&rubro=-1&anio=2024&cotiz=%200001&llamado=1&tipo=concluidas
El proyecto que se usó para recopilar esta información se puede ver en Github: MoronParser. Este no es un proyecto que se espere ser mantenido de ninguna forma y sólo se publica a modo demostrativo para su reproducibilidad. Cualquiera es libre de clonar el proyecto, ejecutarlo en su computadora y obtener una copia idéntica a los datos usados en estas páginas.
Los datos finalmente obtenidos se pueden consultar en la seccion de Descargas
Los cambios en las juntas directivas.
Las compras del municipio ya incluyen todas las razones sociales y CUITs de las empresas que proveen el bien o servicio comprado, por lo que ya tenemos una lista de todas las empresas a buscar.
En el Boletín Oficial de la República Argentina se publican los cambios en las juntas directivas de las empresas. El BORA es un documento de publicación diaria con cientos de páginas de las cuales sólo unas pocas son de nuestro interés. Para este trabajo, se usó el buscador de sociedades del BORA https://timeline.boletinoficial.gob.ar/.
Este no es un buscador que recorra todas las palabras de cada Boletín en existencia, dicha tarea sería extremadamente lenta. Por el contrario, según indica la fuente, utilizan técnicas de IA y aproximaciones estadísticas para indexar las sociedades nombradas en los boletines.
Al hacer click en uno de los puntos nos lleva (si tenemos suerte) al fragmento del BORA que hace referencia. Ejemplo 1
En otros casos, sobretodo para avisos viejos, nos lleva a un PDF embebido en la pagina. Ejemplo 2
Y para casos de boletines aun mas viejos, esto nos puede llevar a una pagina con un PDF embebido con un escaneo de una fotocopia. Ejemplo 3
El parsing para el caso 2 es muy difícil, puesto que cada numero arbitrario de palabras en el PDF embebido aparece como un elemento <div canva en el HTML solo luego de ejecutar el codigo javascript de pdf.js
, por lo que requiere interaccion con un usuario para poder obtener la información. Además, el fragmento donde se menciona a la empresa en cuestión puede estar en cualquier lugar del boletín.
El parsing para el caso 3 es cercano a imposible, puesto que ademas de tener que descargar dinamicamente el documento PDF, hay que hacer OCR, luego parsing para encontrar el párrafo de mención y finalmente otro parsing para encontrar quienes son mencionados.
Esto nos deja solo con los casos que den como resultado de búsqueda el ejemplo 1. De todos modos dado que nuestro dataset solo contiene compras desde 2007 en adelante y dado que las empresas que ganan las compras del municipio no suelen tener 1 siglo de historia, esto no supuso un problema muy grande.
La informatizacion
Al hacer una búsqueda en la pagina, nuestro navegador hace una POST request al endpoint https://timeline.boletinoficial.gob.ar/
con el siguiente body
searchtext_type=society&searchtext_society=DISALAR
Por lo que cambiando "DISALAR" por la razón social que queramos, podemos automatizar el uso del buscador. La respuesta es una pagina HTML entera con el timeline mostrado en las imágenes anteriores. Por suerte, hay un codigo javascript embebido en el html en la pagina devuelta que tiene la información de todos los puntos del timeline
... codigo html anterior ...
<script type="text/javascript">
const societies = [{"indice": 1, "items": [{"avisos": [{"accion": "CONTRATOS SOBRE PERS.JURIDICAS", "asuntos": [], "denominacion": "DISALAR S.R.L.", "denominacion_original": "DISALAR S.R.L.", "fecha_desde": "13-09-2004", "fecha_hasta": "13-09-2004", "fecha_publicado": "13-09-2004", "id_aviso": "H1995248", "id_rubro": 1000, "nuevo": true, "rubro": "CONTRATOS SOBRE PERS.JURIDICAS", "tags": {"autoridad_designada": [], "cuit": [], "direccion_sociedad": [], "dni": [], "fecha_constitucion": [], "firmante": [], "integrante": [], "integrante_renunciante": [], "objeto_social": []}}], "fecha_desde": "13-09-2004", "fecha_hasta": "13-09-2004"}, {"avisos": [{"accion": "MODIFICACIONES SRL", "asuntos": ["AUMENTO DE CAPITAL CON REFORMA ", "Renuncia Gerente(s) Y Designa Gerente(s)"], "denominacion": "DISALAR S.R.L.", "denominacion_original": "DISALAR S.R.L.", "fecha_desde": "08-01-2014", "fecha_hasta": "08-01-2014", "fecha_publicado": "08-01-2014", "id_aviso": "A318451", "id_rubro": 1220, "nuevo": true, "rubro": "MODIFICACIONES SRL", "tags": {"autoridad_designada": ["miguel angel ladelfa", "dami\u00e1n ladelfa"], "cuit": [], "direccion_sociedad": [], "dni": [], "fecha_constitucion": [], "firmante": ["gerson cesar gonsales"], "integrante": ["miguel angel ladelfa", "dami\u00e1n ladelfa"], "integrante_renunciante": [], "objeto_social": []}}], "fecha_desde": "08-01-2014", "fecha_hasta": "08-01-2014"}, {"avisos": [{"accion": "MODIFICACIONES SRL", "asuntos": ["REFORMA", "SEDE"], "denominacion": "DISALAR S.R.L.", "denominacion_original": "DISALAR S.R.L.", "fecha_desde": "30-09-2011", "fecha_hasta": "30-09-2011", "fecha_publicado": "30-09-2011", "id_aviso": "A56887", "id_rubro": 1220, "nuevo": true, "rubro": "MODIFICACIONES SRL", "tags": {"autoridad_designada": [], "cuit": [], "direccion_sociedad": ["av. rivadavia 5496, 1\u00b0 piso, departamento d, caba", "av. rivadavia 5496, 1\u00b0 piso, departamento d, caba"], "dni": [], "fecha_constitucion": [], "firmante": [], "integrante": [], "integrante_renunciante": [], "objeto_social": []}}], "fecha_desde": "30-09-2011", "fecha_hasta": "30-09-2011"}], "razon_social": "DISALAR S.R.L.", "tipo": "PERS.JURIDICAS", "total_avisos": 3}];
const bora_avisos_segunda_url = "https://www.boletinoficial.gob.ar/detalleAviso/segunda/";
... resto del codigo ...
Notar que la declaración de variable "societies" tiene en su contenido un json
con la información que necesitamos para ir a la publicación, ademas de un poco de información contextual. Algo interesante de la información contextual que devuelve la página es que no se muestra de ninguna manera y que parece tener un objetivo similar al nuestro. El json mejor formateado de un aviso de ejemplo va a dejar el punto mas claro
{
"avisos": [
{
"accion": "MODIFICACIONES SRL",
"asuntos": [
"AUMENTO DE CAPITAL CON REFORMA ",
"Renuncia Gerente(s) Y Designa Gerente(s)"
],
"denominacion": "DISALAR S.R.L.",
"denominacion_original": "DISALAR S.R.L.",
"fecha_desde": "08-01-2014",
"fecha_hasta": "08-01-2014",
"fecha_publicado": "08-01-2014",
"id_aviso": "A318451",
"id_rubro": 1220,
"nuevo": true,
"rubro": "MODIFICACIONES SRL",
"tags": {
"autoridad_designada": [
"miguel angel ladelfa",
"damián ladelfa"
],
"cuit": [],
"direccion_sociedad": [],
"dni": [],
"fecha_constitucion": [],
"firmante": [
"gerson cesar gonsales"
],
"integrante": [
"miguel angel ladelfa",
"damián ladelfa"
],
"integrante_renunciante": [],
"objeto_social": []
}
}
],
"fecha_desde": "08-01-2014",
"fecha_hasta": "08-01-2014"
}
El json embebido en la variable del codigo javascript contiene un parametro autoridad_designada que nombra a los directivos de la junta de disalar mencionados en el fragmento. Esto es lo que nosotros queremos lograr, pero en nuestro caso vimos dos principales fallos por los cuales no pudimos aprovechar este trabajo.
- Suele estar vacia la "autoridad_designada" en la mayoria de casos
- No indica el puesto al cual se designo la autoridad. Podria ser presidente, gerente; etc. Esto es importante para nosotros porque queremos saber la evolucion del mismo puesto en funcion del tiempo
Entonces lo unico que nos sirve de esta parte es el "id_aviso" y "fecha_hasta", puesto que nos deja formar la URL para consultar la fuente: https://www.boletinoficial.gob.ar/detalleAviso/segunda/A318451/20140108
Haciendo una GET request a la fuente obtenemos el codigo HTML de la pagina del BORA con el fragmento original. El mismo es amigable para parsear puesto que el fragmento esta envuelto en un <div> con id constante y unico: "cuerpoDetalleAviso"
<div id="cuerpoDetalleAviso" class="col-md-12 form-group detalle-cuerpo justified">
<p><style>table tr td {border: 1px solid grey; border-spacing: 0px; border-collapse: collapse; padding: 5px; font-size: 12px; text-align: center;}</style><p>Acto privado: 30/12/13. Actas: 22/10/12 y 19/9/13. Se designan gerentes a Miguel Angel Ladelfa y Damián Ladelfa, ambos con domicilio especial en Rivadavia 5496, 1º piso, dpto D, CABA. Aumento de capital y reforma cláusula 4º y 5º. Capital: $ 112000. Administración: 1 o más gerentes socios o no, indistinta por el plazo social. Autorizado por acto ut supra. Gerson Cesar Gonsales Habilitado D.N.R.O. Nº 2983</p><p>e. 08/01/2014 Nº 296/14 v. 08/01/2014</p></p>
</div>
Por lo que con una librería como HtmlAgilityPack
es trivial obtener el nodo con ese ID y extraer el texto entre <p> tags.
El código por el cual se obtuvieron los recortes de los boletines oficiales que eran relevantes a las empresas nombradas en las compras concluidas del municipio se puede consultar en Github: MoronParser, en este caso es el proyecto "CeoParser" en la solución. Igual que se dijo anteriormente, este no es un proyecto que vaya a ser mantenido y se deja a modo de ejemplo y reproducibilidad. Cualquiera es libre de clonar el proyecto, ejecutarlo y obtener una copia idéntica a los datos que usan estas páginas.
Ahora empieza lo difícil. Los avisos son completamente desestructurados y si bien es sencillo saber de que empresa el aviso esta hablando (simplemente buscando una mención a su razón social), nosotros queremos saber qué personas fueron designadas a qué posiciones. Una primera aproximación para resolver este problema podría ser usar Regex, por ejemplo
Se\sdesignan\s(\w*\s)a\s(([a-zA-Záéíóú]*\s){3})y\s([a-zA-Záéíóú]*\s?){2}
Con este regex se obtiene el cargo y los nombres en grupos distintos. El problema con esta aproximación es que en la práctica no sabemos realmente cuando termina un nombre, empieza un cargo u otro nombre. Podriamos guiarnos por las mayúsculas, pero estariamos trabajando con la asunción de que todos los nombres están normalizados lo cual, como no podía ser de otra manera, no se cumple. Véase el siguiente aviso A891948
CUIT 30-70716045-0. Por 1 día por Acta de Asamblea Ordinaria del 31 de octubre de 2019 se designa por unanimidad: PRESIDENTE: Luis Miguel MAJUL, DNI 14.680.215. Director Suplente: Santiago José Alcazar, DNI 14343714. Ejerceran su cargo por el termino de tres ejercicios. Constituyen domicilio especial y legal en Concepción Arenal 4865 CABA. Autorizado según instrumento público Esc. Nº 204 de fecha 20/11/2019 Reg. Nº 1374 juana ceber - Matrícula: 3531 C.E.C.B.A.
Este aviso no respeta ningun esquema del aviso anterior. Los cargos estan precedidos por ':', estan escritos en mayúsculas, el nombre del presidente termina con su apellido completo en mayúscula, le sigue su DNI con puntos, luego empieza el siguiente cargo con el nombre esta vez si normalizado y con su DNI sin puntos. Incluso en un mismo aviso, de una misma empresa, escrito por un mismo escribano, no se respeta una estructura ni en la enumeración de nombres. Regex solo puede detectar Lenguajes Regulares y este lenguaje utilizado es No Restringido.
Intenté por algunos meses capturar todos los casos posibles con distintos Regex, pero eventualmente se llegó a situaciones indecidibles donde dos lenguajes regex distintos detectaban partes del mismo fragmento y decidir cual era correcto era imposible.
Intenté también usar técnicas de Procesamiento de Lenguaje Natural (NLP), pero los modelos existentes en ese momento no tenian buena precisión con el lenguaje utilizado en el BORA. Crear un modelo propio requeriría etiquetar muchos fragmentos del BORA, lo cual vence al propósito de automatizar la información.
Inteligencia Artificial.
El proyecto quedó estancado mucho tiempo en el problema del parsing desestructurado. El dataset ya estaba listo y fue sencillo de crear simplmente haciendo scrapping de las paginas del BORA, pero no era posible seguir avanzando con hardware realista y las técnicas existentes.
Eventualmente con la salida de LLMs se pudo intentar una solución distinta; pedirle a un asistente de IA que formatee la información como la necesitamos.
Instruct vs Chat
Los modelos LLMs a los que estamos acostumbrados suelen ser modelos entrenados para sesiones de chat. Estos modelos (como Chat GPT) son muy verbosos, estan entrenados para vomitar mucho mas texto del que se les pide, mientras que nosotros queremos que dado un fragmento del BORA, devuelva quienes son las nuevas autoridades de la empresa.
Los modelos instruct son aquellos entrenados para lo opuesto a los modelos de chat. Tratan de dar respuestas lo mas cortas y precisas posibles a su instruccion. Suelen usarse para traducir textos o formatearlos. En la pagina de OpenAI se pueden usar modelos de Chat como modelos Instruct o puros (no sesgados en ninguna direccion, chat o instruct).
Por lo tanto, los modelos instruct podrían llegar a resolver el problema
Modelos
Los ensayos iniciales no fueron buenos. Davinci-003 (el modelo detras de Chat GPT 3) era realmente malo con la tarea, incluso con multiples ejemplos correctos para que tome de referencia (esto se conoce como few-shot) no lograba una buena precisión.
En otro frente, la comunidad open source tenia Llama, el modelo de Meta que era libre de ser modificado; esto hizo que se publicaran muchos modelos instruct basados en Llama, pero ninguno era especialmente bueno para este caso de uso.
Cuando GPT4 llega al publico esto cambio, GPT4 sí era capaz de obtener la información desde el texto desestructurado, pero no cumple con el propósito de este trabajo. El acceso a la información ha de ser público y al alcance de todos. Un modelo cerrado que es extremadamente caro de hacer funcionar (costaria varios miles de dólares usarlo para el boletín argentino) no es accesible.
La respuesta a GPT4
por parte de Meta fue Llama 2
, tambien de codigo pseudoabierto que permitia publicar modificaciones al modelo original. Al mismo tiempo una nueva técnica para ejecutar modelos en computadoras de bajos recursos empezaba a surgir en la comunidad científica: la quantización, esta técnica permite reducir la memoria necesaria para ejecutar un modelo y lo optimiza para su ejecución en un procesador en lugar de una GPU (a costa de perder calidad y performance). Los papers que salieron con esta técnica y su posterior implementación en librerías como pytorch
o tensorflow
generaron una carrera por hacer modelos los más eficientes posibles y que consuman la menor memoria.
Un modelo que fue probado contra el dataset fue Orca, este modelo trata de imitar las respuestas de modelos mas grandes (LFMs) como GPT4 o Vicuna pero usando menos parametros. Este modelo fue luego reducido a 7B de parametros y publicado como Orca mini y finalmente quantizado por el usuario TheBloke
dando lugar a Orca mini GPTQ, modelo el cual necesita poco más de 2GB de memoria para funcionar. Este modelo quantizado y reducido tuvo una precision baja, pero mejor que GPT3, corriendo en un procesador Ryzen 5 1600AF, algo impensable algunos meses atras.
Mas tarde se publica Orca 2 con un gran paper: Orca 2: Teaching Small Language Models How to Reason donde uno de los autores porta nuestra bandera 🇦🇷. Este modelo también fue quantizado por el usuario TheBloke
y finalmente fue el primer modelo que se pudo ejecutar en hardware disponible para el usuario de a pie que a la vez logró una precisión del 96%.
from ctransformers import AutoModelForCausalLM
import requests
import csv
from bs4 import BeautifulSoup
import time
from datetime import datetime
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--input_data", type=str)
parser.add_argument("--output_data", type=str)
args = parser.parse_args()
llm = AutoModelForCausalLM.from_pretrained(
model_path_or_repo_id="TheBloke/Orca-2-13B-GGUF",
model_type="llama",
gpu_layers=0, # CPU only execution
context_length=2048,
stop="###",
threads=16)
system = """
The following are a series of newspaper posts of companies that changed their board of directors.
The posts are in Spanish. These are different companies, so their board positions names and the amount of board members may differ between the companies.
Your job is to answer how the new board is constituted using their position name and the new person in charge.
Always only answer the Board Position and Name of the person in json. This json can be indefinetely large
###
Informa que: Por Asamblea Ordinaria del 23/12/16 se eligió directorio, por vencimiento de mandato: Presidente: Eduardo Enrique Martinez; Vicepresidente: Víctor Sebastian Lusardi y Director suplente: Victor Luis Lusardi. Fijan domicilio especial en San Martín 66, piso 4º, Depto 409, CABA. Autorizado según instrumento privado Asamblea Ordinaria de fecha 23/12/2016 Yamila Soledad Rodriguez Foulon - T°: 97 F°: 542 C.P.A.C.F.
{
\"Presidente\": "Eduardo Enrique Martinez\",
\"Vicepresidente\": \"Víctor Sebastian Lusardi\",
\"Director suplente\": \"Victor Luis Lusardi\"
}
###
"""
now = datetime.now()
output_file = open(args.output_data, "w", encoding="utf-8")
with open(args.input_data, "r", encoding="utf-8") as f:
writer = csv.writer(output_file, delimiter=';')
reader = csv.reader(f, delimiter=';')
for row in reader:
start_time = time.time()
url = row[1]
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')
div = soup.find(id="cuerpoDetalleAviso")
result = llm(f"{system}{div.text}")
writer.writerow([row[0], row[1], result])
elapsed_time = time.time() - start_time
print(f"Time elapsed: {elapsed_time} seconds")
output_file.close()
Con este pedazo de codigo, la VM más barata con al menos 16 vCores, 64gb de RAM en Azure y 20 horas de ejecución, se logró procesar todas las menciones en boletines oficiales de las empresas nombradas en compras del municipio. El mismo modelo se ejecuto en una notebook con un procesador de bajo consumo Ryzen 7 3600u y en una computadora de escritorio en un procesador Ryzen 5 3600G, la ejecucion en ambos casos tardo aproximadamente 9 horas y obtuvo resultados similares. El costo de ejecución en Azure fue de U$D18.
Notar que el prompt es one-shot
, esto significa que sólo se le dió 1 ejemplo de cómo hacer la tarea al modelo, pues Orca 2 esta especialmente entrenado para responder a instrucciones con pocos ejemplos, de acuerdo al paper. Llama 2 tuvo peor performance con 3-shot
y 4-shot
, igual que Orca 1 y GPT3.
A continuacion se pueden consultar los resultados de dicha ejecucion, son los mismos que se muestran en la seccion de Descargas