main.bend
mlbend-datasets@0.1.1.0 · 192 lines · 0 definitions
1# mlbend-datasets: the datasets of scikit-learn, fetched from the hub.
2#
3# import mlbend-datasets@0.1.0.0/main.bend as Datasets
4#
5# The features of a dataset and its labels beside them: a `Mat<r, c>` of
6# the features and a list of the labels.
7#
8# Every dataset here comes from HuggingFace, over the network, at run
9# time: the effect is at the edge and the data that comes back is a
10# value, so an estimator over it stays pure and a run is reproducible.
11#
12# The iris dataset is the one scikit-learn calls `load_iris`: 150 rows,
13# four features, three classes. It is fetched as the file the dataset's
14# own repository holds, which holds the same numbers scikit-learn ships.
15#
16# The file has one row per line, comma separated, the four features and
17# then the class:
18#
19# 5.1,3.5,1.4,0.2,Iris-setosa
20
21import Base
22import mlbend-huggingface@0.1.0.0/main.bend as Hf
23import bend-ml-tensor-array@0.1.14.0/main.bend as TA
24
25# A dataset: the features and the labels beside them. The flat tensor is
26# a Type, so the record that holds it is one too.
27type Table is Type:
28 Table{features: TA.Mat<150n, 4n>, labels: List<&2, Nat>}
29
30# One row of the file: its four numbers and its class.
31type Row is Data:
32 Row{features: List<&2, F32>, label: Nat}
33
34# The rows read so far.
35type Rows is Data:
36 Rows{rows: List<&2, Row>, labels: List<&2, Nat>}
37
38# The class of a row: 0, 1 or 2 for the three iris species.
39def add_features(acc: List<&2, F32>, r: Row) -> List<&2, F32>:
40 match r:
41 case Row{features, +label}:
42 List.append(&2, F32, acc, features)
43
44# The dataset the rows make.
45def add_row_of(acc: Rows, r: Result<&1, &1, String, Row>) -> Rows:
46 match acc:
47 case Rows{rows, labels}:
48 match r:
49 case Fail{why}:
50 Rows{rows, labels}
51 case Done{row}:
52 match row:
53 case Row{features, +label}:
54 Rows{List.append(&2, Row, rows, row <> Nil{}),
55 List.append(&2, Nat, labels, label <> Nil{})}
56
57# The rows of a file.
58def number_or_zero(m: Maybe<&2, F32>) -> F32:
59 match m:
60 case Some{v}:
61 v
62 case None{}:
63 0.0
64
65# The text of a field, when the field is there.
66def features4(a: Maybe<&2, F32>, b: Maybe<&2, F32>, c: Maybe<&2, F32>, d: Maybe<&2, F32>) ->
67 List<&2, F32>:
68 number_or_zero(a) <> (number_or_zero(b) <> (number_or_zero(c) <> (number_or_zero(d) <> Nil{})))
69
70# The class of a row, when the row has one.
71def number_of_text(t: Maybe<&2, String>) -> Maybe<&2, F32>:
72 match t:
73 case Some{s}:
74 F32.read(s)
75 case None{}:
76 None{}
77
78# The four features of a row.
79def text_at(+xs: List<&2, String>, n: Nat) -> Maybe<&2, String>:
80 List.get(&2, String, xs, n)
81
82# The number of a field, when the field is there.
83def number_at(+xs: List<&2, String>, n: Nat) -> Maybe<&2, F32>:
84 number_of_text(text_at(xs, n))
85
86# The number some text holds, when it is one that fits.
87def features_of(+xs: List<&2, String>) -> List<&2, F32>:
88 features4(number_at(xs, 0n), number_at(xs, 1n), number_at(xs, 2n), number_at(xs, 3n))
89
90# The four features of a row, from the numbers.
91def class_of(name: String) -> Nat:
92 match name:
93 case "Iris-setosa":
94 0n
95 case "Iris-versicolor":
96 1n
97 case "Iris-virginica":
98 2n
99 case _:
100 0n
101
102# A number that may be there, or zero when it is not.
103def class_at(m: Maybe<&2, String>) -> Maybe<&2, Nat>:
104 match m:
105 case Some{s}:
106 Some{class_of(s)}
107 case None{}:
108 None{}
109
110# One row of the file.
111def label_of(+xs: List<&2, String>) -> Maybe<&2, Nat>:
112 class_at(text_at(xs, 4n))
113
114# The class of a field, when the field is there.
115def count_ok(n: Nat, fs: List<&2, F32>, label: Nat) -> Result<&1, &1, String, Row>:
116 Bool.pick(Result<&1, &1, String, Row>, Nat.is_eq(n, 4n),
117 Done{Row{fs, label}},
118 Fail{"a row with the wrong number of features"})
119
120# The rows of a file, and the row one line makes.
121def row_of_parts(+fs: List<&2, F32>, l: Maybe<&2, Nat>) -> Result<&1, &1, String, Row>:
122 match l:
123 case Some{label}:
124 count_ok(List.length(&2, F32, fs), fs, label)
125 case None{}:
126 Fail{"a row with no class"}
127
128# A row with the right number of features, or the reason it has not.
129def row_of(line: String) -> Result<&1, &1, String, Row>:
130 +xs = String.split(line, Chr{44})
131 row_of_parts(features_of(xs), label_of(xs))
132
133# One row of the file, from its features and its class.
134def add_row(acc: Rows, line: String) -> Rows:
135 add_row_of(acc, row_of(line))
136
137# The rows of a file, and the row one line makes.
138def build_data(d: Result<&1, &1, String, Table>) -> Result<&1, &1, Hf.Err, Table>:
139 match d:
140 case Fail{why}:
141 Fail{Hf.ErrJson{}}
142 case Done{table}:
143 Done{table}
144
145# The text of a file that may be there, and the dataset it makes.
146def matrix_of_mat(m: Maybe<&1, TA.Mat<150n, 4n>>, labels: List<&2, Nat>) ->
147 Result<&1, &1, String, Table>:
148 match m:
149 case Some{matrix}:
150 Done{Table{matrix, labels}}
151 case None{}:
152 Fail{"the rows are not 150 of four"}
153
154# The rows of a file, and the dataset they make.
155def matrix_of(fs: List<&2, F32>, labels: List<&2, Nat>) -> Result<&1, &1, String, Table>:
156 matrix_of_mat(TA.Mat.of_list(150n, 4n, fs), labels)
157
158# The matrix the features make, when it is one.
159def data_of(acc: Rows) -> Result<&1, &1, String, Table>:
160 match acc:
161 case Rows{rows, labels}:
162 matrix_of(List.foldl(&2, Row, List<&2, F32>, add_features, rows, Nil{}), labels)
163
164# The matrix the features make, and the labels.
165def build_rows(r: Result<&1, &1, String, Rows>) -> Result<&1, &1, Hf.Err, Table>:
166 match r:
167 case Fail{why}:
168 Fail{Hf.ErrJson{}}
169 case Done{rows}:
170 build_data(data_of(rows))
171
172# The dataset the rows make.
173def rows_of(lines: List<&2, String>) -> Result<&1, &1, String, Rows>:
174 Done{List.foldl(&2, String, Rows, add_row, lines, Rows{Nil{}, Nil{}})}
175
176# The features of every row, one after the other.
177def build(text: String) -> Result<&1, &1, Hf.Err, Table>:
178 build_rows(rows_of(String.lines(text)))
179
180# The rows of a file, and the dataset they make.
181def build_of(t: Result<&1, &1, Hf.Err, String>) -> Result<&1, &1, Hf.Err, Table>:
182 match t:
183 case Fail{e}:
184 Fail{e}
185 case Done{text}:
186 build(text)
187
188# The iris dataset, fetched from the hub.
189def iris() -> IO(Result<&1, &1, Hf.Err, Table>):
190 do IO<Result<&1, &1, Hf.Err, Table>>:
191 t : Result<&1, &1, Hf.Err, String> <- Hf.file_text("mstz/iris", "main", "iris.data")
192 return build_of(t)