dataframe-learn-1.1.0.0: src/DataFrame/SymbolicRegression/Optimize.hs
{-# LANGUAGE BangPatterns #-}
{- | Constant optimization for symbolic-regression candidates: finite-difference
gradient descent with backtracking line search on the embedded constants. Pure
and dependency-free; effective at the one-to-few constants a tree carries, which
is where random restarts plus a quasi-Newton step would otherwise be used.
-}
module DataFrame.SymbolicRegression.Optimize (
optimizeConstants,
meanSquaredError,
) where
import qualified Data.Vector as V
import qualified Data.Vector.Unboxed as VU
import DataFrame.SymbolicRegression.Expr (
SRExpr,
constants,
evalSR,
setConstants,
)
-- | Mean squared error of an expression's predictions against the target.
meanSquaredError ::
V.Vector (VU.Vector Double) -> Int -> VU.Vector Double -> SRExpr -> Double
meanSquaredError feats n target e =
let pred = evalSR feats n e
diff = VU.zipWith (-) pred target
in VU.sum (VU.map (\x -> x * x) diff) / fromIntegral (max 1 n)
-- | Refine an expression's constants to reduce MSE (no-op when constant-free).
optimizeConstants ::
V.Vector (VU.Vector Double) ->
Int ->
VU.Vector Double ->
Int ->
SRExpr ->
SRExpr
optimizeConstants feats n target iters expr
| null theta0 = expr
| otherwise = setConstants (descend iters theta0) expr
where
theta0 = constants expr
eps = 1e-6
mseAt theta = meanSquaredError feats n target (setConstants theta expr)
descend 0 theta = theta
descend k theta =
let f0 = mseAt theta
g = numGrad theta
gn = sqrt (sum (map (\x -> x * x) g))
in if gn < 1e-10
then theta
else
let theta' = lineSearch theta g f0
in if theta' == theta then theta else descend (k - 1) theta'
numGrad theta =
[ (mseAt (bump i eps theta) - mseAt (bump i (negate eps) theta)) / (2 * eps)
| i <- [0 .. length theta - 1]
]
bump i delta theta =
[if j == i then t + delta else t | (j, t) <- zip [0 ..] theta]
lineSearch theta g f0 = go (1.0 :: Double)
where
go !step
| step < 1e-8 = theta
| otherwise =
let theta' = zipWith (\t gi -> t - step * gi) theta g
in if mseAt theta' < f0 then theta' else go (step / 2)