DEV Community

Rigel Carbajal
Rigel Carbajal

Posted on

MLLinearRegression & Bitcoin con Swift

(Contexto) La empresa para compra y trading de criptomonedas Bitso lanzó el pasado 15 de enero del 2022 un concurso donde debes adivinar el precio que tendrá el bitcoin el día 7 de marzo del mismo año, y al que acierte ó quede más cerca del precio ganará, nada más y nada menos que un Bitcoin.

Para no dejarme llevar por la intuición, y, aún sabiendo que es muy difícil ó imposible, dependiendo a quien le preguntes, adivinar que precio tendrá el Bitcoin dentro de un mes, decidí aplicar el framework de Apple llamado CreateML para la elaboración de un modelo de Machine Learning que me ayude, a, por lo menos, tener una vaga noción del precio que podría llegar a tener, y como excusa para usar Swift en Machine Learning.

Si bien Python es un lenguaje muy bonito y útil, la verdad es que Swift me parece tiene un gran potencial que está bastante desperdiciado, pienso que ser conocido como el “lenguaje para desarrollo de apps de Apple” no le ayuda para sus otras áreas, como quizás sabrán, ya desde hace algunos años atrás es open source y es compatible con Windows y Linux, además que cuenta con un framework de Backend llamado Vapor, que es bastante rápido, asíncrono, fácil de usar, y por su playground es muy sencillo comenzar a probar y jugar con código como lo veremos a continuación.

Swift fue parte también de la investigación de Google para su uso en Machine Learning, ya que lo integraban con Google Colab y TensorFlow, pero no me atrevo a hablar mucho del tema dado que no lo he probado en esa plataforma, pero evidencia su uso en el área de Ciencia de Datos, algunas de sus ventajas es que es rápido, tiene un gran manejo de memoria, es de tipado fuerte, es fácil de aprender, se puede usar de backend, usa fluent como lenguaje de plantilla en html, se puede usar para consola, mobile y desktop, es muy seguro, está respaldado por Apple, barre, trapea y lava los trastes; hay talento, solo hace falta apoyarlo jaja.

En fin, al ser la primera vez que utilizaba Swift tuve que leer y ver algunos videos de documentación, pero nada realmente complicado, utilicé la librería TabularData para el manejo y un poco de limpieza de los datos.

Este video de Exploración y Manipulación de Datos me sirvió bastante para tomar sentido de lo que estaba haciendo. Y como no puede ser de otro modo el sitio de hackingswift que me guió y dio la idea.

Los datos los descargué de la página de Nasdaq, un archivo CSV con las fechas y los precios de cierre que utilicé para la regresión lineal.

Ya dame el maldito código!

En fin, sin tanto rodeo, ahí les va el código, todo está hecho en un Playground de Xcode, que, la verdad, si noté uno que otro bug, ó será que mi MacBook es del 2015 con intel como toda una guerrera vikinga y no una m2 como las de ahora jaja parece le dio amsiedad, de repente no me mostraba los resultados o se quedaba como trabada sin estar trabada, cosas raras, pero en pocas ocasiones realmente(ó no tan pocas).


// Primero importamos las librerías que utilizaremos
import CreateML
import TabularData
import Foundation
// -------------------------------------------------

// Instanciamos CSVReadingOptions, lo usaremos para convertir la fecha de String a Date.
var options = CSVReadingOptions()
// --------------------------------------------------

// Creamos la estrategía para convertir la fecha de String a Date, dependiendo el formato-
// en el que se encuentre en el archivo CSV, en mi caso era MM/DD/YYYY. 01/31/2022.
options.addDateParseStrategy(
    Date.ParseStrategy(
        format: "\(month: .twoDigits)/\(day: .twoDigits)/\(year: .defaultDigits)",
        locale: Locale(identifier: "en_US"),
        timeZone: TimeZone(abbreviation: "CST")!
    )
)
// --------------------------------------------------

// Aquí leemos los archivos CSV, debemos arrastrar los archivos a la sección Resources, 
// de lado izquierdo en la barra de nuextro Xcode.
// En este caso son dos archivos, el que contiene los datos y un segundo que creé con fechas
// de prueba para ver los resultados.
let csvFile = Bundle.main.url(forResource: "HistoricalData", withExtension: "csv")!
let csvPred = Bundle.main.url(forResource: "prediction", withExtension: "csv")!
// --------------------------------------------------

// Creamos nuestro primer DataFrame que usaremos para hacer el modelo de Regresión Líneal
var dataFrame = try DataFrame (
    contentsOfCSVFile: csvFile,                        // Colocamos la variable que contiene nuestro CSV.
    columns: ["Fecha","Cerrar/último"],                // Indicamos que columnas usaremos de nuestro CSV
    types: ["Fecha": .date, "Cerrar/último": .double], // Indicamos el tipo de dato que contienen las columnas.
    options: options // Cargamos nuestras opciones para leer correctamente las fechas.
)
// --------------------------------------------------

// Imprimimos nuestro DataFrame para conocer su estructura, como podemos observar
// la columna Fecha es de tipo Date y la columna Cierre es de tipo Double como indicamos
// en la configuración de arriba. Son los datos con los que entrenaremos nuestro modelo.
print(dataFrame)
//┏━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┓
//┃    ┃ Fecha                     ┃ Cierre   ┃
//┃    ┃ <Date>                    ┃ <Double> ┃
//┡━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━┩
//│ 0  │ 2022-01-30 06:00:00 +0000 │ 36,958.5 │
//│ 1  │ 2022-01-29 06:00:00 +0000 │ 38,040.5 │
//│ 2  │ 2022-01-28 06:00:00 +0000 │ 37,844.5 │
//│ 3  │ 2022-01-27 06:00:00 +0000 │ 37,151.3 │
//│ 4  │ 2022-01-26 06:00:00 +0000 │ 35,905.8 │
//│ 5  │ 2022-01-25 06:00:00 +0000 │ 36,391.9 │
//│ 6  │ 2022-01-24 06:00:00 +0000 │ 36,013.2 │
//│ 7  │ 2022-01-23 06:00:00 +0000 │ 35,095.6 │
//│ 8  │ 2022-01-22 06:00:00 +0000 │ 35,245.4 │
//│ 9  │ 2022-01-21 06:00:00 +0000 │ 36,303.9 │
//│ 10 │ 2022-01-20 06:00:00 +0000 │ 38,853.1 │
//│ 11 │ 2022-01-19 06:00:00 +0000 │ 41,929.2 │
//│ 12 │ 2022-01-18 06:00:00 +0000 │ 41,705.1 │
//│ 13 │ 2022-01-17 06:00:00 +0000 │ 42,026.6 │
//│ 14 │ 2022-01-16 06:00:00 +0000 │ 42,721.7 │
//│ 15 │ 2022-01-15 06:00:00 +0000 │ 43,069.7 │
//│ 16 │ 2022-01-14 06:00:00 +0000 │ 42,878.6 │
//│ 17 │ 2022-01-13 06:00:00 +0000 │ 42,747.6 │
//│ 18 │ 2022-01-12 06:00:00 +0000 │ 43,618.7 │
//│ 19 │ 2022-01-11 06:00:00 +0000 │ 42,576.5 │
//┢╍╍╍╍┷╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┷╍╍╍╍╍╍╍╍╍╍┪
//┇ ...                                       ┇
//┗╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍╍┛

// Lo mismo que arriba, pero solo para una sola columna.
var dfPred = try DataFrame(
    contentsOfCSVFile: csvPred,
    columns: ["Fecha"],
    types: ["Fecha": .date],
    options: options
)

// Las fechas para las que haremos las predicciones de los precios.
//┏━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
//┃    ┃ Fecha                     ┃
//┃    ┃ <Date>                    ┃
//┡━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
//│ 0  │ 2022-03-01 06:00:00 +0000 │
//│ 1  │ 2022-03-02 06:00:00 +0000 │
//│ 2  │ 2022-03-03 06:00:00 +0000 │
//│ 3  │ 2022-03-04 06:00:00 +0000 │
//│ 4  │ 2022-03-05 06:00:00 +0000 │
//│ 5  │ 2022-03-06 06:00:00 +0000 │
//│ 6  │ 2022-03-07 06:00:00 +0000 │<-- Fecha concurso Bitso
//│ 7  │ 2022-03-08 06:00:00 +0000 │
//│ 8  │ 2022-03-09 06:00:00 +0000 │
//│ 9  │ 2022-03-10 06:00:00 +0000 │
//│ 10 │ 2022-03-11 06:00:00 +0000 │
//│ 11 │ 2022-03-12 06:00:00 +0000 │
//└────┴───────────────────────────┘
// --------------------------------------------------

// Usamos la función renameColumn para cambiar el nombre de la columna, tiene muchas otras funciones útiles
// que me recuerda a Pandas para el manejo y transofrmación de datos, en este caso para que sea más simple la lectura de esa columna.
dataFrame.renameColumn("Cerrar/último", to: "Cierre")
// --------------------------------------------------

// Separamos los datos de entrenamiento y prueba
let (trainingData, testingData) = dataFrame.randomSplit(by: 0.8)
// --------------------------------------------------

// Al igual que con CSVReadingOptions, instanciamos ModelParameters para acceder y configurar
// propiedades para generar nuestro modelo.
var params = MLLinearRegressor.ModelParameters()
params.maxIterations = 10000 // Iteramos 10000 para mayor placer (exactitud).
// --------------------------------------------------

// Generamos nuestro modelo con MLLinearRegressor
let btcPricer = try MLLinearRegressor(
    trainingData: trainingData.base, // Le pasamos nuestros datos de entrenamiento creados anteriormente.
    targetColumn: "Cierre",          // Colocamos el nombre de la columna de la que queremos las predicciones (output)
    featureColumns: ["Fecha"],       // Colocamos el dato que será de entrada (el segundo CSV en este caso), las fechas para que prediga el precio (Cierre)
    parameters: params.              // Colocamos la variable con los parametros establecidos en la sección anterior.
)
// --------------------------------------------------

// Aquí nos muestra la información de nuestro modelo y su nivel de exactitud
print(btcPricer.description)

//Parameters
//Max Iterations: 10000
//L1 Penalty: 0.0
//L2 Penalty: 0.01
//Step Size: 1.0
//Convergence Threshold: 0.01
//Feature Rescaling: true
//Performance on Training Data
//Max error: 27973.70
//Root mean squared error: 10217.46
//Performance on Validation Data
//Max error: 24645.20
//Root mean squared error: 10218.74
// --------------------------------------------------

// Basicamente lo mismo de arriba, pero resumido.
let evaluationMetrics = btcPricer.evaluation(on: testingData.base)
print(evaluationMetrics.rootMeanSquaredError)
print(evaluationMetrics.maximumError)
// --------------------------------------------------

// La metadata que contendrá nuestro modelo
let metadata = MLModelMetadata(
    author: "RigelGC",
    shortDescription: "Bitcoin Price Predictor 8000",
    version: "1.0"
)
// --------------------------------------------------

// Aquí guardamos nuestro modelo de Machine Learning para hacer predicciones, lo podemos usar con Python
// con la librería CoreMLTools y hacer predicciones desde ahí.
try btcPricer.write(
    to: URL(fileURLWithPath: "/Users/rigelgc/Downloads/btcPricer.mlmodel"), // La ruta donde se guardará nuestro archivo *.mlmodel
metadata: metadata                                                          // La metadata que generamos anteriormente.
)
// --------------------------------------------------

// Imprimimos nuestra predicción, mandamos llamar btcPricer.Predictions y dentro le pasamos nuestro segundo DataFrame
// Con los datos de entrada, en este caso solo una columna llamada Fechas con las fechas a las cuales quiero hacer la predicción.
print(try btcPricer.predictions(from: dfPred))

// Nos regresa el siguiente resultado para el segundo DataFrame
// con la columna Fecha y sus 12 fechas.
//┏━━━━━━━━━━━━━┓
//┃ Predictions ┃
//┃ <Double>    ┃
//┡━━━━━━━━━━━━━┩
//│  53,086.847 │
//│  53,140.417 │
//│  53,193.986 │
//│  53,247.555 │
//│  53,301.125 │
//│  53,354.694 │
//│  53,408.263 │ 2022-03-07 <-- Fecha concurso Bitso
//│  53,461.832 │.               Para esa Fecha ese es el precio que predice que tendrá.
//│  53,515.402 │
//│  53,568.971 │
//│   53,622.54 │
//│   53,676.11 │
//└─────────────┘

Enter fullscreen mode Exit fullscreen mode

Ahora imaginen que tienen una tienda, por ejemplo, y quieren conocer como será la venta de cierto artículo para algún mes o fracción del año, pueden realizar la extracción de información de sus bases de datos del punto de venta y aplicar este mismo método para predecirlo.

Si bien es cierto en este modelo hay muuuuuuucho! que mejorar, como primer acercamiento al lenguaje y frameworks de Apple me ha parecido muy interesante. Cierto es que es mucho más fácil de utilizar que TensorFlow ó SKLearn u otros en Python, pero en este último la automatización, de momento, es más sencilla para muchos casos de uso.

En fin, espero que está pequeña guía les halla servido de utilidad, queda mucho por aprender, lo más complicado fue la transformación de datos jaja y entender algunas situaciones entre DataFrame y MLDataTable que si fue un poco dolor de cabeza, pero siempre muy interesante 😄
Saludos a todos.

Bitso es la mejor plataforma para adquirir criptos en México por el momento, les dejo él link.

Top comments (0)