{"version":"https://jsonfeed.org/version/1","title":"Abe Estrada","home_page_url":"https://abeestrada.com/","feed_url":"https://feed.abeestrada.com/all/json","author":{"name":"Abe Estrada"},"items":[{"id":"https://abeestrada.com/post/feeds-sin-actualizar/","url":"https://abeestrada.com/post/feeds-sin-actualizar/","title":"Feeds sin actualizar","content_html":"
Una de las cosas que extraño cuando empecé a utilizar Newsboat\n (no es queja), es que no puedo revisar de una forma fácil como en Feedbin\n. Tengo una lista donde feeds no han sido actualizados desde hace tiempo, o feeds con problemas, ya sea por que cambiaron de dominio o están rotos, etc.
\nPor lo anterior, tengo que crear mis propias herramientas. Para esto he creado un pequeño programa en Go, para evaluar cada feed.
\nLo que hago, es leer de una archivo urls.txt
todas las urls, luego una por una si es un feed válido, busco la última vez que fueron actualizados, y muestro los feeds que tienen más de 6 meses sin actualizar y de paso los que tienen algún error, ya sea para quitarlo o notificar al dueño.
package main\n\nimport (\n "bufio"\n "context"\n "fmt"\n "os"\n "regexp"\n "sync"\n "time"\n\n "github.com/mmcdole/gofeed"\n)\n\nfunc getFeed(url string) {\n today := time.Now().Local()\n // Timeout: 60 seconds\n ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)\n defer cancel()\n fp := gofeed.NewParser()\n feed, err := fp.ParseURLWithContext(url, ctx)\n if err != nil {\n fmt.Println(">>", url, err)\n return\n }\n var updated *time.Time\n if feed.UpdatedParsed != nil {\n updated = feed.UpdatedParsed\n } else if feed.PublishedParsed != nil {\n updated = feed.PublishedParsed\n } else if len(feed.Items) > 0 && feed.Items[0].UpdatedParsed != nil {\n updated = feed.Items[0].UpdatedParsed\n } else if len(feed.Items) > 0 && feed.Items[0].PublishedParsed != nil {\n updated = feed.Items[0].PublishedParsed\n } else {\n // Couldn't find the last update\n fmt.Println(">", url)\n return\n }\n duration := today.Sub(*updated)\n days := int(duration.Hours() / 24)\n if days > 180 { // Older: 6 months\n fmt.Println(days, url)\n }\n}\n\nfunc main() {\n file, err := os.Open("urls.txt")\n if err != nil {\n fmt.Println("Error:", err)\n return\n }\n defer file.Close()\n scanner := bufio.NewScanner(file)\n urls := []string{}\n pattern := `\\bhttps?:\\/\\/[-A-Za-z0-9+&@#\\/%?=~_|!:,.;]*[-A-Za-z0-9+&@#\\/%=~_|]`\n regex := regexp.MustCompile(pattern)\n for scanner.Scan() {\n line := scanner.Text()\n if line != "" {\n matches := regex.FindAllString(line, -1)\n for _, url := range matches {\n urls = append(urls, url)\n }\n }\n }\n if err := scanner.Err(); err != nil {\n fmt.Println("Error:", err)\n return\n }\n var wg sync.WaitGroup\n wg.Add(len(urls))\n for _, url := range urls {\n go func(url string) {\n defer wg.Done()\n getFeed(url)\n }(url)\n }\n wg.Wait()\n}\n
Este es un ejemplo de como crear resúmenes de documentos (txt y pdf) en Español utilizando el modelo gratuito de Google: Gemma\n en sus dos variantes 2B y 7B en dispositivos con Apple Silicon utilizando Python y MLX\n.
\npip install mlx_lm torch langchain langchain_community pypdf tqdm\n
from mlx_lm import load, generate\nfrom jinja2 import Template\nfrom tqdm import tqdm\nfrom langchain_community.document_loaders import PyPDFLoader, TextLoader\nfrom langchain.text_splitter import RecursiveCharacterTextSplitter\n\n# model_id = "mlx-community/quantized-gemma-7b-it"\nmodel_id = "mlx-community/quantized-gemma-2b-it"\nmodel, tokenizer = load(model_id)\n\nloaders = {"pdf": PyPDFLoader, "txt": TextLoader}\ntext_splitter = RecursiveCharacterTextSplitter(\n chunk_size=5000,\n chunk_overlap=20,\n length_function=len,\n is_separator_regex=False,\n)\n\n# https://storage.googleapis.com/deepmind-media/gemma/gemma-report.pdf\ndef prompt_template(messages, add_generation=True):\n template_str = """\n {% for item in messages %}\n <start_of_turn>{{ item.role }}\\n{{ item.content }}<end_of_turn>\\n\n {% if loop.last %}{% if add_generation %}<start_of_turn>model\\n{% endif %}{% endif %}\n {% endfor %}\n """\n template = Template(template_str)\n result = template.render(messages=messages, add_generation=add_generation)\n return result\n\n\n# https://python.langchain.com/docs/modules/data_connection/document_loaders/\ndef load_file(file_name, file_type):\n path = f"./assets/{file_name}.{file_type}"\n LoaderClass = loaders.get(file_type)\n if LoaderClass is None:\n raise ValueError(f"Unknown file type: {file_type}")\n loader = LoaderClass(path)\n return loader.load_and_split(text_splitter)\n\n\ndef main():\n # https://normas-apa.org/wp-content/uploads/Guia-Normas-APA-7ma-edicion.pdf\n # document = load_file("Guia-Normas-APA-7ma-edicion", "pdf")\n\n # https://www.gutenberg.org/ebooks/2000\n document = load_file("pg2000", "txt")\n prompt = "Crea un resumen del siguiente documento:"\n\n summaries = []\n for doc in tqdm(document[:48]):\n summaries.append(\n generate(\n model,\n tokenizer=tokenizer,\n prompt=prompt_template(\n [{\n "content": prompt + f"'{doc.page_content}'",\n "role": "user"\n }]),\n temp=0.2,\n max_tokens=512,\n ))\n summaries_text = "\\n".join(summaries)\n prompt_summary = {\n "content": f"Crea un resumen del siguiente documento: '{summaries_text}'",\n "role": "user"\n }\n result = generate(\n model,\n tokenizer=tokenizer,\n prompt=prompt_template([prompt_summary]),\n temp=0.2,\n max_tokens=1024\n )\n print(result)\n\n\nif __name__ == '__main__':\n main()\n
Lo que hace el código anterior es cargar un archivo, partirlo en fragmentos y crear pequeños resúmenes, para luego en base a esos resúmenes crear un resumen general. Se pueden modificar las variables para extraer información más puntual, empezando con prompt_summary
para buscar nombres por ejemplo, cambiar la temperatura para obtener diferentes resultados y el tamaño del texto de los fragmentos (max_tokens
) del documento para un resultado más amplio, en caso de los resúmenes es de esperarse que alguna información se pierda.
Si el tamaño del documento es muy grande para la capacidad de procesamiento, se puede partir el documento para agilizarlo y no acabarnos la memoria, por ejemplo for doc in tqdm(document[:48]):
solo obtenemos el primer 10% del documento en caso de que el total sea 481 en el caso del documento pg2000.txt
. El tamaño ideal puede variar entre capacidades de cómputo y el tamaño del documento.
\n\nEl documento es un resumen del libro “El ingenioso hidalgo de la Mancha” de Miguel de Cervantes Saavedra. Describe la historia de un caballero llamado Don Quijote, incluyendo su vida y las muchas aventuras que lo han marcado.
\n
\n\n\n\nEl documento es un resumen del libro “Don Quijote” de Miguel de Cervantes Saavedra. Describe la historia del ingenioso hidalgo don Quijote de la Mancha, desde su infancia hasta su muerte. Se cuenta cómo Don Quijote tuvo una serie de aventuras y experiencias, incluyendo una aventura con los cuadrilleros, una aventura con el Caballero del Bosque, una aventura con el pastor enamorado y una aventura con el rebuño y el titlerero. También se cuenta cómo Don Quijote tuvo varias relaciones amorosas y cómo finalmente llegó al casamiento que quería con Dulcinea del Toboso.
\n
El modelo Qwen\n de Alibaba ha sido actualizado\n y ya se puede ejecutar con MLX\n:
\npip install -U mlx-lm\npython -m mlx_lm.generate\\\n --model "mlx-community/Qwen1.5-7B-Chat-4bit-mlx"\\\n --eos-token "<|im_end|>"\\\n --max-tokens 500\\\n --prompt "write a quick sort in python"\n
Algo que me entretuvo bastante tiempo, es la forma en que podemos agregar un #Preview
con contenido de algún modelo utilizando SwiftData a una vista anidada, que no sea la principal o típica ContentView
en SwiftUI.
El ejemplo que encontré por todos lados es el siguiente:
\nTenemos el modelo y agregamos una extensión llamada preview
que podemos utilizar en la vista base.
import Foundation\nimport SwiftData\n\n@Model\nclass Item: Identifiable{\n var name: String\n init(name: String){\n self.name = name\n }\n}\nextension Item {\n @MainActor\n static var preview: ModelContainer {\n let container = try! ModelContainer(for: Item.self,\n configurations: ModelConfiguration(isStoredInMemoryOnly: true))\n let items = [\n Item(name: "Ejemplo 1"),\n Item(name: "Ejemplo 2")\n ]\n for item in items {\n container.mainContext.insert(item)\n }\n return container\n }\n}\n
El cual podemos utilizar de la siguiente forma:
\n#Preview {\n ContentView()\n .modelContainer(Item.preview)\n}\n
El problema surge cuando queremos accesar al modelContainer
en otra vista, ya que este fue solo declarado dentro de la *App.Swift
para ContentView
.
import SwiftUI\n\n@main\nstruct MyApp: App {\n var body: some Scene {\n WindowGroup() {\n ContentView()\n }\n .modelContainer(for: Item.self)\n }\n}\n
Para esto, debemos declarar primero el mainContext
y con esto podemos agregar objetos:
#Preview {\n let context = Item.preview.mainContext\n let item = Item(name: "Ejemplo 3")\n context.insert(item)\n return NavigationStack {\n AnotherView(item: item)\n }\n}\n
Fuente: Hacking with Swift forums\n
\n","date_published":"2024-02-02T19:00:00-07:00"},{"id":"https://abeestrada.com/post/swiftui-userinterfacesizeclass/","url":"https://abeestrada.com/post/swiftui-userinterfacesizeclass/","title":"SwiftUI UserInterfaceSizeClass","content_html":"Hasta ahora, todos los ejemplos para utilizar UserInterfaceSizeClass\n son así:
\nstruct MyView: View {\n @Environment(\\.horizontalSizeClass) private var horizontalSizeClass\n\n var body: some View {\n if horizontalSizeClass == .compact {\n Text("Portrait")\n } else {\n Text("Landscape")\n }\n }\n}\n
Pero al momento de ejecutar el código, no funciona, ya que al parecer horizontalSizeClass
no es suficiente para determinar si un dispositivo a cambiado de orientación.
Y este ejemplo, es el único que me ha funcionado:
\nstruct MyView: View {\n @Environment(\\.verticalSizeClass) private var verticalSizeClass\n @Environment(\\.horizontalSizeClass) private var horizontalSizeClass\n\n var body: some View {\n if (horizontalSizeClass == .regular && verticalSizeClass == .compact) ||\n (horizontalSizeClass == .compact && verticalSizeClass == .compact) {\n Text("Landscape (iPhone)")\n } else if horizontalSizeClass == .compact && verticalSizeClass == .regular {\n Text("Portrait (iPhone)")\n } else {\n Text("iPad")\n }\n }\n}\n
Fuente: SwiftUIでサイズクラスに応じてレイアウトを変える\n
\n","date_published":"2024-01-29T17:00:00-07:00"},{"id":"https://abeestrada.com/post/wordpress-playground/","url":"https://abeestrada.com/post/wordpress-playground/","title":"WordPress Playground","content_html":"Siendo uno de mis primeros lenguajes que aprendí, tengo más de 10 años sin escribir código en PHP\n.
\nY el día de hoy aprendí algo que me voló la cabeza 🤯 y es que ya se puede ejecutar PHP por medio de Node.js\n sin instalar nada extra, gracias a WebAssembly\n (Wasm). Esto se debe a que lograron compilar todo el código de PHP, escrito en un 75% de C a Wasm el cual es interpretado por JavaScript.
\nTan sencillo como:
\n$ npx @php-wasm/cli -v\nPHP 8.3.0-dev (cli) (built: Jan 17 2024 23:41:46) (NTS)\nCopyright (c) The PHP Group\nZend Engine v4.3.0-dev, Copyright (c) Zend Technologies\n
Además de también poder cambiar la versión:
\n$ PHP=7.4 npx @php-wasm/cli -v\nPHP 7.4.31-dev (cli) (built: Jan 17 2024 23:43:33) ( NTS )\nCopyright (c) The PHP Group\nZend Engine v3.4.0, Copyright (c) Zend Technologies\n
Las versiones disponibles\n por el momento, son:
\nIncluso se puede ejecutar el servidor web que incluye PHP:
\nnpx @php-wasm/cli -S localhost:8000\n
Y es así como después de más de 10 años, pude escribir PHP de nuevo:
\n<?php phpinfo();\n
El proyecto es creado y mantenido por WordPress\n/wordpress-playground\n.
\n","date_published":"2024-01-25T16:30:00-07:00"},{"id":"https://abeestrada.com/uses/","url":"https://abeestrada.com/uses/","title":"Tools I use","content_html":"Based on https://uses.tech\n
\nMe acabo de enterar que Intel ha creado\n un conjunto de herramientas (plugins) de IA (que en realidad es Machine Learning) para Audacity, útiles tanto para audio solo con voz como para música. Estas funciones se ejecutan de forma 100% local.
\nPara los contenidos de solo voz, el plugin de supresión de ruido es procesado utilizando OpenVINO.
\nPor el momento el plugin solo esta disponible para Windows (precompilado) y para Linux si lo compilas desde cero.
\nEn mi casolo, solo quiero probar el Noise Suppression en macOS.
\nPrimero hay que crear un ambiente virtual de Python\n para este proyecto.
\nEmpezamos con instalar OpenVINO.
\npip install openvino\n
Y verificamos que funcione:
\npython -c "from openvino.runtime import Core; print(Core().available_devices)"\n
Luego tenemos que conseguir los modelos, para lo cual tenemos dos opciones:
\nopenvino-models.zip
\n y extraemos los archivos:noise-suppression-denseunet-ll-0001.bin
noise-suppression-denseunet-ll-0001.xml
open_model_zoo
podemos ver los enlaces de descarga directa: https://github.com/openvinotoolkit/open_model_zoo/blob/master/models/intel/noise-suppression-poconetlike-0001/model.yml\n y podemos obtener:noise-suppression-poconetlike-0001.bin
noise-suppression-poconetlike-0001.xml
Cualquiera de los dos modelos funciona.
\nLuego tenemos que convertir el archivo de audio con ruido que queremos procesar a 16kHz:
\nffmpeg -i noisy.flac -ar 16000 noisy.wav\n
Y obtenemos el archivo de ejemplo:
\n\nLuego lo ejecutamos de la siguiente forma:
\npython noise_suppression_demo.py \\\n--model=models/noise-suppression-denseunet-ll-0001.xml \\\n--input=noisy.wav \\\n--output=cleaned.wav\n
De esta forma podemos procesar el audio en macOS sin tener que esperar al plugin para Audacity.
\n","date_published":"2024-01-04T21:00:00-07:00"},{"id":"https://abeestrada.com/post/mixtral-8x7b/","url":"https://abeestrada.com/post/mixtral-8x7b/","title":"Mixtral 8x7B","content_html":"Siguiendo con mis experimentos con MLX\n, me puse a intentar correr el nuevo modelo de Mistral: Mixtral 8x7B\n.
\nPara esto, necesitamos los directorios donde vamos a trabajar:
\nmkdir mixtral-8x7b\ncd mixtral-8x7b\n
Descargamos el modelo de aproximadamente 86GB por medio de un torrent que fue tuiteado\n directamente de la cuenta de @MistralAI\n. En mi caso utilizo aria2\n, pero cualquier cliente que acepte enlaces magnet
va a funcionar. El modelo se va a encontrar dentro de mixtral-8x7b-32kseqlen/
.
aria2c 'magnet:?xt=urn:btih:5546272da9065eddeb6fcd7ffddeef5b75be79a7&dn=mixtral-8x7b-32kseqlen&tr=udp%3A%2F%http://2Fopentracker.i2p.rocks%3A6969%2Fannounce&tr=http%3A%2F%http://2Ftracker.openbittorrent.com%3A80%2Fannounce'\n
Instalamos las dependencias requeridas:
\npip install mlx sentencepiece torch numpy\n
Del repositorio de ejemplos\n, descargamos los siguientes archivos:
\nConvertimos el modelo:
\npython convert.py --model_path mixtral-8x7b-32kseqlen/\n
Y ejecutamos:
\npython mixtral.py --model_path mixtral-8x7b-32kseqlen/\n
Y si tenemos más de 100GB de RAM debe funcionar.
\nDesgraciadamente me dí cuenta hasta que la ejecución el script de Python estaba siendo terminada por falta de RAM 😔.
\nDespués de mi intento fallido, y esperar dos días, Mozilla ha lanzado el modelo en formato de llamafile
, que es un ejecutable multiplataforma, optmizado, y que para este modelo viene en dos versiones:
mixtral-8x7b-instruct-v0.1.Q3
mixtral-8x7b-instruct-v0.1.Q6
Que a su vez tienen dos versiones cada uno, linea de comand o server.
\nPara mis especificaciones, descargué mixtral-8x7b-instruct-v0.1.Q3_K_M-server.llamafile
\n.
chmod +x mixtral-8x7b-instruct-v0.1.Q3_K_M-server.llamafile\n./mixtral-8x7b-instruct-v0.1.Q3_K_M-server.llamafile\n
Esta versión tan solo requiere 30GB de RAM para ejecutarse.
\n\n\n","date_published":"2023-12-14T13:00:00-07:00"},{"id":"https://abeestrada.com/post/mlx--whisper/","url":"https://abeestrada.com/post/mlx--whisper/","title":"MLX + Whisper","content_html":"Llevo tiempo siguiendo el avance que tiene Whisper\n para la transcripción de texto. Hasta ahora había sido muy lento, tomando aproximadamente el doble de tiempo del audio para trasncribir el texto, por lo cual hacía no factible su uso en una computadora personal, todos los demas ejemplos se ejecutaban en servidores con tarjeta gráficas más potentes.
\nHace unos días, Apple lanzó MLX\n, un framework que viene a más o menos reemplazar PyTorch para proyectos que tengan que ver con redes neurales, con la ventaja de que esta 100% optimizada para utilizar todos los recursos que ofrecen los procesadores M, en este caso el “Neural Engine” integrado. Entre los cuales se encuentra Whisper.
\nEn el repositorio existen varios ejemplos\n, utilizando el ejemplo de mlx-examples/whisper\n:
\nmain.py
from pprint import pprint\nfrom whisper import transcribe\n\nMODEL = 'large-v3' # ~/.cache/whisper\nAUDIO = './whisper/assets/<AUDIO>.wav'\n\n\ndef main():\n x = transcribe(audio=AUDIO, model=MODEL, verbose=False)\n pprint(x)\n\n\nif __name__ == '__main__':\n main()\n
Y utilizando al 100% el Neural Engine del procesador M, obtenemos las transcripciones en un 10% del tiempo de la duración del audio original.
\n\n\nFuente:
\n