IA: cómo montar un Sistema Basado en Reglas con PHP

2021-05-30 - Categorías: Inteligencia Artificial / PHP

Realmente no hacen falta complejos sistemas informáticos para montar inteligencias artificiales, o más bien, para aplicar inteligencia artificial a la resolución de los problemas.

Aquí tenemos a modo de codekata, un sencillo howto hecho 100% en PHP, para montar una pequeña IA. Un Sistema Basado en Reglas, es una de las formas más sencillas de aplicar inteligencia artificial a la resolución de problemas. Los SBR son Sistemas Expertos, que comenzaron a usarse por la comunidad a mediados de 1960, con lo que tienen su recorrido..

Qué es un sistema basado en reglas

Esta es una de las formas de emular fácilmente la lógica de los seres humanos para tomar decisiones. Se trata de plasmar el conocimiento experto en un tema. Su forma de actuar es sencilla, a base de una batería de reglas que especifican criterios, que si se cumplen, desembocarán en acciones o nos darán información:

  • Si ocurren una serie de factores A => se dispararán una serie de acciones A.
  • Si ocurren una serie de factores B => se dispararán una serie de acciones B.
  • Si ocurren una serie de factores C => se dispararán una serie de acciones C.
  • Etc..

Por ejemplo, imaginemos que queremos calcular los precios automáticamente de todos los productos de un negocio, todos los días, tres veces al día, tres iteraciones diarias, atendiendo a muchos factores a la vez. Este tipo de tareas que implican muchos criterios de decisión, las hacen muy bien los sistemas basados en reglas. Podremos administrar aplicando tantas reglas como queramos, solapándolas o aplicando de forma restrictivas unas u otras. Podremos terminar la iteración si uno de los criterios casa. O podremos permitir que múltiples criterios casen, y se apliquen en cada iteración de forma acumulativa.

Por ejemplo, si tuviéramos el caso de recalcular precios, podríamos fijarnos en variables como:

  • El stock actual.
  • Stock medio en proveedores.
  • Stock en proveedor favorito.
  • Precio actual.
  • Precio actual medio de proveedores.
  • Precio menor de los proveedores.
  • Precio inicial de compra del stock.
  • Tiempo del producto.
  • Precio medio de la competencia en Amazon, eBay, AliExpress..
  • Precio puntual de un competidor concreto en su web.
  • Ventas hoy.
  • Ventas en la semana.
  • Ventas en el mes.
  • Ciclo de vida estimado del producto.
  • ..
  • Último precio del producto.
  • Precio de ayer del producto.
  • ..
  • Etcétera..

Podremos ir añadiendo tantas variables como se estime necesario. Y en función a cómo estén todo este conjunto de variables, se recalcularía el precio, de cada producto, en cada iteración, tantas veces como haga falta.

La inferencia, el aprendizaje

En estos sistemas tendremos una gran batería de reglas, que irán aumentando. Este conjunto de reglas será el conocimiento. Conforme seamos más capaces de plasmar más conocimiento, más inteligente será el sistema.

Estos sistemas serán manejables y bien mantenibles, porque los tendremos bien estructurados.

La inferencia, por ejemplo, en el caso del cálculo de precios, es que el mismo sistema se realimentará de forma automática con los resultados. El propio sistema reaccionará conforme las variables del entorno cambien. Por ejemplo, algunas reglas podrían ser:

  • Si tenemos un producto que no se ha vendido nada en la última semana, le bajamos el precio 1 €.
  • Si se ha vendido 1 unidad en la última semana, le bajamos 0,1 €.
  • Si se ha vendido 3 unidades en la última semana y no hay proveedores que lo tengan, le subimos 1 €.
  • Si se ha vendido ayer 1 unidad, pero en toda la semana sólo 1 unidad, le bajamos 0,2 €.
  • Si el precio del proveedor + margen de X por ciento, es menor que nuestro precio, aplicamos precio de proveedor.
  • Si hay 27 proveedores que lo tienen, no vendemos nada del producto en un mes, lo ponemos a precio de coste.
  • Etcétera..

Podemos deducir que si aplicamos la primera regla y bajamos el precio de un producto de 2 € a 1 €, quizá ahora se venden 20 unidades. En la siguiente ronda se podría aplicar la tercera regla, y subirse de nuevo 1 €. Aquí tenemos una forma de reaccionar de forma inteligente al entorno, aplicando el conocimiento experto plasmado en el conjunto de reglas.

Manos a la obra, show me the code

Dejando de lado la teoría, vamos a ver cómo montar algo que funcione y sencillo, en puro PHP. Para arrancar con un sistema basado en reglas necesitaremos mínimo tres elementos básicos:

  • Las variables de entrada.
  • Las reglas, que implican saber qué hacemos, y cuándo.
  • El bucle principal.

A modo de codekata, sólo ilustrativo, entonces podríamos definir una serie de variables:

$a = true;
$b = false;
$c = 100;
$d = 3.1416;
$e = 'valor de una cadena';
$f = strlen(file_get_contents('https://jnjsite.com/'));

Estas variables pueden definirse hardcodeadas en el programa, o pueden venir desde otros sistemas, desde la base de datos, desde páginas web, etc.. Las reglas entonces, podríamos definirlas de forma sencilla como un array en PHP:

$rules = [
    [ // Regla 0
        'criterio' => '($a or $b) and $c >= 100',
        'accion' => 'accionQueQueremosQueDispareUno',
    ],
    [ // Regla 1
        'criterio' => '($a and !$b) and $c / $d * 100 < 100',
        'accion' => 'accionQueQueremosQueDispareDos',
    ],
    [ // Regla 3
        'criterio' => 'preg_match("/valor/", $e) and $f > 0',
        'accion' => 'accionQueQueremosQueDispareTres',
    ],
];

También este array de reglas podría cargar desde base de datos, tener un panel de control para que sea fácilmente administrable, etc.. Lo siguiente que necesitaremos sería aplicar el bucle. La forma en que se apliquen o se ordenen las reglas se definirá en función al problema. Por ejemplo, un bucle que aplicaría todas las reglas que casen, con las acciones que cumplan el criterio sería:

// THE MAIN LOOP
echo '¿Qué actiones se aplicarían?'.PHP_EOL;
for ($i = 0; $i < count($rules); ++$i) {
    $matchesTheCriteria = eval('return '.$rules[$i]['criteria'].';');
    echo 'La regla '.$i.': '
        .($matchesTheCriteria ? 'true' : 'false')
        .PHP_EOL;
    if ($matchesTheCriteria) {
        call_user_func($rules[$i]['action']);
    }
}

Si quisiéramos que sólo aplicara la primera regla que case con el criterio, tendríamos que cortar la ejecución cuando case, cambiaría simplemente a ponerle un exit o die:

// THE MAIN LOOP
echo '¿Qué actiones se aplicarían?'.PHP_EOL;
for ($i = 0; $i < count($rules); ++$i) {
    $matchesTheCriteria = eval('return '.$rules[$i]['criteria'].';');
    echo 'La regla '.$i.': '
        .($matchesTheCriteria ? 'true' : 'false')
        .PHP_EOL;
    if ($matchesTheCriteria) {
        call_user_func($rules[$i]['action']);
        exit;
    }
}

Otra variante del sistema para mejorar su mantenimiento podría ser el ordenar las reglas por prioridad. Podríamos añadir una variable ‘priority’ a cada regla para así ordenarlas. Podrían quedar así:

$rules = [
    [ // Regla 0
        'id' => 0,
        'criteria' => '($a or $b) and $c >= 100',
        'action' => 'accionQueQueremosQueDispareUno',
        'priority' => 100,
    ],
    [ // Regla 1
        'id' => 1,
        'criteria' => '($a and !$b) and $c / $d * 100 < 100',
        'action' => 'accionQueQueremosQueDispareDos',
        'priority' => 90,
    ],
    [ // Regla 2
        'id' => 2,
        'criteria' => 'preg_match("/valor/", $e) and $f > 0',
        'action' => 'accionQueQueremosQueDispareTres', 
        'priority' => 80,
    ],
];

..quedaría ordenar por prioridad las reglas, a mayor prioridad aplicando primero sería:

function sortByPriority($a, $b) {
    return $a['priority'] > $b['priority'];
}

usort($rules, 'sortByPriority');

..ahora el bucle que recorre las reglas podría ser:

// THE MAIN LOOP
echo '¿Qué actiones se aplicarían?'.PHP_EOL;
foreach ($rules as $rule) {
    $matchesTheCriteria = eval('return '.$rule['criteria'].';');
    echo 'La regla '.$rule['id'].': '
        .($matchesTheCriteria ? 'true' : 'false')
        .PHP_EOL;
    if ($matchesTheCriteria) {
        call_user_func($rule['action']);
    }
}

Sólo queda juntarlo todo en un solo script que sería el SBR completo:

<?php

$a = true;
$b = false;
$c = 100;
$d = 3.1416;
$e = 'valor de una cadena';
$f = strlen(file_get_contents('https://jnjsite.com/'));

$rules = [
    [ // Regla 0
        'id' => 0,
        'criteria' => '($a or $b) and $c >= 100',
        'action' => 'accionQueQueremosQueDispareUno',
        'priority' => 100,
    ],
    [ // Regla 1
        'id' => 1,
        'criteria' => '($a and !$b) and $c / $d * 100 < 100',
        'action' => 'accionQueQueremosQueDispareDos',
        'priority' => 90,
    ],
    [ // Regla 2
        'id' => 2,
        'criteria' => 'preg_match("/valor/", $e) and $f > 0',
        'action' => 'accionQueQueremosQueDispareTres', 
        'priority' => 80,
    ],
];

function sortByPriority($a, $b) {
    return $a['priority'] > $b['priority'];
}

usort($rules, 'sortByPriority');

// THE MAIN LOOP
echo '¿Qué actiones se aplicarían?'.PHP_EOL;
foreach ($rules as $rule) {
    $matchesTheCriteria = eval('return '.$rule['criteria'].';');
    echo 'La regla '.$rule['id'].': '
        .($matchesTheCriteria ? 'true' : 'false')
        .PHP_EOL;
    if ($matchesTheCriteria) {
        call_user_func($rule['action']);
    }
}

function accionQueQueremosQueDispareUno()
{
    echo 'Acción uno.'.PHP_EOL;
}

function accionQueQueremosQueDispareDos()
{
    echo 'Acción dos.'.PHP_EOL;
}

function accionQueQueremosQueDispareTres()
{
    echo 'Acción tres.'.PHP_EOL;
}

Ya a partir de aquí es suma y sigue.. 😉

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

 

© 2021 JnjSite.com - MIT license

Sitio hecho con WordPress, diseño y programación del tema por Jnj.