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)