1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
use super::logistic_regression_train::Model;
use std::format;
use std::io::{self, BufRead, BufReader, Write};
use teaclave_types::{FunctionArguments, FunctionRuntime};
use rusty_machine::learning::logistic_reg::LogisticRegressor;
use rusty_machine::learning::SupModel;
use rusty_machine::linalg;
const MODEL_FILE: &str = "model_file";
const INPUT_DATA: &str = "data_file";
const RESULT: &str = "result_file";
#[derive(Default)]
pub struct LogisticRegressionPredict;
impl LogisticRegressionPredict {
pub const NAME: &'static str = "builtin-logistic-regression-predict";
pub fn new() -> Self {
Default::default()
}
pub fn run(
&self,
_arguments: FunctionArguments,
runtime: FunctionRuntime,
) -> anyhow::Result<String> {
let mut model_json = String::new();
let mut f = runtime.open_input(MODEL_FILE)?;
f.read_to_string(&mut model_json)?;
let model: Model = serde_json::from_str(&model_json)?;
let alg = model.alg();
let para = model.parameters();
let mut lr = LogisticRegressor::new(alg);
lr.set_parameters(para);
let feature_size = lr
.parameters()
.ok_or_else(|| anyhow::anyhow!("Model parameter is None"))?
.size()
- 1;
let input = runtime.open_input(INPUT_DATA)?;
let data_matrix = parse_input_data(input, feature_size)?;
let result = lr.predict(&data_matrix)?;
let mut output = runtime.create_output(RESULT)?;
let result_cnt = result.data().len();
for c in result.data().iter() {
writeln!(&mut output, "{:.4}", c)?;
}
Ok(format!("Predicted {} lines of data.", result_cnt))
}
}
fn parse_input_data(
input: impl io::Read,
feature_size: usize,
) -> anyhow::Result<linalg::Matrix<f64>> {
let mut flattened_data = Vec::new();
let mut count = 0;
let reader = BufReader::new(input);
for line_result in reader.lines() {
let line = line_result?;
let trimed_line = line.trim();
anyhow::ensure!(!trimed_line.is_empty(), "Empty line");
let v: Vec<f64> = trimed_line
.split(',')
.map(|x| x.parse::<f64>())
.collect::<std::result::Result<_, _>>()?;
anyhow::ensure!(
v.len() == feature_size,
"Data format error: column len = {}, expected = {}",
v.len(),
feature_size
);
flattened_data.extend(v);
count += 1;
}
Ok(linalg::Matrix::new(count, feature_size, flattened_data))
}
#[cfg(feature = "enclave_unit_test")]
pub mod tests {
use super::*;
use std::path::Path;
use std::untrusted::fs;
use teaclave_crypto::*;
use teaclave_runtime::*;
use teaclave_test_utils::*;
use teaclave_types::*;
pub fn run_tests() -> bool {
run_tests!(test_logistic_regression_predict)
}
fn test_logistic_regression_predict() {
let arguments = FunctionArguments::default();
let base = Path::new("fixtures/functions/logistic_regression_prediction");
let model = base.join("model.txt");
let plain_input = base.join("predict_input.txt");
let plain_output = base.join("predict_result.txt.out");
let expected_output = base.join("expected_result.txt");
let input_files = StagedFiles::new(hashmap!(
MODEL_FILE =>
StagedFileInfo::new(&model, TeaclaveFile128Key::random(), FileAuthTag::mock()),
INPUT_DATA =>
StagedFileInfo::new(&plain_input, TeaclaveFile128Key::random(), FileAuthTag::mock()),
));
let output_files = StagedFiles::new(hashmap!(
RESULT =>
StagedFileInfo::new(&plain_output, TeaclaveFile128Key::random(), FileAuthTag::mock())
));
let runtime = Box::new(RawIoRuntime::new(input_files, output_files));
let summary = LogisticRegressionPredict::new()
.run(arguments, runtime)
.unwrap();
assert_eq!(summary, "Predicted 5 lines of data.");
let result = fs::read_to_string(&plain_output).unwrap();
let expected = fs::read_to_string(&expected_output).unwrap();
assert_eq!(&result[..], &expected[..]);
}
}