diff --git a/src/main.nim b/src/main.nim index b1061e4..0f56cdf 100644 --- a/src/main.nim +++ b/src/main.nim @@ -3,7 +3,11 @@ import nn/util/activations import nn/util/losses -var net = newNeuralNetwork(@[2, 3, 2], activationFunc=newActivation(sigmoid, func (x, y: float): float = 0.0), - lossFunc=newLoss(mse, mse), weightRange=(-1.0, +1.0), learnRate=0.05) -var prediction = net.predict(newMatrix[float](@[2.7, 3.0])) -echo prediction +const InitialSize = 50 + + +var networks: seq[NeuralNetwork] = @[] +for _ in 0.. 0 and b.shape.rows > 0 and a.shape != b.shape: raise newException(ValueError, &"incompatible argument shapes for addition") @@ -445,7 +492,7 @@ proc `*`*[T](a, b: MatrixView[T]): Matrix[T] = result.data[].add(a[i] * b[i]) -proc `*`*[T](a, b: Matrix[T]): Matrix[T] {.raises: [ValueError].} = +proc `*`*[T](a, b: Matrix[T]): Matrix[T] = when not defined(release): if a.shape.rows > 0 and b.shape.rows > 0 and a.shape.cols != b.shape.rows: raise newException(ValueError, &"incompatible argument shapes for multiplication") @@ -468,6 +515,12 @@ proc `*`*[T](a, b: Matrix[T]): Matrix[T] {.raises: [ValueError].} = for m in r1 * r2: for element in m: result.data[].add(element) + else: + for r1 in a: + for r2 in b: + for m in r1 * r2: + for element in m: + result.data[].add(element) else: result = a[0] * b[0] @@ -521,7 +574,53 @@ proc `>=`*[T](a: Matrix[T], b: T): Matrix[bool] = result.data[].add(e >= b) -proc `==`*[T](a, b: Matrix[T]): Matrix[bool] {.raises: [ValueError].} = +proc `==`*[T](a: MatrixView[T], b: MatrixView[T]): Matrix[bool] = + when not defined(release): + if a.len() != b.len(): + raise newException(ValueError, "invalid shapes for comparison") + new(result) + new(result.data) + result.shape = a.shape + result.order = RowMajor + result.data[] = newSeqOfCap[bool](result.shape.getSize()) + var col = 0 + while col < result.shape.cols: + result.data[].add(a[col] == b[col]) + inc(col) + + +proc `==`*[T](a: Matrix[T], b: MatrixView[T]): Matrix[bool] = + when not defined(release): + if a.shape.cols != b.len() or a.shape.rows > 0: + raise newException(ValueError, "invalid shapes for comparison") + return a[0] == b + + +proc diag*[T](a: Matrix[T], diagonal: int): Matrix[T] = + ## Returns the chosen diagonal of the given + ## matrix as a linear array. Diagonal 0 means left, + ## 1 means right + when not defined(release): + if a.shape.rows != a.shape.cols: + raise newException(ValueError, "only square matrices have diagonals") + var res = newSeqOfCap[T](a.shape.getSize()) + case diagonal: + of 0: + for i in 0..`*[T](a, b: Matrix[T]): Matrix[bool] {.raises: [ValueError].} = +proc `>`*[T](a, b: Matrix[T]): Matrix[bool] = when not defined(release): if a.shape != b.shape: raise newException(ValueError, "can't compare matrices of different shapes") @@ -544,12 +645,14 @@ proc `>`*[T](a, b: Matrix[T]): Matrix[bool] {.raises: [ValueError].} = result.shape = a.shape result.order = RowMajor result.data[] = newSeqOfCap[bool](result.shape.getSize()) + if a.shape.rows == 0: + result = a[0] > b[0] for r in 0.. b[r, c]) -proc `>=`*[T](a, b: Matrix[T]): Matrix[bool] {.raises: [ValueError].} = +proc `>=`*[T](a, b: Matrix[T]): Matrix[bool] = when not defined(release): if a.shape != b.shape: raise newException(ValueError, "can't compare matrices of different shapes") @@ -558,12 +661,14 @@ proc `>=`*[T](a, b: Matrix[T]): Matrix[bool] {.raises: [ValueError].} = result.shape = a.shape result.order = RowMajor result.data[] = newSeqOfCap[bool](result.shape.getSize()) + if a.shape.rows == 0: + result = a[0] >= b[0] for r in 0..= b[r, c]) -proc `<=`*[T](a, b: Matrix[T]): Matrix[bool] {.raises: [ValueError].} = +proc `<=`*[T](a, b: Matrix[T]): Matrix[bool] = when not defined(release): if a.shape != b.shape: raise newException(ValueError, "can't compare matrices of different shapes") @@ -572,6 +677,8 @@ proc `<=`*[T](a, b: Matrix[T]): Matrix[bool] {.raises: [ValueError].} = result.shape = a.shape result.order = RowMajor result.data[] = newSeqOfCap[bool](result.shape.getSize()) + if a.shape.rows == 0: + result = a[0] <= b[0] for r in 0.. a -proc `!=`*[T](a, b: Matrix[T]): Matrix[bool] {.raises: [ValueError].} = not a == b -proc `*`*[T](a: Matrix[T], b: MatrixView[T]): Matrix[T] {.raises: [ValueError].} = b * a +proc `<`*[T](a, b: Matrix[T]): Matrix[bool] = b > a +proc `!=`*[T](a, b: Matrix[T]): Matrix[bool] = not a == b +proc `*`*[T](a: Matrix[T], b: MatrixView[T]): Matrix[T] = b * a proc `==`*[T](a: T, b: Matrix[T]): Matrix[bool] = b == a +proc `==`*[T](a: MatrixView[T], b: Matrix[T]): Matrix[bool] = b == a proc `!=`*[T](a: Matrix[T], b: T): Matrix[bool] = not a == b proc `!=`*[T](a: T, b: Matrix[T]): Matrix[bool] = not b == a -proc toRowMajor*[T](self: Matrix[T]): Matrix[T] = +proc toRowMajor*[T](self: Matrix[T], copy: bool = true): Matrix[T] = ## Converts a column-major matrix to a - ## row-major one + ## row-major one. Returns a copy unless + ## copy equals false if self.order == RowMajor: - return - self.order = RowMajor - let orig = self.data[] - self.data[] = @[] - var idx = 0 - var col = 0 - while col < self.shape.cols: - self.data[].add(orig[idx]) - idx += self.shape.cols - if idx > orig.high(): - inc(col) - idx = col - result = self + return self + if copy: + result = self.copy() + else: + result = self + result.order = RowMajor + for row in self: + for element in row: + self.data[].add(element) -proc toColumnMajor*[T](self: Matrix[T]): Matrix[T] = +proc toColumnMajor*[T](self: Matrix[T], copy: bool = true): Matrix[T] = ## Converts a row-major matrix to a ## column-major one - new(result) if self.order == ColumnMajor: - return + return self + if copy: + result = self.copy() + else: + result = self self.order = ColumnMajor let orig = self.data[] self.data[] = @[] @@ -674,7 +790,7 @@ proc `$`*[T](self: MatrixView[T]): string = proc `$`*[T](self: Matrix[T]): string = ## Stringifies the matrix if self.shape.rows == 0: - return $self[0] + return $(self[0]) result &= "[" for i, row in self: result &= "[" @@ -693,10 +809,34 @@ proc `$`*[T](self: Matrix[T]): string = proc dot*[T](self, other: Matrix[T]): Matrix[T] = ## Computes the dot product of the two ## input matrices - when not defined(release): - if a.shape.cols != b.shape.rows: - raise newException(ValueError, &"incompatible argument shapes for dot product") - # TODO + if self.shape.rows > 1 and other.shape.rows > 1: + when not defined(release): + if self.shape.rows != other.shape.cols: + raise newException(ValueError, &"incompatible argument shapes for dot product") + result = zeros[T]((self.shape.rows, other.shape.cols)) + echo self + var other = other.transpose() + echo other + for i in 0.. 1: + when not defined(release): + if self.shape.cols != other.shape.cols: + raise newException(ValueError, &"incompatible argument shapes for dot product") + result = zeros[T]((0, self.shape.rows)) + for i in 0.. 1: + when not defined(release): + if self.shape.cols != other.shape.cols: + raise newException(ValueError, &"incompatible argument shapes for dot product") + result = zeros[T]((0, self.shape.cols)) + var other = other.transpose() + for i in 0.. 10, y) works as expected +proc where*[T](self: Matrix[T], cond: Matrix[bool], other: Matrix[T]): Matrix[T] = cond.where(self, other) + + +proc max*[T](self: Matrix[T]): T = + ## Returns the largest element + ## into the matrix + var m: T = self[0, 0] + for row in self: + for element in row: + if m < element: + m = element + return m + + +proc argmax*[T](self: Matrix[T]): T = + ## Returns the index largest element + ## into the matrix + var m: T = self[0, 0] + var + row = 0 + col = 0 + while row < self.shape.rows: + while col < self.shape.cols: + if self[row, col] > m: + m = self[row, col] + if self.shape.rows == 0: + while col < self.shape.cols: + if self[0, col] > m: + m = self[0, col] + inc(col) + return m + + +proc contains*[T](self: Matrix[T], e: T): bool = + ## Returns wherher the matrix contains + ## the element e + for row in self: + for element in row: + if element == e: + return true + return false + when isMainModule: import math @@ -729,6 +913,7 @@ when isMainModule: var m = newMatrix[int](@[@[1, 2, 3], @[4, 5, 6]]) var k = m.transpose() + assert k[2, 1] == m[1, 2], "transpose mismatch" assert all(m.transpose() == k), "transpose mismatch" assert k.sum() == m.sum(), "element sum mismatch" assert all(k.sum(axis=1) == m.sum(axis=0)), "sum over axis mismatch" @@ -741,4 +926,9 @@ when isMainModule: assert (m * z).sum() == 46, "matrix multiplication mismatch" assert all(z * z == z.apply(pow, 2, axis = -1, copy=true)), "matrix multiplication mismatch" var x = newMatrix[int](@[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) - assert (x < 5).where(x, x * 10).sum() == 360, "where mismatch" \ No newline at end of file + assert (x < 5).where(x, x * 10).sum() == 360, "where mismatch" + assert all((x < 5).where(x, x * 10) == x.where(x < 5, x * 10)), "where mismatch" + assert x.max() == 9, "max mismatch" + assert x.argmax() == 9, "argmax mismatch" + discard newMatrix[int](@[12, 23]).dot(newMatrix[int](@[@[11, 22], @[33, 44]])) + discard newMatrix[int](@[@[1, 2, 3], @[2, 3, 4]]).dot(newMatrix[int](@[1, 2, 3])) \ No newline at end of file diff --git a/src/nn/util/preprocessing.nim b/src/nn/util/preprocessing.nim new file mode 100644 index 0000000..be5a706 --- /dev/null +++ b/src/nn/util/preprocessing.nim @@ -0,0 +1,76 @@ +# Copyright 2022 Mattia Giambirtone +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +## Various data preprocessing tools + +import matrix + + +import strformat +import sets + + +type + LabelEncoder* = ref object + ## An encoder to assign a numerical value in the + ## range from 0 to n_labels - 1 to the labels + # of some categorical data, reversibly + isFit: bool + labels: Matrix[string] + + +proc newLabelEncoder*: LabelEncoder = + ## Initializes a new LabelEncoder object + new(result) + + +proc toOrderedSet[T](m: Matrix[T]): OrderedSet[T] = + result = initOrderedSet[T]() + for row in m: + for element in row: + result.incl(element) + + +proc fit*(self: LabelEncoder, labels: Matrix[string]) = + # Fits the encoder to the given labels + var lbl: seq[string] = @[] + for label in toOrderedSet(labels): + lbl.add(label) + self.labels = newMatrix(lbl) + self.is_fit = true + + +proc transform*(self: LabelEncoder, labels: Matrix[string]): Matrix[int] = + ## Transforms a vector of labels into a vector of encoded + ## integers. Duplicate labels are assigned the same integer + assert self.isFit, "The estimator must be fit!" + var res: seq[int] = @[] + for row in labels: + for label in row: + if label notin self.labels: + raise newException(ValueError, &"Unknown label '{label}'") + res.add(self.labels.raw[].find(label)) + result = newMatrix(res) + + +proc reverseTransform*(self: LabelEncoder, labels: Matrix[int]): Matrix[string] = + ## Reverses the transformation of the integer labels back to a string + assert self.is_fit, "The estimator must be fit!" + var res: seq[string] = @[] + for row in labels: + for label in row: + if label notin 0..