@@ -19,7 +19,7 @@ use crate::domain::{
1919 AdamConfig , CurveFamily , CurveParams , FitResult , InputError , LbfgsConfig , NelderMeadConfig ,
2020 NewtonCgConfig , OptimizerConfig , Points , SgdConfig , SteepestDescentConfig ,
2121} ;
22- use crate :: models:: { self , GradientComputation } ;
22+ use crate :: models:: { self , ObjectiveGrad , ObjectiveHessian , ObjectiveValue , PredictionLoss } ;
2323
2424mod curve;
2525mod simd;
@@ -136,8 +136,6 @@ const STEEPEST_DESCENT_GRAD_TOL: f64 = 1e-12;
136136const HESSIAN_FD_REL_STEP : f64 = 1e-4 ;
137137const HESSIAN_FD_MIN_STEP : f64 = 1e-6 ;
138138pub ( crate ) const HESSIAN_DIAGONAL_JITTER : f64 = models:: HESSIAN_DIAGONAL_JITTER ;
139- const GRADIENT_FD_REL_STEP : f64 = 1e-5 ;
140- const GRADIENT_FD_MIN_STEP : f64 = 1e-7 ;
141139
142140fn positive_x ( value : f64 ) -> f64 {
143141 models:: positive_x ( value)
@@ -166,12 +164,6 @@ fn array1_as_slice(values: &Array1<f64>) -> &[f64] {
166164 . expect ( "Array1 parameters must have contiguous memory layout" )
167165}
168166
169- fn array1_as_slice_mut ( values : & mut Array1 < f64 > ) -> & mut [ f64 ] {
170- values
171- . as_slice_mut ( )
172- . expect ( "Array1 parameters must have contiguous memory layout" )
173- }
174-
175167fn stabilize_hessian ( hessian : & mut Array2 < f64 > ) {
176168 let dimension = hessian. nrows ( ) ;
177169 debug_assert_eq ! ( dimension, hessian. ncols( ) ) ;
@@ -410,6 +402,142 @@ struct CurveProblem {
410402 residual_quantizer : ResidualQuantizer ,
411403}
412404
405+ #[ derive( Clone , Copy ) ]
406+ struct CurveProblemPredictionLoss < ' a > {
407+ problem : & ' a CurveProblem ,
408+ }
409+
410+ impl PredictionLoss for CurveProblemPredictionLoss < ' _ > {
411+ fn value ( & self , prediction : f64 , target : f64 ) -> f64 {
412+ self . problem . loss_value_from_prediction ( prediction, target)
413+ }
414+
415+ fn d_prediction ( & self , prediction : f64 , target : f64 ) -> f64 {
416+ self . problem
417+ . loss_derivative_from_prediction ( prediction, target)
418+ }
419+
420+ fn d2_prediction ( & self , prediction : f64 , target : f64 ) -> f64 {
421+ self . problem
422+ . loss_second_derivative_from_prediction ( prediction, target)
423+ }
424+ }
425+
426+ struct CurveProblemObjective < ' a > {
427+ problem : & ' a CurveProblem ,
428+ }
429+
430+ impl CurveProblemObjective < ' _ > {
431+ fn simd_enabled ( & self ) -> bool {
432+ matches ! (
433+ self . problem. metric_quantization,
434+ MetricQuantization :: Disabled
435+ )
436+ }
437+
438+ fn value ( & self , param : & [ f64 ] ) -> f64 {
439+ if self . simd_enabled ( ) && self . problem . family . is_polynomial ( ) {
440+ return simd:: polynomial_cost (
441+ param,
442+ self . problem . point_x . as_ref ( ) ,
443+ self . problem . point_y . as_ref ( ) ,
444+ self . problem . loss_metric ,
445+ ) ;
446+ }
447+ if self . simd_enabled ( ) && self . problem . family == CurveFamily :: Inverse {
448+ return simd:: inverse_cost (
449+ param,
450+ self . problem . point_x . as_ref ( ) ,
451+ self . problem . point_y . as_ref ( ) ,
452+ self . problem . loss_metric ,
453+ ) ;
454+ }
455+
456+ let loss = CurveProblemPredictionLoss {
457+ problem : self . problem ,
458+ } ;
459+ let term = models:: DataTerm :: new (
460+ self . problem . family ,
461+ self . problem . point_x . as_ref ( ) ,
462+ self . problem . point_y . as_ref ( ) ,
463+ loss,
464+ ) ;
465+ let objective = models:: CurveObjective :: new ( self . problem . family . parameter_count ( ) , term) ;
466+ objective. value ( param)
467+ }
468+
469+ fn value_grad ( & self , param : & [ f64 ] ) -> ( f64 , Vec < f64 > ) {
470+ if self . simd_enabled ( ) && self . problem . family . is_polynomial ( ) {
471+ let mut gradient = vec ! [ 0.0 ; self . problem. family. parameter_count( ) ] ;
472+ simd:: accumulate_polynomial_gradient (
473+ self . problem . point_x . as_ref ( ) ,
474+ self . problem . point_y . as_ref ( ) ,
475+ param,
476+ self . problem . loss_metric ,
477+ & mut gradient,
478+ ) ;
479+ let sample_scale = 1.0 / self . problem . point_x . len ( ) as f64 ;
480+ for value in & mut gradient {
481+ * value *= sample_scale;
482+ }
483+ let value = simd:: polynomial_cost (
484+ param,
485+ self . problem . point_x . as_ref ( ) ,
486+ self . problem . point_y . as_ref ( ) ,
487+ self . problem . loss_metric ,
488+ ) ;
489+ return ( value, gradient) ;
490+ }
491+ if self . simd_enabled ( ) && self . problem . family == CurveFamily :: Inverse {
492+ let mut gradient = vec ! [ 0.0 ; self . problem. family. parameter_count( ) ] ;
493+ simd:: accumulate_inverse_gradient (
494+ self . problem . point_x . as_ref ( ) ,
495+ self . problem . point_y . as_ref ( ) ,
496+ param,
497+ self . problem . loss_metric ,
498+ & mut gradient,
499+ ) ;
500+ let sample_scale = 1.0 / self . problem . point_x . len ( ) as f64 ;
501+ for value in & mut gradient {
502+ * value *= sample_scale;
503+ }
504+ let value = simd:: inverse_cost (
505+ param,
506+ self . problem . point_x . as_ref ( ) ,
507+ self . problem . point_y . as_ref ( ) ,
508+ self . problem . loss_metric ,
509+ ) ;
510+ return ( value, gradient) ;
511+ }
512+
513+ let loss = CurveProblemPredictionLoss {
514+ problem : self . problem ,
515+ } ;
516+ let term = models:: DataTerm :: new (
517+ self . problem . family ,
518+ self . problem . point_x . as_ref ( ) ,
519+ self . problem . point_y . as_ref ( ) ,
520+ loss,
521+ ) ;
522+ let objective = models:: CurveObjective :: new ( self . problem . family . parameter_count ( ) , term) ;
523+ objective. value_grad ( param)
524+ }
525+
526+ fn value_grad_hessian ( & self , param : & [ f64 ] ) -> ( f64 , Vec < f64 > , Array2 < f64 > ) {
527+ let loss = CurveProblemPredictionLoss {
528+ problem : self . problem ,
529+ } ;
530+ let term = models:: DataTerm :: new (
531+ self . problem . family ,
532+ self . problem . point_x . as_ref ( ) ,
533+ self . problem . point_y . as_ref ( ) ,
534+ loss,
535+ ) ;
536+ let objective = models:: CurveObjective :: new ( self . problem . family . parameter_count ( ) , term) ;
537+ objective. value_grad_hessian ( param)
538+ }
539+ }
540+
413541impl CurveProblem {
414542 fn new_with_metric_quantization (
415543 family : CurveFamily ,
@@ -456,39 +584,8 @@ impl CurveProblem {
456584 . residual_second_derivative ( self . residual ( predicted, observed) )
457585 }
458586
459- fn analytic_hessian_for_supported_families ( & self , param : & Array1 < f64 > ) -> Option < Array2 < f64 > > {
460- if matches ! ( self . loss_metric, OptimizationLossMetric :: Mae ) {
461- return None ;
462- }
463-
464- let param = array1_as_slice ( param) ;
465- models:: analytic_hessian (
466- self . family ,
467- self . point_x . as_ref ( ) ,
468- self . point_y . as_ref ( ) ,
469- param,
470- |predicted, observed| self . loss_derivative_from_prediction ( predicted, observed) ,
471- |predicted, observed| self . loss_second_derivative_from_prediction ( predicted, observed) ,
472- )
473- }
474-
475- fn numerical_gradient_from_cost (
476- & self ,
477- param : & [ f64 ] ,
478- gradient : & mut [ f64 ] ,
479- ) -> Result < ( ) , argmin:: core:: Error > {
480- let mut probe = vec_to_array1 ( param) ;
481- for index in 0 ..gradient. len ( ) {
482- let step =
483- ( ( param[ index] . abs ( ) + 1.0 ) * GRADIENT_FD_REL_STEP ) . max ( GRADIENT_FD_MIN_STEP ) ;
484- probe[ index] = param[ index] + step;
485- let cost_plus = CostFunction :: cost ( self , & probe) ?;
486- probe[ index] = param[ index] - step;
487- let cost_minus = CostFunction :: cost ( self , & probe) ?;
488- probe[ index] = param[ index] ;
489- gradient[ index] = ( cost_plus - cost_minus) / ( 2.0 * step) ;
490- }
491- Ok ( ( ) )
587+ fn objective ( & self ) -> CurveProblemObjective < ' _ > {
588+ CurveProblemObjective { problem : self }
492589 }
493590}
494591
@@ -1622,48 +1719,12 @@ impl CostFunction for CurveProblem {
16221719
16231720 fn cost ( & self , param : & Self :: Param ) -> Result < Self :: Output , argmin:: core:: Error > {
16241721 let param = array1_as_slice ( param) ;
1625- if self . family . is_polynomial ( )
1626- && matches ! ( self . metric_quantization, MetricQuantization :: Disabled )
1627- {
1628- return Ok ( simd:: polynomial_cost (
1629- param,
1630- self . point_x . as_ref ( ) ,
1631- self . point_y . as_ref ( ) ,
1632- self . loss_metric ,
1633- ) ) ;
1634- }
1635-
1636- if self . family == CurveFamily :: Inverse
1637- && matches ! ( self . metric_quantization, MetricQuantization :: Disabled )
1638- {
1639- return Ok ( simd:: inverse_cost (
1640- param,
1641- self . point_x . as_ref ( ) ,
1642- self . point_y . as_ref ( ) ,
1643- self . loss_metric ,
1644- ) ) ;
1645- }
1646-
1647- let sample_count = self . point_x . len ( ) as f64 ;
1648- let mut sum = 0.0 ;
1649-
1650- let mut index = 0 ;
1651- while index < self . point_x . len ( ) {
1652- let x = self . point_x [ index] ;
1653- let y = self . point_y [ index] ;
1654- let predicted = self . family . evaluate_raw ( param, x) ;
1655- let residual = self . residual ( predicted, y) ;
1656- if !residual. is_finite ( ) {
1657- return Ok ( LARGE_COST ) ;
1658- }
1659- sum += self . loss_value_from_prediction ( predicted, y) ;
1660- if !sum. is_finite ( ) {
1661- return Ok ( LARGE_COST ) ;
1662- }
1663- index += 1 ;
1722+ let value = self . objective ( ) . value ( param) ;
1723+ if value. is_finite ( ) {
1724+ Ok ( value)
1725+ } else {
1726+ Ok ( LARGE_COST )
16641727 }
1665-
1666- Ok ( sum / sample_count)
16671728 }
16681729}
16691730
@@ -1673,57 +1734,13 @@ impl Gradient for CurveProblem {
16731734
16741735 fn gradient ( & self , param : & Self :: Param ) -> Result < Self :: Gradient , argmin:: core:: Error > {
16751736 let param = array1_as_slice ( param) ;
1676- let mut gradient = Array1 :: zeros ( self . family . parameter_count ( ) ) ;
1677- let sample_scale = 1.0 / self . point_x . len ( ) as f64 ;
1678- let gradient_slice = array1_as_slice_mut ( & mut gradient) ;
1679-
1680- if self . family . is_polynomial ( )
1681- && matches ! ( self . metric_quantization, MetricQuantization :: Disabled )
1682- {
1683- simd:: accumulate_polynomial_gradient (
1684- self . point_x . as_ref ( ) ,
1685- self . point_y . as_ref ( ) ,
1686- param,
1687- self . loss_metric ,
1688- gradient_slice,
1689- ) ;
1690- } else if self . family == CurveFamily :: Inverse
1691- && matches ! ( self . metric_quantization, MetricQuantization :: Disabled )
1692- {
1693- simd:: accumulate_inverse_gradient (
1694- self . point_x . as_ref ( ) ,
1695- self . point_y . as_ref ( ) ,
1696- param,
1697- self . loss_metric ,
1698- gradient_slice,
1699- ) ;
1700- } else {
1701- let mode = models:: accumulate_gradient (
1702- self . family ,
1703- self . point_x . as_ref ( ) ,
1704- self . point_y . as_ref ( ) ,
1705- param,
1706- |predicted, observed| self . loss_derivative_from_prediction ( predicted, observed) ,
1707- gradient_slice,
1708- ) ;
1709- if matches ! ( mode, GradientComputation :: NeedsNumerical ) {
1710- self . numerical_gradient_from_cost ( param, gradient_slice) ?;
1711- // Далее применяется общий sample_scale, поэтому здесь возвращается сумма.
1712- let sample_count = self . point_x . len ( ) as f64 ;
1713- for value in gradient_slice. iter_mut ( ) {
1714- * value *= sample_count;
1715- }
1716- }
1717- }
1718-
1719- for value in gradient_slice. iter_mut ( ) {
1720- * value *= sample_scale;
1737+ let ( _, mut gradient) = self . objective ( ) . value_grad ( param) ;
1738+ for value in & mut gradient {
17211739 if !value. is_finite ( ) {
17221740 * value = LARGE_COST ;
17231741 }
17241742 }
1725-
1726- Ok ( gradient)
1743+ Ok ( vec_to_array1 ( & gradient) )
17271744 }
17281745}
17291746
@@ -1732,7 +1749,8 @@ impl Hessian for CurveProblem {
17321749 type Hessian = Array2 < f64 > ;
17331750
17341751 fn hessian ( & self , param : & Self :: Param ) -> Result < Self :: Hessian , argmin:: core:: Error > {
1735- if let Some ( hessian) = self . analytic_hessian_for_supported_families ( param) {
1752+ if !matches ! ( self . loss_metric, OptimizationLossMetric :: Mae ) {
1753+ let ( _, _, hessian) = self . objective ( ) . value_grad_hessian ( array1_as_slice ( param) ) ;
17361754 return Ok ( hessian) ;
17371755 }
17381756 numerical_hessian_from_gradient ( self , param)
0 commit comments