Skip to main content

rust_lstm/
persistence.rs

1use ndarray::{Array2, Dimension};
2use serde::{Deserialize, Serialize};
3use std::fs::File;
4use std::io::{Read, Write};
5use std::path::Path;
6
7use crate::layers::lstm_cell::LSTMCell;
8use crate::models::lstm_network::LSTMNetwork;
9
10/// Serializable version of Array2<f64> for persistence
11#[derive(Serialize, Deserialize, Debug, Clone)]
12struct SerializableArray2 {
13    data: Vec<f64>,
14    shape: (usize, usize),
15}
16
17impl From<&Array2<f64>> for SerializableArray2 {
18    fn from(array: &Array2<f64>) -> Self {
19        Self {
20            data: array.iter().cloned().collect(),
21            shape: array.raw_dim().into_pattern(),
22        }
23    }
24}
25
26impl From<SerializableArray2> for Array2<f64> {
27    fn from(val: SerializableArray2) -> Self {
28        Array2::from_shape_vec(val.shape, val.data)
29            .expect("Failed to reconstruct Array2 from serialized data")
30    }
31}
32
33/// Serializable LSTM cell parameters
34#[derive(Serialize, Deserialize, Debug, Clone)]
35pub struct SerializableLSTMCell {
36    w_ih: SerializableArray2,
37    w_hh: SerializableArray2,
38    b_ih: SerializableArray2,
39    b_hh: SerializableArray2,
40    hidden_size: usize,
41}
42
43impl From<&LSTMCell> for SerializableLSTMCell {
44    fn from(cell: &LSTMCell) -> Self {
45        Self {
46            w_ih: (&cell.w_ih).into(),
47            w_hh: (&cell.w_hh).into(),
48            b_ih: (&cell.b_ih).into(),
49            b_hh: (&cell.b_hh).into(),
50            hidden_size: cell.hidden_size,
51        }
52    }
53}
54
55impl From<SerializableLSTMCell> for LSTMCell {
56    fn from(val: SerializableLSTMCell) -> Self {
57        LSTMCell {
58            w_ih: val.w_ih.into(),
59            w_hh: val.w_hh.into(),
60            b_ih: val.b_ih.into(),
61            b_hh: val.b_hh.into(),
62            hidden_size: val.hidden_size,
63            input_dropout: None,
64            recurrent_dropout: None,
65            output_dropout: None,
66            cell_update_dropout: None,
67            zoneout: None,
68            is_training: true,
69        }
70    }
71}
72
73/// Serializable LSTM network
74#[derive(Serialize, Deserialize, Debug, Clone)]
75pub struct SerializableLSTMNetwork {
76    cells: Vec<SerializableLSTMCell>,
77    input_size: usize,
78    hidden_size: usize,
79    num_layers: usize,
80}
81
82impl From<&LSTMNetwork> for SerializableLSTMNetwork {
83    fn from(network: &LSTMNetwork) -> Self {
84        Self {
85            cells: network.get_cells().iter().map(|cell| cell.into()).collect(),
86            input_size: network.input_size,
87            hidden_size: network.hidden_size,
88            num_layers: network.num_layers,
89        }
90    }
91}
92
93impl From<SerializableLSTMNetwork> for LSTMNetwork {
94    fn from(val: SerializableLSTMNetwork) -> Self {
95        LSTMNetwork::from_cells(
96            val.cells.into_iter().map(|cell| cell.into()).collect(),
97            val.input_size,
98            val.hidden_size,
99            val.num_layers,
100        )
101    }
102}
103
104/// Model metadata for tracking training information
105#[derive(Serialize, Deserialize, Clone)]
106pub struct ModelMetadata {
107    pub model_name: String,
108    pub version: String,
109    pub created_at: String,
110    pub input_size: usize,
111    pub hidden_size: usize,
112    pub num_layers: usize,
113    pub total_epochs: usize,
114    pub final_loss: Option<f64>,
115    pub description: Option<String>,
116}
117
118/// Complete saved model including network and metadata
119#[derive(Serialize, Deserialize)]
120pub struct SavedModel {
121    pub network: SerializableLSTMNetwork,
122    pub metadata: ModelMetadata,
123}
124
125/// Errors that can occur during model persistence operations
126#[derive(Debug)]
127pub enum PersistenceError {
128    IoError(std::io::Error),
129    SerializationError(String),
130}
131
132impl std::fmt::Display for PersistenceError {
133    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134        match self {
135            PersistenceError::IoError(err) => write!(f, "IO error: {}", err),
136            PersistenceError::SerializationError(err) => write!(f, "Serialization error: {}", err),
137        }
138    }
139}
140
141impl std::error::Error for PersistenceError {
142    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
143        match self {
144            PersistenceError::IoError(err) => Some(err),
145            PersistenceError::SerializationError(_) => None,
146        }
147    }
148}
149
150impl From<std::io::Error> for PersistenceError {
151    fn from(error: std::io::Error) -> Self {
152        PersistenceError::IoError(error)
153    }
154}
155
156impl From<serde_json::Error> for PersistenceError {
157    fn from(error: serde_json::Error) -> Self {
158        PersistenceError::SerializationError(error.to_string())
159    }
160}
161
162impl From<bincode::Error> for PersistenceError {
163    fn from(error: bincode::Error) -> Self {
164        PersistenceError::SerializationError(error.to_string())
165    }
166}
167
168/// Model persistence operations
169pub struct ModelPersistence;
170
171impl ModelPersistence {
172    /// Save model to JSON format (human-readable)
173    pub fn save_to_json<P: AsRef<Path>>(
174        model: &SavedModel,
175        path: P,
176    ) -> Result<(), PersistenceError> {
177        let json = serde_json::to_string_pretty(model)?;
178        let mut file = File::create(path)?;
179        file.write_all(json.as_bytes())?;
180        Ok(())
181    }
182
183    /// Load model from JSON format
184    pub fn load_from_json<P: AsRef<Path>>(path: P) -> Result<SavedModel, PersistenceError> {
185        let mut file = File::open(path)?;
186        let mut contents = String::new();
187        file.read_to_string(&mut contents)?;
188        let model = serde_json::from_str(&contents)?;
189        Ok(model)
190    }
191
192    /// Save model to binary format (compact and fast)
193    pub fn save_to_binary<P: AsRef<Path>>(
194        model: &SavedModel,
195        path: P,
196    ) -> Result<(), PersistenceError> {
197        let encoded = bincode::serialize(model)?;
198        let mut file = File::create(path)?;
199        file.write_all(&encoded)?;
200        Ok(())
201    }
202
203    /// Load model from binary format
204    pub fn load_from_binary<P: AsRef<Path>>(path: P) -> Result<SavedModel, PersistenceError> {
205        let mut file = File::open(path)?;
206        let mut contents = Vec::new();
207        file.read_to_end(&mut contents)?;
208        let model = bincode::deserialize(&contents)?;
209        Ok(model)
210    }
211
212    /// Create a model with metadata
213    pub fn create_saved_model(
214        network: &LSTMNetwork,
215        model_name: String,
216        total_epochs: usize,
217        final_loss: Option<f64>,
218        description: Option<String>,
219    ) -> SavedModel {
220        let metadata = ModelMetadata {
221            model_name,
222            version: env!("CARGO_PKG_VERSION").to_string(),
223            created_at: chrono::Utc::now().to_rfc3339(),
224            input_size: network.input_size,
225            hidden_size: network.hidden_size,
226            num_layers: network.num_layers,
227            total_epochs,
228            final_loss,
229            description,
230        };
231
232        SavedModel {
233            network: network.into(),
234            metadata,
235        }
236    }
237}
238
239/// Convenience trait for easy model saving/loading
240pub trait PersistentModel {
241    /// Save model to file (format determined by file extension)
242    fn save<P: AsRef<Path>>(
243        &self,
244        path: P,
245        metadata: ModelMetadata,
246    ) -> Result<(), PersistenceError>;
247
248    /// Load model from file (format determined by file extension)
249    fn load<P: AsRef<Path>>(path: P) -> Result<(Self, ModelMetadata), PersistenceError>
250    where
251        Self: Sized;
252}
253
254impl PersistentModel for LSTMNetwork {
255    fn save<P: AsRef<Path>>(
256        &self,
257        path: P,
258        metadata: ModelMetadata,
259    ) -> Result<(), PersistenceError> {
260        let saved_model = SavedModel {
261            network: self.into(),
262            metadata,
263        };
264
265        let path_ref = path.as_ref();
266        match path_ref.extension().and_then(|s| s.to_str()) {
267            Some("json") => ModelPersistence::save_to_json(&saved_model, path),
268            Some("bin") | Some("model") => ModelPersistence::save_to_binary(&saved_model, path),
269            _ => ModelPersistence::save_to_binary(&saved_model, path), // Default to binary
270        }
271    }
272
273    fn load<P: AsRef<Path>>(path: P) -> Result<(Self, ModelMetadata), PersistenceError> {
274        let path_ref = path.as_ref();
275        let saved_model = match path_ref.extension().and_then(|s| s.to_str()) {
276            Some("json") => ModelPersistence::load_from_json(path)?,
277            Some("bin") | Some("model") => ModelPersistence::load_from_binary(path)?,
278            _ => ModelPersistence::load_from_binary(path)?, // Default to binary
279        };
280
281        Ok((saved_model.network.into(), saved_model.metadata))
282    }
283}