From 919b158f899e258f8e4ac0261949508e1dd12446 Mon Sep 17 00:00:00 2001 From: norm4nn Date: Wed, 21 Aug 2024 23:15:00 +0200 Subject: [PATCH 1/4] Splitted RadiusNearestNeighbors module to RNNClassifier and RNNRegressor --- ...nearest_neighbors.ex => rnn_classifier.ex} | 203 ++++++--------- lib/scholar/neighbors/rnn_regressor.ex | 232 ++++++++++++++++++ 2 files changed, 304 insertions(+), 131 deletions(-) rename lib/scholar/neighbors/{radius_nearest_neighbors.ex => rnn_classifier.ex} (61%) create mode 100644 lib/scholar/neighbors/rnn_regressor.ex diff --git a/lib/scholar/neighbors/radius_nearest_neighbors.ex b/lib/scholar/neighbors/rnn_classifier.ex similarity index 61% rename from lib/scholar/neighbors/radius_nearest_neighbors.ex rename to lib/scholar/neighbors/rnn_classifier.ex index 1fe6d8d2..791d4ef6 100644 --- a/lib/scholar/neighbors/radius_nearest_neighbors.ex +++ b/lib/scholar/neighbors/rnn_classifier.ex @@ -1,16 +1,16 @@ -defmodule Scholar.Neighbors.RadiusNearestNeighbors do +defmodule Scholar.Neighbors.RNNClassifier do @moduledoc """ The Radius Nearest Neighbors. - It implements both classification and regression. + It implements classification. """ import Nx.Defn import Scholar.Shared require Nx @derive {Nx.Container, - keep: [:weights, :num_classes, :task, :metric, :radius], containers: [:data, :labels]} - defstruct [:data, :labels, :weights, :num_classes, :task, :metric, :radius] + keep: [:weights, :num_classes, :metric, :radius], containers: [:data, :labels]} + defstruct [:data, :labels, :weights, :num_classes, :metric, :radius] opts = [ radius: [ @@ -20,6 +20,7 @@ defmodule Scholar.Neighbors.RadiusNearestNeighbors do ], num_classes: [ type: :pos_integer, + required: true, doc: "Number of classes in provided labels" ], weights: [ @@ -47,18 +48,6 @@ defmodule Scholar.Neighbors.RadiusNearestNeighbors do * Anonymous function of arity 2 that takes two rank-2 tensors. """ - ], - task: [ - type: {:in, [:classification, :regression]}, - default: :classification, - doc: """ - Task that will be performed using Radius Nearest Neighbors. Possible values: - - * `:classification` - Classifier implementing the Radius Nearest Neighbors vote. - - * `:regression` - Regression based on Radius Nearest Neighbors. - The target is predicted by local interpolation of the targets associated of the nearest neighbors in the training set. - """ ] ] @@ -70,8 +59,6 @@ defmodule Scholar.Neighbors.RadiusNearestNeighbors do For classification, provided labels need to be consecutive non-negative integers. If your labels does not meet this condition please use `Scholar.Preprocessing.ordinal_encode` - Currently 2D labels are only supported for regression tasks. - ## Options #{NimbleOptions.docs(@opts_schema)} @@ -88,10 +75,6 @@ defmodule Scholar.Neighbors.RadiusNearestNeighbors do * `:num_classes` - Number of classes in provided labels. - * `:task` - Task that will be performed using Radius Nearest Neighbors. - For `:classification` task, model will be a classifier implementing the Radius Nearest Neighbors vote. - For `:regression` task, model is a regressor based on Radius Nearest Neighbors. - * `:metric` - The metric function used. * `:radius` - Radius of neighborhood. @@ -100,22 +83,23 @@ defmodule Scholar.Neighbors.RadiusNearestNeighbors do iex> x = Nx.tensor([[1, 2], [2, 4], [1, 3], [2, 5]]) iex> y = Nx.tensor([1, 0, 1, 1]) - iex> Scholar.Neighbors.RadiusNearestNeighbors.fit(x, y, num_classes: 2) - %Scholar.Neighbors.RadiusNearestNeighbors{ - data: Nx.tensor( + iex> Scholar.Neighbors.RNNClassifier.fit(x, y, num_classes: 2) + %Scholar.Neighbors.RNNClassifier{ + data: #Nx.Tensor< + s64[4][2] [ [1, 2], [2, 4], [1, 3], [2, 5] ] - ), - labels: Nx.tensor( + >, + labels: #Nx.Tensor< + s64[4] [1, 0, 1, 1] - ), + >, weights: :uniform, num_classes: 2, - task: :classification, metric: &Scholar.Metrics.Distance.pairwise_minkowski/2, radius: 1.0 } @@ -144,17 +128,11 @@ defmodule Scholar.Neighbors.RadiusNearestNeighbors do opts = NimbleOptions.validate!(opts, @opts_schema) - if opts[:num_classes] == nil and opts[:task] == :classification do - raise ArgumentError, - "expected :num_classes to be provided for task :classification" - end - %__MODULE__{ data: x, labels: y, weights: opts[:weights], num_classes: opts[:num_classes], - task: opts[:task], metric: opts[:metric], radius: opts[:radius] } @@ -171,54 +149,17 @@ defmodule Scholar.Neighbors.RadiusNearestNeighbors do iex> x = Nx.tensor([[1, 2], [2, 4], [1, 3], [2, 5]]) iex> y = Nx.tensor([1, 0, 1, 1]) - iex> model = Scholar.Neighbors.RadiusNearestNeighbors.fit(x, y, num_classes: 2) - iex> Scholar.Neighbors.RadiusNearestNeighbors.predict(model, Nx.tensor([[1.9, 4.3], [1.1, 2.0]])) - Nx.tensor( + iex> model = Scholar.Neighbors.RNNClassifier.fit(x, y, num_classes: 2) + iex> Scholar.Neighbors.RNNClassifier.predict(model, Nx.tensor([[1.9, 4.3], [1.1, 2.0]])) + #Nx.Tensor< + s64[2] [0, 1] - ) + > """ - defn predict(%__MODULE__{labels: labels, weights: weights, task: task} = model, x) do - case task do - :classification -> - {probabilities, outliers_mask} = predict_probability(model, x) - results = Nx.argmax(probabilities, axis: 1) - Nx.select(outliers_mask, -1, results) - - :regression -> - {distances, indices} = radius_neighbors(model, x) - - x_num_samples = Nx.axis_size(x, 0) - train_num_samples = Nx.axis_size(labels, 0) - labels_rank = Nx.rank(labels) - - labels = - if labels_rank == 1 do - Nx.new_axis(labels, 0) |> Nx.broadcast({x_num_samples, train_num_samples}) - else - out_size = Nx.axis_size(labels, 1) - Nx.new_axis(labels, 0) |> Nx.broadcast({x_num_samples, train_num_samples, out_size}) - end - - indices = - if labels_rank == 2, - do: Nx.new_axis(indices, -1) |> Nx.broadcast(labels), - else: indices - - case weights do - :distance -> - weights = check_weights(distances) - - weights = - if labels_rank == 2, - do: Nx.new_axis(weights, -1) |> Nx.broadcast(labels), - else: weights - - Nx.weighted_mean(labels, indices * weights, axes: [1]) - - :uniform -> - Nx.weighted_mean(labels, indices, axes: [1]) - end - end + defn predict(model, x) do + {probabilities, outliers_mask} = predict_probability(model, x) + results = Nx.argmax(probabilities, axis: 1) + Nx.select(outliers_mask, -1, results) end @doc """ @@ -234,61 +175,28 @@ defmodule Scholar.Neighbors.RadiusNearestNeighbors do iex> x = Nx.tensor([[1, 2], [2, 4], [1, 3], [2, 5]]) iex> y = Nx.tensor([1, 0, 1, 1]) - iex> model = Scholar.Neighbors.RadiusNearestNeighbors.fit(x, y, num_classes: 2) - iex> Scholar.Neighbors.RadiusNearestNeighbors.predict_probability(model, Nx.tensor([[1.9, 4.3], [1.1, 2.0]])) - {Nx.tensor( + iex> model = Scholar.Neighbors.RNNClassifier.fit(x, y, num_classes: 2) + iex> Scholar.Neighbors.RNNClassifier.predict_probability(model, Nx.tensor([[1.9, 4.3], [1.1, 2.0]])) + {#Nx.Tensor< + f32[2][2] [ [0.5, 0.5], [0.0, 1.0] ] - ), - Nx.tensor( - [0, 0], type: :u8 - )} - """ - deftransform predict_probability(%__MODULE__{task: :classification} = model, x) do - predict_proba_n(model, x) - end - - @doc """ - Find the Radius neighbors of a point. - - ## Return Values - - Returns indices of the selected neighbor points as a mask (1 if a point is a neighbor, 0 otherwise) and their respective distances. - - ## Examples - - iex> x = Nx.tensor([[1, 2], [2, 4], [1, 3], [2, 5]]) - iex> y = Nx.tensor([1, 0, 1, 1]) - iex> model = Scholar.Neighbors.RadiusNearestNeighbors.fit(x, y, num_classes: 2) - iex> Scholar.Neighbors.RadiusNearestNeighbors.radius_neighbors(model, Nx.tensor([[1.9, 4.3], [1.1, 2.0]])) - {Nx.tensor( - [ - [2.469818353652954, 0.3162313997745514, 1.5811394453048706, 0.7071067690849304], - [0.10000114142894745, 2.1931710243225098, 1.0049877166748047, 3.132091760635376] - ] - ), - Nx.tensor( - [ - [0, 1, 0, 1], - [1, 0, 0, 0] - ], type: :u8 - )} + >, + #Nx.Tensor< + u8[2] + [0, 0] + >} """ - defn radius_neighbors(%__MODULE__{metric: metric, radius: radius, data: data}, x) do - distances = metric.(x, data) - {distances, distances <= radius} - end - - defnp predict_proba_n( - %__MODULE__{ - labels: labels, - weights: weights, - num_classes: num_classes - } = model, - x - ) do + defn predict_probability( + %__MODULE__{ + labels: labels, + weights: weights, + num_classes: num_classes + } = model, + x + ) do {distances, indices} = radius_neighbors(model, x) num_samples = Nx.axis_size(x, 0) outliers_mask = Nx.sum(indices, axes: [1]) == 0 @@ -315,6 +223,39 @@ defmodule Scholar.Neighbors.RadiusNearestNeighbors do {final_probabilities / Nx.new_axis(normalizer, -1), outliers_mask} end + @doc """ + Find the Radius neighbors of a point. + + ## Return Values + + Returns indices of the selected neighbor points as a mask (1 if a point is a neighbor, 0 otherwise) and their respective distances. + + ## Examples + + iex> x = Nx.tensor([[1, 2], [2, 4], [1, 3], [2, 5]]) + iex> y = Nx.tensor([1, 0, 1, 1]) + iex> model = Scholar.Neighbors.RNNClassifier.fit(x, y, num_classes: 2) + iex> Scholar.Neighbors.RNNClassifier.radius_neighbors(model, Nx.tensor([[1.9, 4.3], [1.1, 2.0]])) + {#Nx.Tensor< + f32[2][4] + [ + [2.469818353652954, 0.3162313997745514, 1.5811394453048706, 0.7071067690849304], + [0.10000114142894745, 2.1931710243225098, 1.0049877166748047, 3.132091760635376] + ] + >, + #Nx.Tensor< + u8[2][4] + [ + [0, 1, 0, 1], + [1, 0, 0, 0] + ] + >} + """ + defn radius_neighbors(%__MODULE__{metric: metric, radius: radius, data: data}, x) do + distances = metric.(x, data) + {distances, distances <= radius} + end + defnp check_weights(weights) do zero_mask = weights == 0 zero_rows = zero_mask |> Nx.any(axes: [1], keep_axes: true) |> Nx.broadcast(weights) diff --git a/lib/scholar/neighbors/rnn_regressor.ex b/lib/scholar/neighbors/rnn_regressor.ex new file mode 100644 index 00000000..9f704b76 --- /dev/null +++ b/lib/scholar/neighbors/rnn_regressor.ex @@ -0,0 +1,232 @@ +defmodule Scholar.Neighbors.RNNRegressor do + @moduledoc """ + The Radius Nearest Neighbors. + + It implements regression. + """ + import Nx.Defn + require Nx + + @derive {Nx.Container, + keep: [:weights, :num_classes, :metric, :radius], containers: [:data, :labels]} + defstruct [:data, :labels, :weights, :num_classes, :metric, :radius] + + opts = [ + radius: [ + type: {:custom, Scholar.Options, :positive_number, []}, + default: 1.0, + doc: "Radius of neighborhood" + ], + num_classes: [ + type: :pos_integer, + doc: "Number of classes in provided labels" + ], + weights: [ + type: {:in, [:uniform, :distance]}, + default: :uniform, + doc: """ + Weight function used in prediction. Possible values: + + * `:uniform` - uniform weights. All points in each neighborhood are weighted equally. + + * `:distance` - weight points by the inverse of their distance. in this case, closer neighbors of + a query point will have a greater influence than neighbors which are further away. + """ + ], + metric: [ + type: {:custom, Scholar.Neighbors.Utils, :pairwise_metric, []}, + default: &Scholar.Metrics.Distance.pairwise_minkowski/2, + doc: ~S""" + The function that measures the pairwise distance between two points. Possible values: + + * `{:minkowski, p}` - Minkowski metric. By changing the value of `p` parameter (a positive number or `:infinity`) + we can set Manhattan (`1`), Euclidean (`2`), Chebyshev (`:infinity`), or any arbitrary $L_p$ metric. + + * `:cosine` - Cosine metric. + + * Anonymous function of arity 2 that takes two rank-2 tensors. + """ + ] + ] + + @opts_schema NimbleOptions.new!(opts) + + @doc """ + Fit the Radius nearest neighbors classifier from the training data set. + + Currently 2D labels are only supported. + + ## Options + + #{NimbleOptions.docs(@opts_schema)} + + ## Return Values + + The function returns a struct with the following parameters: + + * `:data` - Training data. + + * `:labels` - Labels of each point. + + * `:weights` - Weight function used in prediction. + + * `:num_classes` - Number of classes in provided labels. + + * `:metric` - The metric function used. + + * `:radius` - Radius of neighborhood. + + ## Examples + + iex> x = Nx.tensor([[1, 2], [2, 4], [1, 3], [2, 5]]) + iex> y = Nx.tensor([1, 0, 1, 1]) + iex> Scholar.Neighbors.RNNRegressor.fit(x, y, num_classes: 2) + %Scholar.Neighbors.RNNRegressor{ + data: #Nx.Tensor< + s64[4][2] + [ + [1, 2], + [2, 4], + [1, 3], + [2, 5] + ] + >, + labels: #Nx.Tensor< + s64[4] + [1, 0, 1, 1] + >, + weights: :uniform, + num_classes: 2, + metric: &Scholar.Metrics.Distance.pairwise_minkowski/2, + radius: 1.0 + } + """ + deftransform fit(x, y, opts \\ []) do + if Nx.rank(x) != 2 do + raise ArgumentError, + "expected input tensor to have shape {n_samples, n_features} or {num_samples, num_samples}, + got tensor with shape: #{inspect(Nx.shape(x))}" + end + + if Nx.rank(y) > 2 do + raise ArgumentError, + "expected labels to have shape {num_samples} or {num_samples, num_outputs}, + got tensor with shape: #{inspect(Nx.shape(y))}" + end + + {num_samples, _} = Nx.shape(x) + num_targets = Nx.axis_size(y, 0) + + if num_samples != num_targets do + raise ArgumentError, + "expected labels to have the same size of the first axis as data, + got: #{inspect(num_samples)} != #{inspect(num_targets)}" + end + + opts = NimbleOptions.validate!(opts, @opts_schema) + + %__MODULE__{ + data: x, + labels: y, + weights: opts[:weights], + num_classes: opts[:num_classes], + metric: opts[:metric], + radius: opts[:radius] + } + end + + @doc """ + Makes predictions with the given `model` on inputs `x`. + + ## Return Values + + It returns a tensor with predicted class labels. + + ## Examples + + iex> x = Nx.tensor([[1, 2], [2, 4], [1, 3], [2, 5]]) + iex> y = Nx.tensor([1, 0, 1, 1]) + iex> model = Scholar.Neighbors.RNNRegressor.fit(x, y, num_classes: 2) + iex> Scholar.Neighbors.RNNRegressor.predict(model, Nx.tensor([[1.9, 4.3], [1.1, 2.0]])) + #Nx.Tensor< + f32[2] + [0.5, 1.0] + > + """ + defn predict(%__MODULE__{labels: labels, weights: weights} = model, x) do + {distances, indices} = radius_neighbors(model, x) + + x_num_samples = Nx.axis_size(x, 0) + train_num_samples = Nx.axis_size(labels, 0) + labels_rank = Nx.rank(labels) + + labels = + if labels_rank == 1 do + Nx.new_axis(labels, 0) |> Nx.broadcast({x_num_samples, train_num_samples}) + else + out_size = Nx.axis_size(labels, 1) + Nx.new_axis(labels, 0) |> Nx.broadcast({x_num_samples, train_num_samples, out_size}) + end + + indices = + if labels_rank == 2, + do: Nx.new_axis(indices, -1) |> Nx.broadcast(labels), + else: indices + + case weights do + :distance -> + weights = check_weights(distances) + + weights = + if labels_rank == 2, + do: Nx.new_axis(weights, -1) |> Nx.broadcast(labels), + else: weights + + Nx.weighted_mean(labels, indices * weights, axes: [1]) + + :uniform -> + Nx.weighted_mean(labels, indices, axes: [1]) + end + end + + @doc """ + Find the Radius neighbors of a point. + + ## Return Values + + Returns indices of the selected neighbor points as a mask (1 if a point is a neighbor, 0 otherwise) and their respective distances. + + ## Examples + + iex> x = Nx.tensor([[1, 2], [2, 4], [1, 3], [2, 5]]) + iex> y = Nx.tensor([1, 0, 1, 1]) + iex> model = Scholar.Neighbors.RNNRegressor.fit(x, y, num_classes: 2) + iex> Scholar.Neighbors.RNNRegressor.radius_neighbors(model, Nx.tensor([[1.9, 4.3], [1.1, 2.0]])) + {#Nx.Tensor< + f32[2][4] + [ + [2.469818353652954, 0.3162313997745514, 1.5811394453048706, 0.7071067690849304], + [0.10000114142894745, 2.1931710243225098, 1.0049877166748047, 3.132091760635376] + ] + >, + #Nx.Tensor< + u8[2][4] + [ + [0, 1, 0, 1], + [1, 0, 0, 0] + ] + >} + """ + defn radius_neighbors(%__MODULE__{metric: metric, radius: radius, data: data}, x) do + distances = metric.(x, data) + {distances, distances <= radius} + end + + defnp check_weights(weights) do + zero_mask = weights == 0 + zero_rows = zero_mask |> Nx.any(axes: [1], keep_axes: true) |> Nx.broadcast(weights) + weights = Nx.select(zero_mask, 1, weights) + weights_inv = 1 / weights + Nx.select(zero_rows, Nx.select(zero_mask, 1, 0), weights_inv) + end +end From 42cd80aebf438c9f207f9f9456f41fa52f4b1a71 Mon Sep 17 00:00:00 2001 From: norm4nn Date: Mon, 9 Sep 2024 23:02:30 +0200 Subject: [PATCH 2/4] updated tests --- lib/scholar/cluster/dbscan.ex | 4 +- mix.exs | 3 +- ...hbors_test.exs => rnn_classifier_test.exs} | 130 +++++------------- test/scholar/neighbors/rnn_regressor_test.exs | 118 ++++++++++++++++ 4 files changed, 158 insertions(+), 97 deletions(-) rename test/scholar/neighbors/{radius_nearest_neighbors_test.exs => rnn_classifier_test.exs} (64%) create mode 100644 test/scholar/neighbors/rnn_regressor_test.exs diff --git a/lib/scholar/cluster/dbscan.ex b/lib/scholar/cluster/dbscan.ex index 6bd55ea8..1a76f1d1 100644 --- a/lib/scholar/cluster/dbscan.ex +++ b/lib/scholar/cluster/dbscan.ex @@ -98,14 +98,14 @@ defmodule Scholar.Cluster.DBSCAN do y_dummy = Nx.broadcast(Nx.tensor(0), {num_samples}) neighbor_model = - Scholar.Neighbors.RadiusNearestNeighbors.fit(x, y_dummy, + Scholar.Neighbors.RNNClassifier.fit(x, y_dummy, num_classes: 1, radius: opts[:eps], metric: opts[:metric] ) {_dist, indices} = - Scholar.Neighbors.RadiusNearestNeighbors.radius_neighbors(neighbor_model, x) + Scholar.Neighbors.RNNClassifier.radius_neighbors(neighbor_model, x) n_neighbors = Nx.sum(indices * weights, axes: [1]) core_samples = n_neighbors >= opts[:min_samples] diff --git a/mix.exs b/mix.exs index 439a86cd..bbbebaff 100644 --- a/mix.exs +++ b/mix.exs @@ -96,7 +96,8 @@ defmodule Scholar.MixProject do Scholar.Neighbors.KNNRegressor, Scholar.Neighbors.LargeVis, Scholar.Neighbors.NNDescent, - Scholar.Neighbors.RadiusNearestNeighbors, + Scholar.Neighbors.RNNClassifier, + Scholar.Neighbors.RNNRegressor, Scholar.Neighbors.RandomProjectionForest ], Utilities: [ diff --git a/test/scholar/neighbors/radius_nearest_neighbors_test.exs b/test/scholar/neighbors/rnn_classifier_test.exs similarity index 64% rename from test/scholar/neighbors/radius_nearest_neighbors_test.exs rename to test/scholar/neighbors/rnn_classifier_test.exs index c8c9dd73..806431da 100644 --- a/test/scholar/neighbors/radius_nearest_neighbors_test.exs +++ b/test/scholar/neighbors/rnn_classifier_test.exs @@ -1,7 +1,7 @@ -defmodule Scholar.Neighbors.RadiusNearestNeighborsTest do +defmodule Scholar.Neighbors.RNNClassifierTest do use Scholar.Case, async: true - alias Scholar.Neighbors.RadiusNearestNeighbors - doctest RadiusNearestNeighbors + alias Scholar.Neighbors.RNNClassifier + doctest RNNClassifier defp x do Nx.tensor([ @@ -28,10 +28,9 @@ defmodule Scholar.Neighbors.RadiusNearestNeighborsTest do describe "fit" do test "fit with default parameters - :num_classes set to 2" do - model = RadiusNearestNeighbors.fit(x(), y(), num_classes: 2) + model = RNNClassifier.fit(x(), y(), num_classes: 2) assert model.weights == :uniform - assert model.task == :classification assert model.num_classes == 2 assert model.data == x() assert model.labels == y() @@ -39,111 +38,54 @@ defmodule Scholar.Neighbors.RadiusNearestNeighborsTest do end describe "predict" do - test "predict with default values - classification task" do - model = RadiusNearestNeighbors.fit(x(), y(), num_classes: 2) - predictions = RadiusNearestNeighbors.predict(model, x_pred()) + test "predict with default values" do + model = RNNClassifier.fit(x(), y(), num_classes: 2) + predictions = RNNClassifier.predict(model, x_pred()) assert predictions == Nx.tensor([-1, -1, -1, -1]) end - test "predict with radius set to 10 - classification task" do - model = RadiusNearestNeighbors.fit(x(), y(), num_classes: 2, radius: 10) - predictions = RadiusNearestNeighbors.predict(model, x_pred()) + test "predict with radius set to 10" do + model = RNNClassifier.fit(x(), y(), num_classes: 2, radius: 10) + predictions = RNNClassifier.predict(model, x_pred()) assert predictions == Nx.tensor([1, 1, 1, 1]) end - test "predict with default values - regression task" do - model = RadiusNearestNeighbors.fit(x(), y(), num_classes: 2, radius: 10, task: :regression) - predictions = RadiusNearestNeighbors.predict(model, x_pred()) - assert_all_close(predictions, Nx.tensor([0.7, 0.75, 0.77777778, 0.7])) - end - test "predict with weights set to :distance - classification task" do - model = RadiusNearestNeighbors.fit(x(), y(), num_classes: 2, radius: 10, weights: :distance) + model = RNNClassifier.fit(x(), y(), num_classes: 2, radius: 10, weights: :distance) - predictions = RadiusNearestNeighbors.predict(model, x_pred()) + predictions = RNNClassifier.predict(model, x_pred()) assert predictions == Nx.tensor([1, 1, 1, 1]) end - test "predict with weights set to :distance - regression task" do - model = - RadiusNearestNeighbors.fit(x(), y(), - num_classes: 2, - radius: 10, - task: :regression, - weights: :distance - ) - - predictions = RadiusNearestNeighbors.predict(model, x_pred()) - assert_all_close(predictions, Nx.tensor([0.69033845, 0.71773642, 0.68217609, 0.75918273])) - end - test "predict with weights set to :distance and with specific metric - classification task" do + test "predict with weights set to :distance and with specific metric" do model = - RadiusNearestNeighbors.fit(x(), y(), + RNNClassifier.fit(x(), y(), num_classes: 2, radius: 10, weights: :distance, metric: {:minkowski, 1.5} ) - predictions = RadiusNearestNeighbors.predict(model, x_pred()) + predictions = RNNClassifier.predict(model, x_pred()) assert predictions == Nx.tensor([1, 1, 1, 1]) end - test "predict with weights set to :distance and with specific metric - regression task" do - model = - RadiusNearestNeighbors.fit(x(), y(), - num_classes: 2, - radius: 10, - task: :regression, - weights: :distance, - metric: :cosine - ) - - predictions = RadiusNearestNeighbors.predict(model, x_pred()) - assert_all_close(predictions, Nx.tensor([0.683947, 0.54694187, 0.59806132, 0.86398641])) - end - - test "predict with weights set to :distance and with specific metric and 2d labels - regression task" do - y = - Nx.tensor([[1, 4], [0, 3], [2, 5], [0, 3], [0, 3], [1, 4], [2, 5], [0, 3], [1, 4], [2, 5]]) - - model = - RadiusNearestNeighbors.fit(x(), y, - num_classes: 3, - radius: 10, - task: :regression, - weights: :distance, - metric: :cosine - ) - - predictions = RadiusNearestNeighbors.predict(model, x_pred()) - - assert_all_close( - predictions, - Nx.tensor([ - [0.99475077, 3.99475077], - [1.20828527, 4.20828527], - [1.15227075, 4.15227075], - [0.37743229, 3.37743229] - ]) - ) - end test "predict with weights set to :distance and with x_pred that contains sample with zero-distance" do x_pred = Nx.tensor([[3, 6, 7, 5], [1, 6, 1, 1], [3, 7, 9, 2], [5, 2, 1, 2]]) - model = RadiusNearestNeighbors.fit(x(), y(), num_classes: 2, radius: 10, weights: :distance) + model = RNNClassifier.fit(x(), y(), num_classes: 2, radius: 10, weights: :distance) - predictions = RadiusNearestNeighbors.predict(model, x_pred) + predictions = RNNClassifier.predict(model, x_pred) assert predictions == Nx.tensor([0, 1, 1, 1]) end end describe "predict_proba" do test "predict_proba with default values except radius set to 10" do - model = RadiusNearestNeighbors.fit(x(), y(), num_classes: 2, radius: 10) - {predictions, outliers_mask} = RadiusNearestNeighbors.predict_probability(model, x_pred()) + model = RNNClassifier.fit(x(), y(), num_classes: 2, radius: 10) + {predictions, outliers_mask} = RNNClassifier.predict_probability(model, x_pred()) assert_all_close( predictions, @@ -154,9 +96,9 @@ defmodule Scholar.Neighbors.RadiusNearestNeighborsTest do end test "predict_proba with weights set to :distance" do - model = RadiusNearestNeighbors.fit(x(), y(), num_classes: 2, radius: 10, weights: :distance) + model = RNNClassifier.fit(x(), y(), num_classes: 2, radius: 10, weights: :distance) - {predictions, outliers_mask} = RadiusNearestNeighbors.predict_probability(model, x_pred()) + {predictions, outliers_mask} = RNNClassifier.predict_probability(model, x_pred()) assert_all_close( predictions, @@ -173,14 +115,14 @@ defmodule Scholar.Neighbors.RadiusNearestNeighborsTest do test "predict_proba with weights set to :distance and with specific metric" do model = - RadiusNearestNeighbors.fit(x(), y(), + RNNClassifier.fit(x(), y(), num_classes: 2, radius: 10, weights: :distance, metric: {:minkowski, 1.5} ) - {predictions, outliers_mask} = RadiusNearestNeighbors.predict_probability(model, x_pred()) + {predictions, outliers_mask} = RNNClassifier.predict_probability(model, x_pred()) assert_all_close( predictions, @@ -198,9 +140,9 @@ defmodule Scholar.Neighbors.RadiusNearestNeighborsTest do test "predict_proba with weights set to :distance and with x_pred that contains sample with zero-distance" do x_pred = Nx.tensor([[3, 6, 7, 5], [1, 6, 1, 1], [3, 7, 9, 2], [5, 2, 1, 2]]) - model = RadiusNearestNeighbors.fit(x(), y(), num_classes: 2, radius: 10, weights: :distance) + model = RNNClassifier.fit(x(), y(), num_classes: 2, radius: 10, weights: :distance) - {predictions, outliers_mask} = RadiusNearestNeighbors.predict_probability(model, x_pred) + {predictions, outliers_mask} = RNNClassifier.predict_probability(model, x_pred) assert_all_close( predictions, @@ -218,8 +160,8 @@ defmodule Scholar.Neighbors.RadiusNearestNeighborsTest do describe "radius_neighbors" do test "radius_neighbors with default values except radius set to 10" do - model = RadiusNearestNeighbors.fit(x(), y(), num_classes: 2, radius: 10) - {distances, indices} = RadiusNearestNeighbors.radius_neighbors(model, x_pred()) + model = RNNClassifier.fit(x(), y(), num_classes: 2, radius: 10) + {distances, indices} = RNNClassifier.radius_neighbors(model, x_pred()) assert_all_close( distances, @@ -289,13 +231,13 @@ defmodule Scholar.Neighbors.RadiusNearestNeighborsTest do test "radius_neighbors with specific metric" do model = - RadiusNearestNeighbors.fit(x(), y(), + RNNClassifier.fit(x(), y(), num_classes: 2, radius: 10, metric: {:minkowski, 1.5} ) - {distances, indices} = RadiusNearestNeighbors.radius_neighbors(model, x_pred()) + {distances, indices} = RNNClassifier.radius_neighbors(model, x_pred()) assert_all_close( distances, @@ -373,7 +315,7 @@ defmodule Scholar.Neighbors.RadiusNearestNeighborsTest do "expected input tensor to have shape {n_samples, n_features} or {num_samples, num_samples}, got tensor with shape: {5}", fn -> - RadiusNearestNeighbors.fit(x, y, num_classes: 5) + RNNClassifier.fit(x, y, num_classes: 5) end end @@ -385,7 +327,7 @@ defmodule Scholar.Neighbors.RadiusNearestNeighborsTest do "expected labels to have shape {num_samples} or {num_samples, num_outputs}, got tensor with shape: {1, 1, 5}", fn -> - RadiusNearestNeighbors.fit(x, y, num_classes: 5) + RNNClassifier.fit(x, y, num_classes: 5) end end @@ -397,18 +339,18 @@ defmodule Scholar.Neighbors.RadiusNearestNeighborsTest do "expected labels to have the same size of the first axis as data, got: 6 != 5", fn -> - RadiusNearestNeighbors.fit(x, y, num_classes: 5) + RNNClassifier.fit(x, y, num_classes: 5) end end - test ":num_classes not provided for task :classification" do + test ":num_classes not provided" do x = Nx.tensor([[1], [2], [3], [4], [5], [6]]) y = Nx.tensor([1, 2, 3, 4, 5, 6]) - assert_raise ArgumentError, - "expected :num_classes to be provided for task :classification", + assert_raise NimbleOptions.ValidationError, + "required :num_classes option not found, received options: []", fn -> - RadiusNearestNeighbors.fit(x, y, task: :classification) + RNNClassifier.fit(x, y) end end end diff --git a/test/scholar/neighbors/rnn_regressor_test.exs b/test/scholar/neighbors/rnn_regressor_test.exs new file mode 100644 index 00000000..1f14c993 --- /dev/null +++ b/test/scholar/neighbors/rnn_regressor_test.exs @@ -0,0 +1,118 @@ +defmodule Scholar.Neighbors.RNNRegressorTest do + use Scholar.Case, async: true + alias Scholar.Neighbors.RNNRegressor + doctest RNNRegressor + + defp x do + Nx.tensor([ + [3, 6, 7, 5], + [9, 8, 5, 4], + [4, 4, 4, 1], + [9, 4, 5, 6], + [6, 4, 5, 7], + [4, 5, 3, 3], + [4, 5, 7, 8], + [9, 4, 4, 5], + [8, 4, 3, 9], + [2, 8, 4, 4] + ]) + end + + defp y do + Nx.tensor([0, 1, 1, 1, 1, 1, 1, 1, 0, 0]) + end + + defp x_pred do + Nx.tensor([[4, 3, 8, 4], [1, 6, 1, 1], [3, 7, 9, 2], [5, 2, 1, 2]]) + end + + describe "predict" do + test "predict with weights set to :distance" do + model = + RNNRegressor.fit(x(), y(), + num_classes: 2, + radius: 10, + weights: :distance + ) + + predictions = RNNRegressor.predict(model, x_pred()) + assert_all_close(predictions, Nx.tensor([0.69033845, 0.71773642, 0.68217609, 0.75918273])) + end + + test "predict with weights set to :distance and with specific metric" do + model = + RNNRegressor.fit(x(), y(), + num_classes: 2, + radius: 10, + weights: :distance, + metric: :cosine + ) + + predictions = RNNRegressor.predict(model, x_pred()) + assert_all_close(predictions, Nx.tensor([0.683947, 0.54694187, 0.59806132, 0.86398641])) + end + + test "predict with weights set to :distance and with specific metric and 2d labels" do + y = + Nx.tensor([[1, 4], [0, 3], [2, 5], [0, 3], [0, 3], [1, 4], [2, 5], [0, 3], [1, 4], [2, 5]]) + + model = + RNNRegressor.fit(x(), y, + num_classes: 3, + radius: 10, + weights: :distance, + metric: :cosine + ) + + predictions = RNNRegressor.predict(model, x_pred()) + + assert_all_close( + predictions, + Nx.tensor([ + [0.99475077, 3.99475077], + [1.20828527, 4.20828527], + [1.15227075, 4.15227075], + [0.37743229, 3.37743229] + ]) + ) + end + end + + describe "errors" do + test "wrong shape of x" do + x = Nx.tensor([1, 2, 3, 4, 5]) + y = Nx.tensor([1, 2, 3, 4, 5]) + + assert_raise ArgumentError, + "expected input tensor to have shape {n_samples, n_features} or {num_samples, num_samples}, + got tensor with shape: {5}", + fn -> + RNNRegressor.fit(x, y, num_classes: 5) + end + end + + test "wrong shape of y" do + x = Nx.tensor([[1], [2], [3], [4], [5], [6]]) + y = Nx.tensor([[[1, 2, 3, 4, 5]]]) + + assert_raise ArgumentError, + "expected labels to have shape {num_samples} or {num_samples, num_outputs}, + got tensor with shape: {1, 1, 5}", + fn -> + RNNRegressor.fit(x, y, num_classes: 5) + end + end + + test "incompatible shapes of x and y" do + x = Nx.tensor([[1], [2], [3], [4], [5], [6]]) + y = Nx.tensor([1, 2, 3, 4, 5]) + + assert_raise ArgumentError, + "expected labels to have the same size of the first axis as data, + got: 6 != 5", + fn -> + RNNRegressor.fit(x, y, num_classes: 5) + end + end + end +end From 7a601b36188727fc69fef8241b30b81ed978bae5 Mon Sep 17 00:00:00 2001 From: norm4nn Date: Mon, 9 Sep 2024 23:02:30 +0200 Subject: [PATCH 3/4] updated tests --- lib/scholar/cluster/dbscan.ex | 4 +- mix.exs | 3 +- ...hbors_test.exs => rnn_classifier_test.exs} | 132 +++++------------- test/scholar/neighbors/rnn_regressor_test.exs | 118 ++++++++++++++++ 4 files changed, 158 insertions(+), 99 deletions(-) rename test/scholar/neighbors/{radius_nearest_neighbors_test.exs => rnn_classifier_test.exs} (64%) create mode 100644 test/scholar/neighbors/rnn_regressor_test.exs diff --git a/lib/scholar/cluster/dbscan.ex b/lib/scholar/cluster/dbscan.ex index 6bd55ea8..1a76f1d1 100644 --- a/lib/scholar/cluster/dbscan.ex +++ b/lib/scholar/cluster/dbscan.ex @@ -98,14 +98,14 @@ defmodule Scholar.Cluster.DBSCAN do y_dummy = Nx.broadcast(Nx.tensor(0), {num_samples}) neighbor_model = - Scholar.Neighbors.RadiusNearestNeighbors.fit(x, y_dummy, + Scholar.Neighbors.RNNClassifier.fit(x, y_dummy, num_classes: 1, radius: opts[:eps], metric: opts[:metric] ) {_dist, indices} = - Scholar.Neighbors.RadiusNearestNeighbors.radius_neighbors(neighbor_model, x) + Scholar.Neighbors.RNNClassifier.radius_neighbors(neighbor_model, x) n_neighbors = Nx.sum(indices * weights, axes: [1]) core_samples = n_neighbors >= opts[:min_samples] diff --git a/mix.exs b/mix.exs index 439a86cd..bbbebaff 100644 --- a/mix.exs +++ b/mix.exs @@ -96,7 +96,8 @@ defmodule Scholar.MixProject do Scholar.Neighbors.KNNRegressor, Scholar.Neighbors.LargeVis, Scholar.Neighbors.NNDescent, - Scholar.Neighbors.RadiusNearestNeighbors, + Scholar.Neighbors.RNNClassifier, + Scholar.Neighbors.RNNRegressor, Scholar.Neighbors.RandomProjectionForest ], Utilities: [ diff --git a/test/scholar/neighbors/radius_nearest_neighbors_test.exs b/test/scholar/neighbors/rnn_classifier_test.exs similarity index 64% rename from test/scholar/neighbors/radius_nearest_neighbors_test.exs rename to test/scholar/neighbors/rnn_classifier_test.exs index c8c9dd73..bc9d6474 100644 --- a/test/scholar/neighbors/radius_nearest_neighbors_test.exs +++ b/test/scholar/neighbors/rnn_classifier_test.exs @@ -1,7 +1,7 @@ -defmodule Scholar.Neighbors.RadiusNearestNeighborsTest do +defmodule Scholar.Neighbors.RNNClassifierTest do use Scholar.Case, async: true - alias Scholar.Neighbors.RadiusNearestNeighbors - doctest RadiusNearestNeighbors + alias Scholar.Neighbors.RNNClassifier + doctest RNNClassifier defp x do Nx.tensor([ @@ -28,10 +28,9 @@ defmodule Scholar.Neighbors.RadiusNearestNeighborsTest do describe "fit" do test "fit with default parameters - :num_classes set to 2" do - model = RadiusNearestNeighbors.fit(x(), y(), num_classes: 2) + model = RNNClassifier.fit(x(), y(), num_classes: 2) assert model.weights == :uniform - assert model.task == :classification assert model.num_classes == 2 assert model.data == x() assert model.labels == y() @@ -39,111 +38,52 @@ defmodule Scholar.Neighbors.RadiusNearestNeighborsTest do end describe "predict" do - test "predict with default values - classification task" do - model = RadiusNearestNeighbors.fit(x(), y(), num_classes: 2) - predictions = RadiusNearestNeighbors.predict(model, x_pred()) + test "predict with default values" do + model = RNNClassifier.fit(x(), y(), num_classes: 2) + predictions = RNNClassifier.predict(model, x_pred()) assert predictions == Nx.tensor([-1, -1, -1, -1]) end - test "predict with radius set to 10 - classification task" do - model = RadiusNearestNeighbors.fit(x(), y(), num_classes: 2, radius: 10) - predictions = RadiusNearestNeighbors.predict(model, x_pred()) + test "predict with radius set to 10" do + model = RNNClassifier.fit(x(), y(), num_classes: 2, radius: 10) + predictions = RNNClassifier.predict(model, x_pred()) assert predictions == Nx.tensor([1, 1, 1, 1]) end - test "predict with default values - regression task" do - model = RadiusNearestNeighbors.fit(x(), y(), num_classes: 2, radius: 10, task: :regression) - predictions = RadiusNearestNeighbors.predict(model, x_pred()) - assert_all_close(predictions, Nx.tensor([0.7, 0.75, 0.77777778, 0.7])) - end - test "predict with weights set to :distance - classification task" do - model = RadiusNearestNeighbors.fit(x(), y(), num_classes: 2, radius: 10, weights: :distance) + model = RNNClassifier.fit(x(), y(), num_classes: 2, radius: 10, weights: :distance) - predictions = RadiusNearestNeighbors.predict(model, x_pred()) + predictions = RNNClassifier.predict(model, x_pred()) assert predictions == Nx.tensor([1, 1, 1, 1]) end - test "predict with weights set to :distance - regression task" do - model = - RadiusNearestNeighbors.fit(x(), y(), - num_classes: 2, - radius: 10, - task: :regression, - weights: :distance - ) - - predictions = RadiusNearestNeighbors.predict(model, x_pred()) - assert_all_close(predictions, Nx.tensor([0.69033845, 0.71773642, 0.68217609, 0.75918273])) - end - - test "predict with weights set to :distance and with specific metric - classification task" do + test "predict with weights set to :distance and with specific metric" do model = - RadiusNearestNeighbors.fit(x(), y(), + RNNClassifier.fit(x(), y(), num_classes: 2, radius: 10, weights: :distance, metric: {:minkowski, 1.5} ) - predictions = RadiusNearestNeighbors.predict(model, x_pred()) + predictions = RNNClassifier.predict(model, x_pred()) assert predictions == Nx.tensor([1, 1, 1, 1]) end - test "predict with weights set to :distance and with specific metric - regression task" do - model = - RadiusNearestNeighbors.fit(x(), y(), - num_classes: 2, - radius: 10, - task: :regression, - weights: :distance, - metric: :cosine - ) - - predictions = RadiusNearestNeighbors.predict(model, x_pred()) - assert_all_close(predictions, Nx.tensor([0.683947, 0.54694187, 0.59806132, 0.86398641])) - end - - test "predict with weights set to :distance and with specific metric and 2d labels - regression task" do - y = - Nx.tensor([[1, 4], [0, 3], [2, 5], [0, 3], [0, 3], [1, 4], [2, 5], [0, 3], [1, 4], [2, 5]]) - - model = - RadiusNearestNeighbors.fit(x(), y, - num_classes: 3, - radius: 10, - task: :regression, - weights: :distance, - metric: :cosine - ) - - predictions = RadiusNearestNeighbors.predict(model, x_pred()) - - assert_all_close( - predictions, - Nx.tensor([ - [0.99475077, 3.99475077], - [1.20828527, 4.20828527], - [1.15227075, 4.15227075], - [0.37743229, 3.37743229] - ]) - ) - end - test "predict with weights set to :distance and with x_pred that contains sample with zero-distance" do x_pred = Nx.tensor([[3, 6, 7, 5], [1, 6, 1, 1], [3, 7, 9, 2], [5, 2, 1, 2]]) - model = RadiusNearestNeighbors.fit(x(), y(), num_classes: 2, radius: 10, weights: :distance) + model = RNNClassifier.fit(x(), y(), num_classes: 2, radius: 10, weights: :distance) - predictions = RadiusNearestNeighbors.predict(model, x_pred) + predictions = RNNClassifier.predict(model, x_pred) assert predictions == Nx.tensor([0, 1, 1, 1]) end end describe "predict_proba" do test "predict_proba with default values except radius set to 10" do - model = RadiusNearestNeighbors.fit(x(), y(), num_classes: 2, radius: 10) - {predictions, outliers_mask} = RadiusNearestNeighbors.predict_probability(model, x_pred()) + model = RNNClassifier.fit(x(), y(), num_classes: 2, radius: 10) + {predictions, outliers_mask} = RNNClassifier.predict_probability(model, x_pred()) assert_all_close( predictions, @@ -154,9 +94,9 @@ defmodule Scholar.Neighbors.RadiusNearestNeighborsTest do end test "predict_proba with weights set to :distance" do - model = RadiusNearestNeighbors.fit(x(), y(), num_classes: 2, radius: 10, weights: :distance) + model = RNNClassifier.fit(x(), y(), num_classes: 2, radius: 10, weights: :distance) - {predictions, outliers_mask} = RadiusNearestNeighbors.predict_probability(model, x_pred()) + {predictions, outliers_mask} = RNNClassifier.predict_probability(model, x_pred()) assert_all_close( predictions, @@ -173,14 +113,14 @@ defmodule Scholar.Neighbors.RadiusNearestNeighborsTest do test "predict_proba with weights set to :distance and with specific metric" do model = - RadiusNearestNeighbors.fit(x(), y(), + RNNClassifier.fit(x(), y(), num_classes: 2, radius: 10, weights: :distance, metric: {:minkowski, 1.5} ) - {predictions, outliers_mask} = RadiusNearestNeighbors.predict_probability(model, x_pred()) + {predictions, outliers_mask} = RNNClassifier.predict_probability(model, x_pred()) assert_all_close( predictions, @@ -198,9 +138,9 @@ defmodule Scholar.Neighbors.RadiusNearestNeighborsTest do test "predict_proba with weights set to :distance and with x_pred that contains sample with zero-distance" do x_pred = Nx.tensor([[3, 6, 7, 5], [1, 6, 1, 1], [3, 7, 9, 2], [5, 2, 1, 2]]) - model = RadiusNearestNeighbors.fit(x(), y(), num_classes: 2, radius: 10, weights: :distance) + model = RNNClassifier.fit(x(), y(), num_classes: 2, radius: 10, weights: :distance) - {predictions, outliers_mask} = RadiusNearestNeighbors.predict_probability(model, x_pred) + {predictions, outliers_mask} = RNNClassifier.predict_probability(model, x_pred) assert_all_close( predictions, @@ -218,8 +158,8 @@ defmodule Scholar.Neighbors.RadiusNearestNeighborsTest do describe "radius_neighbors" do test "radius_neighbors with default values except radius set to 10" do - model = RadiusNearestNeighbors.fit(x(), y(), num_classes: 2, radius: 10) - {distances, indices} = RadiusNearestNeighbors.radius_neighbors(model, x_pred()) + model = RNNClassifier.fit(x(), y(), num_classes: 2, radius: 10) + {distances, indices} = RNNClassifier.radius_neighbors(model, x_pred()) assert_all_close( distances, @@ -289,13 +229,13 @@ defmodule Scholar.Neighbors.RadiusNearestNeighborsTest do test "radius_neighbors with specific metric" do model = - RadiusNearestNeighbors.fit(x(), y(), + RNNClassifier.fit(x(), y(), num_classes: 2, radius: 10, metric: {:minkowski, 1.5} ) - {distances, indices} = RadiusNearestNeighbors.radius_neighbors(model, x_pred()) + {distances, indices} = RNNClassifier.radius_neighbors(model, x_pred()) assert_all_close( distances, @@ -373,7 +313,7 @@ defmodule Scholar.Neighbors.RadiusNearestNeighborsTest do "expected input tensor to have shape {n_samples, n_features} or {num_samples, num_samples}, got tensor with shape: {5}", fn -> - RadiusNearestNeighbors.fit(x, y, num_classes: 5) + RNNClassifier.fit(x, y, num_classes: 5) end end @@ -385,7 +325,7 @@ defmodule Scholar.Neighbors.RadiusNearestNeighborsTest do "expected labels to have shape {num_samples} or {num_samples, num_outputs}, got tensor with shape: {1, 1, 5}", fn -> - RadiusNearestNeighbors.fit(x, y, num_classes: 5) + RNNClassifier.fit(x, y, num_classes: 5) end end @@ -397,18 +337,18 @@ defmodule Scholar.Neighbors.RadiusNearestNeighborsTest do "expected labels to have the same size of the first axis as data, got: 6 != 5", fn -> - RadiusNearestNeighbors.fit(x, y, num_classes: 5) + RNNClassifier.fit(x, y, num_classes: 5) end end - test ":num_classes not provided for task :classification" do + test ":num_classes not provided" do x = Nx.tensor([[1], [2], [3], [4], [5], [6]]) y = Nx.tensor([1, 2, 3, 4, 5, 6]) - assert_raise ArgumentError, - "expected :num_classes to be provided for task :classification", + assert_raise NimbleOptions.ValidationError, + "required :num_classes option not found, received options: []", fn -> - RadiusNearestNeighbors.fit(x, y, task: :classification) + RNNClassifier.fit(x, y) end end end diff --git a/test/scholar/neighbors/rnn_regressor_test.exs b/test/scholar/neighbors/rnn_regressor_test.exs new file mode 100644 index 00000000..1f14c993 --- /dev/null +++ b/test/scholar/neighbors/rnn_regressor_test.exs @@ -0,0 +1,118 @@ +defmodule Scholar.Neighbors.RNNRegressorTest do + use Scholar.Case, async: true + alias Scholar.Neighbors.RNNRegressor + doctest RNNRegressor + + defp x do + Nx.tensor([ + [3, 6, 7, 5], + [9, 8, 5, 4], + [4, 4, 4, 1], + [9, 4, 5, 6], + [6, 4, 5, 7], + [4, 5, 3, 3], + [4, 5, 7, 8], + [9, 4, 4, 5], + [8, 4, 3, 9], + [2, 8, 4, 4] + ]) + end + + defp y do + Nx.tensor([0, 1, 1, 1, 1, 1, 1, 1, 0, 0]) + end + + defp x_pred do + Nx.tensor([[4, 3, 8, 4], [1, 6, 1, 1], [3, 7, 9, 2], [5, 2, 1, 2]]) + end + + describe "predict" do + test "predict with weights set to :distance" do + model = + RNNRegressor.fit(x(), y(), + num_classes: 2, + radius: 10, + weights: :distance + ) + + predictions = RNNRegressor.predict(model, x_pred()) + assert_all_close(predictions, Nx.tensor([0.69033845, 0.71773642, 0.68217609, 0.75918273])) + end + + test "predict with weights set to :distance and with specific metric" do + model = + RNNRegressor.fit(x(), y(), + num_classes: 2, + radius: 10, + weights: :distance, + metric: :cosine + ) + + predictions = RNNRegressor.predict(model, x_pred()) + assert_all_close(predictions, Nx.tensor([0.683947, 0.54694187, 0.59806132, 0.86398641])) + end + + test "predict with weights set to :distance and with specific metric and 2d labels" do + y = + Nx.tensor([[1, 4], [0, 3], [2, 5], [0, 3], [0, 3], [1, 4], [2, 5], [0, 3], [1, 4], [2, 5]]) + + model = + RNNRegressor.fit(x(), y, + num_classes: 3, + radius: 10, + weights: :distance, + metric: :cosine + ) + + predictions = RNNRegressor.predict(model, x_pred()) + + assert_all_close( + predictions, + Nx.tensor([ + [0.99475077, 3.99475077], + [1.20828527, 4.20828527], + [1.15227075, 4.15227075], + [0.37743229, 3.37743229] + ]) + ) + end + end + + describe "errors" do + test "wrong shape of x" do + x = Nx.tensor([1, 2, 3, 4, 5]) + y = Nx.tensor([1, 2, 3, 4, 5]) + + assert_raise ArgumentError, + "expected input tensor to have shape {n_samples, n_features} or {num_samples, num_samples}, + got tensor with shape: {5}", + fn -> + RNNRegressor.fit(x, y, num_classes: 5) + end + end + + test "wrong shape of y" do + x = Nx.tensor([[1], [2], [3], [4], [5], [6]]) + y = Nx.tensor([[[1, 2, 3, 4, 5]]]) + + assert_raise ArgumentError, + "expected labels to have shape {num_samples} or {num_samples, num_outputs}, + got tensor with shape: {1, 1, 5}", + fn -> + RNNRegressor.fit(x, y, num_classes: 5) + end + end + + test "incompatible shapes of x and y" do + x = Nx.tensor([[1], [2], [3], [4], [5], [6]]) + y = Nx.tensor([1, 2, 3, 4, 5]) + + assert_raise ArgumentError, + "expected labels to have the same size of the first axis as data, + got: 6 != 5", + fn -> + RNNRegressor.fit(x, y, num_classes: 5) + end + end + end +end From 51099fecc61401cab433ace259597d2d7483cfcc Mon Sep 17 00:00:00 2001 From: norm4nn Date: Tue, 10 Sep 2024 16:40:13 +0200 Subject: [PATCH 4/4] Fixed syntax in doctests --- lib/scholar/neighbors/rnn_classifier.ex | 3 +++ lib/scholar/neighbors/rnn_regressor.ex | 2 ++ 2 files changed, 5 insertions(+) diff --git a/lib/scholar/neighbors/rnn_classifier.ex b/lib/scholar/neighbors/rnn_classifier.ex index 791d4ef6..fc22e86a 100644 --- a/lib/scholar/neighbors/rnn_classifier.ex +++ b/lib/scholar/neighbors/rnn_classifier.ex @@ -84,6 +84,7 @@ defmodule Scholar.Neighbors.RNNClassifier do iex> x = Nx.tensor([[1, 2], [2, 4], [1, 3], [2, 5]]) iex> y = Nx.tensor([1, 0, 1, 1]) iex> Scholar.Neighbors.RNNClassifier.fit(x, y, num_classes: 2) + %Scholar.Neighbors.RNNClassifier{ data: #Nx.Tensor< s64[4][2] @@ -177,6 +178,7 @@ defmodule Scholar.Neighbors.RNNClassifier do iex> y = Nx.tensor([1, 0, 1, 1]) iex> model = Scholar.Neighbors.RNNClassifier.fit(x, y, num_classes: 2) iex> Scholar.Neighbors.RNNClassifier.predict_probability(model, Nx.tensor([[1.9, 4.3], [1.1, 2.0]])) + {#Nx.Tensor< f32[2][2] [ @@ -236,6 +238,7 @@ defmodule Scholar.Neighbors.RNNClassifier do iex> y = Nx.tensor([1, 0, 1, 1]) iex> model = Scholar.Neighbors.RNNClassifier.fit(x, y, num_classes: 2) iex> Scholar.Neighbors.RNNClassifier.radius_neighbors(model, Nx.tensor([[1.9, 4.3], [1.1, 2.0]])) + {#Nx.Tensor< f32[2][4] [ diff --git a/lib/scholar/neighbors/rnn_regressor.ex b/lib/scholar/neighbors/rnn_regressor.ex index 9f704b76..e285f353 100644 --- a/lib/scholar/neighbors/rnn_regressor.ex +++ b/lib/scholar/neighbors/rnn_regressor.ex @@ -81,6 +81,7 @@ defmodule Scholar.Neighbors.RNNRegressor do iex> x = Nx.tensor([[1, 2], [2, 4], [1, 3], [2, 5]]) iex> y = Nx.tensor([1, 0, 1, 1]) iex> Scholar.Neighbors.RNNRegressor.fit(x, y, num_classes: 2) + %Scholar.Neighbors.RNNRegressor{ data: #Nx.Tensor< s64[4][2] @@ -202,6 +203,7 @@ defmodule Scholar.Neighbors.RNNRegressor do iex> y = Nx.tensor([1, 0, 1, 1]) iex> model = Scholar.Neighbors.RNNRegressor.fit(x, y, num_classes: 2) iex> Scholar.Neighbors.RNNRegressor.radius_neighbors(model, Nx.tensor([[1.9, 4.3], [1.1, 2.0]])) + {#Nx.Tensor< f32[2][4] [