Sigo trasteando con julia

julia
produccion
linux
2022
Author

jlcr

Published

October 26, 2022

Siguiendo con lo que contaba aquí me he construido un binario para predecir usando un modelo de xgboost con Julia. La ventaja es que tengo un tar.gz que puedo descomprimir en cualquier linux (por ejemplo un entorno de producción sin acceso a internet y que no tenga ni vaya a tener julia instalado, ni docker ni nada de nada), descomprimir y poder hacer un miapp_para_predecir mi_modelo_entrenado.jls csv_to_predict.csv resultado.csv y que funcione y vaya como un tiro.

Pongo aquí los ficheros relevantes.

Por ejemplo mi fichero para entrenar un modelo y salvarlo .

Fichero train_ boston.jl

# Training model julia
using  CSV,CategoricalArrays, DataFrames, MLJ, MLJXGBoostInterface


df1 = CSV.read("data/boston.csv", DataFrame)

df1[:, :target] .= ifelse.(df1[!, :medv_20].== "NG20", 1, 0)
const target = CategoricalArray(df1[:, :target])

const X = df1[:, Not([:medv_20, :target])]

Tree = @load XGBoostClassifier pkg=XGBoost
tree_model = Tree(objective="binary:logistic", max_depth = 6, num_round = 800)
mach = machine(tree_model, X, target)

Threads.nthreads()
evaluate(tree_model, X, target, resampling=CV(shuffle=true),measure=log_loss, verbosity=0)
evaluate(tree_model, X, target,
                resampling=CV(shuffle=true), measure=bac, operation=predict_mode, verbosity=0)



train, test = partition(eachindex(target), 0.7, shuffle=true)

fit!(mach, rows=train)

yhat = predict(mach, X[test,:])

evaluate(tree_model, X[test,:], target[test], measure=auc, operation=predict_mode, verbosity=0)

niveles = levels.(yhat)[1]
niveles[1]

log_loss(yhat, target[test]) |> mean

res = pdf(yhat, niveles)
res_df = DataFrame(res,:auto)

MLJ.save("models/boston_xg.jls", mach)

Y luego los ficheros que uso para construirme la app binaria .. Recordemos del post que mencionaba que lo que necesito es el código del programa principal (el main) y un fichero de precompilación que sirve para que al crear la app se compilen las funciones que voy a usar.

fichero precomp.jl,

using CSV, DataFrames, MLJ, MLJBase, MLJXGBoostInterface

# uso rutas absolutas
df1 = CSV.read("data/iris.csv", DataFrame)
X = df1[:, Not(:Species)]

predict_only_mach = machine("models/mimodelo_xg_binario.jls")

= predict(predict_only_mach, X) 


predict_mode(predict_only_mach, X)

niveles = levels.(ŷ)[1]

res = pdf(ŷ, niveles) # con pdf nos da la probabilidad de cada nivel
res_df = DataFrame(res,:auto)
rename!(res_df, ["target_0", "target_1"])

CSV.write("data/predicciones.csv", res_df)

fichero xgboost_predict_binomial.jl , aquí es dónde está el main

module xgboost_predict_binomial

using CSV, DataFrames, MLJ, MLJBase, MLJXGBoostInterface

function julia_main()::Cint
    try
        real_main()
    catch
        Base.invokelatest(Base.display_error, Base.catch_stack())
        return 1
    end
    return 0
end

# ARGS son los argumentos pasados por consola 

function real_main()
    if length(ARGS) == 0
        error("pass arguments")
    end

# Read model
    modelo = machine(ARGS[1])
# read data. El fichero qeu pasemos tiene que tener solo las X.(con su nombre)
    X = CSV.read(ARGS[2], DataFrame, ntasks= Sys.CPU_THREADS)
# Predict    
= predict(modelo, X)            # predict
    niveles = levels.(ŷ)[1]           # get levels of target
    res = pdf(ŷ, niveles)             # predict probabilities for each level
    
    res_df = DataFrame(res,:auto)     # convert to DataFrame
    rename!(res_df, ["target_0", "target_1"])          # Column rename
    CSV.write(ARGS[3], res_df)        # Write in csv
end


end # module

y si todo está correcto y siguiendo las instrucciones del post anterior, se compilaría haciendo por ejemplo esto

using PackageCompiler
create_app("../xgboost_predict_binomial", "../xg_binomial_inference",
 precompile_execution_file="../xgboost_predict_binomial/src/precomp_file.jl", force=true, filter_stdlibs = true, cpu_target = "x86_64")

Y esto me crea una estructura de directorios dónde está mi app y todo lo necesario para ejecutar julia en cualqueir linux.


╰─ $ ▶ tree -L 2 xg_binomial_inference
xg_binomial_inference
├── bin
│   ├── julia
│   └── xgboost_predict_binomial
├── lib
│   ├── julia
│   ├── libjulia.so -> libjulia.so.1.8
│   ├── libjulia.so.1 -> libjulia.so.1.8
│   └── libjulia.so.1.8
└── share
    └── julia

y poner por ejemplo en el .bashrc el siguiente alias.

alias motor_xgboost=/home/jose/Julia_projects/xgboost_model/xg_binomial_inference/bin/xgboost_predict_binomial

y ya está listo.

Ahora tengo un dataset a predecir de 5 millones de filas


╰─ $ ▶ wc -l data/test.csv 
5060001 data/test.csv

 head -n4 data/test.csv 
crim,zn,indus,chas,nox,rm,age,dis,rad,tax,ptratio,lstat
0.00632,18,2.31,0,0.538,6.575,65.2,4.09,1,296,15.3,4.98
0.02731,0,7.07,0,0.469,6.421,78.9,4.9671,2,242,17.8,9.14
0.02729,0,7.07,0,0.469,7.185,61.1,4.9671,2,242,17.8,4.03

y bueno, tardo unos 11 segundos en obtener las predicciones y escribir el resultado

╰─ $ ▶ time motor_xgboost models/boston_xg.jls data/test.csv pred.csv

real    0m11,091s
user    0m53,293s
sys 0m2,321s

y comprobamos que lo ha hecho bien


╰─ $ ▶ wc -l  pred.csv 
5060001 pred.csv


╰─ $ ▶ head -n 5 pred.csv 
target_0,target_1
0.9999237,7.63197e-5
0.99120975,0.008790266
0.99989164,0.00010834133
0.99970543,0.00029458306

Y nada, pues esto puede servir para subir modelos a producción en entornos poco amigables (sin python3, sin R, sin julia, sin spark, sin docker, sin internet). Es un poco old style que me diría mi arquenazi favorito Rubén, pero

Os dejo el tar.gz para que probéis, también os dejo el Project.tomly el Manifest.toml y el fichero con el que he entrenado los datos. para que uséis el mismo entorno de julia que he usado yo.

enlace_drive