thresher-py 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- thresher/__init__.py +3 -0
- thresher/algorithm.py +28 -0
- thresher/algs/__init__.py +0 -0
- thresher/algs/common/__init__.py +0 -0
- thresher/algs/common/meta_optimizer.py +9 -0
- thresher/algs/common/stochastic.py +21 -0
- thresher/algs/common/tools.py +3 -0
- thresher/algs/genetic/__init__.py +0 -0
- thresher/algs/genetic/compute.py +90 -0
- thresher/algs/grid/__init__.py +0 -0
- thresher/algs/grid/compute.py +63 -0
- thresher/algs/linear/__init__.py +0 -0
- thresher/algs/linear/compute.py +75 -0
- thresher/algs/sgd/__init__.py +0 -0
- thresher/algs/sgd/compute.py +80 -0
- thresher/exceptions.py +2 -0
- thresher/interface.py +104 -0
- thresher/oracle.py +53 -0
- thresher/utils.py +51 -0
- thresher_py-0.2.1.dist-info/METADATA +231 -0
- thresher_py-0.2.1.dist-info/RECORD +23 -0
- thresher_py-0.2.1.dist-info/WHEEL +4 -0
- thresher_py-0.2.1.dist-info/licenses/LICENSE +21 -0
thresher/__init__.py
ADDED
thresher/algorithm.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from collections import namedtuple
|
|
2
|
+
|
|
3
|
+
Algorithm = namedtuple('Algorithm', ['id', 'full_name', 'synonyms', 'data_vol_thresh'])
|
|
4
|
+
|
|
5
|
+
available_algorithms = {'auto': Algorithm(id='auto', synonyms=['default', 'default_heuristics'],
|
|
6
|
+
data_vol_thresh=None, full_name='Default heuristics'),
|
|
7
|
+
'ls': Algorithm(id='ls', synonyms=['linear', 'linear_search'],
|
|
8
|
+
data_vol_thresh=1000, full_name='Linear search'),
|
|
9
|
+
'sgd': Algorithm(id='sgd', synonyms=['curve_fitting', ],
|
|
10
|
+
data_vol_thresh=None, full_name='Stochastic gradient descent'),
|
|
11
|
+
'gen': Algorithm(id='gen', synonyms=['genetic', 'sim'],
|
|
12
|
+
data_vol_thresh=None, full_name='Genetic algorithm'),
|
|
13
|
+
'grid': Algorithm(id='grid', synonyms=['grid-search', 'gs'],
|
|
14
|
+
data_vol_thresh=50*1000, full_name='Grid search'),
|
|
15
|
+
'sgrid': Algorithm(id='sgrid', synonyms=['random-grid-search', 'rn-grid', 's-grid'],
|
|
16
|
+
data_vol_thresh=None, full_name='Stochastic grid search'),
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
DEFAULT = available_algorithms['auto']
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def retrieve_by_alias(name: str):
|
|
23
|
+
name = name.lower()
|
|
24
|
+
try:
|
|
25
|
+
return available_algorithms[name]
|
|
26
|
+
except KeyError:
|
|
27
|
+
# try to match by the 'alternate name'
|
|
28
|
+
return next(_ for _ in available_algorithms.values() if name in _.synonyms)
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def calculate_range_mean(scores, actual_classes, label):
|
|
5
|
+
return np.mean([_[0] for _ in zip(scores, actual_classes) if _[1] == label])
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_mean_value_for_class_pd(label, label_column, data, data_column):
|
|
9
|
+
return np.mean(data[data[label_column] == label][data_column])
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import random
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def stochastic_process(evaluated, scores, actual_classes, random_factor, miss_class=True):
|
|
5
|
+
population_size = len(scores)
|
|
6
|
+
|
|
7
|
+
sample = random.sample(range(population_size), int(random_factor * population_size))
|
|
8
|
+
number_of_correct, number_of_incorrect = 0, 0
|
|
9
|
+
for idx in sample:
|
|
10
|
+
element = scores[idx]
|
|
11
|
+
actual_class = actual_classes[idx]
|
|
12
|
+
pred = 1 if element > evaluated else -1
|
|
13
|
+
if pred == actual_class:
|
|
14
|
+
number_of_correct += 1
|
|
15
|
+
else:
|
|
16
|
+
number_of_incorrect += 1
|
|
17
|
+
|
|
18
|
+
if miss_class:
|
|
19
|
+
return number_of_incorrect / (number_of_incorrect + number_of_correct) # ratio of mis-class
|
|
20
|
+
else:
|
|
21
|
+
return number_of_correct / (number_of_incorrect + number_of_correct)
|
|
File without changes
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
from thresher.algs.common.meta_optimizer import calculate_range_mean
|
|
5
|
+
from thresher.algs.common.stochastic import stochastic_process
|
|
6
|
+
from thresher.utils import get_or_default, print_progress_bar
|
|
7
|
+
|
|
8
|
+
population_size_default = 30
|
|
9
|
+
number_of_generations_default = 20
|
|
10
|
+
number_of_iterations_default = 10
|
|
11
|
+
sus_factor_default = 2
|
|
12
|
+
stoch_ratio_default = 0.02
|
|
13
|
+
mutation_chance_default = 0.05
|
|
14
|
+
mutation_factor_default = 0.10
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def run(scores, actual_classes, verbose, progress_bar, alg_options) -> float:
|
|
18
|
+
if verbose and progress_bar:
|
|
19
|
+
print('Warning! Enabling verbosity automatically disables a progress bar.')
|
|
20
|
+
progress_bar = False
|
|
21
|
+
|
|
22
|
+
# Defining the population size
|
|
23
|
+
population_size = get_or_default(alg_options, 'population_size', population_size_default)
|
|
24
|
+
population_initial_range = (calculate_range_mean(scores, actual_classes, -1),
|
|
25
|
+
calculate_range_mean(scores, actual_classes, 1))
|
|
26
|
+
|
|
27
|
+
number_of_generations = get_or_default(alg_options, 'number_of_generations', number_of_generations_default)
|
|
28
|
+
number_of_iterations = get_or_default(alg_options, 'number_of_iterations', number_of_iterations_default)
|
|
29
|
+
|
|
30
|
+
sus_factor = population_size - get_or_default(alg_options, 'sus_factor', sus_factor_default)
|
|
31
|
+
# how many agents should die child-less after a generation
|
|
32
|
+
|
|
33
|
+
stoch_ratio = get_or_default(alg_options, 'stoch_ratio', stoch_ratio_default)
|
|
34
|
+
# random ratio - the lower, the faster sim
|
|
35
|
+
|
|
36
|
+
mutation_factor = get_or_default(alg_options, 'mutation_factor', mutation_factor_default)
|
|
37
|
+
|
|
38
|
+
population = []
|
|
39
|
+
|
|
40
|
+
# Build the population
|
|
41
|
+
for i in range(population_size):
|
|
42
|
+
population.append({'id': f'agent_{i}',
|
|
43
|
+
'trait': random.uniform(population_initial_range[0], population_initial_range[1]),
|
|
44
|
+
'trait_eff': list()})
|
|
45
|
+
|
|
46
|
+
for generation_no in range(number_of_generations):
|
|
47
|
+
if verbose:
|
|
48
|
+
print(f'Running generation no {generation_no}')
|
|
49
|
+
|
|
50
|
+
if progress_bar:
|
|
51
|
+
print_progress_bar(generation_no, number_of_generations)
|
|
52
|
+
|
|
53
|
+
for iteration_no in range(number_of_iterations):
|
|
54
|
+
for agent in population:
|
|
55
|
+
agent['trait_eff'].append(stochastic_process(agent['trait'],
|
|
56
|
+
scores, actual_classes, random_factor=stoch_ratio))
|
|
57
|
+
# for every iteration, get a stochastic fitness score
|
|
58
|
+
|
|
59
|
+
# calculate fitness score - the mean mis-classification ratio over this
|
|
60
|
+
# generation's iterations, so that lower is fitter
|
|
61
|
+
for agent in population:
|
|
62
|
+
agent['trait_eff'] = np.mean(agent['trait_eff'])
|
|
63
|
+
|
|
64
|
+
# select most fit (SUS)
|
|
65
|
+
sort_by_fit = [_ for _ in sorted(population, key=lambda x: x['trait_eff'], reverse=False)][0:sus_factor]
|
|
66
|
+
|
|
67
|
+
# do crossover
|
|
68
|
+
population = []
|
|
69
|
+
for i in range(population_size):
|
|
70
|
+
l = random.sample(sort_by_fit, 1)[0]['trait']
|
|
71
|
+
r = random.sample(sort_by_fit, 1)[0]['trait']
|
|
72
|
+
if l > r:
|
|
73
|
+
l, r = r, l
|
|
74
|
+
# new_trait = random.sample([l, l + ((r-l)*random.random()), r], 1)[0]
|
|
75
|
+
new_trait = l + ((r-l)*random.random())
|
|
76
|
+
population.append({'id': f'agent_{i}',
|
|
77
|
+
'trait': new_trait,
|
|
78
|
+
'trait_eff': list()})
|
|
79
|
+
|
|
80
|
+
# mutate
|
|
81
|
+
if random.random() < get_or_default(alg_options, 'mutation_chance', mutation_chance_default):
|
|
82
|
+
population[int(len(population) * random.random())]['trait'] += mutation_factor * random.random()
|
|
83
|
+
|
|
84
|
+
if verbose:
|
|
85
|
+
print(f'Population after gen: {generation_no} - {[_["trait"] for _ in population]}')
|
|
86
|
+
|
|
87
|
+
if progress_bar:
|
|
88
|
+
print_progress_bar(number_of_generations, number_of_generations)
|
|
89
|
+
|
|
90
|
+
return float(np.mean([_["trait"] for _ in population]))
|
|
File without changes
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from thresher.utils import get_or_default, print_progress_bar
|
|
3
|
+
import random
|
|
4
|
+
|
|
5
|
+
no_of_decimal_places_default = 2
|
|
6
|
+
stoch_ratio_default = 0.05
|
|
7
|
+
reshuffle_default = False
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def run_stoch(scores, actual_classes, verbose, progress_bar, alg_options):
|
|
11
|
+
return run(scores, actual_classes, verbose, progress_bar, alg_options, True)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def run(scores, actual_classes, verbose, progress_bar, alg_options, stochastic=False):
|
|
15
|
+
best_threshold, best_accuracy, iteration = None, -1, 0
|
|
16
|
+
|
|
17
|
+
no_of_decimal_places = get_or_default(alg_options, 'no_of_decimal_places', no_of_decimal_places_default)
|
|
18
|
+
stoch_ratio = get_or_default(alg_options, 'stoch_ratio', stoch_ratio_default)
|
|
19
|
+
reshuffle = get_or_default(alg_options, 'reshuffle', reshuffle_default)
|
|
20
|
+
|
|
21
|
+
batch_size = (10**no_of_decimal_places)+1
|
|
22
|
+
|
|
23
|
+
if verbose:
|
|
24
|
+
print(f'Evaluating {batch_size} solutions. Please wait for results.')
|
|
25
|
+
|
|
26
|
+
def get_random_projection(_scores, _actual_classes, _stoch_ratio):
|
|
27
|
+
return random.sample(list(zip(_scores, _actual_classes)), int(_stoch_ratio * len(_scores)))
|
|
28
|
+
|
|
29
|
+
if stochastic and (not reshuffle):
|
|
30
|
+
one_time_projection = get_random_projection(scores, actual_classes, stoch_ratio)
|
|
31
|
+
|
|
32
|
+
for single_point in np.linspace(0, 1, batch_size):
|
|
33
|
+
iteration += 1
|
|
34
|
+
|
|
35
|
+
if progress_bar:
|
|
36
|
+
print_progress_bar(iteration, batch_size)
|
|
37
|
+
|
|
38
|
+
count_correct, count_incorrect = 0, 0
|
|
39
|
+
|
|
40
|
+
if stochastic:
|
|
41
|
+
if reshuffle:
|
|
42
|
+
projection = get_random_projection(scores, actual_classes, stoch_ratio)
|
|
43
|
+
else:
|
|
44
|
+
projection = one_time_projection
|
|
45
|
+
else:
|
|
46
|
+
projection = zip(scores, actual_classes)
|
|
47
|
+
|
|
48
|
+
for l, r in projection:
|
|
49
|
+
predicted = 1 if l > single_point else -1
|
|
50
|
+
if predicted == r:
|
|
51
|
+
count_correct += 1
|
|
52
|
+
else:
|
|
53
|
+
count_incorrect += 1
|
|
54
|
+
|
|
55
|
+
accuracy = count_correct / (count_correct + count_incorrect)
|
|
56
|
+
|
|
57
|
+
if accuracy > best_accuracy:
|
|
58
|
+
best_threshold, best_accuracy = single_point, accuracy
|
|
59
|
+
|
|
60
|
+
if progress_bar:
|
|
61
|
+
print_progress_bar(batch_size, batch_size)
|
|
62
|
+
|
|
63
|
+
return best_threshold
|
|
File without changes
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from thresher.utils import pairwise, print_progress_bar
|
|
2
|
+
import multiprocessing as mp
|
|
3
|
+
from functools import partial
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def process_batch(scores, actual_classes, data_point):
|
|
7
|
+
count_correct, count_incorrect = 0, 0
|
|
8
|
+
|
|
9
|
+
for l, r in zip(scores, actual_classes):
|
|
10
|
+
predicted = 1 if l > data_point else -1
|
|
11
|
+
if predicted == r:
|
|
12
|
+
count_correct += 1
|
|
13
|
+
else:
|
|
14
|
+
count_incorrect += 1
|
|
15
|
+
|
|
16
|
+
accuracy = count_correct / (count_correct + count_incorrect)
|
|
17
|
+
|
|
18
|
+
return data_point, accuracy
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def run_parallel(scores, actual_classes, verbose, n_jobs) -> float:
|
|
22
|
+
batch_size = len(scores)
|
|
23
|
+
number_of_processors = mp.cpu_count()
|
|
24
|
+
|
|
25
|
+
if (n_jobs < -1) or (n_jobs > number_of_processors):
|
|
26
|
+
print(f'Improper value for n_jobs. It must be either -1, or at most, the number of available processors')
|
|
27
|
+
|
|
28
|
+
if verbose:
|
|
29
|
+
print(f'Doing linear search with {batch_size} iterations, running in parallel {n_jobs} jobs.')
|
|
30
|
+
|
|
31
|
+
def iterate_through_scores():
|
|
32
|
+
for score in scores:
|
|
33
|
+
yield score
|
|
34
|
+
|
|
35
|
+
pool = mp.Pool(processes=number_of_processors-1 if n_jobs == -1 else n_jobs)
|
|
36
|
+
mp_func = partial(process_batch, scores, actual_classes)
|
|
37
|
+
results = pool.map(func=mp_func, iterable=iterate_through_scores(), chunksize=int(batch_size/n_jobs))
|
|
38
|
+
|
|
39
|
+
return next(i[0] for i in sorted(results, key=lambda x: x[1], reverse=True))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def run(scores, actual_classes, verbose, progress_bar) -> float:
|
|
43
|
+
best_threshold, best_accuracy, iteration = None, -1, 0
|
|
44
|
+
|
|
45
|
+
batch_size = len(scores)
|
|
46
|
+
|
|
47
|
+
if verbose:
|
|
48
|
+
print(f'Doing linear search with {batch_size} iterations. It can take some time, depending on the data volume.')
|
|
49
|
+
|
|
50
|
+
for a, b in pairwise(sorted(scores)):
|
|
51
|
+
iteration += 1
|
|
52
|
+
|
|
53
|
+
if progress_bar:
|
|
54
|
+
print_progress_bar(iteration, batch_size)
|
|
55
|
+
|
|
56
|
+
middle = (a + b) / 2
|
|
57
|
+
|
|
58
|
+
count_correct, count_incorrect = 0, 0
|
|
59
|
+
|
|
60
|
+
for l, r in zip(scores, actual_classes):
|
|
61
|
+
predicted = 1 if l > middle else -1
|
|
62
|
+
if predicted == r:
|
|
63
|
+
count_correct += 1
|
|
64
|
+
else:
|
|
65
|
+
count_incorrect += 1
|
|
66
|
+
|
|
67
|
+
accuracy = count_correct / (count_correct + count_incorrect)
|
|
68
|
+
|
|
69
|
+
if accuracy > best_accuracy:
|
|
70
|
+
best_threshold, best_accuracy = middle, accuracy
|
|
71
|
+
|
|
72
|
+
if progress_bar:
|
|
73
|
+
print_progress_bar(batch_size, batch_size)
|
|
74
|
+
|
|
75
|
+
return best_threshold
|
|
File without changes
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from thresher.algs.common.stochastic import stochastic_process
|
|
3
|
+
from thresher.utils import get_or_default
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
num_of_iters_default = 200
|
|
7
|
+
stop_thresh_default = 0.001
|
|
8
|
+
alpha_default = 0.01
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def sgd_solver(eval_func, starting_point, gradient, verbose, num_of_iters, stop_thresh, alpha):
|
|
12
|
+
previous_eval_point = starting_point
|
|
13
|
+
previous_eval = 0.0
|
|
14
|
+
|
|
15
|
+
first_run = eval_func(previous_eval_point, previous_eval)
|
|
16
|
+
|
|
17
|
+
evaluation, gain = first_run[0], -first_run[1]
|
|
18
|
+
|
|
19
|
+
if verbose:
|
|
20
|
+
print(f'SGD initial run (from point {starting_point}). Evaluation: {evaluation} and gain: {gain}')
|
|
21
|
+
|
|
22
|
+
for iter_no in range(num_of_iters):
|
|
23
|
+
|
|
24
|
+
previous_eval = evaluation
|
|
25
|
+
previous_gain = gain
|
|
26
|
+
|
|
27
|
+
if verbose:
|
|
28
|
+
print(f'SGD iteration {iter_no}. Previous evaluation: {previous_eval} for X:{previous_eval_point} and previous gain: {previous_gain}')
|
|
29
|
+
|
|
30
|
+
new_point = previous_eval_point + gradient
|
|
31
|
+
|
|
32
|
+
if verbose:
|
|
33
|
+
print(f'SGD iteration {iter_no}. New point set to: {new_point} because gradient: {gradient}')
|
|
34
|
+
|
|
35
|
+
evaluation, gain = eval_func(new_point, previous_eval)
|
|
36
|
+
|
|
37
|
+
if verbose:
|
|
38
|
+
print(f'SGD iteration {iter_no}. Evaluation: {evaluation} and gain: {gain}')
|
|
39
|
+
|
|
40
|
+
previous_eval_point = new_point
|
|
41
|
+
|
|
42
|
+
gradient = gradient * (gain/previous_eval) * (1.0 - alpha)
|
|
43
|
+
if gain < 0:
|
|
44
|
+
gradient *= -1.0
|
|
45
|
+
|
|
46
|
+
if verbose:
|
|
47
|
+
print(f'SGD iteration {iter_no}. New gradient set to: {gradient}')
|
|
48
|
+
|
|
49
|
+
if abs(gain) < stop_thresh:
|
|
50
|
+
return previous_eval_point
|
|
51
|
+
|
|
52
|
+
# hadn't converged with 'num_of_iters', return anyway
|
|
53
|
+
return previous_eval_point
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def run(scores, actual_classes, verbose, progress_bar, alg_options) -> float:
|
|
57
|
+
|
|
58
|
+
def evaluate_threshold(threshold, previous_eval, random_factor=0.05):
|
|
59
|
+
if verbose:
|
|
60
|
+
print(f'Currently evaluating threshold: {threshold}')
|
|
61
|
+
|
|
62
|
+
new_eval = stochastic_process(threshold, scores, actual_classes, random_factor)
|
|
63
|
+
gain = previous_eval - new_eval
|
|
64
|
+
|
|
65
|
+
return new_eval, gain
|
|
66
|
+
|
|
67
|
+
starting_point = np.mean(scores)
|
|
68
|
+
if verbose:
|
|
69
|
+
print(f'Starting point set to: {starting_point}')
|
|
70
|
+
|
|
71
|
+
starting_gradient = 0.05
|
|
72
|
+
|
|
73
|
+
num_of_iters = get_or_default(alg_options, 'num_of_iters', num_of_iters_default)
|
|
74
|
+
stop_thresh = get_or_default(alg_options, 'stop_thresh', stop_thresh_default)
|
|
75
|
+
alpha = get_or_default(alg_options, 'alpha', alpha_default)
|
|
76
|
+
|
|
77
|
+
result = sgd_solver(evaluate_threshold, starting_point, starting_gradient, verbose,
|
|
78
|
+
num_of_iters=num_of_iters, stop_thresh=stop_thresh, alpha=alpha)
|
|
79
|
+
|
|
80
|
+
return result
|
thresher/exceptions.py
ADDED
thresher/interface.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
|
|
7
|
+
from thresher import algorithm
|
|
8
|
+
from thresher.oracle import run_oracle, run_computations
|
|
9
|
+
from thresher.exceptions import NOT_IMPLEMENTED_ERROR
|
|
10
|
+
from thresher.utils import map_labels
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ThresherBase(object):
|
|
14
|
+
|
|
15
|
+
def _run_oracle(self, data_traits: dict):
|
|
16
|
+
if self.options['algorithm'] == algorithm.DEFAULT:
|
|
17
|
+
if self.options['verbose']:
|
|
18
|
+
print("Running heuristics on choosing a proper algorithm")
|
|
19
|
+
chosen_algorithm = run_oracle(data_traits)
|
|
20
|
+
|
|
21
|
+
else:
|
|
22
|
+
chosen_algorithm = self.options['algorithm']
|
|
23
|
+
|
|
24
|
+
if self.options['verbose']:
|
|
25
|
+
print(f"Chosen algorithm: {chosen_algorithm.full_name}")
|
|
26
|
+
|
|
27
|
+
return chosen_algorithm
|
|
28
|
+
|
|
29
|
+
def _compute(self, chosen_algorithm, scores, actual_classes):
|
|
30
|
+
return run_computations(chosen_algorithm, scores, actual_classes,
|
|
31
|
+
self.options['verbose'],
|
|
32
|
+
self.options['progress_bar'],
|
|
33
|
+
self.options['allow_parallel'],
|
|
34
|
+
self.options['algorithm_params'])
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Thresher(ThresherBase):
|
|
38
|
+
|
|
39
|
+
def __init__(self, **kwargs):
|
|
40
|
+
"""Creates a new Thresher object, an interface to the Thesher evaluator.
|
|
41
|
+
|
|
42
|
+
The __init__ method creates the Thresher object.
|
|
43
|
+
|
|
44
|
+
Note:
|
|
45
|
+
No need to pass any extra arguments if you don't understand what you're doing
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
kwargs (:obj:`dict`, optional): Any hidden arguments, which you wish to pass.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
super(ThresherBase, self).__init__()
|
|
52
|
+
|
|
53
|
+
self.options = {
|
|
54
|
+
'algorithm': 'auto',
|
|
55
|
+
'allow_parallel': True,
|
|
56
|
+
'verbose': False,
|
|
57
|
+
'progress_bar': False,
|
|
58
|
+
'algorithm_params': {}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
self.options.update(kwargs)
|
|
62
|
+
|
|
63
|
+
self.options['algorithm'] = algorithm.retrieve_by_alias(self.options['algorithm'])
|
|
64
|
+
|
|
65
|
+
def get_current_algorithm(self):
|
|
66
|
+
"""Get current language."""
|
|
67
|
+
with self.options['algorithm'] as current_algorithm:
|
|
68
|
+
return {'name': current_algorithm.id, 'object': current_algorithm}
|
|
69
|
+
|
|
70
|
+
def get_current_options(self):
|
|
71
|
+
return self.options
|
|
72
|
+
|
|
73
|
+
def set_algorithm(self, algorithm_name):
|
|
74
|
+
try:
|
|
75
|
+
self.options['algorithm'] = algorithm.retrieve_by_alias(algorithm_name)
|
|
76
|
+
except StopIteration:
|
|
77
|
+
print('Unknown algorithm, please use a name available in get_supported_algorithms()')
|
|
78
|
+
return self
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def get_supported_algorithms(as_dict=False):
|
|
82
|
+
"""Get list of supported languages."""
|
|
83
|
+
if as_dict:
|
|
84
|
+
return {k: v.full_name for k, v in algorithm.available_algorithms.items()}
|
|
85
|
+
else:
|
|
86
|
+
return list(algorithm.available_algorithms.keys())
|
|
87
|
+
|
|
88
|
+
def optimize_threshold(self, scores, actual_classes):
|
|
89
|
+
if not isinstance(scores, Iterable):
|
|
90
|
+
raise AttributeError(NOT_IMPLEMENTED_ERROR)
|
|
91
|
+
if not isinstance(actual_classes, Iterable):
|
|
92
|
+
raise AttributeError(NOT_IMPLEMENTED_ERROR)
|
|
93
|
+
|
|
94
|
+
scores = list(scores)
|
|
95
|
+
if ('labels' in self.options) and (isinstance(self.options['labels'], Iterable)):
|
|
96
|
+
actual_classes = list(map_labels(actual_classes, self.options['labels']))
|
|
97
|
+
else:
|
|
98
|
+
actual_classes = list(actual_classes)
|
|
99
|
+
|
|
100
|
+
data_traits = {'data_length': len(scores)}
|
|
101
|
+
|
|
102
|
+
chosen_algorithm = self._run_oracle(data_traits)
|
|
103
|
+
|
|
104
|
+
return self._compute(chosen_algorithm, scores, actual_classes)
|
thresher/oracle.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from thresher import algorithm
|
|
2
|
+
from thresher.algs.linear import compute as linear_compute
|
|
3
|
+
from thresher.algs.sgd import compute as sgd_compute
|
|
4
|
+
from thresher.algs.genetic import compute as gen_compute
|
|
5
|
+
from thresher.algs.grid import compute as grid_compute
|
|
6
|
+
from thresher.exceptions import UNKNOWN_ALGORITHM
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
LINEAR_ALGORITHM = algorithm.available_algorithms['ls']
|
|
10
|
+
STOCHASTIC_GRADIENT_DESCENT = algorithm.available_algorithms['sgd']
|
|
11
|
+
GENETIC_ALGORITHM = algorithm.available_algorithms['gen']
|
|
12
|
+
GRID_SEARCH_ALGORITHM = algorithm.available_algorithms['grid']
|
|
13
|
+
STOCHASTIC_GRID_SEARCH_ALGORITHM = algorithm.available_algorithms['sgrid']
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def run_oracle(data_traits: dict):
|
|
17
|
+
data_volume = data_traits['data_length']
|
|
18
|
+
|
|
19
|
+
# some the 'ThresherPerformanceTest.ipynb' notebook for some thought process behind this
|
|
20
|
+
# an algorithm of recommendation for big datasets is currently 'sgd'
|
|
21
|
+
|
|
22
|
+
if data_volume <= algorithm.available_algorithms['ls'].data_vol_thresh:
|
|
23
|
+
chosen_algorithm = algorithm.available_algorithms['ls']
|
|
24
|
+
elif data_volume <= algorithm.available_algorithms['grid'].data_vol_thresh:
|
|
25
|
+
chosen_algorithm = algorithm.available_algorithms['grid']
|
|
26
|
+
else:
|
|
27
|
+
chosen_algorithm = algorithm.available_algorithms['sgd']
|
|
28
|
+
|
|
29
|
+
return chosen_algorithm
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def run_computations(chosen_algorithm: algorithm.Algorithm, scores, actual_classes,
|
|
33
|
+
verbose, progress_bar, allow_parallel, alg_options) -> float:
|
|
34
|
+
assert set(actual_classes) == {-1, 1}
|
|
35
|
+
|
|
36
|
+
if verbose:
|
|
37
|
+
print(f'Executing the {chosen_algorithm.full_name} algorithm... please wait for the result.')
|
|
38
|
+
|
|
39
|
+
if chosen_algorithm == LINEAR_ALGORITHM:
|
|
40
|
+
if allow_parallel and ('n_jobs' in alg_options) and (alg_options['n_jobs'] != 1):
|
|
41
|
+
return linear_compute.run_parallel(scores, actual_classes, verbose, alg_options['n_jobs'])
|
|
42
|
+
else:
|
|
43
|
+
return linear_compute.run(scores, actual_classes, verbose, progress_bar)
|
|
44
|
+
elif chosen_algorithm == STOCHASTIC_GRADIENT_DESCENT:
|
|
45
|
+
return sgd_compute.run(scores, actual_classes, verbose, progress_bar, alg_options)
|
|
46
|
+
elif chosen_algorithm == GENETIC_ALGORITHM:
|
|
47
|
+
return gen_compute.run(scores, actual_classes, verbose, progress_bar, alg_options)
|
|
48
|
+
elif chosen_algorithm == GRID_SEARCH_ALGORITHM:
|
|
49
|
+
return grid_compute.run(scores, actual_classes, verbose, progress_bar, alg_options)
|
|
50
|
+
elif chosen_algorithm == STOCHASTIC_GRID_SEARCH_ALGORITHM:
|
|
51
|
+
return grid_compute.run_stoch(scores, actual_classes, verbose, progress_bar, alg_options)
|
|
52
|
+
else:
|
|
53
|
+
raise NotImplementedError(UNKNOWN_ALGORITHM)
|
thresher/utils.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from itertools import tee
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
NEGATIVE_LABEL = -1
|
|
5
|
+
POSITIVE_LABEL = 1
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def map_labels(labels, mapping):
|
|
9
|
+
assert type(mapping) in [list, tuple]
|
|
10
|
+
for label in labels:
|
|
11
|
+
if label == mapping[0]:
|
|
12
|
+
yield NEGATIVE_LABEL
|
|
13
|
+
elif label == mapping[1]:
|
|
14
|
+
yield POSITIVE_LABEL
|
|
15
|
+
else:
|
|
16
|
+
raise TypeError('Value not found in the mapping - map_labels() cannot map label classes.')
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_or_default(options, key, default):
|
|
20
|
+
if key in options:
|
|
21
|
+
return options[key]
|
|
22
|
+
else:
|
|
23
|
+
return default
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def pairwise(iterable):
|
|
27
|
+
a, b = tee(iterable)
|
|
28
|
+
next(b, None)
|
|
29
|
+
return zip(a, b)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Print iterations progress
|
|
33
|
+
def print_progress_bar(iteration, total, prefix='', suffix='', decimals=1, length=100, fill='#'):
|
|
34
|
+
"""
|
|
35
|
+
Call in a loop to create terminal progress bar
|
|
36
|
+
@params:
|
|
37
|
+
iteration - Required : current iteration (Int)
|
|
38
|
+
total - Required : total iterations (Int)
|
|
39
|
+
prefix - Optional : prefix string (Str)
|
|
40
|
+
suffix - Optional : suffix string (Str)
|
|
41
|
+
decimals - Optional : positive number of decimals in percent complete (Int)
|
|
42
|
+
length - Optional : character length of bar (Int)
|
|
43
|
+
fill - Optional : bar fill character (Str)
|
|
44
|
+
"""
|
|
45
|
+
percent = ("{0:." + str(decimals) + "f}").format(100 * (iteration / float(total)))
|
|
46
|
+
filled_length = int(length * iteration // total)
|
|
47
|
+
bar = fill * filled_length + '-' * (length - filled_length)
|
|
48
|
+
print('\r%s |%s| %s%% %s' % (prefix, bar, percent, suffix), end='\r')
|
|
49
|
+
# Print New Line on Complete
|
|
50
|
+
if iteration == total:
|
|
51
|
+
print()
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: thresher-py
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: Find threshold for fine-tuning output from predict_proba
|
|
5
|
+
Project-URL: Homepage, https://github.com/oskar-j/thresher
|
|
6
|
+
Project-URL: Repository, https://github.com/oskar-j/thresher
|
|
7
|
+
Project-URL: Issues, https://github.com/oskar-j/thresher/issues
|
|
8
|
+
Author-email: Oskar Jarczyk <oskar.jarczyk@gmail.com>
|
|
9
|
+
Maintainer-email: Oskar Jarczyk <oskar.jarczyk@gmail.com>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: AutoML,Finetuning,Hyper-parameters,ML,Optimization
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: numpy
|
|
24
|
+
Requires-Dist: pandas
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# Thresher - THRESHold EvaluatoR for Python
|
|
28
|
+
|
|
29
|
+

|
|
30
|
+
|
|
31
|
+
_That's either a young girl's head, or an old woman face - it all depends on what the brain chooses to see._
|
|
32
|
+
|
|
33
|
+
_Choose your cut-off point wise!_
|
|
34
|
+
|
|
35
|
+
## Project description
|
|
36
|
+
|
|
37
|
+
A bare pandas implementation of a tool for finding the threshold which maximizes accuracy
|
|
38
|
+
of `predict_proba` like-outputs (from e.g. `scikit-learn`), in regard to the provided ground truth (labels).
|
|
39
|
+
|
|
40
|
+
_Note: you can jump directly to the sample usage [here](https://github.com/oskar-j/thresher#sample-usage)._
|
|
41
|
+
|
|
42
|
+
Method interesting for the user is `optimize_threshold(scores, actual_classes)`, which is available
|
|
43
|
+
from the `Thresher` class. This method, for given _scores_ and _actual classes_,
|
|
44
|
+
returns a threshold that yields the _**highest fraction** of correctly classified_ samples.
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
optimize_threshold parameters:
|
|
48
|
+
scores:list
|
|
49
|
+
The list of scores.
|
|
50
|
+
actual_classes:list
|
|
51
|
+
The list of ground truth (correct) classes.
|
|
52
|
+
Classes are represented as -1 and 1.
|
|
53
|
+
returns:
|
|
54
|
+
threshold:float
|
|
55
|
+
The threshold value that yields the highest fraction of correctly classified
|
|
56
|
+
samples. If multiple thresholds give the optimal fraction, return any threshold.
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### An oracle mechanism
|
|
60
|
+
|
|
61
|
+
We implemented a meta-optimizer - an 'oracle' mechanism, which chooses a proper algorithm in regard to the provided data. This is the default behaviour, and can be controlled by changing the `algorithm` param of the `Thresher` constructor. See the source code of [oracle.py](https://github.com/oskar-j/thresher/blob/main/thresher/oracle.py) and [interface.py](https://github.com/oskar-j/thresher/blob/main/thresher/interface.py) for more details.
|
|
62
|
+
|
|
63
|
+
### Implemented algorithms
|
|
64
|
+
|
|
65
|
+
### Linear search
|
|
66
|
+
|
|
67
|
+
This is the most basic, iterative approach. Recommended for smaller datasets. For every _threshold_ present in the input (in the _scores_ list), we evaluate it by calculating the exact accuracy of _split_ produced by such threshold. Then, return the threshold which produce the most accurate split.
|
|
68
|
+
|
|
69
|
+
List of parameters to customize:
|
|
70
|
+
* `n_jobs` (default: 1) - set to `-1` for using all available processors except one; any value of `2` or more
|
|
71
|
+
enables multiprocessing, while the default value of `1` disables multiprocessing
|
|
72
|
+
|
|
73
|
+
### 2-dim Stochastic Gradient Descent
|
|
74
|
+
|
|
75
|
+
This algorithm uses a naive implementation of the popular algorithm 'Stochastic Gradient Descent', which tries to converge over a function - in our case, it
|
|
76
|
+
is an error curve representing ratio of miss-classifies for a threshold. Using a gradient, algorithm follows the curve to find the optimal value, that is,
|
|
77
|
+
a threshold producing the smaller number of miss-classifies. The disadvantage of this algorithm is it's questionable robustness - it may happen
|
|
78
|
+
that it converges to a local optimum instead of a global one.
|
|
79
|
+
|
|
80
|
+
List of parameters to customize:
|
|
81
|
+
* `num_of_iters` (default: 200) - number of iterations during which algorithm tries to converge
|
|
82
|
+
* `stop_thresh` (default: 0.001) - minimal value of improvement, below which algorithm stops
|
|
83
|
+
* `alpha` (default: 0.01)
|
|
84
|
+
|
|
85
|
+
### Evolutionary algorithm
|
|
86
|
+
|
|
87
|
+
This is a simulation approach which uses an evolutionary algorithm. It works by simulating multiple generations of a "population" of candidate solutions. During every iteration of a single generation, algorithm stochasticly evaluates the candidate solution. After the end of a single generation, we remove the from the population least fit agents (solutions), and do the _crossover_ between the left solitions to produce new "offspring" candidate solutions. Moreover, they may mutate to provide additional random chance.
|
|
88
|
+
|
|
89
|
+
List of parameters to customize:
|
|
90
|
+
* `population_size` (default: 30) - number of agents in the simulation
|
|
91
|
+
* `number_of_generations` (default: 20) - number of generations
|
|
92
|
+
* `number_of_iterations` (default: 10) - number of iterations per a generation
|
|
93
|
+
* `sus_factor` (default: 2) - how many least-fit agents should be childless at the end of generation
|
|
94
|
+
* `stoch_ratio` (default: 0.02) - percentage of data to evaluate fit of a single agent per iteration
|
|
95
|
+
* `optimized_start` (default: True)
|
|
96
|
+
* `mutation_chance` (default: 0.05)
|
|
97
|
+
* `mutation_factor` (default: 0.10)
|
|
98
|
+
|
|
99
|
+
### Grid search
|
|
100
|
+
|
|
101
|
+
Added in version `0.1.2`. This algorithm works by generate a grid of possible solutions, with a granularity set
|
|
102
|
+
by parameter named `no_of_decimal_places`. All candidate solutions are evaluated thoroughly
|
|
103
|
+
and the best one is chosen at the end.
|
|
104
|
+
|
|
105
|
+
List of parameters to customize:
|
|
106
|
+
* `no_of_decimal_places` (default: 2) - generate the grid by rounding the number to the given number of decimal places
|
|
107
|
+
|
|
108
|
+
### Stochastic Grid search
|
|
109
|
+
|
|
110
|
+
Added in version `0.1.2`. This algorithm works similarly like the above-mentioned 'Grid search' method, with the difference, that
|
|
111
|
+
every single point generated by the grid is evaluated only partially (which can be controlled by the `stoch_ratio` parameter)
|
|
112
|
+
|
|
113
|
+
List of parameters to customize:
|
|
114
|
+
* `no_of_decimal_places` (default: 2) - generate the grid by rounding the number to the given number of decimal places
|
|
115
|
+
* `stoch_ratio` (default: 0.05) - percentage of data to evaluate fit of a candidate number in the grid
|
|
116
|
+
* `reshuffle` (default: False) - set whether the random projection should be calculated every step, or not
|
|
117
|
+
|
|
118
|
+
## How to setup?
|
|
119
|
+
|
|
120
|
+
The process is rather straightforward, you just need to just whether to install
|
|
121
|
+
from the sources (latest revision), or from the PyPI repository (stable release).
|
|
122
|
+
|
|
123
|
+
### Requirements
|
|
124
|
+
|
|
125
|
+
Requires Python `3.10+`. Tested on Python 3.10, 3.11, 3.12, 3.13 and 3.14.
|
|
126
|
+
|
|
127
|
+
### Installation
|
|
128
|
+
|
|
129
|
+
Stable release using the `pip` tool:
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
pip install thresher-py
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Or with [uv](https://docs.astral.sh/uv/):
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
uv add thresher-py
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Installation from source (latest revision):
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
pip install git+https://github.com/oskar-j/thresher.git
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Development setup
|
|
148
|
+
|
|
149
|
+
This project uses [uv](https://docs.astral.sh/uv/) for dependency management, with
|
|
150
|
+
`pyproject.toml` and a locked `uv.lock`:
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
uv sync --group dev
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Run the test suite (it loads its `.xlsx` fixtures through a relative path, so it must be
|
|
157
|
+
run from inside `thresher/tests`):
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
cd thresher/tests && uv run python -m unittest test
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Custom parameters
|
|
164
|
+
|
|
165
|
+
It's possible to provide additional parameters in the `Thresher` constructor.
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
Thresher(algorithm='auto',
|
|
169
|
+
allow_parallel=True,
|
|
170
|
+
verbose=False,
|
|
171
|
+
progress_bar=False,
|
|
172
|
+
labels=(0,1))
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Here is a description of what does every particular parameter do:
|
|
176
|
+
|
|
177
|
+
* **algorithm** (default value: `'auto'`) - allows to manually choose the algorithm from the list of available algorithms.
|
|
178
|
+
Same effect can be achieved with running the method called `set_algorithm(algorithm_name)` on the `Thresher` instance.
|
|
179
|
+
The default value is 'auto', which means that the tool uses an oracle mechanism to manually choose a proper algorithm.
|
|
180
|
+
* **allow_parallel** (default value: `True`) - enables/disabled multiprocessing for algorithms
|
|
181
|
+
* **verbose** (default value: `False`) - enables verbosity
|
|
182
|
+
* **progress_bar** (default value: `False`) - shows a progress bar in the terminal (if supported by the algorithm)
|
|
183
|
+
* **labels** - necessary if your labels are different from `(-1, 1)` - first item from the tuple/list is a negative label,
|
|
184
|
+
and the second item is a positive label
|
|
185
|
+
|
|
186
|
+
### Control parameters for the algorithms
|
|
187
|
+
|
|
188
|
+
Some of the above-mentioned algorithms allow to change their parameters.
|
|
189
|
+
They should be provided in a dictionary, inside the `algorithm_params` parameter.
|
|
190
|
+
If no such customs parameters are provided, default values apply.
|
|
191
|
+
|
|
192
|
+
Examples:
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
t = thresher.Thresher(algorithm_params={'n_jobs': 3})
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
t = thresher.Thresher(algorithm_params={'no_of_decimal_places': 3,
|
|
200
|
+
'stoch_ratio': 0.10})
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Sample usage
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
import thresher
|
|
207
|
+
|
|
208
|
+
t = thresher.Thresher()
|
|
209
|
+
|
|
210
|
+
print('Currently supported algorithms:')
|
|
211
|
+
print(t.get_supported_algorithms())
|
|
212
|
+
|
|
213
|
+
cases = [0.1, 0.3, 0.4, 0.7]
|
|
214
|
+
actual_labels = [-1, -1, 1, 1]
|
|
215
|
+
|
|
216
|
+
print(f'Optimization result: {t.optimize_threshold(cases, actual_labels)}')
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
See the [examples](https://github.com/oskar-j/thresher/tree/main/examples) directory for more sample code.
|
|
220
|
+
|
|
221
|
+
## Performance tests
|
|
222
|
+
|
|
223
|
+
A very basic performance test (with 10 repeats, on a real-world [anonymized data](https://github.com/oskar-j/thresher/blob/main/examples/performance_test/milion_samples.7z) consisting of `10^6` rows) can be found in the Notebook [located here](https://github.com/oskar-j/thresher/blob/main/examples/performance_test/TresherPerformanceTest.ipynb).
|
|
224
|
+
Similar experiment, but with more iterations, was conducted in the file [TresherPerformanceTestExtended.ipynb](https://github.com/oskar-j/thresher/blob/main/examples/performance_test/TresherPerformanceTestExtended.ipynb) to test the oracle.
|
|
225
|
+
|
|
226
|
+
## Future work
|
|
227
|
+
|
|
228
|
+
* adding more algorithms,
|
|
229
|
+
* publishing on conda,
|
|
230
|
+
* more heavy test loads,
|
|
231
|
+
* python docs.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
thresher/__init__.py,sha256=nJVLhxEftlPxSvsogYM1GI3CTUFznc0F5FSBsR08jyY,63
|
|
2
|
+
thresher/algorithm.py,sha256=i-WoIZeUaSi7c9muumYlecebSkihJ3SVlcW7NTBncOA,1575
|
|
3
|
+
thresher/exceptions.py,sha256=FoobZr7e4jyojF0UE-abZGQOVSsrasDaCoU24XCrxK4,214
|
|
4
|
+
thresher/interface.py,sha256=K4orNGmJc_baCV2BrrCo6iC7FirZUGFwJ1XydMM9uus,3585
|
|
5
|
+
thresher/oracle.py,sha256=hMhyUzPh4k-HLZizy_POELSX7xj8NCRKWGChjnoRcNo,2591
|
|
6
|
+
thresher/utils.py,sha256=Nal9ukEiFranYApqfEylSEa8J6-yZOPlxKIfKUnQuQY,1633
|
|
7
|
+
thresher/algs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
thresher/algs/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
thresher/algs/common/meta_optimizer.py,sha256=rnmlO_OiMldTuc-JSVh3K63imKDAXy-7EHsljh96TE0,301
|
|
10
|
+
thresher/algs/common/stochastic.py,sha256=iwb7jURZTgnYnO3RWJfSBfXaet1LL7u6AyxCzssemWw,752
|
|
11
|
+
thresher/algs/common/tools.py,sha256=u1Ge5hu_flxQXPd4ik3zoRGp-xXRgBUR8K4HwReQ9lo,141
|
|
12
|
+
thresher/algs/genetic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
+
thresher/algs/genetic/compute.py,sha256=86_zuSVPWEMYzh2112bX-bLgXMkrIiqBghW4_OiX0uY,3872
|
|
14
|
+
thresher/algs/grid/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
thresher/algs/grid/compute.py,sha256=L945wLXt2n8V5HEvC7IY355Qn-woljG2lgpxmqPyV9Y,2160
|
|
16
|
+
thresher/algs/linear/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
thresher/algs/linear/compute.py,sha256=QlgGVk6gPkq9srust55UC6EetEdRSO4LhuyUS7fd0fw,2369
|
|
18
|
+
thresher/algs/sgd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
+
thresher/algs/sgd/compute.py,sha256=S3a-lYueKtWyEfsDHLFWp836xOTZdYpr30vfKVxHGGM,2628
|
|
20
|
+
thresher_py-0.2.1.dist-info/METADATA,sha256=09OWQX5oJoMJQBqpmlkI1gEkqs2S2_J5nwtvulq743o,10093
|
|
21
|
+
thresher_py-0.2.1.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
22
|
+
thresher_py-0.2.1.dist-info/licenses/LICENSE,sha256=FRXuRbOPMCNUgzV23MpfHA-3hE-5GsNKT-PP9SbgKNo,1070
|
|
23
|
+
thresher_py-0.2.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Oskar Jarczyk
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|