Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[add:lib] Add trailing underscore to trainable parameters #158

Merged
merged 1 commit into from
Jan 24, 2021
Merged
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
87 changes: 45 additions & 42 deletions lib/sequentia/classifiers/hmm/gmmhmm.py
Original file line number Diff line number Diff line change
@@ -30,37 +30,40 @@ class GMMHMM:

Attributes
----------
label: str or numeric
label (property): str or numeric
The label for the model.

n_states: int
model (property): hmmlearn.hmm.GMMHMM
The underlying GMMHMM model from `hmmlearn <https://hmmlearn.readthedocs.io/en/latest/api.html#gmmhmm>`_.

n_states (property): int
The number of states for the model.

n_components: int
n_components (property): int
The number of mixture components used in the emission distribution for each state.

covariance_type: str
covariance_type (property): str
The covariance matrix type for emission distributions.

frozen: set (str)
frozen (property): set (str)
The frozen parameters of the HMM or its GMM emission distributions (see :func:`freeze`).

n_seqs: int
n_seqs_ (property): int
The number of observation sequences used to train the model.

initial: numpy.ndarray (float)
initial_ (property/setter): numpy.ndarray (float)
The initial state distribution of the model.

transitions: numpy.ndarray (float)
transitions_ (property/setter): numpy.ndarray (float)
The transition matrix of the model.

weights: numpy.ndarray (float)
weights_ (property): numpy.ndarray (float)
The mixture weights of the GMM emission distributions.

means: numpy.ndarray (float)
means_ (property): numpy.ndarray (float)
The mean vectors of the GMM emission distributions.

covars: numpy.ndarray (float)
covars_ (property): numpy.ndarray (float)
The covariance matrices of the GMM emission distributions.
"""

@@ -84,27 +87,27 @@ def __init__(self, label, n_states, n_components=1, covariance_type='full', topo

def set_uniform_initial(self):
"""Sets a uniform initial state distribution :math:`\\boldsymbol{\\pi}=(\\pi_1,\\pi_2,\\ldots,\\pi_M)` where :math:`\\pi_i=1/M\\quad\\forall i`."""
self._initial = self._topology.uniform_initial()
self._initial_ = self._topology.uniform_initial()

def set_random_initial(self):
"""Sets a random initial state distribution by sampling :math:`\\boldsymbol{\\pi}\\sim\\mathrm{Dir}(\\mathbf{1}_M)` where

- :math:`\\boldsymbol{\\pi}=(\\pi_1,\\pi_2,\\ldots,\\pi_M)` are the initial state probabilities for each state,
- :math:`\\mathbf{1}_M` is a vector of :math:`M` ones which are used as the concentration parameters for the Dirichlet distribution.
"""
self._initial = self._topology.random_initial()
self._initial_ = self._topology.random_initial()

def set_uniform_transitions(self):
"""Sets a uniform transition matrix according to the topology, so that given the HMM is in state :math:`i`,
all permissible transitions (i.e. such that :math:`p_{ij}\\neq0`) :math:`\\forall j` are equally probable."""
self._transitions = self._topology.uniform_transitions()
self._transitions_ = self._topology.uniform_transitions()

def set_random_transitions(self):
"""Sets a random transition matrix according to the topology, so that given the HMM is in state :math:`i`,
all out-going transition probabilities :math:`\\mathbf{p}_i=(p_{i1},p_{i2},\\ldots,p_{iM})` from state :math:`i`
are generated by sampling :math:`\\mathbf{p}_i\\sim\\mathrm{Dir}(\\mathbf{1})` with a vector of ones of appropriate
size used as concentration parameters, so that only transitions permitted by the topology are non-zero."""
self._transitions = self._topology.random_transitions()
self._transitions_ = self._topology.random_transitions()

def fit(self, X):
"""Fits the HMM to observation sequences assumed to be labeled as the class that the model represents.
@@ -118,12 +121,12 @@ def fit(self, X):
X = self._val.observation_sequences(X)

try:
(self._initial, self._transitions)
(self._initial_, self._transitions_)
except AttributeError as e:
raise AttributeError('Must specify initial state distribution and transitions before the HMM can be fitted') from e

self._n_seqs = len(X)
self._n_features = X[0].shape[1]
self._n_seqs_ = len(X)
self._n_features_ = X[0].shape[1]

# Initialize the GMMHMM with the specified initial state distribution and transition matrix
self._model = hmmlearn.hmm.GMMHMM(
@@ -134,13 +137,13 @@ def fit(self, X):
init_params='mcw', # only initialize means, covariances and mixture weights
params=(set('stmcw') - self._frozen)
)
self._model.startprob_, self._model.transmat_ = self._initial, self._transitions
self._model.startprob_, self._model.transmat_ = self._initial_, self._transitions_

# Perform the Baum-Welch algorithm to fit the model to the observations
self._model.fit(np.vstack(X), [len(x) for x in X])

# Update the initial state distribution and transitions to reflect the updated parameters
self._initial, self._transitions = self._model.startprob_, self._model.transmat_
self._initial_, self._transitions_ = self._model.startprob_, self._model.transmat_

def forward(self, x):
"""Runs the forward algorithm to calculate the (log) likelihood of the model generating an observation sequence.
@@ -163,7 +166,7 @@ def forward(self, x):
raise AttributeError('The model must be fitted before running the forward algorithm') from e

x = self._val.observation_sequences(x, allow_single=True)
if not x.shape[1] == self._n_features:
if not x.shape[1] == self._n_features_:
raise ValueError('Number of observation features must match the dimensionality of the original data used to fit the model')

return self._model.score(x, lengths=None)
@@ -243,9 +246,9 @@ def covariance_type(self):
return self._covariance_type

@property
def n_seqs(self):
def n_seqs_(self):
try:
return self._n_seqs
return self._n_seqs_
except AttributeError as e:
raise AttributeError('The model has not been fitted and has not seen any observation sequences') from e

@@ -261,45 +264,45 @@ def model(self):
raise AttributeError('The model must be fitted first') from e

@property
def initial(self):
def initial_(self):
try:
return self._initial
return self._initial_
except AttributeError as e:
raise AttributeError('No initial state distribution has been defined') from e

@initial.setter
def initial(self, probabilities):
@initial_.setter
def initial_(self, probabilities):
self._topology.validate_initial(probabilities)
self._initial = probabilities
self._initial_ = probabilities

@property
def transitions(self):
def transitions_(self):
try:
return self._transitions
return self._transitions_
except AttributeError as e:
raise AttributeError('No transition matrix has been defined') from e

@transitions.setter
def transitions(self, probabilities):
@transitions_.setter
def transitions_(self, probabilities):
self._topology.validate_transitions(probabilities)
self._transitions = probabilities
self._transitions_ = probabilities

@property
def weights(self):
def weights_(self):
try:
return self._model.weights_
except AttributeError as e:
raise AttributeError('The model must be fitted first') from e

@property
def means(self):
def means_(self):
try:
return self._model.means_
except AttributeError as e:
raise AttributeError('The model must be fitted first') from e

@property
def covars(self):
def covars_(self):
try:
return self._model.covars_
except AttributeError as e:
@@ -317,17 +320,17 @@ def __repr__(self):
]
try:
self._n_seqs
attrs.append(('n_seqs', repr(self._n_seqs)))
attrs.append(('n_seqs_', repr(self._n_seqs)))
self._initial
attrs.append(('initial', 'array([...])'))
attrs.append(('initial_', 'array([...])'))
self._transitions
attrs.append(('transitions', 'array([...])'))
attrs.append(('transitions_', 'array([...])'))
self.weights
attrs.append(('weights', 'array([...])'))
attrs.append(('weights_', 'array([...])'))
self.means
attrs.append(('means', 'array([...])'))
attrs.append(('means_', 'array([...])'))
self.covars
attrs.append(('covars', 'array([...])'))
attrs.append(('covars_', 'array([...])'))
except AttributeError:
pass
return out + ', '.join('{}={}'.format(name, val) for name, val in attrs) + ')'
48 changes: 24 additions & 24 deletions lib/sequentia/classifiers/hmm/hmm_classifier.py
Original file line number Diff line number Diff line change
@@ -11,13 +11,13 @@ class HMMClassifier:

Attributes
----------
models: list of GMMHMM
models_ (property): list of GMMHMM
A collection of the :class:`~GMMHMM` objects to use for classification.

encoder: sklearn.preprocessing.LabelEncoder
encoder_ (property): sklearn.preprocessing.LabelEncoder
The label encoder fitted on the set of ``classes`` provided during instantiation.

classes: numpy.ndarray (str/numeric)
classes_ (property): numpy.ndarray (str/numeric)
The complete set of possible classes/labels.
"""

@@ -38,12 +38,12 @@ def fit(self, models):
raise TypeError('Expected all models to be GMMHMM objects')

if len(models) > 0:
self._models = models
self._models_ = models
else:
raise RuntimeError('The classifier must be fitted with at least one HMM')

self._encoder = LabelEncoder()
self._encoder.fit([model.label for model in models])
self._encoder_ = LabelEncoder()
self._encoder_.fit([model.label for model in models])

def predict(self, X, prior='frequency', return_scores=False, original_labels=True, verbose=True, n_jobs=1):
"""Predicts the label for an observation sequence (or multiple sequences) according to maximum likelihood or posterior scores.
@@ -92,14 +92,14 @@ def predict(self, X, prior='frequency', return_scores=False, original_labels=Tru
for each of the :math:`M` HMMs. Only returned if ``return_scores`` is true.
"""
try:
self._models
self._models_
except AttributeError as e:
raise AttributeError('The classifier needs to be fitted before predictions are made') from e

X = self._val.observation_sequences(X, allow_single=True)
if not isinstance(prior, str):
self._val.iterable(prior, 'prior')
assert len(prior) == len(self._models), 'There must be a class prior for each HMM or class'
assert len(prior) == len(self._models_), 'There must be a class prior for each HMM or class'
assert all(isinstance(p, (int, float)) for p in prior), 'Class priors must be numerical'
assert all(0. <= p <= 1. for p in prior), 'Class priors must each be between zero and one'
assert np.isclose(sum(prior), 1.), 'Class priors must form a probability distribution by summing to one'
@@ -112,18 +112,18 @@ def predict(self, X, prior='frequency', return_scores=False, original_labels=Tru

# Create look-up for prior probabilities
if prior == 'frequency':
total_seqs = sum(model.n_seqs for model in self._models)
prior = {model.label:(model.n_seqs / total_seqs) for model in self._models}
total_seqs = sum(model.n_seqs_ for model in self._models_)
prior = {model.label:(model.n_seqs_ / total_seqs) for model in self._models_}
elif prior == 'uniform':
prior = {model.label:(1. / len(self._models)) for model in self._models}
prior = {model.label:(1. / len(self._models_)) for model in self._models_}
else:
prior = {model.label:prior[self._encoder.transform([model.label]).item()] for model in self._models}
prior = {model.label:prior[self._encoder_.transform([model.label]).item()] for model in self._models_}

# Convert single observation sequence to a singleton list
X = [X] if isinstance(X, np.ndarray) else X

# Lambda for calculating the log un-normalized posteriors as a sum of the log forward probabilities (likelihoods) and log priors
posteriors = lambda x: np.array([model.forward(x) + np.log(prior[model.label]) for model in self._models])
posteriors = lambda x: np.array([model.forward(x) + np.log(prior[model.label]) for model in self._models_])

# Calculate log un-normalized posteriors as a sum of the log forward probabilities (likelihoods) and log priors
# Perform the MAP classification rule and return labels to original encoding if necessary
@@ -132,7 +132,7 @@ def predict(self, X, prior='frequency', return_scores=False, original_labels=Tru
scores = Parallel(n_jobs=n_jobs)(delayed(self._chunk_predict)(i+1, posteriors, chunk, verbose) for i, chunk in enumerate(X_chunks))
scores = np.concatenate(scores)
best_idxs = np.atleast_1d(scores.argmax(axis=1))
labels = self._encoder.inverse_transform(best_idxs) if original_labels else best_idxs
labels = self._encoder_.inverse_transform(best_idxs) if original_labels else best_idxs

if len(X) == 1:
return (labels.item(), scores.flatten()) if return_scores else labels.item()
@@ -181,7 +181,7 @@ def evaluate(self, X, y, prior='frequency', verbose=True, n_jobs=1):
"""
X, y = self._val.observation_sequences_and_labels(X, y)
predictions = self.predict(X, prior=prior, return_scores=False, original_labels=False, verbose=verbose, n_jobs=n_jobs)
cm = confusion_matrix(self._encoder.transform(y), predictions, labels=self._encoder.transform(self._encoder.classes_))
cm = confusion_matrix(self._encoder_.transform(y), predictions, labels=self._encoder_.transform(self._encoder_.classes_))
return np.sum(np.diag(cm)) / np.sum(cm), cm

def save(self, path):
@@ -193,7 +193,7 @@ def save(self, path):
File path (usually with `.pkl` extension) to store the serialized :class:`HMMClassifier` object.
"""
try:
self._models
self._models_
except AttributeError:
raise RuntimeError('The classifier needs to be fitted before it can be saved')

@@ -225,30 +225,30 @@ def _chunk_predict(self, process, posteriors, chunk, verbose): # Requires fit
)])

@property
def models(self):
def models_(self):
try:
return self._models
return self._models_
except AttributeError as e:
raise AttributeError('No models available - the classifier must be fitted first') from e

@property
def encoder(self):
def encoder_(self):
try:
return self._encoder
return self._encoder_
except AttributeError as e:
raise AttributeError('No label encoder has been defined - the classifier must be fitted first') from e

@property
def classes(self):
return self.encoder.classes_
def classes_(self):
return self._encoder.classes_

def __repr__(self):
module = self.__class__.__module__
out = '{}{}('.format('' if module == '__main__' else '{}.'.format(module), self.__class__.__name__)
try:
self._models
self._models_
out += 'models=[\n '
out += ',\n '.join(repr(model) for model in self._models)
out += ',\n '.join(repr(model) for model in self._models_)
out += '\n]'
except AttributeError:
pass
74 changes: 40 additions & 34 deletions lib/sequentia/classifiers/knn/knn_classifier.py
Original file line number Diff line number Diff line change
@@ -68,23 +68,29 @@ class KNNClassifier:
Attributes
----------
k: int > 0
k (property): int > 0
The number of neighbors.
weighting: callable
weighting (property): callable
The distance weighting function.
window: 0 ≤ float ≤ 1
window (property): 0 ≤ float ≤ 1
The width of the Sakoe-Chiba band global constraint as a fraction of the length of the longest of the two sequences.
use_c: bool
use_c (property): bool
Whether or not to use fast pure C compiled functions to perform the DTW computations.
encoder: sklearn.preprocessing.LabelEncoder
encoder_ (property): sklearn.preprocessing.LabelEncoder
The label encoder fitted on the set of ``classes`` provided during instantiation.
classes: numpy.ndarray (str/numeric)
classes_ (property): numpy.ndarray (str/numeric)
The complete set of possible classes/labels.
X_ (property): list of numpy.ndarray (float)
A list of multiple observation sequences used to fit the classifier.
y_ (property): numpy.ndarray (int)
The encoded labels for the observation sequences used to fit the classifier.
"""

def __init__(self, k, classes, weighting='uniform', window=1., use_c=False, independent=False, random_state=None):
@@ -98,7 +104,7 @@ def __init__(self, k, classes, weighting='uniform', window=1., use_c=False, inde
self._val.iterable(classes, 'classes')
self._val.string_or_numeric(classes[0], 'each class')
if all(isinstance(label, type(classes[0])) for label in classes[1:]):
self._encoder = LabelEncoder().fit(classes)
self._encoder_ = LabelEncoder().fit(classes)
else:
raise TypeError('Expected all classes to be of the same type')

@@ -134,8 +140,8 @@ def fit(self, X, y):
An iterable of labels for the observation sequences.
"""
X, y = self._val.observation_sequences_and_labels(X, y)
self._X, self._y = X, self._encoder.transform(y)
self._n_features = X[0].shape[1]
self._X_, self._y_ = X, self._encoder_.transform(y)
self._n_features_ = X[0].shape[1]

def predict(self, X, original_labels=True, verbose=True, n_jobs=1):
"""Predicts the label for an observation sequence (or multiple sequences).
@@ -169,7 +175,7 @@ def predict(self, X, original_labels=True, verbose=True, n_jobs=1):
inverse-transformed into their original encoding.
"""
try:
(self._X, self._y)
(self._X_, self._y_)
except AttributeError:
raise RuntimeError('The classifier needs to be fitted before predictions are made')

@@ -179,7 +185,7 @@ def predict(self, X, original_labels=True, verbose=True, n_jobs=1):
self._val.restricted_integer(n_jobs, lambda x: x == -1 or x > 0, 'number of jobs', '-1 or greater than zero')

if isinstance(X, np.ndarray):
distances = np.array([self._dtw(X, x) for x in tqdm.auto.tqdm(self._X, desc='Calculating distances', disable=not(verbose))])
distances = np.array([self._dtw(X, x) for x in tqdm.auto.tqdm(self._X_, desc='Calculating distances', disable=not(verbose))])
return self._output(self._find_nearest(distances), original_labels)
else:
n_jobs = min(cpu_count() if n_jobs == -1 else n_jobs, len(X))
@@ -216,7 +222,7 @@ def evaluate(self, X, y, verbose=True, n_jobs=1):
X, y = self._val.observation_sequences_and_labels(X, y)
self._val.boolean(verbose, desc='verbose')
predictions = self.predict(X, original_labels=False, verbose=verbose, n_jobs=n_jobs)
cm = confusion_matrix(self._encoder.transform(y), predictions, labels=self._encoder.transform(self._encoder.classes_))
cm = confusion_matrix(self._encoder_.transform(y), predictions, labels=self._encoder_.transform(self._encoder_.classes_))
return np.sum(np.diag(cm)) / np.sum(cm), cm

def save(self, path):
@@ -232,24 +238,24 @@ def save(self, path):
File path (usually with `.pkl` extension) to store the serialized :class:`KNNClassifier` object.
"""
try:
(self._X, self._y)
(self._X_, self._y_)
except AttributeError:
raise RuntimeError('The classifier needs to be fitted before it can be saved')

# Pickle the necessary hyper-parameters, variables and data
with open(path, 'wb') as file:
pickle.dump({
'k': self._k,
'classes': self._encoder.classes_,
'classes': self._encoder_.classes_,
# Serialize the weighting function into a byte-string
'weighting': marshal.dumps((self._weighting.__code__, self._weighting.__name__)),
'window': self._window,
'use_c': self._use_c,
'independent': self._independent,
'random_state': self._random_state,
'X': self._X,
'y': self._y,
'n_features': self._n_features
'X': self._X_,
'y': self._y_,
'n_features': self._n_features_
}, file)

@classmethod
@@ -293,8 +299,8 @@ def load(cls, path):
)

# Load the data directly
clf._X, clf._y = data['X'], data['y']
clf._n_features = data['n_features']
clf._X_, clf._y_ = data['X'], data['y']
clf._n_features_ = data['n_features']

return clf

@@ -305,7 +311,7 @@ def _dtw_1d(self, a, b, window): # Requires fit
def _dtwi(self, A, B): # Requires fit
"""Computes the multivariate DTW distance as the sum of the pairwise per-feature DTW distances, allowing each feature to be warped independently."""
window = max(1, int(self._window * max(len(A), len(B))))
return np.sum([self._dtw_1d(A[:, i], B[:, i], window=window) for i in range(self._n_features)])
return np.sum([self._dtw_1d(A[:, i], B[:, i], window=window) for i in range(self._n_features_)])

def _dtwd(self, A, B): # Requires fit
"""Computes the multivariate DTW distance so that the warping of the features depends on each other, by modifying the local distance measure."""
@@ -330,7 +336,7 @@ def _find_nearest(self, distances): # Requires fit
"""
# Find the indices, labels and distances of the k-nearest neighbours
idx = np.argpartition(distances, self._k)[:self._k]
nearest_labels = self._y[idx]
nearest_labels = self._y_[idx]
nearest_distances = self._weighting(distances[idx])
# Combine labels and distances into one array and sort by label
labels_distances = np.vstack((nearest_labels, nearest_distances))
@@ -352,16 +358,16 @@ def _chunk_predict(self, process, chunk, verbose): # Requires fit
desc='Classifying examples (process {})'.format(process),
disable=not(verbose), position=process-1)
):
distances = np.array([self._dtw(sequence, x) for x in self._X])
distances = np.array([self._dtw(sequence, x) for x in self._X_])
labels[i] = self._find_nearest(distances)
return labels

def _output(self, out, original_labels):
"""Inverse-transforms the labels if necessary, and returns them."""
if isinstance(out, np.ndarray):
return self._encoder.inverse_transform(out) if original_labels else out
return self._encoder_.inverse_transform(out) if original_labels else out
else:
return self._encoder.inverse_transform([out]).item() if original_labels else out
return self._encoder_.inverse_transform([out]).item() if original_labels else out

@property
def k(self):
@@ -380,24 +386,24 @@ def use_c(self):
return self._use_c

@property
def encoder(self):
return self._encoder
def encoder_(self):
return self._encoder_

@property
def classes(self):
return self._encoder.classes_
def classes_(self):
return self._encoder_.classes_

@property
def X(self):
def X_(self):
try:
return self._X
return self._X_
except AttributeError:
raise RuntimeError('The classifier needs to be fitted first')

@property
def y(self):
def y_(self):
try:
return self._y
return self._y_
except AttributeError:
raise RuntimeError('The classifier needs to be fitted first')

@@ -409,10 +415,10 @@ def __repr__(self):
('window', repr(self._window)),
('use_c', repr(self._use_c)),
('independent', repr(self._independent)),
('classes', repr(list(self._encoder.classes_)))
('classes', repr(list(self._encoder_.classes_)))
]
try:
(self._X, self._y)
(self._X_, self._y_)
attrs.extend([('X', '[...]'), ('y', 'array([...])')])
except AttributeError:
pass
304 changes: 152 additions & 152 deletions lib/test/lib/classifiers/hmm/test_gmmhmm.py

Large diffs are not rendered by default.

9 changes: 4 additions & 5 deletions lib/test/lib/classifiers/hmm/test_hmm_classifier.py
Original file line number Diff line number Diff line change
@@ -37,7 +37,7 @@ def test_fit_list():
"""Fit the classifier using a list of HMMs"""
clf = HMMClassifier()
clf.fit(hmms)
assert clf._models == hmms
assert clf.models_ == hmms

def test_fit_list_empty():
"""Fit the classifier using an empty list"""
@@ -145,7 +145,6 @@ def test_evaluate():
"""Evaluate performance on some observation sequences and labels"""
acc, cm = hmm_clf.evaluate(X, Y, prior='frequency')
assert acc == 1 / 3
print(repr(cm))
assert_equal(cm, np.array([
[1, 0, 0, 0, 0],
[2, 0, 0, 0, 0],
@@ -197,9 +196,9 @@ def test_load_valid():
clf = HMMClassifier.load('test.pkl')
# Check that all fields are still the same
assert isinstance(clf, HMMClassifier)
assert all(isinstance(model, GMMHMM) for model in clf._models)
assert [model.label for model in clf._models] == labels
assert list(clf._encoder.classes_) == labels
assert all(isinstance(model, GMMHMM) for model in clf.models_)
assert [model.label for model in clf.models_] == labels
assert list(clf.encoder_.classes_) == labels
predictions = clf.predict(X, prior='frequency', return_scores=True, original_labels=True)
assert isinstance(predictions, tuple)
assert all(np.equal(predictions[0].astype(object), np.array(['c0', 'c0', 'c0'], dtype=object)))
22 changes: 11 additions & 11 deletions lib/test/lib/classifiers/knn/test_knn_classifier.py
Original file line number Diff line number Diff line change
@@ -35,9 +35,9 @@ def test_fit_sets_attributes():
"""Check that fitting sets the hidden attributes"""
clf = clfs['k=1']
clf.fit(X, y)
assert_all_equal(clf._X, X)
assert_equal(clf._y, clf._encoder.transform(y))
assert clf._n_features == 3
assert_all_equal(clf.X_, X)
assert_equal(clf.y_, clf.encoder_.transform(y))
assert clf._n_features_ == 3

# ======================= #
# KNNClassifier.predict() #
@@ -271,14 +271,14 @@ def test_load_valid_no_weighting():
# Check that all fields are still the same
assert isinstance(clf, KNNClassifier)
assert clf._k == 3
assert list(clf._encoder.classes_) == classes
assert list(clf.encoder_.classes_) == classes
assert clf._window == 1.
assert clf._use_c == False
assert clf._independent == False
assert deepcopy(clf._random_state).normal() == deepcopy(rng).normal()
assert_all_equal(clf._X, X)
assert_equal(clf._y, clf._encoder.transform(y))
assert clf._n_features == 3
assert_all_equal(clf.X_, X)
assert_equal(clf.y_, clf.encoder_.transform(y))
assert clf._n_features_ == 3
# Check that weighting functions are the same for x=0 to x=1000
xs = np.arange(1000, step=0.1)
weighting = lambda x: np.ones(x.size)
@@ -294,14 +294,14 @@ def test_load_valid_weighting():
# Check that all fields are still the same
assert isinstance(clf, KNNClassifier)
assert clf._k == 3
assert list(clf._encoder.classes_) == classes
assert list(clf.encoder_.classes_) == classes
assert clf._window == 1.
assert clf._use_c == False
assert clf._independent == False
assert deepcopy(clf._random_state).normal() == deepcopy(rng).normal()
assert_all_equal(clf._X, X)
assert_equal(clf._y, clf._encoder.transform(y))
assert clf._n_features == 3
assert_all_equal(clf.X_, X)
assert_equal(clf.y_, clf.encoder_.transform(y))
assert clf._n_features_ == 3
# Check that weighting functions are the same for x=0 to x=1000
xs = np.arange(1000, step=0.1)
weighting = lambda x: np.exp(-x)