Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 53 additions & 5 deletions tests/test_visualisation.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,64 @@ def test_core_element_plot_works_labelled_and_unlabelled(seed, labelled):
assert isinstance(ax, matplotlib.axes.Axes)


@pytest.mark.parametrize("weight_scale", [1, 100, 0.01])
@pytest.mark.parametrize("normalised", [True, False])
def test_core_element_plot_normalised_flag(seed, normalised):
def test_core_element_has_correct_title(seed, normalised, weight_scale):
"""This test checks that the title of the core element plot is correct.

There are six scenarios to test (CC=Core consistency):

1. CC = 100, normalised=True
2. CC = 100, normalised=False
3. 0 <= CC < 100, normalised=True
4. 0 <= CC < 100, normalised=False
5. CC < 0, normalised=True
6. CC < 0, normalised=False

To test both scenarios when CC = 100, we have a noise-free dataset and the correct
decomposition.

To test scenario 3, we need that :math:`0 < \|G - T\| \leq \|G\|`. To accomplish this, can
have large entries in :math:`G` that point in the "same direction" as :math:`T`. Similarly, to
test scenario 4, we need that :math:`0 < \|G - T\| \geq R`, which we can accomplish by having
small entries in :math:`G` that point in the "same direction" as :math:`T`.

To test scenario 5, we need that :math:`\|G - T\| > \|G\|`, which we can accomplish by having
small entries in :math:`G`. To test scenario 6, we need that :math:`\|G - T\| > R`, which we
can accomplish by having large entries in :math:`G`.

We can, in other words, create decompositions that cover all 6 above scenarios so long as we
can create decompositions whose optimal core tensor "points in the same direction" as the
superdiagonal tensor consisting only ones and whose optimal core tensor's magnitude we can
control. Luckily, we can control this by only scaling the weights by positive number. Then,
the optimal core tensor will be a superdiagonal tensor whose elements are the reciprocal of
the scaling coefficient.
"""
rank = 3
cp_tensor, X = simulated_random_cp_tensor((10, 20, 30), rank, noise_level=0.2, seed=seed)
# If not normalised
cp_tensor, X = simulated_random_cp_tensor((10, 20, 30), rank, noise_level=0.0, seed=seed)
cp_tensor[0][:] *= weight_scale

ax = visualisation.core_element_plot(cp_tensor, X, normalised=normalised)
title = ax.get_title()
title_core_consistency = float(title.split(": ")[1])
core_consistency = model_evaluation.core_consistency(cp_tensor, X, normalised=normalised)
assert title_core_consistency == pytest.approx(core_consistency, abs=0.1)

# Case 5 and 6, respectively:
if (weight_scale > 1 and normalised) or (weight_scale < 1 and not normalised):
assert title.split(": ")[1] == "<0"
else:
title_core_consistency = float(title.split(": ")[1])
assert title_core_consistency == pytest.approx(core_consistency, abs=0.1)


@pytest.mark.parametrize("weight_scale", [1, 100, 0.01])
@pytest.mark.parametrize("normalised", [True, False])
def test_core_element_has_core_element_scatter_points(seed, normalised, weight_scale):
rank = 3
cp_tensor, X = simulated_random_cp_tensor((10, 20, 30), rank, noise_level=0.1, seed=seed)
cp_tensor[0][:] *= weight_scale

ax = visualisation.core_element_plot(cp_tensor, X, normalised=normalised)
core_consistency = model_evaluation.core_consistency(cp_tensor, X, normalised=normalised)

superdiag_x, superdiag_y = ax.lines[-2].get_data()
offdiag_x, offdiag_y = ax.lines[-1].get_data()
Expand Down
6 changes: 3 additions & 3 deletions tlviz/model_evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,15 +334,15 @@ def predictive_power(cp_tensor, y, sklearn_estimator, mode=0, metric=None, axis=
>>> from tlviz.data import simulated_random_cp_tensor
>>> import numpy as np
>>> rng = np.random.default_rng(0)
>>> cp_tensor, X = simulated_random_cp_tensor((30, 10, 10), 5, noise_level=0.3, seed=rng)
>>> cp_tensor, X = simulated_random_cp_tensor((30, 10, 10), 3, noise_level=0.1, seed=rng)
>>> weights, (A, B, C) = cp_tensor
>>> regression_coefficients = rng.standard_normal((5, 1))
>>> regression_coefficients = rng.standard_normal((3, 1))
>>> Y = A @ regression_coefficients

Next, we fit a PARAFAC model to this data

>>> from tensorly.decomposition import parafac
>>> est_cp_tensor = parafac(X, 5)
>>> est_cp_tensor = parafac(X, 3)

Finally, we see how well the estimated decomposition can describe our target variable, ``Y``.
This will use the :math:`R^2`-coefficient for scoring, as that is the default scoring method
Expand Down