1#![allow(non_snake_case)] use scirs2_core::ndarray::{s, Array1, Array2, Array3, ArrayView1};
10use scirs2_core::random::thread_rng;
11use scirs2_core::random::RandNormal;
12use sklears_core::{
13 error::{Result as SklResult, SklearsError},
14 traits::{Estimator, Fit, Predict, Untrained},
15 types::Float,
16};
17use std::collections::{HashMap, HashSet};
18
19#[derive(Debug, Clone)]
45pub struct StructuredPerceptron<State = Untrained> {
46 max_iterations: usize,
47 learning_rate: Float,
48 random_state: Option<u64>,
49 state: State,
50}
51
52#[derive(Debug, Clone)]
54pub struct StructuredPerceptronTrained {
55 weights: Array1<Float>,
56 n_features: usize,
57 n_classes: usize,
58}
59
60impl Default for StructuredPerceptron<Untrained> {
61 fn default() -> Self {
62 Self::new()
63 }
64}
65
66impl StructuredPerceptron<Untrained> {
67 pub fn new() -> Self {
69 Self {
70 max_iterations: 100,
71 learning_rate: 1.0,
72 random_state: None,
73 state: Untrained,
74 }
75 }
76
77 pub fn max_iterations(mut self, max_iterations: usize) -> Self {
79 self.max_iterations = max_iterations;
80 self
81 }
82
83 pub fn learning_rate(mut self, learning_rate: Float) -> Self {
85 self.learning_rate = learning_rate;
86 self
87 }
88
89 pub fn random_state(mut self, random_state: u64) -> Self {
91 self.random_state = Some(random_state);
92 self
93 }
94}
95
96impl Estimator for StructuredPerceptron<Untrained> {
97 type Config = ();
98 type Error = SklearsError;
99 type Float = Float;
100
101 fn config(&self) -> &Self::Config {
102 &()
103 }
104}
105
106impl Fit<Array3<Float>, Array2<i32>> for StructuredPerceptron<Untrained> {
107 type Fitted = StructuredPerceptron<StructuredPerceptronTrained>;
108
109 fn fit(self, X: &Array3<Float>, y: &Array2<i32>) -> SklResult<Self::Fitted> {
110 let (n_sequences, max_seq_len, n_features) = X.dim();
111
112 if n_sequences != y.nrows() {
113 return Err(SklearsError::InvalidInput(
114 "X and y must have the same number of sequences".to_string(),
115 ));
116 }
117
118 if y.ncols() != max_seq_len {
119 return Err(SklearsError::InvalidInput(
120 "y sequence length must match X sequence length".to_string(),
121 ));
122 }
123
124 let n_classes = y.iter().max().unwrap_or(&0) + 1;
125 let feature_dim = n_features * n_classes as usize + n_classes as usize * n_classes as usize;
126 let mut weights = Array1::<Float>::zeros(feature_dim);
127
128 let _rng = thread_rng();
129
130 for _iteration in 0..self.max_iterations {
131 let mut updated = false;
132
133 for seq_idx in 0..n_sequences {
134 let sequence = X.slice(s![seq_idx, .., ..]);
135 let true_labels = y.row(seq_idx);
136
137 let mut predicted_labels = Array1::<Float>::zeros(max_seq_len);
139
140 for pos in 0..max_seq_len {
141 let features = sequence.slice(s![pos, ..]);
142 let mut best_score = Float::NEG_INFINITY;
143 let mut best_label = 0;
144
145 for label in 0..n_classes {
146 let feature_offset = label as usize * n_features;
147 let score = features
148 .iter()
149 .enumerate()
150 .map(|(feat_idx, &feat_val)| {
151 weights[feature_offset + feat_idx] * feat_val
152 })
153 .sum::<Float>();
154
155 if score > best_score {
156 best_score = score;
157 best_label = label;
158 }
159 }
160 predicted_labels[pos] = best_label as Float;
161 }
162
163 let correct = true_labels
165 .iter()
166 .zip(predicted_labels.iter())
167 .all(|(&true_label, &pred_label)| true_label == pred_label as i32);
168
169 if !correct {
170 for pos in 0..max_seq_len {
172 let features = sequence.slice(s![pos, ..]);
173 let true_label = true_labels[pos] as usize;
174 let pred_label = predicted_labels[pos] as usize;
175
176 let true_offset = true_label * n_features;
178 for (feat_idx, &feat_val) in features.iter().enumerate() {
179 weights[true_offset + feat_idx] += self.learning_rate * feat_val;
180 }
181
182 let pred_offset = pred_label * n_features;
184 for (feat_idx, &feat_val) in features.iter().enumerate() {
185 weights[pred_offset + feat_idx] -= self.learning_rate * feat_val;
186 }
187 }
188 updated = true;
189 }
190 }
191
192 if !updated {
193 break;
194 }
195 }
196
197 Ok(StructuredPerceptron {
198 max_iterations: self.max_iterations,
199 learning_rate: self.learning_rate,
200 random_state: self.random_state,
201 state: StructuredPerceptronTrained {
202 weights,
203 n_features,
204 n_classes: n_classes as usize,
205 },
206 })
207 }
208}
209
210impl StructuredPerceptron<Untrained> {
211 pub fn weights(&self) -> Option<&Array1<Float>> {
213 None
214 }
215}
216
217impl Predict<Array3<Float>, Array2<i32>> for StructuredPerceptron<StructuredPerceptronTrained> {
218 fn predict(&self, X: &Array3<Float>) -> SklResult<Array2<i32>> {
219 let (n_sequences, max_seq_len, n_features) = X.dim();
220
221 if n_features != self.state.n_features {
222 return Err(SklearsError::InvalidInput(
223 "X has different number of features than training data".to_string(),
224 ));
225 }
226
227 let mut predictions = Array2::<i32>::zeros((n_sequences, max_seq_len));
228
229 for seq_idx in 0..n_sequences {
230 let sequence = X.slice(s![seq_idx, .., ..]);
231
232 for pos in 0..max_seq_len {
233 let features = sequence.slice(s![pos, ..]);
234 let mut best_score = Float::NEG_INFINITY;
235 let mut best_label = 0;
236
237 for label in 0..self.state.n_classes {
238 let feature_offset = label * n_features;
239 let score = features
240 .iter()
241 .enumerate()
242 .map(|(feat_idx, &feat_val)| {
243 self.state.weights[feature_offset + feat_idx] * feat_val
244 })
245 .sum::<Float>();
246
247 if score > best_score {
248 best_score = score;
249 best_label = label;
250 }
251 }
252 predictions[[seq_idx, pos]] = best_label as i32;
253 }
254 }
255
256 Ok(predictions)
257 }
258}
259
260impl StructuredPerceptron<StructuredPerceptronTrained> {
261 pub fn weights(&self) -> &Array1<Float> {
263 &self.state.weights
264 }
265}
266
267#[derive(Debug, Clone)]
290pub struct HiddenMarkovModel<State = Untrained> {
291 n_states: usize,
292 max_iterations: usize,
293 tolerance: Float,
294 random_state: Option<u64>,
295 state: State,
296}
297
298#[derive(Debug, Clone)]
300pub struct HiddenMarkovModelTrained {
301 transition_matrix: Array2<Float>,
302 emission_means: Array2<Float>,
303 #[allow(dead_code)]
304 emission_covariances: Array3<Float>,
305 initial_probs: Array1<Float>,
306 n_features: usize,
307 n_states: usize,
308}
309
310impl Default for HiddenMarkovModel<Untrained> {
311 fn default() -> Self {
312 Self::new()
313 }
314}
315
316impl HiddenMarkovModel<Untrained> {
317 pub fn new() -> Self {
319 Self {
320 n_states: 2,
321 max_iterations: 100,
322 tolerance: 1e-6,
323 random_state: None,
324 state: Untrained,
325 }
326 }
327
328 pub fn n_states(mut self, n_states: usize) -> Self {
330 self.n_states = n_states;
331 self
332 }
333
334 pub fn max_iterations(mut self, max_iterations: usize) -> Self {
336 self.max_iterations = max_iterations;
337 self
338 }
339
340 pub fn tolerance(mut self, tolerance: Float) -> Self {
342 self.tolerance = tolerance;
343 self
344 }
345
346 pub fn random_state(mut self, random_state: u64) -> Self {
348 self.random_state = Some(random_state);
349 self
350 }
351}
352
353impl Estimator for HiddenMarkovModel<Untrained> {
354 type Config = ();
355 type Error = SklearsError;
356 type Float = Float;
357
358 fn config(&self) -> &Self::Config {
359 &()
360 }
361}
362
363impl Fit<Array3<Float>, Array2<i32>> for HiddenMarkovModel<Untrained> {
364 type Fitted = HiddenMarkovModel<HiddenMarkovModelTrained>;
365
366 fn fit(self, X: &Array3<Float>, y: &Array2<i32>) -> SklResult<Self::Fitted> {
367 let (n_sequences, max_seq_len, n_features) = X.dim();
368
369 if n_sequences != y.nrows() {
370 return Err(SklearsError::InvalidInput(
371 "X and y must have the same number of sequences".to_string(),
372 ));
373 }
374
375 if y.ncols() != max_seq_len {
376 return Err(SklearsError::InvalidInput(
377 "y sequence length must match X sequence length".to_string(),
378 ));
379 }
380
381 let mut rng = thread_rng();
382
383 let normal_dist = RandNormal::new(0.0, 1.0).expect("operation should succeed");
385 let mut transition_matrix = Array2::<Float>::zeros((self.n_states, self.n_states));
386 for i in 0..self.n_states {
387 for j in 0..self.n_states {
388 transition_matrix[[i, j]] = rng.sample(normal_dist);
389 }
390 }
391 let mut emission_means = Array2::<Float>::zeros((self.n_states, n_features));
392 for i in 0..self.n_states {
393 for j in 0..n_features {
394 emission_means[[i, j]] = rng.sample(normal_dist);
395 }
396 }
397 let mut emission_covariances =
398 Array3::from_elem((self.n_states, n_features, n_features), 1.0);
399 let mut initial_probs = Array1::from_elem(self.n_states, 1.0 / self.n_states as Float);
400
401 for i in 0..self.n_states {
403 let row_sum = transition_matrix.row(i).sum();
404 if row_sum > 0.0 {
405 for j in 0..self.n_states {
406 transition_matrix[[i, j]] /= row_sum;
407 }
408 }
409 }
410
411 for state in 0..self.n_states {
413 for i in 0..n_features {
414 emission_covariances[[state, i, i]] = 1.0;
415 }
416 }
417
418 let mut prev_likelihood = Float::NEG_INFINITY;
419
420 for _iteration in 0..self.max_iterations {
422 let mut state_counts = Array1::<Float>::zeros(self.n_states);
427 let mut transition_counts = Array2::<Float>::zeros((self.n_states, self.n_states));
428 let mut emission_sums = Array2::<Float>::zeros((self.n_states, n_features));
429
430 for seq_idx in 0..n_sequences {
431 let sequence_data = X.slice(s![seq_idx, .., ..]);
432 let sequence_states = y.row(seq_idx);
433
434 for pos in 0..max_seq_len {
435 let state = sequence_states[pos] as usize;
436 if state < self.n_states {
437 state_counts[state] += 1.0;
438
439 for feat in 0..n_features {
441 emission_sums[[state, feat]] += sequence_data[[pos, feat]];
442 }
443
444 if pos < max_seq_len - 1 {
446 let next_state = sequence_states[pos + 1] as usize;
447 if next_state < self.n_states {
448 transition_counts[[state, next_state]] += 1.0;
449 }
450 }
451 }
452 }
453
454 let first_state = sequence_states[0] as usize;
456 if first_state < self.n_states {
457 initial_probs[first_state] += 1.0;
458 }
459 }
460
461 for state in 0..self.n_states {
463 if state_counts[state] > 0.0 {
464 for feat in 0..n_features {
465 emission_means[[state, feat]] =
466 emission_sums[[state, feat]] / state_counts[state];
467 }
468 }
469
470 let row_sum = transition_counts.row(state).sum();
471 if row_sum > 0.0 {
472 for next_state in 0..self.n_states {
473 transition_matrix[[state, next_state]] =
474 transition_counts[[state, next_state]] / row_sum;
475 }
476 }
477 }
478
479 let init_sum = initial_probs.sum();
481 if init_sum > 0.0 {
482 initial_probs /= init_sum;
483 }
484
485 let total_likelihood = state_counts.sum() as Float;
487
488 if (total_likelihood - prev_likelihood).abs() < self.tolerance {
489 break;
490 }
491 prev_likelihood = total_likelihood;
492 }
493
494 Ok(HiddenMarkovModel {
495 n_states: self.n_states,
496 max_iterations: self.max_iterations,
497 tolerance: self.tolerance,
498 random_state: self.random_state,
499 state: HiddenMarkovModelTrained {
500 transition_matrix,
501 emission_means,
502 emission_covariances,
503 initial_probs,
504 n_features,
505 n_states: self.n_states,
506 },
507 })
508 }
509}
510
511impl HiddenMarkovModel<Untrained> {
512 pub fn transition_matrix(&self) -> Option<&Array2<Float>> {
514 None
515 }
516
517 pub fn emission_means(&self) -> Option<&Array2<Float>> {
519 None
520 }
521
522 pub fn initial_probabilities(&self) -> Option<&Array1<Float>> {
524 None
525 }
526}
527
528impl Predict<Array3<Float>, Array2<i32>> for HiddenMarkovModel<HiddenMarkovModelTrained> {
529 fn predict(&self, X: &Array3<Float>) -> SklResult<Array2<i32>> {
530 let (n_sequences, max_seq_len, n_features) = X.dim();
531
532 if n_features != self.state.n_features {
533 return Err(SklearsError::InvalidInput(
534 "X has different number of features than training data".to_string(),
535 ));
536 }
537
538 let mut predictions = Array2::<i32>::zeros((n_sequences, max_seq_len));
539
540 for seq_idx in 0..n_sequences {
541 let sequence = X.slice(s![seq_idx, .., ..]);
542
543 let mut viterbi = Array2::<Float>::zeros((max_seq_len, self.state.n_states));
545 let mut path = Array2::<Float>::zeros((max_seq_len, self.state.n_states));
546
547 for state in 0..self.state.n_states {
549 let emission_prob = self.gaussian_probability(&sequence.slice(s![0, ..]), state);
550 viterbi[[0, state]] = self.state.initial_probs[state].ln() + emission_prob.ln();
551 }
552
553 for t in 1..max_seq_len {
555 for state in 0..self.state.n_states {
556 let emission_prob =
557 self.gaussian_probability(&sequence.slice(s![t, ..]), state);
558 let mut best_prob = Float::NEG_INFINITY;
559 let mut best_prev_state = 0;
560
561 for prev_state in 0..self.state.n_states {
562 let prob = viterbi[[t - 1, prev_state]]
563 + self.state.transition_matrix[[prev_state, state]].ln()
564 + emission_prob.ln();
565 if prob > best_prob {
566 best_prob = prob;
567 best_prev_state = prev_state;
568 }
569 }
570 viterbi[[t, state]] = best_prob;
571 path[[t, state]] = best_prev_state as Float;
572 }
573 }
574
575 let mut states = Array1::<Float>::zeros(max_seq_len);
577
578 let mut best_final_prob = Float::NEG_INFINITY;
580 let mut best_final_state = 0;
581 for state in 0..self.state.n_states {
582 if viterbi[[max_seq_len - 1, state]] > best_final_prob {
583 best_final_prob = viterbi[[max_seq_len - 1, state]];
584 best_final_state = state;
585 }
586 }
587
588 states[max_seq_len - 1] = best_final_state as Float;
589
590 for t in (0..max_seq_len - 1).rev() {
592 states[t] = path[[t + 1, states[t + 1] as usize]];
593 }
594
595 for t in 0..max_seq_len {
597 predictions[[seq_idx, t]] = states[t] as i32;
598 }
599 }
600
601 Ok(predictions)
602 }
603}
604
605impl HiddenMarkovModel<HiddenMarkovModelTrained> {
606 pub fn transition_matrix(&self) -> &Array2<Float> {
608 &self.state.transition_matrix
609 }
610
611 pub fn emission_means(&self) -> &Array2<Float> {
613 &self.state.emission_means
614 }
615
616 pub fn initial_probabilities(&self) -> &Array1<Float> {
618 &self.state.initial_probs
619 }
620
621 fn gaussian_probability(&self, observation: &ArrayView1<Float>, state: usize) -> Float {
623 let mean = self.state.emission_means.row(state);
624 let diff = observation.to_owned() - &mean.to_owned();
625
626 let exponent = -0.5 * diff.mapv(|x| x * x).sum();
628 let normalization =
629 (2.0 * std::f64::consts::PI).powf(self.state.n_features as f64 / 2.0) as Float;
630
631 (exponent.exp() / normalization).max(1e-10)
632 }
633}
634
635#[derive(Debug, Clone)]
671pub struct MaximumEntropyMarkovModel<S = Untrained> {
672 state: S,
673 max_iter: usize,
674 learning_rate: Float,
675 l2_reg: Float,
676 tolerance: Float,
677 #[allow(dead_code)]
678 feature_functions: Vec<FeatureFunction>,
679 random_state: Option<u64>,
680}
681
682#[derive(Debug, Clone)]
684pub struct MaximumEntropyMarkovModelTrained {
685 weights: Array1<Float>,
686 feature_functions: Vec<FeatureFunction>,
687 n_labels: usize,
688 n_features: usize,
689 label_to_idx: HashMap<i32, usize>,
690 #[allow(dead_code)]
691 idx_to_label: HashMap<usize, i32>,
692}
693
694#[derive(Debug, Clone)]
700pub struct FeatureFunction {
701 pub feature_type: FeatureType,
703 pub weight_index: usize,
705}
706
707#[derive(Debug, Clone)]
709pub enum FeatureType {
710 Observation(usize),
712 PreviousLabel(i32),
714 LabelObservationInteraction(i32, usize),
716}
717
718impl MaximumEntropyMarkovModel<Untrained> {
719 pub fn new() -> Self {
721 Self {
722 state: Untrained,
723 max_iter: 100,
724 learning_rate: 0.01,
725 l2_reg: 0.01,
726 tolerance: 1e-6,
727 feature_functions: Vec::new(),
728 random_state: None,
729 }
730 }
731
732 pub fn max_iter(mut self, max_iter: usize) -> Self {
734 self.max_iter = max_iter;
735 self
736 }
737
738 pub fn learning_rate(mut self, learning_rate: Float) -> Self {
740 self.learning_rate = learning_rate;
741 self
742 }
743
744 pub fn l2_regularization(mut self, l2_reg: Float) -> Self {
746 self.l2_reg = l2_reg;
747 self
748 }
749
750 pub fn tolerance(mut self, tolerance: Float) -> Self {
752 self.tolerance = tolerance;
753 self
754 }
755
756 pub fn random_state(mut self, random_state: u64) -> Self {
758 self.random_state = Some(random_state);
759 self
760 }
761}
762
763impl Default for MaximumEntropyMarkovModel<Untrained> {
764 fn default() -> Self {
765 Self::new()
766 }
767}
768
769impl Estimator for MaximumEntropyMarkovModel<Untrained> {
770 type Config = ();
771 type Error = SklearsError;
772 type Float = Float;
773
774 fn config(&self) -> &Self::Config {
775 &()
776 }
777}
778
779impl Fit<Vec<Array2<Float>>, Vec<Vec<i32>>> for MaximumEntropyMarkovModel<Untrained> {
780 type Fitted = MaximumEntropyMarkovModel<MaximumEntropyMarkovModelTrained>;
781
782 fn fit(self, X: &Vec<Array2<Float>>, y: &Vec<Vec<i32>>) -> SklResult<Self::Fitted> {
783 if X.len() != y.len() {
784 return Err(SklearsError::InvalidInput(
785 "X and y must have the same number of sequences".to_string(),
786 ));
787 }
788
789 if X.is_empty() {
790 return Err(SklearsError::InvalidInput(
791 "Cannot fit with 0 sequences".to_string(),
792 ));
793 }
794
795 let n_features = X[0].ncols();
797 let mut unique_labels = HashSet::new();
798
799 for sequence_labels in y {
800 for &label in sequence_labels {
801 unique_labels.insert(label);
802 }
803 }
804
805 let mut label_to_idx = HashMap::new();
806 let mut idx_to_label = HashMap::new();
807 let mut sorted_labels: Vec<_> = unique_labels.iter().cloned().collect();
809 sorted_labels.sort();
810 for (idx, label) in sorted_labels.iter().enumerate() {
811 label_to_idx.insert(*label, idx);
812 idx_to_label.insert(idx, *label);
813 }
814 let n_labels = unique_labels.len();
815
816 let mut feature_functions = Vec::new();
818 let mut weight_idx = 0;
819
820 for feat_idx in 0..n_features {
822 for &label in &sorted_labels {
823 feature_functions.push(FeatureFunction {
824 feature_type: FeatureType::LabelObservationInteraction(label, feat_idx),
825 weight_index: weight_idx,
826 });
827 weight_idx += 1;
828 }
829 }
830
831 for &prev_label in &sorted_labels {
833 for &_curr_label in &sorted_labels {
834 feature_functions.push(FeatureFunction {
835 feature_type: FeatureType::PreviousLabel(prev_label),
836 weight_index: weight_idx,
837 });
838 weight_idx += 1;
839 }
840 }
841
842 let n_weights = weight_idx;
843 let mut weights = Array1::<Float>::zeros(n_weights);
844
845 let mut rng = if let Some(seed) = self.random_state {
847 scirs2_core::random::seeded_rng(seed)
848 } else {
849 use std::time::{SystemTime, UNIX_EPOCH};
851 let time_seed = SystemTime::now()
852 .duration_since(UNIX_EPOCH)
853 .expect("operation should succeed")
854 .as_secs();
855 scirs2_core::random::seeded_rng(time_seed)
856 };
857
858 for w in weights.iter_mut() {
859 *w = rng.gen_range(-0.1..0.1);
860 }
861
862 for _iter in 0..self.max_iter {
864 let mut gradient = Array1::<Float>::zeros(n_weights);
865 let mut _total_loss = 0.0;
866
867 for (seq_idx, (sequence_x, sequence_y)) in X.iter().zip(y.iter()).enumerate() {
869 let seq_len = sequence_x.nrows();
870
871 if sequence_y.len() != seq_len {
872 return Err(SklearsError::InvalidInput(format!(
873 "Sequence {} length mismatch between X and y",
874 seq_idx
875 )));
876 }
877
878 for pos in 0..seq_len {
880 let current_obs = sequence_x.row(pos);
881 let true_label = sequence_y[pos];
882 let prev_label = if pos == 0 { -1 } else { sequence_y[pos - 1] };
883
884 let _features =
886 self.extract_features(¤t_obs, prev_label, &feature_functions);
887
888 let mut scores = Array1::<Float>::zeros(n_labels);
890 let mut max_score = Float::NEG_INFINITY;
891
892 for (label_idx, _label) in unique_labels.iter().enumerate() {
893 let label_features =
894 self.extract_features(¤t_obs, prev_label, &feature_functions);
895 scores[label_idx] = label_features.dot(&weights);
896 max_score = max_score.max(scores[label_idx]);
897 }
898
899 for score in scores.iter_mut() {
901 *score -= max_score;
902 }
903
904 let exp_scores: Array1<Float> = scores.mapv(|x| x.exp());
906 let sum_exp_scores = exp_scores.sum();
907 let probabilities = exp_scores / sum_exp_scores;
908
909 let true_label_idx = label_to_idx[&true_label];
911 _total_loss -= probabilities[true_label_idx].ln();
912
913 for (label_idx, _label) in sorted_labels.iter().enumerate() {
915 let label_features =
916 self.extract_features(¤t_obs, prev_label, &feature_functions);
917 let prob = probabilities[label_idx];
918 let indicator = if label_idx == true_label_idx {
919 1.0
920 } else {
921 0.0
922 };
923
924 for (feat_idx, &feat_val) in label_features.iter().enumerate() {
925 gradient[feat_idx] += feat_val * (prob - indicator);
926 }
927 }
928 }
929 }
930
931 for (i, w) in weights.iter().enumerate() {
933 gradient[i] += self.l2_reg * w;
934 }
935
936 let gradient_norm = gradient.mapv(|x| x.abs()).sum();
938 weights = &weights - self.learning_rate * &gradient;
939
940 if gradient_norm < self.tolerance {
942 break;
943 }
944 }
945
946 let trained_state = MaximumEntropyMarkovModelTrained {
947 weights,
948 feature_functions,
949 n_labels,
950 n_features,
951 label_to_idx,
952 idx_to_label,
953 };
954
955 Ok(MaximumEntropyMarkovModel {
956 state: trained_state,
957 max_iter: self.max_iter,
958 learning_rate: self.learning_rate,
959 l2_reg: self.l2_reg,
960 tolerance: self.tolerance,
961 feature_functions: Vec::new(),
962 random_state: self.random_state,
963 })
964 }
965}
966
967impl MaximumEntropyMarkovModel<Untrained> {
968 fn extract_features(
970 &self,
971 observation: &ArrayView1<Float>,
972 prev_label: i32,
973 feature_functions: &[FeatureFunction],
974 ) -> Array1<Float> {
975 let mut features = Array1::<Float>::zeros(feature_functions.len());
976
977 for (i, func) in feature_functions.iter().enumerate() {
978 features[i] = match &func.feature_type {
979 FeatureType::Observation(feat_idx) => observation[*feat_idx],
980 FeatureType::PreviousLabel(label) => {
981 if prev_label == *label {
982 1.0
983 } else {
984 0.0
985 }
986 }
987 FeatureType::LabelObservationInteraction(label, feat_idx) => {
988 if prev_label == *label {
989 observation[*feat_idx]
990 } else {
991 0.0
992 }
993 }
994 };
995 }
996
997 features
998 }
999}
1000
1001impl Predict<Vec<Array2<Float>>, Vec<Vec<i32>>>
1002 for MaximumEntropyMarkovModel<MaximumEntropyMarkovModelTrained>
1003{
1004 fn predict(&self, X: &Vec<Array2<Float>>) -> SklResult<Vec<Vec<i32>>> {
1005 if X.is_empty() {
1006 return Ok(Vec::new());
1007 }
1008
1009 let mut predictions = Vec::with_capacity(X.len());
1010
1011 for sequence in X {
1012 let seq_len = sequence.nrows();
1013
1014 if sequence.ncols() != self.state.n_features {
1015 return Err(SklearsError::InvalidInput(
1016 "X has different number of features than training data".to_string(),
1017 ));
1018 }
1019
1020 let mut sequence_predictions = Vec::with_capacity(seq_len);
1021
1022 for pos in 0..seq_len {
1023 let current_obs = sequence.row(pos);
1024 let prev_label = if pos == 0 {
1025 -1
1026 } else {
1027 sequence_predictions[pos - 1]
1028 };
1029
1030 let mut best_score = Float::NEG_INFINITY;
1032 let mut best_label = 0;
1033
1034 let mut labels_sorted: Vec<_> = self.state.label_to_idx.iter().collect();
1036 labels_sorted.sort_by_key(|(&label, _)| label);
1037
1038 for (&label, &_label_idx) in labels_sorted {
1039 let features = self.extract_features(
1040 ¤t_obs,
1041 prev_label,
1042 &self.state.feature_functions,
1043 );
1044 let score = features.dot(&self.state.weights);
1045
1046 if score > best_score {
1047 best_score = score;
1048 best_label = label;
1049 }
1050 }
1051
1052 sequence_predictions.push(best_label);
1053 }
1054
1055 predictions.push(sequence_predictions);
1056 }
1057
1058 Ok(predictions)
1059 }
1060}
1061
1062impl MaximumEntropyMarkovModel<MaximumEntropyMarkovModelTrained> {
1063 pub fn weights(&self) -> &Array1<Float> {
1065 &self.state.weights
1066 }
1067
1068 pub fn n_labels(&self) -> usize {
1070 self.state.n_labels
1071 }
1072
1073 fn extract_features(
1075 &self,
1076 observation: &ArrayView1<Float>,
1077 prev_label: i32,
1078 feature_functions: &[FeatureFunction],
1079 ) -> Array1<Float> {
1080 let mut features = Array1::<Float>::zeros(feature_functions.len());
1081
1082 for (i, func) in feature_functions.iter().enumerate() {
1083 features[i] = match &func.feature_type {
1084 FeatureType::Observation(feat_idx) => observation[*feat_idx],
1085 FeatureType::PreviousLabel(label) => {
1086 if prev_label == *label {
1087 1.0
1088 } else {
1089 0.0
1090 }
1091 }
1092 FeatureType::LabelObservationInteraction(label, feat_idx) => {
1093 if prev_label == *label {
1094 observation[*feat_idx]
1095 } else {
1096 0.0
1097 }
1098 }
1099 };
1100 }
1101
1102 features
1103 }
1104}
1105
1106#[allow(non_snake_case)]
1107#[cfg(test)]
1108mod tests {
1109 use super::*;
1110 use scirs2_core::ndarray::array;
1112
1113 #[test]
1114 #[allow(non_snake_case)]
1115 fn test_structured_perceptron_basic() {
1116 let X = Array3::from_shape_vec((1, 2, 2), vec![1.0, 2.0, 2.0, 3.0])
1117 .expect("shape and data length should match");
1118 let y =
1119 Array2::from_shape_vec((1, 2), vec![0, 1]).expect("shape and data length should match");
1120
1121 let perceptron = StructuredPerceptron::new().max_iterations(10);
1122 let trained = perceptron
1123 .fit(&X, &y)
1124 .expect("model fitting should succeed");
1125 let predictions = trained.predict(&X).expect("prediction should succeed");
1126
1127 assert_eq!(predictions.dim(), (1, 2));
1128 }
1129
1130 #[test]
1131 #[allow(non_snake_case)]
1132 fn test_hidden_markov_model_basic() {
1133 let X = Array3::from_shape_vec((1, 3, 2), vec![1.0, 2.0, 2.0, 3.0, 1.5, 2.5])
1134 .expect("shape and data length should match");
1135 let y = Array2::from_shape_vec((1, 3), vec![0, 1, 0])
1136 .expect("shape and data length should match");
1137
1138 let hmm = HiddenMarkovModel::new().n_states(2).max_iterations(5);
1139 let trained = hmm.fit(&X, &y).expect("model fitting should succeed");
1140 let predictions = trained.predict(&X).expect("prediction should succeed");
1141
1142 assert_eq!(predictions.dim(), (1, 3));
1143 }
1144
1145 #[test]
1146 #[allow(non_snake_case)]
1147 fn test_memm_basic() {
1148 let X = vec![array![[1.0, 2.0], [2.0, 3.0]], array![[3.0, 1.0]]];
1149 let y = vec![vec![0, 1], vec![0]];
1150
1151 let memm = MaximumEntropyMarkovModel::new()
1152 .max_iter(5)
1153 .learning_rate(0.1);
1154 let trained = memm.fit(&X, &y).expect("model fitting should succeed");
1155 let predictions = trained.predict(&X).expect("prediction should succeed");
1156
1157 assert_eq!(predictions.len(), 2);
1158 assert_eq!(predictions[0].len(), 2);
1159 assert_eq!(predictions[1].len(), 1);
1160 }
1161
1162 #[test]
1163 #[allow(non_snake_case)]
1164 fn test_memm_reproducibility() {
1165 let X = vec![
1166 array![[1.0, 2.0], [2.0, 3.0]],
1167 array![[3.0, 1.0], [4.0, 2.0]],
1168 ];
1169 let y = vec![vec![0, 1], vec![1, 0]];
1170
1171 let memm1 = MaximumEntropyMarkovModel::new()
1172 .max_iter(10)
1173 .random_state(42);
1174 let trained_memm1 = memm1.fit(&X, &y).expect("model fitting should succeed");
1175 let pred1 = trained_memm1
1176 .predict(&X)
1177 .expect("prediction should succeed");
1178
1179 let memm2 = MaximumEntropyMarkovModel::new()
1180 .max_iter(10)
1181 .random_state(42);
1182 let trained_memm2 = memm2.fit(&X, &y).expect("model fitting should succeed");
1183 let pred2 = trained_memm2
1184 .predict(&X)
1185 .expect("prediction should succeed");
1186
1187 assert_eq!(pred1, pred2);
1188 }
1189}