Beginning nn project

This commit is contained in:
Grant Sanderson 2017-09-26 17:41:45 -07:00
parent bb958b68d7
commit 9958a73059
19 changed files with 2560 additions and 74 deletions

View file

@ -136,9 +136,9 @@ class ShowPassingFlash(ShowPartial):
}
def get_bounds(self, alpha):
alpha *= (1+self.time_width)
alpha -= self.time_width/2
lower = max(0, alpha - self.time_width/2)
upper = min(1, alpha + self.time_width/2)
alpha -= self.time_width/2.0
lower = max(0, alpha - self.time_width/2.0)
upper = min(1, alpha + self.time_width/2.0)
return (lower, upper)
def clean_up(self, *args, **kwargs):
@ -146,6 +146,12 @@ class ShowPassingFlash(ShowPartial):
for submob, start_submob in self.get_all_families_zipped():
submob.pointwise_become_partial(start_submob, 0, 1)
class ShowCreationThenDestruction(ShowPassingFlash):
CONFIG = {
"time_width" : 2.0,
"run_time" : 1,
}
class MoveAlongPath(Animation):
def __init__(self, mobject, vmobject, **kwargs):
digest_config(self, kwargs, locals())

View file

@ -100,7 +100,7 @@ class Swap(CyclicReplace):
class GrowFromPoint(Transform):
def __init__(self, mobject, point, **kwargs):
target = mobject.copy()
target = mobject.deepcopy()
point_mob = Point(point)
mobject.replace(point_mob)
mobject.highlight(point_mob.get_color())

126
camera.py
View file

@ -7,7 +7,7 @@ from colour import Color
import aggdraw
from helpers import *
from mobject import PMobject, VMobject, ImageMobject
from mobject import Mobject, PMobject, VMobject, ImageMobject, Group
class Camera(object):
CONFIG = {
@ -22,6 +22,8 @@ class Camera(object):
"max_allowable_norm" : 2*SPACE_WIDTH,
"image_mode" : "RGBA",
"n_rgb_coords" : 4,
"background_alpha" : 0, #Out of 255
"pixel_array_dtype" : 'uint8'
}
def __init__(self, background = None, **kwargs):
@ -53,27 +55,34 @@ class Camera(object):
#TODO, how to gracefully handle backgrounds
#with different sizes?
self.background = np.array(image)[:height, :width]
self.background = self.background.astype(self.pixel_array_dtype)
else:
background_rgba = color_to_int_rgba(
self.background_color, alpha = 0
self.background_color, alpha = self.background_alpha
)
self.background = np.zeros(
list(self.pixel_shape)+[self.n_rgb_coords],
dtype = 'uint8'
dtype = self.pixel_array_dtype
)
self.background[:,:] = background_rgba
def get_image(self):
return np.array(self.pixel_array)
return Image.fromarray(
self.pixel_array,
mode = self.image_mode
)
def set_image(self, pixel_array):
def get_pixel_array(self):
return self.pixel_array
def set_pixel_array(self, pixel_array):
self.pixel_array = np.array(pixel_array)
def set_background(self, pixel_array):
self.background = np.array(pixel_array)
def reset(self):
self.set_image(np.array(self.background))
self.set_pixel_array(np.array(self.background))
def capture_mobject(self, mobject):
return self.capture_mobjects([mobject])
@ -97,8 +106,12 @@ class Camera(object):
)
elif isinstance(mobject, ImageMobject):
self.display_image_mobject(mobject)
elif isinstance(mobject, Mobject):
pass #Remainder of loop will handle submobjects
else:
raise Exception("Unknown mobject type: " + type(mobject))
raise Exception(
"Unknown mobject type: " + mobject.__class__.__name__
)
#TODO, more? Call out if it's unknown?
self.display_multiple_vectorized_mobjects(vmobjects)
@ -206,52 +219,73 @@ class Camera(object):
ih, iw = impa.shape[:2] #inner with and height
rgb_len = self.pixel_array.shape[2]
# List of all coordinates of pixels, given as (x, y),
# which matches the return type of points_to_pixel_coords,
# even though np.array indexing naturally happens as (y, x)
all_pixel_coords = np.zeros((oh*ow, 2), dtype = 'int')
a = np.arange(oh*ow, dtype = 'int')
all_pixel_coords[:,0] = a%ow
all_pixel_coords[:,1] = a/ow
image = np.zeros((oh, ow, rgb_len), dtype = self.pixel_array_dtype)
recentered_coords = all_pixel_coords - ul_coords
coord_norms = np.linalg.norm(recentered_coords, axis = 1)
if right_vect[1] == 0 and down_vect[0] == 0:
rv0 = right_vect[0]
dv1 = down_vect[1]
x_indices = np.arange(rv0, dtype = 'int')*iw/rv0
y_indices = np.arange(dv1, dtype = 'int')*ih/dv1
stretched_impa = impa[y_indices][:,x_indices]
with np.errstate(divide = 'ignore'):
ix_coords, iy_coords = [
np.divide(
dim*np.dot(recentered_coords, vect),
np.dot(vect, vect),
)
for vect, dim in (right_vect, iw), (down_vect, ih)
]
to_change = reduce(op.and_, [
ix_coords >= 0, ix_coords < iw,
iy_coords >= 0, iy_coords < ih,
])
n_to_change = np.sum(to_change)
inner_flat_coords = iw*iy_coords[to_change] + ix_coords[to_change]
flat_impa = impa.reshape((iw*ih, rgb_len))
target_rgbas = flat_impa[inner_flat_coords, :]
x0, x1 = ul_coords[0], ur_coords[0]
y0, y1 = ul_coords[1], dl_coords[1]
if x0 >= ow or x1 < 0 or y0 >= oh or y1 < 0:
return
siy0 = max(-y0, 0) #stretched_impa y0
siy1 = dv1 - max(y1-oh, 0)
six0 = max(-x0, 0)
six1 = rv0 - max(x1-ow, 0)
x0 = max(x0, 0)
y0 = max(y0, 0)
image[y0:y1, x0:x1] = stretched_impa[siy0:siy1, six0:six1]
else:
# Alternate (slower) tactice if image is tilted
# List of all coordinates of pixels, given as (x, y),
# which matches the return type of points_to_pixel_coords,
# even though np.array indexing naturally happens as (y, x)
all_pixel_coords = np.zeros((oh*ow, 2), dtype = 'int')
a = np.arange(oh*ow, dtype = 'int')
all_pixel_coords[:,0] = a%ow
all_pixel_coords[:,1] = a/ow
image = np.zeros((ow*oh, rgb_len), dtype = 'uint8')
image[to_change] = target_rgbas
image = image.reshape((oh, ow, rgb_len))
recentered_coords = all_pixel_coords - ul_coords
coord_norms = np.linalg.norm(recentered_coords, axis = 1)
with np.errstate(divide = 'ignore'):
ix_coords, iy_coords = [
np.divide(
dim*np.dot(recentered_coords, vect),
np.dot(vect, vect),
)
for vect, dim in (right_vect, iw), (down_vect, ih)
]
to_change = reduce(op.and_, [
ix_coords >= 0, ix_coords < iw,
iy_coords >= 0, iy_coords < ih,
])
n_to_change = np.sum(to_change)
inner_flat_coords = iw*iy_coords[to_change] + ix_coords[to_change]
flat_impa = impa.reshape((iw*ih, rgb_len))
target_rgbas = flat_impa[inner_flat_coords, :]
image = image.reshape((ow*oh, rgb_len))
image[to_change] = target_rgbas
image = image.reshape((oh, ow, rgb_len))
self.overlay_rgba_array(image)
def overlay_rgba_array(self, arr):
""" Overlays arr onto self.pixel_array with relevant alphas"""
# """ Overlays arr onto self.pixel_array with relevant alphas"""
bg, fg = self.pixel_array/255.0, arr/255.0
A = 1 - (1 - bg[:,:,3])*(1 - fg[:,:,3])
alpha_sum = bg[:,:,3] + fg[:,:,3]
for i in range(3):
with np.errstate(divide = 'ignore', invalid='ignore'):
bg[:,:,i] = reduce(op.add, [
np.divide(arr[:,:,i]*arr[:,:,3], alpha_sum)
for arr in fg, bg
])
bg[:,:,3] = A
self.pixel_array = (255*bg).astype('uint8')
bga, fga = [arr[:,:,3:] for arr in bg, fg]
alpha_sum = fga + (1-fga)*bga
with np.errstate(divide = 'ignore', invalid='ignore'):
bg[:,:,:3] = reduce(op.add, [
np.divide(fg[:,:,:3]*fga, alpha_sum),
np.divide(bg[:,:,:3]*bga*(1-fga), alpha_sum),
])
bg[:,:,3:] = 1 - (1 - bga)*(1 - fga)
self.pixel_array = (255*bg).astype(self.pixel_array_dtype)
def align_points_to_camera(self, points):
## This is where projection should live

View file

@ -6,7 +6,8 @@ DEFAULT_WIDTH = 1920
LOW_QUALITY_FRAME_DURATION = 1./15
MEDIUM_QUALITY_FRAME_DURATION = 1./30
PRODUCTION_QUALITY_FRAME_DURATION = 1./60
# PRODUCTION_QUALITY_FRAME_DURATION = 1./60
PRODUCTION_QUALITY_FRAME_DURATION = 1./30
#There might be other configuration than pixel_shape later...
PRODUCTION_QUALITY_CAMERA_CONFIG = {

View file

@ -42,7 +42,7 @@ NO_SCENE_MESSAGE = """
def get_configuration(sys_argv):
try:
opts, args = getopt.getopt(sys_argv[1:], 'hlmpwsqao:')
opts, args = getopt.getopt(sys_argv[1:], 'hlmpwstqao:')
except getopt.GetoptError as err:
print str(err)
sys.exit(2)
@ -55,6 +55,8 @@ def get_configuration(sys_argv):
"write_to_movie" : False,
"save_frames" : False,
"save_image" : False,
#If -t is passed in (for transparent), this will be RGBA
"saved_image_mode": "RGB",
"quiet" : False,
"write_all" : False,
"output_name" : None,
@ -76,6 +78,8 @@ def get_configuration(sys_argv):
config["write_to_movie"] = True
if opt == '-s':
config["save_image"] = True
if opt == '-t':
config["saved_image_mode"] = "RGBA"
if opt in ['-q', '-a']:
config["quiet"] = True
if opt == '-a':
@ -106,7 +110,7 @@ def handle_scene(scene, **config):
if config["save_image"]:
if not config["write_all"]:
scene.show_frame()
scene.save_image()
scene.save_image(mode = config["saved_image_mode"])
if config["quiet"]:
sys.stdout.close()

View file

@ -17,7 +17,8 @@ class ImageMobject(Mobject):
"invert" : False,
# "use_cache" : True,
"height": 2.0,
"image_mode" : "RGBA"
"image_mode" : "RGBA",
"pixel_array_dtype" : "uint8",
}
def __init__(self, filename_or_array, **kwargs):
digest_config(self, kwargs)
@ -27,8 +28,29 @@ class ImageMobject(Mobject):
self.pixel_array = np.array(image)
else:
self.pixel_array = np.array(filename_or_array)
self.change_to_rgba_array()
Mobject.__init__(self, **kwargs)
def change_to_rgba_array(self):
pa = self.pixel_array
if len(pa.shape) == 2:
pa = pa.reshape(list(pa.shape) + [1])
if pa.shape[2] == 1:
pa = pa.repeat(3, axis = 2)
if pa.shape[2] == 3:
alphas = 255*np.ones(
list(pa.shape[:2])+[1],
dtype = self.pixel_array_dtype
)
pa = np.append(pa, alphas, axis = 2)
self.pixel_array = pa
def highlight(self, color, alpha = 1, family = True):
rgba = color_to_int_rgba(color, alpha = int(255*alpha))
self.pixel_array[:,:] = rgba
for submob in self.submobjects:
submob.highlight(color, alpha, family)
return self
def init_points(self):
#Corresponding corners of image are fixed to these
@ -52,4 +74,11 @@ class ImageMobject(Mobject):
return self
def interpolate_color(self, mobject1, mobject2, alpha):
assert(mobject1.pixel_array.shape == mobject2.pixel_array.shape)
self.pixel_array = interpolate(
mobject1.pixel_array, mobject2.pixel_array, alpha
)

View file

@ -87,7 +87,7 @@ class Mobject(object):
from camera import Camera
camera = Camera()
camera.capture_mobject(self)
return Image.fromarray(camera.get_image())
return camera.get_image()
def show(self, camera = None):
self.get_image(camera = camera).show()
@ -611,11 +611,11 @@ class Mobject(object):
if n_rows is not None:
v1 = RIGHT
v2 = DOWN
n = n_rows
n = len(submobs) / n_rows
elif n_cols is not None:
v1 = DOWN
v2 = RIGHT
n = n_cols
n = len(submobs) / n_cols
Group(*[
Group(*submobs[i:i+n]).arrange_submobjects(v1, **kwargs)
for i in range(0, len(submobs), n)

0
nn/__init__.py Normal file
View file

1137
nn/image_map Normal file

File diff suppressed because one or more lines are too long

85
nn/mnist_loader.py Normal file
View file

@ -0,0 +1,85 @@
"""
mnist_loader
~~~~~~~~~~~~
A library to load the MNIST image data. For details of the data
structures that are returned, see the doc strings for ``load_data``
and ``load_data_wrapper``. In practice, ``load_data_wrapper`` is the
function usually called by our neural network code.
"""
#### Libraries
# Standard library
import cPickle
import gzip
# Third-party libraries
import numpy as np
def load_data():
"""Return the MNIST data as a tuple containing the training data,
the validation data, and the test data.
The ``training_data`` is returned as a tuple with two entries.
The first entry contains the actual training images. This is a
numpy ndarray with 50,000 entries. Each entry is, in turn, a
numpy ndarray with 784 values, representing the 28 * 28 = 784
pixels in a single MNIST image.
The second entry in the ``training_data`` tuple is a numpy ndarray
containing 50,000 entries. Those entries are just the digit
values (0...9) for the corresponding images contained in the first
entry of the tuple.
The ``validation_data`` and ``test_data`` are similar, except
each contains only 10,000 images.
This is a nice data format, but for use in neural networks it's
helpful to modify the format of the ``training_data`` a little.
That's done in the wrapper function ``load_data_wrapper()``, see
below.
"""
f = gzip.open('/Users/grant/cs/neural-networks-and-deep-learning/data/mnist.pkl.gz', 'rb')
training_data, validation_data, test_data = cPickle.load(f)
f.close()
return (training_data, validation_data, test_data)
def load_data_wrapper():
"""Return a tuple containing ``(training_data, validation_data,
test_data)``. Based on ``load_data``, but the format is more
convenient for use in our implementation of neural networks.
In particular, ``training_data`` is a list containing 50,000
2-tuples ``(x, y)``. ``x`` is a 784-dimensional numpy.ndarray
containing the input image. ``y`` is a 10-dimensional
numpy.ndarray representing the unit vector corresponding to the
correct digit for ``x``.
``validation_data`` and ``test_data`` are lists containing 10,000
2-tuples ``(x, y)``. In each case, ``x`` is a 784-dimensional
numpy.ndarry containing the input image, and ``y`` is the
corresponding classification, i.e., the digit values (integers)
corresponding to ``x``.
Obviously, this means we're using slightly different formats for
the training data and the validation / test data. These formats
turn out to be the most convenient for use in our neural network
code."""
tr_d, va_d, te_d = load_data()
training_inputs = [np.reshape(x, (784, 1)) for x in tr_d[0]]
training_results = [vectorized_result(y) for y in tr_d[1]]
training_data = zip(training_inputs, training_results)
validation_inputs = [np.reshape(x, (784, 1)) for x in va_d[0]]
validation_data = zip(validation_inputs, va_d[1])
test_inputs = [np.reshape(x, (784, 1)) for x in te_d[0]]
test_data = zip(test_inputs, te_d[1])
return (training_data, validation_data, test_data)
def vectorized_result(j):
"""Return a 10-dimensional unit vector with a 1.0 in the jth
position and zeroes elsewhere. This is used to convert a digit
(0...9) into a corresponding desired output from the neural
network."""
e = np.zeros((10, 1))
e[j] = 1.0
return e

321
nn/network.py Normal file
View file

@ -0,0 +1,321 @@
"""
network.py
~~~~~~~~~~
A module to implement the stochastic gradient descent learning
algorithm for a feedforward neural network. Gradients are calculated
using backpropagation. Note that I have focused on making the code
simple, easily readable, and easily modifiable. It is not optimized,
and omits many desirable features.
"""
#### Libraries
# Standard library
import random
# Third-party libraries
import numpy as np
import os
from PIL import Image
import cPickle
from nn.mnist_loader import load_data_wrapper
NN_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
PRETRAINED_DATA_FILE = os.path.join(NN_DIRECTORY, "pretrained_weights_and_biases")
IMAGE_MAP_DATA_FILE = os.path.join(NN_DIRECTORY, "image_map")
# PRETRAINED_DATA_FILE = "/Users/grant/cs/manim/nn/pretrained_weights_and_biases_on_zero"
DEFAULT_LAYER_SIZES = [28**2, 16, 16, 10]
class Network(object):
def __init__(self, sizes):
"""The list ``sizes`` contains the number of neurons in the
respective layers of the network. For example, if the list
was [2, 3, 1] then it would be a three-layer network, with the
first layer containing 2 neurons, the second layer 3 neurons,
and the third layer 1 neuron. The biases and weights for the
network are initialized randomly, using a Gaussian
distribution with mean 0, and variance 1. Note that the first
layer is assumed to be an input layer, and by convention we
won't set any biases for those neurons, since biases are only
ever used in computing the outputs from later layers."""
self.num_layers = len(sizes)
self.sizes = sizes
self.biases = [np.random.randn(y, 1) for y in sizes[1:]]
self.weights = [np.random.randn(y, x)
for x, y in zip(sizes[:-1], sizes[1:])]
def feedforward(self, a):
"""Return the output of the network if ``a`` is input."""
for b, w in zip(self.biases, self.weights):
a = sigmoid(np.dot(w, a)+b)
return a
def get_activation_of_all_layers(self, input_a, n_layers = None):
if n_layers is None:
n_layers = self.num_layers
activations = [input_a.reshape((input_a.size, 1))]
for bias, weight in zip(self.biases, self.weights)[:n_layers]:
last_a = activations[-1]
new_a = sigmoid(np.dot(weight, last_a) + bias)
new_a = new_a.reshape((new_a.size, 1))
activations.append(new_a)
return activations
def SGD(self, training_data, epochs, mini_batch_size, eta,
test_data=None):
"""Train the neural network using mini-batch stochastic
gradient descent. The ``training_data`` is a list of tuples
``(x, y)`` representing the training inputs and the desired
outputs. The other non-optional parameters are
self-explanatory. If ``test_data`` is provided then the
network will be evaluated against the test data after each
epoch, and partial progress printed out. This is useful for
tracking progress, but slows things down substantially."""
if test_data: n_test = len(test_data)
n = len(training_data)
for j in xrange(epochs):
random.shuffle(training_data)
mini_batches = [
training_data[k:k+mini_batch_size]
for k in xrange(0, n, mini_batch_size)]
for mini_batch in mini_batches:
self.update_mini_batch(mini_batch, eta)
if test_data:
print "Epoch {0}: {1} / {2}".format(
j, self.evaluate(test_data), n_test)
else:
print "Epoch {0} complete".format(j)
def update_mini_batch(self, mini_batch, eta):
"""Update the network's weights and biases by applying
gradient descent using backpropagation to a single mini batch.
The ``mini_batch`` is a list of tuples ``(x, y)``, and ``eta``
is the learning rate."""
nabla_b = [np.zeros(b.shape) for b in self.biases]
nabla_w = [np.zeros(w.shape) for w in self.weights]
for x, y in mini_batch:
delta_nabla_b, delta_nabla_w = self.backprop(x, y)
nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]
self.weights = [w-(eta/len(mini_batch))*nw
for w, nw in zip(self.weights, nabla_w)]
self.biases = [b-(eta/len(mini_batch))*nb
for b, nb in zip(self.biases, nabla_b)]
def backprop(self, x, y):
"""Return a tuple ``(nabla_b, nabla_w)`` representing the
gradient for the cost function C_x. ``nabla_b`` and
``nabla_w`` are layer-by-layer lists of numpy arrays, similar
to ``self.biases`` and ``self.weights``."""
nabla_b = [np.zeros(b.shape) for b in self.biases]
nabla_w = [np.zeros(w.shape) for w in self.weights]
# feedforward
activation = x
activations = [x] # list to store all the activations, layer by layer
zs = [] # list to store all the z vectors, layer by layer
for b, w in zip(self.biases, self.weights):
z = np.dot(w, activation)+b
zs.append(z)
activation = sigmoid(z)
activations.append(activation)
# backward pass
delta = self.cost_derivative(activations[-1], y) * \
sigmoid_prime(zs[-1])
nabla_b[-1] = delta
nabla_w[-1] = np.dot(delta, activations[-2].transpose())
# Note that the variable l in the loop below is used a little
# differently to the notation in Chapter 2 of the book. Here,
# l = 1 means the last layer of neurons, l = 2 is the
# second-last layer, and so on. It's a renumbering of the
# scheme in the book, used here to take advantage of the fact
# that Python can use negative indices in lists.
for l in xrange(2, self.num_layers):
z = zs[-l]
sp = sigmoid_prime(z)
delta = np.dot(self.weights[-l+1].transpose(), delta) * sp
nabla_b[-l] = delta
nabla_w[-l] = np.dot(delta, activations[-l-1].transpose())
return (nabla_b, nabla_w)
def evaluate(self, test_data):
"""Return the number of test inputs for which the neural
network outputs the correct result. Note that the neural
network's output is assumed to be the index of whichever
neuron in the final layer has the highest activation."""
test_results = [(np.argmax(self.feedforward(x)), y)
for (x, y) in test_data]
return sum(int(x == y) for (x, y) in test_results)
def cost_derivative(self, output_activations, y):
"""Return the vector of partial derivatives \partial C_x /
\partial a for the output activations."""
return (output_activations-y)
#### Miscellaneous functions
def sigmoid(z):
"""The sigmoid function."""
return 1.0/(1.0+np.exp(-z))
def sigmoid_prime(z):
"""Derivative of the sigmoid function."""
return sigmoid(z)*(1-sigmoid(z))
def sigmoid_inverse(z):
# z = 0.998*z + 0.001
assert(np.max(z) <= 1.0 and np.min(z) >= 0.0)
z = 0.998*z + 0.001
return np.log(np.true_divide(
1.0, (np.true_divide(1.0, z) - 1)
))
def get_pretrained_network():
data_file = open(PRETRAINED_DATA_FILE)
weights, biases = cPickle.load(data_file)
sizes = [w.shape[1] for w in weights]
sizes.append(weights[-1].shape[0])
network = Network(sizes)
network.weights = weights
network.biases = biases
return network
def save_pretrained_network(epochs = 30, mini_batch_size = 10, eta = 3.0):
network = Network(sizes = DEFAULT_LAYER_SIZES)
training_data, validation_data, test_data = load_data_wrapper()
network.SGD(training_data, epochs, mini_batch_size, eta)
weights_and_biases = (network.weights, network.biases)
data_file = open(PRETRAINED_DATA_FILE, mode = 'w')
cPickle.dump(weights_and_biases, data_file)
data_file.close()
def test_network():
network = get_pretrained_network()
training_data, validation_data, test_data = load_data_wrapper()
n_right, n_wrong = 0, 0
for test_in, test_out in test_data[:30]:
if np.argmax(network.feedforward(test_in)) == test_out:
n_right += 1
else:
n_wrong += 1
print float(n_right)/(n_right + n_wrong)
def layer_to_image_array(layer):
w = int(np.ceil(np.sqrt(len(layer))))
if len(layer) < w**2:
layer = np.append(layer, np.zeros(w**2 - len(layer)))
layer = layer.reshape((w, w))
# return Image.fromarray((255*layer).astype('uint8'))
return (255*layer).astype('int')
def maximizing_input(network, layer_index, layer_vect, n_steps = 100, seed_guess = None):
pre_sig_layer_vect = sigmoid_inverse(layer_vect)
weights, biases = network.weights, network.biases
# guess = np.random.random(weights[0].shape[1])
if seed_guess is not None:
pre_sig_guess = sigmoid_inverse(seed_guess.flatten())
else:
pre_sig_guess = np.random.randn(weights[0].shape[1])
norms = []
for step in xrange(n_steps):
activations = network.get_activation_of_all_layers(
sigmoid(pre_sig_guess), layer_index
)
jacobian = np.diag(sigmoid_prime(pre_sig_guess).flatten())
for W, a, b in zip(weights, activations, biases):
jacobian = np.dot(W, jacobian)
a = a.reshape((a.size, 1))
sp = sigmoid_prime(np.dot(W, a) + b)
jacobian = np.dot(np.diag(sp.flatten()), jacobian)
gradient = np.dot(
np.array(layer_vect).reshape((1, len(layer_vect))),
jacobian
).flatten()
norm = np.linalg.norm(gradient)
if norm == 0:
break
norms.append(norm)
old_pre_sig_guess = np.array(pre_sig_guess)
pre_sig_guess += 0.1*gradient
print np.linalg.norm(old_pre_sig_guess - pre_sig_guess)
print ""
return sigmoid(pre_sig_guess)
def save_organized_images(n_images_per_number = 10):
training_data, validation_data, test_data = load_data_wrapper()
image_map = dict([(k, []) for k in range(10)])
for im, output_arr in training_data:
if min(map(len, image_map.values())) >= n_images_per_number:
break
value = int(np.argmax(output_arr))
if len(image_map[value]) >= n_images_per_number:
continue
image_map[value].append(im)
data_file = open(IMAGE_MAP_DATA_FILE, mode = 'w')
cPickle.dump(image_map, data_file)
data_file.close()
def get_organized_images():
data_file = open(IMAGE_MAP_DATA_FILE, mode = 'r')
image_map = cPickle.load(data_file)
data_file.close()
return image_map
# def maximizing_input(network, layer_index, layer_vect):
# if layer_index == 0:
# return layer_vect
# W = network.weights[layer_index-1]
# n = max(W.shape)
# W_square = np.identity(n)
# W_square[:W.shape[0], :W.shape[1]] = W
# zeros = np.zeros((n - len(layer_vect), 1))
# vect = layer_vect.reshape((layer_vect.shape[0], 1))
# vect = np.append(vect, zeros, axis = 0)
# b = np.append(network.biases[layer_index-1], zeros, axis = 0)
# prev_vect = np.dot(
# np.linalg.inv(W_square),
# (sigmoid_inverse(vect) - b)
# )
# # print layer_vect, sigmoid(np.dot(W, prev_vect)+b)
# print W.shape
# prev_vect = prev_vect[:W.shape[1]]
# prev_vect /= np.max(np.abs(prev_vect))
# # prev_vect /= 1.1
# return maximizing_input(network, layer_index - 1, prev_vect)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

710
nn/scenes.py Normal file
View file

@ -0,0 +1,710 @@
import sys
import os.path
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
from helpers import *
from mobject.tex_mobject import TexMobject
from mobject import Mobject, Group
from mobject.image_mobject import ImageMobject
from mobject.vectorized_mobject import *
from animation.animation import Animation
from animation.transform import *
from animation.simple_animations import *
from animation.playground import *
from animation.continual_animation import *
from topics.geometry import *
from topics.characters import *
from topics.functions import *
from topics.fractals import *
from topics.number_line import *
from topics.combinatorics import *
from topics.numerals import *
from topics.three_dimensions import *
from topics.objects import *
from topics.probability import *
from topics.complex_numbers import *
from scene import Scene
from scene.reconfigurable_scene import ReconfigurableScene
from scene.zoomed_scene import *
from camera import Camera
from mobject.svg_mobject import *
from mobject.tex_mobject import *
from nn.network import *
#force_skipping
#revert_to_original_skipping_status
class WrappedImage(Group):
CONFIG = {
"rect_kwargs" : {
"color" : BLUE,
"buff" : SMALL_BUFF,
}
}
def __init__(self, image_mobject, **kwargs):
Group.__init__(self, **kwargs)
rect = SurroundingRectangle(
image_mobject, **self.rect_kwargs
)
self.add(rect, image_mobject)
class PixelsAsSquares(VGroup):
CONFIG = {
"height" : 2,
}
def __init__(self, image_mobject, **kwargs):
VGroup.__init__(self, **kwargs)
for row in image_mobject.pixel_array:
for rgba in row:
square = Square(
stroke_width = 0,
fill_opacity = rgba[3]/255.0,
fill_color = rgba_to_color(rgba/255.0),
)
self.add(square)
self.arrange_submobjects_in_grid(
*image_mobject.pixel_array.shape[:2],
buff = 0
)
self.replace(image_mobject)
class PixelsFromVect(PixelsAsSquares):
def __init__(self, vect, **kwargs):
PixelsAsSquares.__init__(self,
ImageMobject(layer_to_image_array(vect)),
**kwargs
)
class MNistMobject(WrappedImage):
def __init__(self, vect, **kwargs):
WrappedImage.__init__(self,
ImageMobject(layer_to_image_array(vect)),
**kwargs
)
class NetworkMobject(VGroup):
CONFIG = {
"neuron_radius" : 0.15,
"neuron_to_neuron_buff" : MED_SMALL_BUFF,
"layer_to_layer_buff" : LARGE_BUFF,
"neuron_stroke_color" : BLUE,
"neuron_fill_color" : GREEN,
"edge_color" : LIGHT_GREY,
"edge_stroke_width" : 2,
"max_shown_neurons" : 16,
"brace_for_large_layers" : True,
}
def __init__(self, neural_network, **kwargs):
VGroup.__init__(self, **kwargs)
self.neural_network = neural_network
self.layer_sizes = neural_network.sizes
self.add_neurons()
self.add_edges()
def add_neurons(self):
layers = VGroup(*[
self.get_layer(size)
for size in self.layer_sizes
])
layers.arrange_submobjects(RIGHT, buff = self.layer_to_layer_buff)
self.layers = layers
self.add(self.layers)
def get_layer(self, size):
layer = VGroup()
n_neurons = size
if n_neurons > self.max_shown_neurons:
n_neurons = self.max_shown_neurons
neurons = VGroup(*[
Circle(
radius = self.neuron_radius,
stroke_color = self.neuron_stroke_color,
)
for x in range(n_neurons)
])
neurons.arrange_submobjects(
DOWN, buff = self.neuron_to_neuron_buff
)
for neuron in neurons:
neuron.edges_in = VGroup()
neuron.edges_out = VGroup()
layer.neurons = neurons
layer.add(neurons)
if size > n_neurons:
dots = TexMobject("\\vdots")
dots.move_to(neurons)
VGroup(*neurons[:len(neurons)/2]).next_to(
dots, UP, MED_SMALL_BUFF
)
VGroup(*neurons[len(neurons)/2:]).next_to(
dots, DOWN, MED_SMALL_BUFF
)
layer.dots = dots
layer.add(dots)
if self.brace_for_large_layers:
brace = Brace(layer, LEFT)
brace_label = brace.get_tex(str(size))
layer.brace = brace
layer.brace_label = brace_label
layer.add(brace, brace_label)
return layer
def add_edges(self):
self.edge_groups = VGroup()
for l1, l2 in zip(self.layers[:-1], self.layers[1:]):
edge_group = VGroup()
for n1, n2 in it.product(l1.neurons, l2.neurons):
edge = Line(
n1.get_center(),
n2.get_center(),
buff = self.neuron_radius,
stroke_color = self.edge_color,
stroke_width = self.edge_stroke_width,
)
edge_group.add(edge)
n1.edges_out.add(edge)
n2.edges_in.add(edge)
self.edge_groups.add(edge_group)
self.add_to_back(self.edge_groups)
def get_active_layer(self, layer_index, activation_vector):
layer = self.layers[layer_index].deepcopy()
n_neurons = len(layer.neurons)
av = activation_vector
if len(av) > n_neurons:
av = av[av > 0][:n_neurons]
for activation, neuron in zip(av, layer.neurons):
neuron.set_fill(
color = self.neuron_fill_color,
opacity = activation
)
return layer
def deactivate_layers(self):
all_neurons = VGroup(*it.chain(*[
layer.neurons
for layer in self.layers
]))
all_neurons.set_fill(opacity = 0)
return self
def get_edge_propogation_animations(self, index):
edge_group_copy = self.edge_groups[index].copy()
edge_group_copy.set_stroke(
self.neuron_fill_color,
width = 1.5*self.edge_stroke_width
)
return map(ShowCreationThenDestruction, edge_group_copy)
class MNistNetworkMobject(NetworkMobject):
CONFIG = {
"neuron_to_neuron_buff" : SMALL_BUFF,
"layer_to_layer_buff" : 1.5,
"edge_stroke_width" : 1,
}
def __init__(self, **kwargs):
network = get_pretrained_network()
NetworkMobject.__init__(self, network, **kwargs)
self.add_output_labels()
def add_output_labels(self):
self.output_labels = VGroup()
for n, neuron in enumerate(self.layers[-1].neurons):
label = TexMobject(str(n))
label.scale_to_fit_height(0.75*neuron.get_height())
label.next_to(neuron, RIGHT, SMALL_BUFF)
self.output_labels.add(label)
self.add(self.output_labels)
class NetworkScene(Scene):
CONFIG = {
"layer_sizes" : [8, 6, 6, 4]
}
def setup(self):
self.add_network()
def add_network(self):
self.network = Network(sizes = self.layer_sizes)
self.network_mob = NetworkMobject(self.network)
self.add(self.network_mob)
def feed_forward(self, input_vector, false_confidence = False):
activations = self.network.get_activation_of_all_layers(
input_vector
)
if false_confidence:
i = np.argmax(activations[-1])
activations[-1] *= 0
activations[-1][i] = 1.0
for i, activation in enumerate(activations):
self.show_activation_of_layer(i, activation)
def show_activation_of_layer(self, layer_index, activation_vector):
layer = self.network_mob.layers[layer_index]
active_layer = self.network_mob.get_active_layer(
layer_index, activation_vector
)
anims = [Transform(layer, active_layer)]
if layer_index > 0:
anims += self.network_mob.get_edge_propogation_animations(
layer_index-1
)
self.play(*anims)
###############################
class ExampleThrees(PiCreatureScene):
def construct(self):
self.show_initial_three()
self.show_alternate_threes()
self.resolve_remaining_threes()
self.show_alternate_digits()
def show_initial_three(self):
randy = self.pi_creature
self.three_mobs = self.get_three_mobs()
three_mob = self.three_mobs[0]
three_mob_copy = three_mob[1].copy()
three_mob_copy.sort_submobjects(lambda p : np.dot(p, DOWN+RIGHT))
braces = VGroup(*[Brace(three_mob, v) for v in LEFT, UP])
brace_labels = VGroup(*[
brace.get_text("28px")
for brace in braces
])
bubble = randy.get_bubble(height = 4, width = 6)
three_mob.generate_target()
three_mob.target.scale_to_fit_height(1)
three_mob.target.next_to(bubble[-1].get_left(), RIGHT, LARGE_BUFF)
arrow = Arrow(LEFT, RIGHT, color = BLUE)
arrow.next_to(three_mob.target, RIGHT)
real_three = TexMobject("3")
real_three.scale_to_fit_height(0.8)
real_three.next_to(arrow, RIGHT)
self.play(
FadeIn(three_mob[0]),
LaggedStart(FadeIn, three_mob[1])
)
self.dither()
self.play(
LaggedStart(
DrawBorderThenFill, three_mob_copy,
run_time = 3,
stroke_color = WHITE,
remover = True,
),
randy.change, "sassy",
*it.chain(
map(GrowFromCenter, braces),
map(FadeIn, brace_labels)
)
)
self.dither()
self.play(
ShowCreation(bubble),
MoveToTarget(three_mob),
FadeOut(braces),
FadeOut(brace_labels),
randy.change, "pondering"
)
self.play(
ShowCreation(arrow),
Write(real_three)
)
self.dither()
self.bubble = bubble
self.arrow = arrow
self.real_three = real_three
def show_alternate_threes(self):
randy = self.pi_creature
three = self.three_mobs[0]
three.generate_target()
three.target[0].set_fill(opacity = 0, family = False)
for square in three.target[1]:
yellow_rgb = color_to_rgb(YELLOW)
square_rgb = color_to_rgb(square.get_fill_color())
square.set_fill(
rgba_to_color(yellow_rgb*square_rgb),
opacity = 0.5
)
alt_threes = VGroup(*self.three_mobs[1:])
alt_threes.arrange_submobjects(DOWN)
alt_threes.scale_to_fit_height(2*SPACE_HEIGHT - 2)
alt_threes.to_edge(RIGHT)
for alt_three in alt_threes:
self.add(alt_three)
self.dither(0.5)
self.play(
randy.change, "plain",
*map(FadeOut, [
self.bubble, self.arrow, self.real_three
]) + [MoveToTarget(three)]
)
for alt_three in alt_threes[:2]:
self.play(three.replace, alt_three)
self.dither()
for moving_three in three, alt_threes[1]:
moving_three.generate_target()
moving_three.target.next_to(alt_threes, LEFT, LARGE_BUFF)
moving_three.target[0].set_stroke(width = 0)
moving_three.target[1].space_out_submobjects(1.5)
self.play(MoveToTarget(
moving_three, submobject_mode = "lagged_start"
))
self.play(
Animation(randy),
moving_three.replace, randy.eyes[1],
moving_three.scale_in_place, 0.7,
run_time = 2,
submobject_mode = "lagged_start",
)
self.play(
Animation(randy),
FadeOut(moving_three)
)
self.remaining_threes = [alt_threes[0], alt_threes[2]]
def resolve_remaining_threes(self):
randy = self.pi_creature
left_three, right_three = self.remaining_threes
equals = TexMobject("=")
equals.move_to(self.arrow)
for three, vect in (left_three, LEFT), (right_three, RIGHT):
three.generate_target()
three.target.scale_to_fit_height(1)
three.target.next_to(equals, vect)
self.play(
randy.change, "thinking",
ShowCreation(self.bubble),
MoveToTarget(left_three),
MoveToTarget(right_three),
Write(equals),
)
self.dither()
self.equals = equals
def show_alternate_digits(self):
randy = self.pi_creature
cross = Cross(self.equals)
cross.stretch_to_fit_height(0.5)
three = self.remaining_threes[1]
image_map = get_organized_images()
arrays = [image_map[k][0] for k in range(8, 4, -1)]
alt_mobs = [
WrappedImage(
PixelsAsSquares(ImageMobject(layer_to_image_array(arr))),
color = LIGHT_GREY,
buff = 0
).replace(three)
for arr in arrays
]
self.play(
randy.change, "sassy",
Transform(three, alt_mobs[0]),
ShowCreation(cross)
)
self.dither()
for mob in alt_mobs[1:]:
self.play(Transform(three, mob))
self.dither()
######
def create_pi_creature(self):
return Randolph().to_corner(DOWN+LEFT)
def get_three_mobs(self):
three_arrays = get_organized_images()[3][:4]
three_mobs = VGroup()
for three_array in three_arrays:
im_mob = ImageMobject(
layer_to_image_array(three_array),
height = 4,
)
pixel_mob = PixelsAsSquares(im_mob)
three_mob = WrappedImage(
pixel_mob,
color = LIGHT_GREY,
buff = 0
)
three_mobs.add(three_mob)
return three_mobs
class WriteAProgram(Scene):
def construct(self):
three_array = get_organized_images()[3][0]
im_mob = ImageMobject(layer_to_image_array(three_array))
three = PixelsAsSquares(im_mob)
three.sort_submobjects(lambda p : np.dot(p, DOWN+RIGHT))
three.scale_to_fit_height(6)
three.next_to(ORIGIN, LEFT)
three_rect = SurroundingRectangle(
three,
color = BLUE,
buff = SMALL_BUFF
)
numbers = VGroup()
for square in three:
rgb = square.fill_rgb
num = DecimalNumber(
square.fill_rgb[0],
num_decimal_points = 1
)
num.set_stroke(width = 1)
color = rgba_to_color(1 - (rgb + 0.2)/1.2)
num.highlight(color)
num.scale_to_fit_width(0.7*square.get_width())
num.move_to(square)
numbers.add(num)
arrow = Arrow(LEFT, RIGHT, color = BLUE)
arrow.next_to(three, RIGHT)
choices = VGroup(*[TexMobject(str(n)) for n in range(10)])
choices.arrange_submobjects(DOWN)
choices.scale_to_fit_height(2*SPACE_HEIGHT - 1)
choices.next_to(arrow, RIGHT)
self.play(
LaggedStart(DrawBorderThenFill, three),
ShowCreation(three_rect)
)
self.play(Write(numbers))
self.play(
ShowCreation(arrow),
LaggedStart(FadeIn, choices),
)
rect = SurroundingRectangle(choices[0], buff = SMALL_BUFF)
q_mark = TexMobject("?")
q_mark.next_to(rect, RIGHT)
self.play(ShowCreation(rect))
for n in 8, 1, 5, 3:
self.play(
rect.move_to, choices[n],
MaintainPositionRelativeTo(q_mark, rect)
)
self.dither(1)
choice = choices[3]
choices.remove(choice)
choice.add(rect)
self.play(
choice.scale, 1.5,
choice.next_to, arrow, RIGHT,
FadeOut(choices),
FadeOut(q_mark),
)
self.dither(2)
class LayOutPlan(TeacherStudentsScene, NetworkScene):
def setup(self):
TeacherStudentsScene.setup(self)
NetworkScene.setup(self)
self.remove(self.network_mob)
def construct(self):
self.show_words()
self.show_network()
self.show_math()
self.ask_about_layers()
self.show_learning()
def show_words(self):
words = VGroup(
TextMobject("Machine", "learning").highlight(GREEN),
TextMobject("Neural network").highlight(BLUE),
)
words.next_to(self.teacher.get_corner(UP+LEFT), UP)
words[0].save_state()
words[0].shift(DOWN)
words[0].fade(1)
self.play(
words[0].restore,
self.teacher.change, "raise_right_hand",
self.get_student_changes("pondering", "erm", "sassy")
)
self.play(
words[0].shift, MED_LARGE_BUFF*UP,
FadeIn(words[1]),
)
self.change_student_modes(
*["pondering"]*3,
look_at_arg = words
)
self.play(words.to_corner, UP+RIGHT)
self.words = words
def show_network(self):
network_mob = self.network_mob
network_mob.next_to(self.students, UP)
self.play(
ReplacementTransform(
VGroup(self.words[1].copy()),
network_mob.layers
),
self.get_student_changes(
*["confused"]*3,
submobject_mode = "all_at_once"
),
run_time = 1
)
self.play(ShowCreation(
network_mob.edge_groups,
submobject_mode = "lagged_start",
run_time = 2,
lag_factor = 8,
rate_func = None,
))
self.play(self.teacher.change, "plain")
in_vect = np.random.random(self.network.sizes[0])
self.feed_forward(in_vect)
self.dither()
def show_math(self):
equation = TexMobject(
"\\textbf{a}_{l+1}", "=",
"\\sigma(",
"W_l", "\\textbf{a}_l", "+", "b_l",
")"
)
equation.highlight_by_tex_to_color_map({
"\\textbf{a}" : GREEN,
})
equation.move_to(self.network_mob.get_corner(UP+RIGHT))
equation.to_edge(UP)
self.play(Write(equation, run_time = 2))
self.dither()
def ask_about_layers(self):
self.student_says(
"Why the layers?",
student_index = 2,
bubble_kwargs = {"direction" : LEFT}
)
self.dither()
self.play(RemovePiCreatureBubble(self.students[2]))
def show_learning(self):
rect = SurroundingRectangle(self.words[0][1], color = YELLOW)
self.network_mob.neuron_fill_color = YELLOW
layer = self.network_mob.layers[-1]
activation = np.zeros(len(layer))
activation[1] = 1.0
active_layer = self.network_mob.get_active_layer(
-1, activation
)
self.play(ShowCreation(rect))
self.play(Transform(layer, active_layer))
for edge_group in reversed(self.network_mob.edge_groups):
edge_group.generate_target()
for edge in edge_group.target:
edge.set_stroke(
YELLOW,
width = 4*np.random.random()**2
)
self.play(MoveToTarget(edge_group))
self.dither()
class PreviewMNistNetwork(NetworkScene):
def construct(self):
training_data, validation_data, test_data = load_data_wrapper()
for data in test_data[:1]:
self.feed_in_image(data[0])
def feed_in_image(self, in_vect):
image = PixelsFromVect(in_vect)
image.next_to(self.network_mob, LEFT, LARGE_BUFF, UP)
rect = SurroundingRectangle(image, color = BLUE)
self.play(FadeIn(image), FadeIn(rect))
print in_vect.shape
# self.feed_forward(in_vect)
# self.play(FadeOut(image))
###
def add_network(self):
self.network_mob = MNistNetworkMobject()
self.network = self.network_mob.neural_network
self.add(self.network_mob)

View file

@ -90,10 +90,13 @@ class Scene(object):
self.camera = camera
def get_frame(self):
return np.array(self.camera.get_pixel_array())
def get_image(self):
return self.camera.get_image()
def set_camera_image(self, pixel_array):
self.camera.set_image(pixel_array)
def set_camera_pixel_array(self, pixel_array):
self.camera.set_pixel_array(pixel_array)
def set_camera_background(self, background):
self.camera.set_background(background)
@ -113,7 +116,7 @@ class Scene(object):
self.mobjects,
)
if background is not None:
self.set_camera_image(background)
self.set_camera_pixel_array(background)
else:
self.reset_camera()
@ -439,19 +442,21 @@ class Scene(object):
def show_frame(self):
self.update_frame()
Image.fromarray(self.get_frame()).show()
self.get_image().show()
def preview(self):
TkSceneRoot(self)
def save_image(self, name = None):
def save_image(self, name = None, mode = "RGB"):
path = os.path.join(self.output_directory, "images")
file_name = (name or str(self)) + ".png"
full_path = os.path.join(path, file_name)
if not os.path.exists(path):
os.makedirs(path)
self.update_frame()
Image.fromarray(self.get_frame()).save(full_path)
image = self.get_image()
image = image.convert(mode)
image.save(full_path)
def get_movie_file_path(self, name, extension):
file_path = os.path.join(self.output_directory, name)

View file

@ -34,7 +34,7 @@ class TkSceneRoot(Tkinter.Tk):
self.mainloop()
def show_new_image(self, frame):
image = Image.fromarray(frame).convert('RGB')
image = Image.fromarray(frame.astype('uint8')).convert('RGB')
photo = ImageTk.PhotoImage(image)
self.canvas.delete(Tkinter.ALL)
self.canvas.create_image(

View file

@ -99,10 +99,10 @@ class ZoomedScene(Scene):
return frame
def set_camera_image(self, pixel_array):
self.camera.set_image(pixel_array)
self.camera.set_pixel_array(pixel_array)
if self.zoom_activated:
(up, left), (down, right) = self.zoomed_canvas_pixel_indices
self.zoomed_camera.set_image(pixel_array[left:right, up:down])
self.zoomed_camera.set_pixel_array(pixel_array[left:right, up:down])
def set_camera_background(self, background):
self.set_camera_image(self, background)

View file

@ -14,6 +14,8 @@
\usepackage{wasysym}
\usepackage{ragged2e}
\usepackage{physics}
\usepackage{xcolor}
\usepackage[UTF8]{ctex}
\begin{document}

View file

@ -648,7 +648,13 @@ class TeacherStudentsScene(PiCreatureScene):
return self.pi_creature_thinks(student, *content, **kwargs)
def change_student_modes(self, *modes, **kwargs):
added_anims = kwargs.get("added_anims", [])
added_anims = kwargs.pop("added_anims", [])
self.play(
self.get_student_changes(*modes, **kwargs),
*added_anims
)
def get_student_changes(self, *modes, **kwargs):
pairs = zip(self.get_students(), modes)
pairs = [(s, m) for s, m in pairs if m is not None]
start = VGroup(*[s for s, m in pairs])
@ -656,13 +662,11 @@ class TeacherStudentsScene(PiCreatureScene):
if "look_at_arg" in kwargs:
for pi in target:
pi.look_at(kwargs["look_at_arg"])
self.play(
Transform(
start, target,
submobject_mode = "lagged_start",
run_time = 2
),
*added_anims
submobject_mode = kwargs.get("submobject_mode", "lagged_start")
return Transform(
start, target,
submobject_mode = submobject_mode,
run_time = 2
)
def zoom_in_on_thought_bubble(self, bubble = None, radius = SPACE_HEIGHT+SPACE_WIDTH):