handwired.prg
#include "hbclass.ch"
PROCEDURE Main()
LOCAL oNet, uUnidades, uEsVip, uMayorQue4, uPrecio, uDescuento, uTotal
// 1. Instanciamos el compilador/red neuronal
oNet := THandwiredNet():New()
// 2. Declaramos las variables de entrada (nodos simb贸licos)
uUnidades := oNet:Input( "unidades" )
uEsVip := oNet:Input( "es_vip" )
// 3. Escribimos la l贸gica de negocio de forma declarativa e intuitiva:
// - Condici贸n: 驴unidades > 4? (Compila a ReLU(u - 4) - ReLU(u - 5))
// - IIF simb贸lico: si > 4 vale 10, si no vale 12
// - Descuento: si es_vip == 1 vale 5, si no 0
uMayorQue4 := oNet:GreaterThan( uUnidades, 4 )
uPrecio := oNet:Iif( uMayorQue4, 10, 12 )
uDescuento := oNet:Iif( oNet:Equals( uEsVip, 1 ), 5, 0 )
// Gracias a la sobrecarga de operadores en THWVar, escribimos la f贸rmula natural:
uTotal := ( uUnidades * uPrecio ) - uDescuento
// 4. Fijamos la salida y compilamos a matrices puras
oNet:SetOutput( uTotal )
oNet:Compile()
// Mostramos la arquitectura generada autom谩ticamente
oNet:DumpArchitecture()
// 5. Ejecutamos la inferencia (100% MatMul + ReLU + ReGLU, cero IFs)
? "=== Inferencia en la Red Neuronal Compilada ==="
? "3 uds, Normal (Esperado: 36) -> Red:", oNet:Run( { "unidades" => 3, "es_vip" => 0 } )
? "5 uds, Normal (Esperado: 50) -> Red:", oNet:Run( { "unidades" => 5, "es_vip" => 0 } )
? "6 uds, VIP (Esperado: 55) -> Red:", oNet:Run( { "unidades" => 6, "es_vip" => 1 } )
RETURN
// ===========================================================================
// CLASE THWVar: Representa un nodo/tensor simb贸lico en el grafo
// ===========================================================================
// Un THWVar almacena c贸mo se calcula su valor como combinaci贸n lineal de
// neuronas ocultas ReLU y t茅rminos bilineales (ReGLU).
// ===========================================================================
CLASS THWVar
DATA oNet
DATA aLinear INIT {} // Pares: { nHiddenIdx, nWeight } (Idx 0 = Bias constante)
DATA aBilinear INIT {} // Tripletas ReGLU: { nHiddenIdxA, nHiddenIdxB, nWeight }
METHOD New( oNet )
METHOD AddLinear( nHiddenIdx, nWeight )
METHOD AddBilinear( nIdxA, nIdxB, nWeight )
METHOD Clone()
// Sobrecarga de operadores nativos de Harbour para expresiones naturales
OPERATOR "+" ARG xOther
OPERATOR "-" ARG xOther
OPERATOR "*" ARG xOther
ENDCLASS
METHOD New( oNet ) CLASS THWVar
::oNet := oNet
RETURN Self
METHOD AddLinear( nHiddenIdx, nWeight ) CLASS THWVar
LOCAL aPair
IF nWeight == 0
RETURN Self
ENDIF
FOR EACH aPair IN ::aLinear
IF aPair[ 1 ] == nHiddenIdx
aPair[ 2 ] += nWeight
RETURN Self
ENDIF
NEXT
AAdd( ::aLinear, { nHiddenIdx, nWeight } )
RETURN Self
METHOD AddBilinear( nIdxA, nIdxB, nWeight ) CLASS THWVar
IF nWeight != 0
AAdd( ::aBilinear, { nIdxA, nIdxB, nWeight } )
ENDIF
RETURN Self
METHOD Clone() CLASS THWVar
LOCAL oCopy := THWVar():New( ::oNet )
LOCAL aItem
FOR EACH aItem IN ::aLinear
AAdd( oCopy:aLinear, { aItem[ 1 ], aItem[ 2 ] } )
NEXT
FOR EACH aItem IN ::aBilinear
AAdd( oCopy:aBilinear, { aItem[ 1 ], aItem[ 2 ], aItem[ 3 ] } )
NEXT
RETURN oCopy
// Suma simb贸lica: Variable + Variable o Variable + Constante
METHOD "+"( xOther ) CLASS THWVar
LOCAL oRes := ::Clone()
LOCAL aItem
IF HB_ISNUMERIC( xOther )
oRes:AddLinear( 0, xOther ) // 0 = Bias constante
ELSEIF HB_ISOBJECT( xOther )
FOR EACH aItem IN xOther:aLinear
oRes:AddLinear( aItem[ 1 ], aItem[ 2 ] )
NEXT
FOR EACH aItem IN xOther:aBilinear
oRes:AddBilinear( aItem[ 1 ], aItem[ 2 ], aItem[ 3 ] )
NEXT
ENDIF
RETURN oRes
// Resta simb贸lica: Variable - Variable o Variable - Constante
METHOD "-"( xOther ) CLASS THWVar
LOCAL oRes := ::Clone()
LOCAL aItem
IF HB_ISNUMERIC( xOther )
oRes:AddLinear( 0, -xOther )
ELSEIF HB_ISOBJECT( xOther )
FOR EACH aItem IN xOther:aLinear
oRes:AddLinear( aItem[ 1 ], -aItem[ 2 ] )
NEXT
FOR EACH aItem IN xOther:aBilinear
oRes:AddBilinear( aItem[ 1 ], aItem[ 2 ], -aItem[ 3 ] )
NEXT
ENDIF
RETURN oRes
// Multiplicaci贸n simb贸lica (Genera compuertas ReGLU cuando multiplica 2 variables)
METHOD "*"( xOther ) CLASS THWVar
LOCAL oRes := THWVar():New( ::oNet )
LOCAL aItemA, aItemB
IF HB_ISNUMERIC( xOther )
FOR EACH aItemA IN ::aLinear
oRes:AddLinear( aItemA[ 1 ], aItemA[ 2 ] * xOther )
NEXT
FOR EACH aItemA IN ::aBilinear
oRes:AddBilinear( aItemA[ 1 ], aItemA[ 2 ], aItemA[ 3 ] * xOther )
NEXT
ELSEIF HB_ISOBJECT( xOther )
// Producto distributivo entre dos expresiones lineales -> Compuertas ReGLU
FOR EACH aItemA IN ::aLinear
FOR EACH aItemB IN xOther:aLinear
IF aItemA[ 1 ] == 0
oRes:AddLinear( aItemB[ 1 ], aItemA[ 2 ] * aItemB[ 2 ] )
ELSEIF aItemB[ 1 ] == 0
oRes:AddLinear( aItemA[ 1 ], aItemA[ 2 ] * aItemB[ 2 ] )
ELSE
// Ambos dependen de neuronas ocultas -> Interacci贸n Bilineal (ReGLU)
oRes:AddBilinear( aItemA[ 1 ], aItemB[ 1 ], aItemA[ 2 ] * aItemB[ 2 ] )
ENDIF
NEXT
NEXT
ENDIF
RETURN oRes
// ===========================================================================
// CLASE THandwiredNet: Compilador de L贸gica a Matrices + Motor de Inferencia
// ===========================================================================
CLASS THandwiredNet
DATA hInputs INIT {=>} // Mapa nombre_entrada -> 铆ndice de columna
DATA aInputNames INIT {} // Lista ordenada de entradas
DATA aHiddenSpecs INIT {} // Definici贸n de cada neurona ReLU de Capa 1
DATA oOutputVar // THWVar de salida
// Matrices compiladas listas para inferencia matricial
DATA aW1 INIT {} // Matriz Capa 1: [ nHidden ][ nInputs + 1 (Bias) ]
DATA aW2Linear INIT {} // Vector Capa 2 Lineal: [ nHidden + 1 (Bias) ]
DATA aW2Bilinear INIT {} // Matriz Capa 2 ReGLU: tripletas { idxA, idxB, peso }
METHOD New()
METHOD Input( cName )
METHOD AddReLUNeuron( nInputIdx, nWeight, nBias )
METHOD GreaterThan( oVar, nThreshold )
METHOD LessThan( oVar, nThreshold )
METHOD Equals( oVar, nValue )
METHOD Iif( oCond, xTrue, xFalse )
METHOD SetOutput( oVar )
METHOD Compile()
METHOD Run( hValues )
METHOD DumpArchitecture()
ENDCLASS
METHOD New() CLASS THandwiredNet
RETURN Self
// Crea una variable de entrada y una neurona ReLU identidad para dejarla pasar
METHOD Input( cName ) CLASS THandwiredNet
LOCAL nInputIdx, nHiddenIdx, oVar
AAdd( ::aInputNames, cName )
nInputIdx := Len( ::aInputNames )
::hInputs[ cName ] := nInputIdx
// Creamos una neurona ReLU(1 * x + 0) que act煤a como "cable" de paso (para x >= 0)
nHiddenIdx := ::AddReLUNeuron( nInputIdx, 1, 0 )
oVar := THWVar():New( Self )
oVar:AddLinear( nHiddenIdx, 1 )
RETURN oVar
// A帽ade una neurona a la Capa 1: ReLU( nWeight * Input[nInputIdx] + nBias )
METHOD AddReLUNeuron( nInputIdx, nWeight, nBias ) CLASS THandwiredNet
AAdd( ::aHiddenSpecs, { nInputIdx, nWeight, nBias } )
RETURN Len( ::aHiddenSpecs )
// Compila (x > k) para enteros: ReLU(x - k) - ReLU(x - (k + 1))
METHOD GreaterThan( oVar, nThreshold ) CLASS THandwiredNet
LOCAL nInputIdx := ::aHiddenSpecs[ oVar:aLinear[ 1 ][ 1 ] ][ 1 ]
LOCAL nH1 := ::AddReLUNeuron( nInputIdx, 1, -nThreshold )
LOCAL nH2 := ::AddReLUNeuron( nInputIdx, 1, -( nThreshold + 1 ) )
LOCAL oCond := THWVar():New( Self )
oCond:AddLinear( nH1, 1 )
oCond:AddLinear( nH2, -1 )
RETURN oCond
// Compila (x < k) para enteros: 1 - (x > k - 1)
METHOD LessThan( oVar, nThreshold ) CLASS THandwiredNet
LOCAL oGe := ::GreaterThan( oVar, nThreshold - 1 )
LOCAL oRes := THWVar():New( Self )
oRes:AddLinear( 0, 1 )
RETURN oRes - oGe
// Compila igualdad exacta (x == k) usando un pulso triangular de 3 ReLUs:
// ReLU(x - (k-1)) - 2*ReLU(x - k) + ReLU(x - (k+1))
METHOD Equals( oVar, nValue ) CLASS THandwiredNet
LOCAL nInputIdx := ::aHiddenSpecs[ oVar:aLinear[ 1 ][ 1 ] ][ 1 ]
LOCAL nH1 := ::AddReLUNeuron( nInputIdx, 1, -( nValue - 1 ) )
LOCAL nH2 := ::AddReLUNeuron( nInputIdx, 1, -nValue )
LOCAL nH3 := ::AddReLUNeuron( nInputIdx, 1, -( nValue + 1 ) )
LOCAL oEq := THWVar():New( Self )
oEq:AddLinear( nH1, 1 )
oEq:AddLinear( nH2, -2 )
oEq:AddLinear( nH3, 1 )
RETURN oEq
// Compila un IIF( oCond, xTrue, xFalse ) como: oCond * xTrue + (1 - oCond) * xFalse
METHOD Iif( oCond, xTrue, xFalse ) CLASS THandwiredNet
LOCAL oOneMinusCond := THWVar():New( Self )
LOCAL oBranchTrue, oBranchFalse
oOneMinusCond:AddLinear( 0, 1 )
oOneMinusCond := oOneMinusCond - oCond
oBranchTrue := iif( HB_ISOBJECT( xTrue ), xTrue, oCond * xTrue )
IF HB_ISOBJECT( xTrue )
oBranchTrue := oCond * xTrue
ENDIF
oBranchFalse := iif( HB_ISOBJECT( xFalse ), oOneMinusCond * xFalse, oOneMinusCond * xFalse )
RETURN oBranchTrue + oBranchFalse
METHOD SetOutput( oVar ) CLASS THandwiredNet
::oOutputVar := oVar
RETURN Self
// ---------------------------------------------------------------------------
// COMPILACI脫N: Ensambla las matrices densas aW1, aW2Linear y aW2Bilinear
// ---------------------------------------------------------------------------
METHOD Compile() CLASS THandwiredNet
LOCAL nInputs := Len( ::aInputNames )
LOCAL nHidden := Len( ::aHiddenSpecs )
LOCAL i, aSpec, aRow, aPair
// 1. Construir Matriz W1 (Capa 1): [ nHidden x (nInputs + 1) ]
::aW1 := Array( nHidden )
FOR i := 1 TO nHidden
aRow := Array( nInputs + 1 )
AFill( aRow, 0 )
aSpec := ::aHiddenSpecs[ i ]
aRow[ aSpec[ 1 ] ] := aSpec[ 2 ] // Peso de la entrada
aRow[ nInputs + 1 ] := aSpec[ 3 ] // Peso del Bias (煤ltima columna)
::aW1[ i ] := aRow
NEXT
// 2. Construir Vector W2 Lineal: [ nHidden + 1 ] (la 煤ltima posici贸n es el Bias de salida)
::aW2Linear := Array( nHidden + 1 )
AFill( ::aW2Linear, 0 )
FOR EACH aPair IN ::oOutputVar:aLinear
IF aPair[ 1 ] == 0
::aW2Linear[ nHidden + 1 ] += aPair[ 2 ]
ELSE
::aW2Linear[ aPair[ 1 ] ] += aPair[ 2 ]
ENDIF
NEXT
// 3. Copiar compuertas bilineales (ReGLU)
::aW2Bilinear := AClone( ::oOutputVar:aBilinear )
RETURN Self
// ---------------------------------------------------------------------------
// MOTOR DE INFERENCIA PURA: Solo multiplicaci贸n de matrices + ReLU + ReGLU
// ---------------------------------------------------------------------------
METHOD Run( hValues ) CLASS THandwiredNet
LOCAL nInputs := Len( ::aInputNames )
LOCAL nHidden := Len( ::aW1 )
LOCAL aX := Array( nInputs + 1 )
LOCAL aH := Array( nHidden + 1 )
LOCAL i, j, nSum, nOut, aGate
// Preparar vector de entrada X con un 1.0 al final para el Bias
FOR i := 1 TO nInputs
aX[ i ] := hValues[ ::aInputNames[ i ] ]
NEXT
aX[ nInputs + 1 ] := 1.0
// CAPA 1: H = ReLU( W1 * X )
FOR i := 1 TO nHidden
nSum := 0
FOR j := 1 TO nInputs + 1
nSum += ::aW1[ i ][ j ] * aX[ j ]
NEXT
aH[ i ] := Max( 0, nSum ) // Funci贸n de activaci贸n ReLU
NEXT
aH[ nHidden + 1 ] := 1.0 // Bias para la Capa 2
// CAPA 2 (Parte A - Producto escalar lineal): Out = W2_Linear 路 H
nOut := 0
FOR i := 1 TO nHidden + 1
nOut += ::aW2Linear[ i ] * aH[ i ]
NEXT
// CAPA 2 (Parte B - Compuertas ReGLU): Out += Suma( w * H[a] * H[b] )
FOR EACH aGate IN ::aW2Bilinear
nOut += aGate[ 3 ] * aH[ aGate[ 1 ] ] * aH[ aGate[ 2 ] ]
NEXT
RETURN nOut
// Muestra por pantalla las matrices compiladas y el n煤mero de pesos
METHOD DumpArchitecture() CLASS THandwiredNet
LOCAL i, nWeightsW1 := Len( ::aW1 ) * ( Len( ::aInputNames ) + 1 )
LOCAL nWeightsW2 := Len( ::aW2Linear ) + Len( ::aW2Bilinear )
? "=== Arquitectura Compilada Automaticamente ==="
? "Entradas :", hb_ValToExp( ::aInputNames )
? "Neuronas ReLU Capa 1:", Len( ::aW1 ), "| Pesos totales:", nWeightsW1 + nWeightsW2
? "--- Matriz W1 (Cada fila es: [", hb_ValToExp( ::aInputNames ), ", Bias ]) ---"
FOR i := 1 TO Len( ::aW1 )
? " Neurona h" + hb_ntos( i ) + ": ReLU(", hb_ValToExp( ::aW1[ i ] ), ")"
NEXT
? "--- Vector W2 Lineal (Pesos sobre h1..h" + hb_ntos( Len( ::aW1 ) ) + " + Bias) ---"
? " ", hb_ValToExp( ::aW2Linear )
? "--- Compuertas ReGLU ( { hA, hB, Peso } ) ---"
? " ", hb_ValToExp( ::aW2Bilinear )
? ""
RETURN Self