Initial commit
This commit is contained in:
commit
8542a1f9ef
59 files changed
+73464
No files matched your search
Whitespace-only changes.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,183 @@
|
||||
import sys
|
||||
sys.path.insert(1, '../')
|
||||
from utils.utils import *
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
### the ABC of data set
|
||||
class DataSet:
|
||||
"""
|
||||
A data set for a machine learning problem. It has the following fields:
|
||||
|
||||
d.examples A list of examples. Each one is a list of attribute values.
|
||||
d.attrs A list of integers to index into an example, so example[attr]
|
||||
gives a value. Normally the same as range(len(d.examples[0])).
|
||||
d.attr_names Optional list of mnemonic names for corresponding attrs.
|
||||
d.target The attribute that a learning algorithm will try to predict.
|
||||
By default the final attribute.
|
||||
d.inputs The list of attrs without the target.
|
||||
d.values A list of lists: each sublist is the set of possible
|
||||
values for the corresponding attribute. If initially None,
|
||||
it is computed from the known examples by self.set_problem.
|
||||
If not None, an erroneous value raises ValueError.
|
||||
d.distance A function from a pair of examples to a non-negative number.
|
||||
Should be symmetric, etc. Defaults to mean_boolean_error
|
||||
since that can handle any field types.
|
||||
d.name Name of the data set (for output display only).
|
||||
d.source URL or other source where the data came from.
|
||||
d.exclude A list of attribute indexes to exclude from d.inputs. Elements
|
||||
of this list can either be integers (attrs) or attr_names.
|
||||
|
||||
Normally, you call the constructor and you're done; then you just
|
||||
access fields like d.examples and d.target and d.inputs.
|
||||
"""
|
||||
|
||||
def __init__(self, examples=None, attrs=None, attr_names=None, target=-1, inputs=None,
|
||||
values=None, distance=mean_boolean_error, name='', source='', exclude=()):
|
||||
"""
|
||||
Accepts any of DataSet's fields. Examples can also be a
|
||||
string or file from which to parse examples using parse_csv.
|
||||
Optional parameter: exclude, as documented in .set_problem().
|
||||
>>> DataSet(examples='1, 2, 3')
|
||||
<DataSet(): 1 examples, 3 attributes>
|
||||
"""
|
||||
self.name = name
|
||||
self.source = source
|
||||
self.values = values
|
||||
self.distance = distance
|
||||
self.got_values_flag = bool(values)
|
||||
|
||||
# initialize .examples from string or list or data directory
|
||||
if isinstance(examples, str):
|
||||
self.examples = parse_csv(examples)
|
||||
elif examples is None:
|
||||
self.examples = parse_csv(open_data(name + '.csv').read())
|
||||
else:
|
||||
self.examples = examples
|
||||
|
||||
# attrs are the indices of examples, unless otherwise stated.
|
||||
if self.examples is not None and attrs is None:
|
||||
attrs = list(range(len(self.examples[0])))
|
||||
|
||||
self.attrs = attrs
|
||||
|
||||
# initialize .attr_names from string, list, or by default
|
||||
if isinstance(attr_names, str):
|
||||
self.attr_names = attr_names.split()
|
||||
else:
|
||||
self.attr_names = attr_names or attrs
|
||||
self.set_problem(target, inputs=inputs, exclude=exclude)
|
||||
|
||||
def set_problem(self, target, inputs=None, exclude=()):
|
||||
"""
|
||||
Set (or change) the target and/or inputs.
|
||||
This way, one DataSet can be used multiple ways. inputs, if specified,
|
||||
is a list of attributes, or specify exclude as a list of attributes
|
||||
to not use in inputs. Attributes can be -n .. n, or an attr_name.
|
||||
Also computes the list of possible values, if that wasn't done yet.
|
||||
"""
|
||||
self.target = self.attr_num(target)
|
||||
exclude = list(map(self.attr_num, exclude))
|
||||
if inputs:
|
||||
self.inputs = remove_all(self.target, inputs)
|
||||
else:
|
||||
self.inputs = [a for a in self.attrs if a != self.target and a not in exclude]
|
||||
if not self.values:
|
||||
self.update_values()
|
||||
self.check_me()
|
||||
|
||||
def check_me(self):
|
||||
"""Check that my fields make sense."""
|
||||
assert len(self.attr_names) == len(self.attrs)
|
||||
assert self.target in self.attrs
|
||||
assert self.target not in self.inputs
|
||||
assert set(self.inputs).issubset(set(self.attrs))
|
||||
if self.got_values_flag:
|
||||
# only check if values are provided while initializing DataSet
|
||||
list(map(self.check_example, self.examples))
|
||||
|
||||
def add_example(self, example):
|
||||
"""Add an example to the list of examples, checking it first."""
|
||||
self.check_example(example)
|
||||
self.examples.append(example)
|
||||
|
||||
def check_example(self, example):
|
||||
"""Raise ValueError if example has any invalid values."""
|
||||
if self.values:
|
||||
for a in self.attrs:
|
||||
if example[a] not in self.values[a]:
|
||||
raise ValueError('Bad value {} for attribute {} in {}'
|
||||
.format(example[a], self.attr_names[a], example))
|
||||
|
||||
def attr_num(self, attr):
|
||||
"""Returns the number used for attr, which can be a name, or -n .. n-1."""
|
||||
if isinstance(attr, str):
|
||||
return self.attr_names.index(attr)
|
||||
elif attr < 0:
|
||||
return len(self.attrs) + attr
|
||||
else:
|
||||
return attr
|
||||
|
||||
def update_values(self):
|
||||
self.values = list(map(unique, zip(*self.examples)))
|
||||
|
||||
def sanitize(self, example):
|
||||
"""Return a copy of example, with non-input attributes replaced by None."""
|
||||
return [attr_i if i in self.inputs else None for i, attr_i in enumerate(example)][:-1]
|
||||
|
||||
def classes_to_numbers(self, classes=None):
|
||||
"""Converts class names to numbers."""
|
||||
if not classes:
|
||||
# if classes were not given, extract them from values
|
||||
classes = sorted(self.values[self.target])
|
||||
for item in self.examples:
|
||||
item[self.target] = classes.index(item[self.target])
|
||||
|
||||
def remove_examples(self, value=''):
|
||||
"""Remove examples that contain given value."""
|
||||
self.examples = [x for x in self.examples if value not in x]
|
||||
self.update_values()
|
||||
|
||||
def split_values_by_classes(self):
|
||||
"""Split values into buckets according to their class."""
|
||||
buckets = defaultdict(lambda: [])
|
||||
target_names = self.values[self.target]
|
||||
|
||||
for v in self.examples:
|
||||
item = [a for a in v if a not in target_names] # remove target from item
|
||||
buckets[v[self.target]].append(item) # add item to bucket of its class
|
||||
|
||||
return buckets
|
||||
|
||||
def find_means_and_deviations(self):
|
||||
"""
|
||||
Finds the means and standard deviations of self.dataset.
|
||||
means : a dictionary for each class/target. Holds a list of the means
|
||||
of the features for the class.
|
||||
deviations: a dictionary for each class/target. Holds a list of the sample
|
||||
standard deviations of the features for the class.
|
||||
"""
|
||||
target_names = self.values[self.target]
|
||||
feature_numbers = len(self.inputs)
|
||||
|
||||
item_buckets = self.split_values_by_classes()
|
||||
|
||||
means = defaultdict(lambda: [0] * feature_numbers)
|
||||
deviations = defaultdict(lambda: [0] * feature_numbers)
|
||||
|
||||
for t in target_names:
|
||||
# find all the item feature values for item in class t
|
||||
features = [[] for _ in range(feature_numbers)]
|
||||
for item in item_buckets[t]:
|
||||
for i in range(feature_numbers):
|
||||
features[i].append(item[i])
|
||||
|
||||
# calculate means and deviations fo the class
|
||||
for i in range(feature_numbers):
|
||||
means[t][i] = mean(features[i])
|
||||
deviations[t][i] = stdev(features[i])
|
||||
|
||||
return means, deviations
|
||||
|
||||
def __repr__(self):
|
||||
return '<DataSet({}): {:d} examples, {:d} attributes>'.format(self.name, len(self.examples), len(self.attrs))
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
### system related operations
|
||||
import os
|
||||
from inspect import getsource
|
||||
from IPython.display import HTML
|
||||
from IPython.display import display
|
||||
|
||||
|
||||
def psource(*functions):
|
||||
"""Print the source code for the given function(s)."""
|
||||
source_code = '\n\n'.join(getsource(fn) for fn in functions)
|
||||
try:
|
||||
from pygments.formatters import HtmlFormatter
|
||||
from pygments.lexers import PythonLexer
|
||||
from pygments import highlight
|
||||
|
||||
display(HTML(highlight(source_code, PythonLexer(), HtmlFormatter(full=True))))
|
||||
|
||||
except ImportError:
|
||||
print(source_code)
|
||||
|
||||
|
||||
def parse_csv(input, delim=','):
|
||||
r"""
|
||||
Input is a string consisting of lines, each line has comma-delimited
|
||||
fields. Convert this into a list of lists. Blank lines are skipped.
|
||||
Fields that look like numbers are converted to numbers.
|
||||
The delim defaults to ',' but '\t' and None are also reasonable values.
|
||||
>>> parse_csv('1, 2, 3 \n 0, 2, na')
|
||||
[[1, 2, 3], [0, 2, 'na']]
|
||||
"""
|
||||
lines = [line for line in input.splitlines() if line.strip()]
|
||||
return [list(map(num_or_str, line.split(delim))) for line in lines]
|
||||
|
||||
|
||||
def open_data(name, mode='r'):
|
||||
data_root = os.path.dirname(__file__)
|
||||
data_file = os.path.join(data_root, *[os.pardir, 'data', name])
|
||||
|
||||
return open(data_file, mode=mode)
|
||||
|
||||
|
||||
### math and data structure
|
||||
import numpy as np
|
||||
import math
|
||||
from statistics import mean, stdev
|
||||
import random
|
||||
|
||||
|
||||
def normalize(dist):
|
||||
"""Multiply each number by a constant such that the sum is 1.0"""
|
||||
if isinstance(dist, dict):
|
||||
total = sum(dist.values())
|
||||
for key in dist:
|
||||
dist[key] = dist[key] / total
|
||||
assert 0 <= dist[key] <= 1 # probabilities must be between 0 and 1
|
||||
return dist
|
||||
total = sum(dist)
|
||||
return [(n / total) for n in dist]
|
||||
|
||||
|
||||
def random_weights(min_value, max_value, num_weights):
|
||||
return [random.uniform(min_value, max_value) for _ in range(num_weights)]
|
||||
|
||||
|
||||
def sigmoid(x):
|
||||
"""Return activation value of x with sigmoid function."""
|
||||
return 1 / (1 + np.exp(-x))
|
||||
|
||||
|
||||
def sigmoid_derivative(value):
|
||||
return value * (1 - value)
|
||||
|
||||
|
||||
def remove_all(item, seq):
|
||||
"""Return a copy of seq (or string) with all occurrences of item removed."""
|
||||
if isinstance(seq, str):
|
||||
return seq.replace(item, '')
|
||||
elif isinstance(seq, set):
|
||||
rest = seq.copy()
|
||||
rest.remove(item)
|
||||
return rest
|
||||
else:
|
||||
return [x for x in seq if x != item]
|
||||
|
||||
|
||||
def unique(seq):
|
||||
"""Remove duplicate elements from seq. Assumes hashable elements."""
|
||||
return list(set(seq))
|
||||
|
||||
|
||||
def num_or_str(x): # TODO: rename as `atom`
|
||||
"""The argument is a string; convert to a number if
|
||||
possible, or strip it."""
|
||||
try:
|
||||
return int(x)
|
||||
except ValueError:
|
||||
try:
|
||||
return float(x)
|
||||
except ValueError:
|
||||
return str(x).strip()
|
||||
|
||||
|
||||
def euclidean_distance(x, y):
|
||||
return np.sqrt(sum((_x - _y) ** 2 for _x, _y in zip(x, y)))
|
||||
|
||||
|
||||
def manhattan_distance(x, y):
|
||||
return sum(abs(_x - _y) for _x, _y in zip(x, y))
|
||||
|
||||
|
||||
def hamming_distance(x, y):
|
||||
return sum(_x != _y for _x, _y in zip(x, y))
|
||||
|
||||
|
||||
def rms_error(x, y):
|
||||
return np.sqrt(ms_error(x, y))
|
||||
|
||||
|
||||
def ms_error(x, y):
|
||||
return mean((x - y) ** 2 for x, y in zip(x, y))
|
||||
|
||||
|
||||
def mean_error(x, y):
|
||||
return mean(abs(x - y) for x, y in zip(x, y))
|
||||
|
||||
|
||||
def mean_boolean_error(x, y):
|
||||
return mean(_x != _y for _x, _y in zip(x, y))
|
||||
|
||||
|
||||
identity = lambda x: x
|
||||
|
||||
|
||||
def argmin_random_tie(seq, key=identity):
|
||||
"""Return a minimum element of seq; break ties at random."""
|
||||
return min(shuffled(seq), key=key)
|
||||
|
||||
|
||||
def argmax_random_tie(seq, key=identity):
|
||||
"""Return an element with highest fn(seq[i]) score; break ties at random."""
|
||||
return max(shuffled(seq), key=key)
|
||||
|
||||
|
||||
def shuffled(iterable):
|
||||
"""Randomly shuffle a copy of iterable."""
|
||||
items = list(iterable)
|
||||
random.shuffle(items)
|
||||
return items
|
||||
|
||||
|
||||
### learner related operations
|
||||
class Activation:
|
||||
|
||||
def function(self, x):
|
||||
return NotImplementedError
|
||||
|
||||
def derivative(self, x):
|
||||
return NotImplementedError
|
||||
|
||||
def __call__(self, x):
|
||||
return self.function(x)
|
||||
|
||||
|
||||
class Sigmoid(Activation):
|
||||
|
||||
def function(self, x):
|
||||
return 1 / (1 + np.exp(-x))
|
||||
|
||||
def derivative(self, value):
|
||||
return value * (1 - value)
|
||||
|
||||
|
||||
def err_ratio(learner, dataset, examples=None):
|
||||
"""
|
||||
Return the proportion of the examples that are NOT correctly predicted.
|
||||
verbose - 0: No output; 1: Output wrong; 2 (or greater): Output correct
|
||||
"""
|
||||
examples = examples or dataset.examples
|
||||
if len(examples) == 0:
|
||||
return 0.0
|
||||
right = 0
|
||||
for example in examples:
|
||||
desired = example[dataset.target]
|
||||
output = learner.predict(dataset.sanitize(example))
|
||||
if output == desired:
|
||||
right += 1
|
||||
return 1 - (right / len(examples))
|
||||
|
||||
|
||||
def grade_learner(learner, tests):
|
||||
"""
|
||||
Grades the given learner based on how many tests it passes.
|
||||
tests is a list with each element in the form: (values, output).
|
||||
"""
|
||||
# for X, y in tests:
|
||||
# print(learner.predict(X), y)
|
||||
return mean([int(learner.predict(X) == y) for X, y in tests])
|
||||
|
||||
Reference in new issue
Block a user