Deep Neural Net implementation in Python3CUDA Kernel - Neural NetNeural Net XOR Genetic AlgorithmsTheano code for 2-layer neural net for mnistNeural net in C++Backpropagation in simple Neural NetworkMNIST Deep Neural Network in TensorFlowSelf-written Neural NetworkBack Propagation Implementation in Python for Deep Neural NetworkDeep Neural Network in PythonSuper-minimal implementation for inference only of fully a connected neural network in Python + Numpy

What kind of SATA connector is this?

Automatically anti-predictably assemble an alliterative aria

declared variable inside void setup is forgotten in void loop

Could there be a material that inverts the colours seen through it?

Anabelian geometry ~ higher category theory

Earliest use of "rookie"?

Why does the headset man not get on the tractor?

Area under the curve - Integrals (Antiderivatives)

Is Germany still exporting arms to countries involved in Yemen?

Can I say: "When was your train leaving?" if the train leaves in the future?

In books, how many dragons are there in present time?

CPLD based Pierce oscillator

Why doesn't Rocket Lab use a solid stage?

How to translate "Keywords" to Spanish "Palabras claves"

What is the largest number of identical satellites launched together?

What information do scammers need to withdraw money from an account?

Was this character’s old age look CGI or make-up?

Jesus' words on the Jews

On what legal basis did the UK remove the 'European Union' from its passport?

Rounding a number extracted by jq to limit the decimal points

Loading Latex packages into Mathematica

Can I use my laptop, which says 100-240V, in the USA?

Extracting sublists that contain similar elements

Interior smooth regularity



Deep Neural Net implementation in Python3


CUDA Kernel - Neural NetNeural Net XOR Genetic AlgorithmsTheano code for 2-layer neural net for mnistNeural net in C++Backpropagation in simple Neural NetworkMNIST Deep Neural Network in TensorFlowSelf-written Neural NetworkBack Propagation Implementation in Python for Deep Neural NetworkDeep Neural Network in PythonSuper-minimal implementation for inference only of fully a connected neural network in Python + Numpy






.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty margin-bottom:0;








7












$begingroup$


I created my first implementation of an arbitrary feed forward neural network with a simple back-propagation training implementation.



class NeuralNet(object):
learning_rate = .1

# Error function
@staticmethod
def J(guess, target):
return 0.5 * np.linalg.norm(guess - target, 2) ** 2

def __init__(self, structure=tuple(), activation_functions=tuple(), activation_derivatives=tuple()):
self.w = []
self.b = []
self.f = list(activation_functions)
self.df = list(activation_derivatives)

li = len(structure) - 1
while li > 0:
self.w.insert(0, np.random.uniform(low=-1.0, high=1.0, size=(structure[li - 1], structure[li])))
self.b.insert(0, np.random.uniform(low=-1.0, high=1.0, size=structure[li]))

li -= 1

def forward(self, x):
z = [0] * len(self.w)
a = [x]

for i, (wi, bi, fi) in enumerate(zip(self.w, self.b, self.f)):
z[i] = a[i] @ wi + bi
a.append(fi(z[i]))

return a[-1]

def train(self, training_data, labels):
# Get a permutation vector for shuffling the inputs and labels in each epoch:
permutation = np.random.permutation(len(inputs))

# Keeping track of all MSE values:
errors = []

# Training loop:
for epoch in range(10000):

# Shuffling the inputs and labels for each epoch:
X = training_data[permutation]
Y = labels[permutation]

# n
# Keeping track of the error: MSE = 1/n * SUM ||Activation - Target|| ** 2
# i=1
error = 0.0
for xi, yi in zip(X, Y):
# Forward pass:
z = [0] * len(self.w)
a = [xi]

for i, (wi, bi, fi) in enumerate(zip(self.w, self.b, self.f)):
z[i] = a[i] @ wi + bi
a.append(fi(z[i]))

# Calculate error for (xi, yi) according to 0.5 * ||xi - yi|| ** 2
error += self.J(a[-1], yi)

# Backwards pass:
# - Calculate deltas:
layer_delta = []
iter_zi = iter(zip(reversed(range(len(z))), reversed(z)))
layer_delta.insert(0, (a[-1] - yi) * self.df[1](next(iter_zi)[1]))
for i, zi in iter_zi:
delta_i = np.multiply(layer_delta[0] @ self.w[i+1].T, self.df[i](z[i]))
layer_delta.insert(0, delta_i)

# - Calculate weight deltas:
weight_delta = []
for i, di in zip(reversed(range(len(layer_delta))), reversed(layer_delta)):
delta_wi = a[i].reshape(-1, 1) * layer_delta[i]
weight_delta.insert(0, delta_wi)

# w[i](new) := w[i](old) - LR * dJ/dw[i]
# b[i](new) := b[i](old) - LR * dJ/db[i]
for i in range(len(self.w)):
self.w[i] = self.w[i] - self.learning_rate * weight_delta[i]
self.b[i] = self.b[i] - self.learning_rate * layer_delta[i]

errors.append(error / len(X))

# Convergence testing, according to the last N errors:
error_delta = sum(reversed(errors[-5:]))
if error_delta < 1.0e-6:
print("Error delta reached, ", epoch, " exiting.")
break

return errors


This seem to work just fine, at least it successfully learnt the XOR problem:



# Inputs and their respective labels:
inputs = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
labels = np.array([[0], [1], [1], [0]])

nn = NeuralNet(structure=(2, 2, 1),
activation_functions=(lambda x: np.tanh(x),
lambda x: .25 * x if x < 0 else x),
activation_derivatives=(lambda x: 1.0 - np.tanh(x) ** 2,
lambda x: .25 if x < 0 else 1))
nn.learning_rate = 0.1
print("Before training:")
print(nn.forward(np.array([0, 0])))
print(nn.forward(np.array([0, 1])))
print(nn.forward(np.array([1, 0])))
print(nn.forward(np.array([1, 1])))

errors = nn.train(inputs, labels)

print("After training:")
print(nn.forward(np.array([0, 0])))
print(nn.forward(np.array([0, 1])))
print(nn.forward(np.array([1, 0])))
print(nn.forward(np.array([1, 1])))

plt.plot(errors)
plt.show()


The calculations for layer_deltas seem particularly complicated, but whenever I try to simplify it, I break something. Any tips I could simplify that or any tips for the above code in general?










share|improve this question











$endgroup$







  • 1




    $begingroup$
    I rolled back your last edit. After getting an answer you are not allowed to change your code anymore. This is to ensure that answers do not get invalidated and have to hit a moving target. If you have changed your code you can either post it as an answer (if it would constitute a code review) or ask a new question with your changed code (linking back to this one as reference). Refer to this post for more information
    $endgroup$
    – Sᴀᴍ Onᴇᴌᴀ
    May 3 at 15:12

















7












$begingroup$


I created my first implementation of an arbitrary feed forward neural network with a simple back-propagation training implementation.



class NeuralNet(object):
learning_rate = .1

# Error function
@staticmethod
def J(guess, target):
return 0.5 * np.linalg.norm(guess - target, 2) ** 2

def __init__(self, structure=tuple(), activation_functions=tuple(), activation_derivatives=tuple()):
self.w = []
self.b = []
self.f = list(activation_functions)
self.df = list(activation_derivatives)

li = len(structure) - 1
while li > 0:
self.w.insert(0, np.random.uniform(low=-1.0, high=1.0, size=(structure[li - 1], structure[li])))
self.b.insert(0, np.random.uniform(low=-1.0, high=1.0, size=structure[li]))

li -= 1

def forward(self, x):
z = [0] * len(self.w)
a = [x]

for i, (wi, bi, fi) in enumerate(zip(self.w, self.b, self.f)):
z[i] = a[i] @ wi + bi
a.append(fi(z[i]))

return a[-1]

def train(self, training_data, labels):
# Get a permutation vector for shuffling the inputs and labels in each epoch:
permutation = np.random.permutation(len(inputs))

# Keeping track of all MSE values:
errors = []

# Training loop:
for epoch in range(10000):

# Shuffling the inputs and labels for each epoch:
X = training_data[permutation]
Y = labels[permutation]

# n
# Keeping track of the error: MSE = 1/n * SUM ||Activation - Target|| ** 2
# i=1
error = 0.0
for xi, yi in zip(X, Y):
# Forward pass:
z = [0] * len(self.w)
a = [xi]

for i, (wi, bi, fi) in enumerate(zip(self.w, self.b, self.f)):
z[i] = a[i] @ wi + bi
a.append(fi(z[i]))

# Calculate error for (xi, yi) according to 0.5 * ||xi - yi|| ** 2
error += self.J(a[-1], yi)

# Backwards pass:
# - Calculate deltas:
layer_delta = []
iter_zi = iter(zip(reversed(range(len(z))), reversed(z)))
layer_delta.insert(0, (a[-1] - yi) * self.df[1](next(iter_zi)[1]))
for i, zi in iter_zi:
delta_i = np.multiply(layer_delta[0] @ self.w[i+1].T, self.df[i](z[i]))
layer_delta.insert(0, delta_i)

# - Calculate weight deltas:
weight_delta = []
for i, di in zip(reversed(range(len(layer_delta))), reversed(layer_delta)):
delta_wi = a[i].reshape(-1, 1) * layer_delta[i]
weight_delta.insert(0, delta_wi)

# w[i](new) := w[i](old) - LR * dJ/dw[i]
# b[i](new) := b[i](old) - LR * dJ/db[i]
for i in range(len(self.w)):
self.w[i] = self.w[i] - self.learning_rate * weight_delta[i]
self.b[i] = self.b[i] - self.learning_rate * layer_delta[i]

errors.append(error / len(X))

# Convergence testing, according to the last N errors:
error_delta = sum(reversed(errors[-5:]))
if error_delta < 1.0e-6:
print("Error delta reached, ", epoch, " exiting.")
break

return errors


This seem to work just fine, at least it successfully learnt the XOR problem:



# Inputs and their respective labels:
inputs = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
labels = np.array([[0], [1], [1], [0]])

nn = NeuralNet(structure=(2, 2, 1),
activation_functions=(lambda x: np.tanh(x),
lambda x: .25 * x if x < 0 else x),
activation_derivatives=(lambda x: 1.0 - np.tanh(x) ** 2,
lambda x: .25 if x < 0 else 1))
nn.learning_rate = 0.1
print("Before training:")
print(nn.forward(np.array([0, 0])))
print(nn.forward(np.array([0, 1])))
print(nn.forward(np.array([1, 0])))
print(nn.forward(np.array([1, 1])))

errors = nn.train(inputs, labels)

print("After training:")
print(nn.forward(np.array([0, 0])))
print(nn.forward(np.array([0, 1])))
print(nn.forward(np.array([1, 0])))
print(nn.forward(np.array([1, 1])))

plt.plot(errors)
plt.show()


The calculations for layer_deltas seem particularly complicated, but whenever I try to simplify it, I break something. Any tips I could simplify that or any tips for the above code in general?










share|improve this question











$endgroup$







  • 1




    $begingroup$
    I rolled back your last edit. After getting an answer you are not allowed to change your code anymore. This is to ensure that answers do not get invalidated and have to hit a moving target. If you have changed your code you can either post it as an answer (if it would constitute a code review) or ask a new question with your changed code (linking back to this one as reference). Refer to this post for more information
    $endgroup$
    – Sᴀᴍ Onᴇᴌᴀ
    May 3 at 15:12













7












7








7


2



$begingroup$


I created my first implementation of an arbitrary feed forward neural network with a simple back-propagation training implementation.



class NeuralNet(object):
learning_rate = .1

# Error function
@staticmethod
def J(guess, target):
return 0.5 * np.linalg.norm(guess - target, 2) ** 2

def __init__(self, structure=tuple(), activation_functions=tuple(), activation_derivatives=tuple()):
self.w = []
self.b = []
self.f = list(activation_functions)
self.df = list(activation_derivatives)

li = len(structure) - 1
while li > 0:
self.w.insert(0, np.random.uniform(low=-1.0, high=1.0, size=(structure[li - 1], structure[li])))
self.b.insert(0, np.random.uniform(low=-1.0, high=1.0, size=structure[li]))

li -= 1

def forward(self, x):
z = [0] * len(self.w)
a = [x]

for i, (wi, bi, fi) in enumerate(zip(self.w, self.b, self.f)):
z[i] = a[i] @ wi + bi
a.append(fi(z[i]))

return a[-1]

def train(self, training_data, labels):
# Get a permutation vector for shuffling the inputs and labels in each epoch:
permutation = np.random.permutation(len(inputs))

# Keeping track of all MSE values:
errors = []

# Training loop:
for epoch in range(10000):

# Shuffling the inputs and labels for each epoch:
X = training_data[permutation]
Y = labels[permutation]

# n
# Keeping track of the error: MSE = 1/n * SUM ||Activation - Target|| ** 2
# i=1
error = 0.0
for xi, yi in zip(X, Y):
# Forward pass:
z = [0] * len(self.w)
a = [xi]

for i, (wi, bi, fi) in enumerate(zip(self.w, self.b, self.f)):
z[i] = a[i] @ wi + bi
a.append(fi(z[i]))

# Calculate error for (xi, yi) according to 0.5 * ||xi - yi|| ** 2
error += self.J(a[-1], yi)

# Backwards pass:
# - Calculate deltas:
layer_delta = []
iter_zi = iter(zip(reversed(range(len(z))), reversed(z)))
layer_delta.insert(0, (a[-1] - yi) * self.df[1](next(iter_zi)[1]))
for i, zi in iter_zi:
delta_i = np.multiply(layer_delta[0] @ self.w[i+1].T, self.df[i](z[i]))
layer_delta.insert(0, delta_i)

# - Calculate weight deltas:
weight_delta = []
for i, di in zip(reversed(range(len(layer_delta))), reversed(layer_delta)):
delta_wi = a[i].reshape(-1, 1) * layer_delta[i]
weight_delta.insert(0, delta_wi)

# w[i](new) := w[i](old) - LR * dJ/dw[i]
# b[i](new) := b[i](old) - LR * dJ/db[i]
for i in range(len(self.w)):
self.w[i] = self.w[i] - self.learning_rate * weight_delta[i]
self.b[i] = self.b[i] - self.learning_rate * layer_delta[i]

errors.append(error / len(X))

# Convergence testing, according to the last N errors:
error_delta = sum(reversed(errors[-5:]))
if error_delta < 1.0e-6:
print("Error delta reached, ", epoch, " exiting.")
break

return errors


This seem to work just fine, at least it successfully learnt the XOR problem:



# Inputs and their respective labels:
inputs = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
labels = np.array([[0], [1], [1], [0]])

nn = NeuralNet(structure=(2, 2, 1),
activation_functions=(lambda x: np.tanh(x),
lambda x: .25 * x if x < 0 else x),
activation_derivatives=(lambda x: 1.0 - np.tanh(x) ** 2,
lambda x: .25 if x < 0 else 1))
nn.learning_rate = 0.1
print("Before training:")
print(nn.forward(np.array([0, 0])))
print(nn.forward(np.array([0, 1])))
print(nn.forward(np.array([1, 0])))
print(nn.forward(np.array([1, 1])))

errors = nn.train(inputs, labels)

print("After training:")
print(nn.forward(np.array([0, 0])))
print(nn.forward(np.array([0, 1])))
print(nn.forward(np.array([1, 0])))
print(nn.forward(np.array([1, 1])))

plt.plot(errors)
plt.show()


The calculations for layer_deltas seem particularly complicated, but whenever I try to simplify it, I break something. Any tips I could simplify that or any tips for the above code in general?










share|improve this question











$endgroup$




I created my first implementation of an arbitrary feed forward neural network with a simple back-propagation training implementation.



class NeuralNet(object):
learning_rate = .1

# Error function
@staticmethod
def J(guess, target):
return 0.5 * np.linalg.norm(guess - target, 2) ** 2

def __init__(self, structure=tuple(), activation_functions=tuple(), activation_derivatives=tuple()):
self.w = []
self.b = []
self.f = list(activation_functions)
self.df = list(activation_derivatives)

li = len(structure) - 1
while li > 0:
self.w.insert(0, np.random.uniform(low=-1.0, high=1.0, size=(structure[li - 1], structure[li])))
self.b.insert(0, np.random.uniform(low=-1.0, high=1.0, size=structure[li]))

li -= 1

def forward(self, x):
z = [0] * len(self.w)
a = [x]

for i, (wi, bi, fi) in enumerate(zip(self.w, self.b, self.f)):
z[i] = a[i] @ wi + bi
a.append(fi(z[i]))

return a[-1]

def train(self, training_data, labels):
# Get a permutation vector for shuffling the inputs and labels in each epoch:
permutation = np.random.permutation(len(inputs))

# Keeping track of all MSE values:
errors = []

# Training loop:
for epoch in range(10000):

# Shuffling the inputs and labels for each epoch:
X = training_data[permutation]
Y = labels[permutation]

# n
# Keeping track of the error: MSE = 1/n * SUM ||Activation - Target|| ** 2
# i=1
error = 0.0
for xi, yi in zip(X, Y):
# Forward pass:
z = [0] * len(self.w)
a = [xi]

for i, (wi, bi, fi) in enumerate(zip(self.w, self.b, self.f)):
z[i] = a[i] @ wi + bi
a.append(fi(z[i]))

# Calculate error for (xi, yi) according to 0.5 * ||xi - yi|| ** 2
error += self.J(a[-1], yi)

# Backwards pass:
# - Calculate deltas:
layer_delta = []
iter_zi = iter(zip(reversed(range(len(z))), reversed(z)))
layer_delta.insert(0, (a[-1] - yi) * self.df[1](next(iter_zi)[1]))
for i, zi in iter_zi:
delta_i = np.multiply(layer_delta[0] @ self.w[i+1].T, self.df[i](z[i]))
layer_delta.insert(0, delta_i)

# - Calculate weight deltas:
weight_delta = []
for i, di in zip(reversed(range(len(layer_delta))), reversed(layer_delta)):
delta_wi = a[i].reshape(-1, 1) * layer_delta[i]
weight_delta.insert(0, delta_wi)

# w[i](new) := w[i](old) - LR * dJ/dw[i]
# b[i](new) := b[i](old) - LR * dJ/db[i]
for i in range(len(self.w)):
self.w[i] = self.w[i] - self.learning_rate * weight_delta[i]
self.b[i] = self.b[i] - self.learning_rate * layer_delta[i]

errors.append(error / len(X))

# Convergence testing, according to the last N errors:
error_delta = sum(reversed(errors[-5:]))
if error_delta < 1.0e-6:
print("Error delta reached, ", epoch, " exiting.")
break

return errors


This seem to work just fine, at least it successfully learnt the XOR problem:



# Inputs and their respective labels:
inputs = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
labels = np.array([[0], [1], [1], [0]])

nn = NeuralNet(structure=(2, 2, 1),
activation_functions=(lambda x: np.tanh(x),
lambda x: .25 * x if x < 0 else x),
activation_derivatives=(lambda x: 1.0 - np.tanh(x) ** 2,
lambda x: .25 if x < 0 else 1))
nn.learning_rate = 0.1
print("Before training:")
print(nn.forward(np.array([0, 0])))
print(nn.forward(np.array([0, 1])))
print(nn.forward(np.array([1, 0])))
print(nn.forward(np.array([1, 1])))

errors = nn.train(inputs, labels)

print("After training:")
print(nn.forward(np.array([0, 0])))
print(nn.forward(np.array([0, 1])))
print(nn.forward(np.array([1, 0])))
print(nn.forward(np.array([1, 1])))

plt.plot(errors)
plt.show()


The calculations for layer_deltas seem particularly complicated, but whenever I try to simplify it, I break something. Any tips I could simplify that or any tips for the above code in general?







python python-3.x numpy neural-network






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited May 3 at 15:12









Sᴀᴍ Onᴇᴌᴀ

10.9k62168




10.9k62168










asked May 2 at 12:44









Display nameDisplay name

504




504







  • 1




    $begingroup$
    I rolled back your last edit. After getting an answer you are not allowed to change your code anymore. This is to ensure that answers do not get invalidated and have to hit a moving target. If you have changed your code you can either post it as an answer (if it would constitute a code review) or ask a new question with your changed code (linking back to this one as reference). Refer to this post for more information
    $endgroup$
    – Sᴀᴍ Onᴇᴌᴀ
    May 3 at 15:12












  • 1




    $begingroup$
    I rolled back your last edit. After getting an answer you are not allowed to change your code anymore. This is to ensure that answers do not get invalidated and have to hit a moving target. If you have changed your code you can either post it as an answer (if it would constitute a code review) or ask a new question with your changed code (linking back to this one as reference). Refer to this post for more information
    $endgroup$
    – Sᴀᴍ Onᴇᴌᴀ
    May 3 at 15:12







1




1




$begingroup$
I rolled back your last edit. After getting an answer you are not allowed to change your code anymore. This is to ensure that answers do not get invalidated and have to hit a moving target. If you have changed your code you can either post it as an answer (if it would constitute a code review) or ask a new question with your changed code (linking back to this one as reference). Refer to this post for more information
$endgroup$
– Sᴀᴍ Onᴇᴌᴀ
May 3 at 15:12




$begingroup$
I rolled back your last edit. After getting an answer you are not allowed to change your code anymore. This is to ensure that answers do not get invalidated and have to hit a moving target. If you have changed your code you can either post it as an answer (if it would constitute a code review) or ask a new question with your changed code (linking back to this one as reference). Refer to this post for more information
$endgroup$
– Sᴀᴍ Onᴇᴌᴀ
May 3 at 15:12










1 Answer
1






active

oldest

votes


















9












$begingroup$

The not so arbitrary Network



Your original claim was that your network is "arbitrary". From what I see, I would tend to say that it's not so arbitrary as one might expect.



Arbitrary:



  • number of neurons per layer (you call it "structure")

  • activation function

  • their derivatives (which means they are not totally arbitrary)

Not arbitrary:



  • learning rate (0.1)

  • network weight initialization

  • error function (Somehow set to half of the squared Euclidean distance? Maybe you wanted the Mean Squared Error here?)

  • number of epochs in training (10000)

  • training mode (sample vs. batch learning)

  • training termination condition (error_delta < 1.0e-6)

  • optimizer

  • ...

Those are all (more or less) important aspects one might like to tune without "monkey-patching" them into the original class (if possible). Some of these parameters could easily be set in the constructor or when calling train (for number of epochs etc.). The error function could also easily be set at initialization, especially since it's already a @staticmethod which does not rely on class internals.



I assume a flexible optimizer would be out of scope for what you want to do (and there are a plethora of frameworks that can do this).



The code itself



The initialization of the network weights and biases is using a while loop and is unnecessarily complicated. You don't need a while loop here since you know exactly how many iterations have to be done. So use a for loop instead, which would lead you to:



for i in range(len(structure)-1):
self.w.append(np.random.uniform(low=-1.0, high=1.0, size=(structure[i], structure[i+1])))
self.b.append(np.random.uniform(low=-1.0, high=1.0, size=(structure[i+1])))


Apart from not needing to keep tracking of the indexing variable, you also don't need indexing tricks with negatives indices and you are able to use .append(...) instead of inserting at the front.



Oh and while we are at initialization: there is no need to convert the functions/derivative tuples to lists. You can iterate over/index tuples the same way as over lists.




Speaking of iterations, this



layer_delta = []
iter_zi = iter(zip(reversed(range(len(z))), reversed(z)))
layer_delta.insert(0, (a[-1] - yi) * self.df[1](next(iter_zi)[1]))
for i, zi in iter_zi:
delta_i = np.multiply(layer_delta[0] @ self.w[i+1].T, self.df[i](z[i]))
layer_delta.insert(0, delta_i)


is madness™ IMHO (and there is also likely a bug with self.df[1]). See this SO post on how to reverse enumerate a Python list, if you really think you need to. I don't think you need to because you use the actual list value only once and the index in all other cases. That would bring that monster down to something like



layer_delta = [(a[-1] - yi) * self.df[-1](z[-1])]
for i in reversed(range(len(z)-1)):
delta_i = np.multiply(layer_delta[0] @ self.w[i+1].T, self.df[i](z[i]))
layer_delta.insert(0, delta_i)


You can and should adapt your other appearances of the monster above as well. Work carefully while refactoring these parts of your code, I cannot guarantee that I didn't make a mistake while deciphering them.




The implementation of the forward pass is also a little bit to complicated IMHO. Since you're only going through the network once, there is no need to store the activation and output of all layers. You only need the last one.



You also implement the forward pass twice, in it's own forward function and in train. If you stick to your original implementation, think about if you would like to return the other values as well. That would leave you with only one piece of code to fix if something is wrong. If you're concerned about usability since the application phase now sees all the internal values, you could implement an internal function , e.g. _forward which does the heavy-lifting and let forward just return the final output.




The comment and variable names at



# Convergence testing, according to the last N errors:
error_delta = sum(reversed(errors[-5:]))
if error_delta < 1.0e-6:
print("Error delta reached, ", epoch, " exiting.")
break


are also a little off. The termination criterion does seem to check if the sum of the last N (where N is hard coded to 5) is below your (arbitrarily chosen) threshold. Also, there is no need to reverse the list here since sum does not care about the order.



Since you are working in Python 3, you can also use the new f-string syntax for output formatting:



print(f"Error delta reached in epoch exiting.")



Speaking of comments, the methods of your class all lack user visible documentation. For that, Python programmers usually use so called """doc strings""" on their methods/classes/functions. As an example:



def train(self, training_data, labels):
"""Train the neural network with all the available data

The training continues until the maximum number of epochs is reached or
the termination criterion is hit.
"""
# your code here ...


Documentation written in this kind of format will be picked up by all major Python IDEs as well as by Python's built-in help(...) function.




I know there are common naming conventions in the neural network community, and when you implement it, you should stick to them as closely as possible which you mostly do. But bear in mind not to sacrifice the clarity and readability of your code. E.g. z could become activation and a could also be named layer_output (plurals might apply where they are used as list).






share|improve this answer











$endgroup$












  • $begingroup$
    Awesome points! Yeah, that those two loops are atrocious, your proposed solution is a lot cleaner. I'll update my code according to your recommendations and will post the updated solution. Also for that not so arbitrary, true. I don't want to create yet another public facing NN framework, there are already enough of them, this is for me learning what NNs actually do. How they behave for different activation functions etc etc.
    $endgroup$
    – Display name
    May 3 at 5:28











Your Answer






StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);













draft saved

draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f219583%2fdeep-neural-net-implementation-in-python3%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown

























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes









9












$begingroup$

The not so arbitrary Network



Your original claim was that your network is "arbitrary". From what I see, I would tend to say that it's not so arbitrary as one might expect.



Arbitrary:



  • number of neurons per layer (you call it "structure")

  • activation function

  • their derivatives (which means they are not totally arbitrary)

Not arbitrary:



  • learning rate (0.1)

  • network weight initialization

  • error function (Somehow set to half of the squared Euclidean distance? Maybe you wanted the Mean Squared Error here?)

  • number of epochs in training (10000)

  • training mode (sample vs. batch learning)

  • training termination condition (error_delta < 1.0e-6)

  • optimizer

  • ...

Those are all (more or less) important aspects one might like to tune without "monkey-patching" them into the original class (if possible). Some of these parameters could easily be set in the constructor or when calling train (for number of epochs etc.). The error function could also easily be set at initialization, especially since it's already a @staticmethod which does not rely on class internals.



I assume a flexible optimizer would be out of scope for what you want to do (and there are a plethora of frameworks that can do this).



The code itself



The initialization of the network weights and biases is using a while loop and is unnecessarily complicated. You don't need a while loop here since you know exactly how many iterations have to be done. So use a for loop instead, which would lead you to:



for i in range(len(structure)-1):
self.w.append(np.random.uniform(low=-1.0, high=1.0, size=(structure[i], structure[i+1])))
self.b.append(np.random.uniform(low=-1.0, high=1.0, size=(structure[i+1])))


Apart from not needing to keep tracking of the indexing variable, you also don't need indexing tricks with negatives indices and you are able to use .append(...) instead of inserting at the front.



Oh and while we are at initialization: there is no need to convert the functions/derivative tuples to lists. You can iterate over/index tuples the same way as over lists.




Speaking of iterations, this



layer_delta = []
iter_zi = iter(zip(reversed(range(len(z))), reversed(z)))
layer_delta.insert(0, (a[-1] - yi) * self.df[1](next(iter_zi)[1]))
for i, zi in iter_zi:
delta_i = np.multiply(layer_delta[0] @ self.w[i+1].T, self.df[i](z[i]))
layer_delta.insert(0, delta_i)


is madness™ IMHO (and there is also likely a bug with self.df[1]). See this SO post on how to reverse enumerate a Python list, if you really think you need to. I don't think you need to because you use the actual list value only once and the index in all other cases. That would bring that monster down to something like



layer_delta = [(a[-1] - yi) * self.df[-1](z[-1])]
for i in reversed(range(len(z)-1)):
delta_i = np.multiply(layer_delta[0] @ self.w[i+1].T, self.df[i](z[i]))
layer_delta.insert(0, delta_i)


You can and should adapt your other appearances of the monster above as well. Work carefully while refactoring these parts of your code, I cannot guarantee that I didn't make a mistake while deciphering them.




The implementation of the forward pass is also a little bit to complicated IMHO. Since you're only going through the network once, there is no need to store the activation and output of all layers. You only need the last one.



You also implement the forward pass twice, in it's own forward function and in train. If you stick to your original implementation, think about if you would like to return the other values as well. That would leave you with only one piece of code to fix if something is wrong. If you're concerned about usability since the application phase now sees all the internal values, you could implement an internal function , e.g. _forward which does the heavy-lifting and let forward just return the final output.




The comment and variable names at



# Convergence testing, according to the last N errors:
error_delta = sum(reversed(errors[-5:]))
if error_delta < 1.0e-6:
print("Error delta reached, ", epoch, " exiting.")
break


are also a little off. The termination criterion does seem to check if the sum of the last N (where N is hard coded to 5) is below your (arbitrarily chosen) threshold. Also, there is no need to reverse the list here since sum does not care about the order.



Since you are working in Python 3, you can also use the new f-string syntax for output formatting:



print(f"Error delta reached in epoch exiting.")



Speaking of comments, the methods of your class all lack user visible documentation. For that, Python programmers usually use so called """doc strings""" on their methods/classes/functions. As an example:



def train(self, training_data, labels):
"""Train the neural network with all the available data

The training continues until the maximum number of epochs is reached or
the termination criterion is hit.
"""
# your code here ...


Documentation written in this kind of format will be picked up by all major Python IDEs as well as by Python's built-in help(...) function.




I know there are common naming conventions in the neural network community, and when you implement it, you should stick to them as closely as possible which you mostly do. But bear in mind not to sacrifice the clarity and readability of your code. E.g. z could become activation and a could also be named layer_output (plurals might apply where they are used as list).






share|improve this answer











$endgroup$












  • $begingroup$
    Awesome points! Yeah, that those two loops are atrocious, your proposed solution is a lot cleaner. I'll update my code according to your recommendations and will post the updated solution. Also for that not so arbitrary, true. I don't want to create yet another public facing NN framework, there are already enough of them, this is for me learning what NNs actually do. How they behave for different activation functions etc etc.
    $endgroup$
    – Display name
    May 3 at 5:28















9












$begingroup$

The not so arbitrary Network



Your original claim was that your network is "arbitrary". From what I see, I would tend to say that it's not so arbitrary as one might expect.



Arbitrary:



  • number of neurons per layer (you call it "structure")

  • activation function

  • their derivatives (which means they are not totally arbitrary)

Not arbitrary:



  • learning rate (0.1)

  • network weight initialization

  • error function (Somehow set to half of the squared Euclidean distance? Maybe you wanted the Mean Squared Error here?)

  • number of epochs in training (10000)

  • training mode (sample vs. batch learning)

  • training termination condition (error_delta < 1.0e-6)

  • optimizer

  • ...

Those are all (more or less) important aspects one might like to tune without "monkey-patching" them into the original class (if possible). Some of these parameters could easily be set in the constructor or when calling train (for number of epochs etc.). The error function could also easily be set at initialization, especially since it's already a @staticmethod which does not rely on class internals.



I assume a flexible optimizer would be out of scope for what you want to do (and there are a plethora of frameworks that can do this).



The code itself



The initialization of the network weights and biases is using a while loop and is unnecessarily complicated. You don't need a while loop here since you know exactly how many iterations have to be done. So use a for loop instead, which would lead you to:



for i in range(len(structure)-1):
self.w.append(np.random.uniform(low=-1.0, high=1.0, size=(structure[i], structure[i+1])))
self.b.append(np.random.uniform(low=-1.0, high=1.0, size=(structure[i+1])))


Apart from not needing to keep tracking of the indexing variable, you also don't need indexing tricks with negatives indices and you are able to use .append(...) instead of inserting at the front.



Oh and while we are at initialization: there is no need to convert the functions/derivative tuples to lists. You can iterate over/index tuples the same way as over lists.




Speaking of iterations, this



layer_delta = []
iter_zi = iter(zip(reversed(range(len(z))), reversed(z)))
layer_delta.insert(0, (a[-1] - yi) * self.df[1](next(iter_zi)[1]))
for i, zi in iter_zi:
delta_i = np.multiply(layer_delta[0] @ self.w[i+1].T, self.df[i](z[i]))
layer_delta.insert(0, delta_i)


is madness™ IMHO (and there is also likely a bug with self.df[1]). See this SO post on how to reverse enumerate a Python list, if you really think you need to. I don't think you need to because you use the actual list value only once and the index in all other cases. That would bring that monster down to something like



layer_delta = [(a[-1] - yi) * self.df[-1](z[-1])]
for i in reversed(range(len(z)-1)):
delta_i = np.multiply(layer_delta[0] @ self.w[i+1].T, self.df[i](z[i]))
layer_delta.insert(0, delta_i)


You can and should adapt your other appearances of the monster above as well. Work carefully while refactoring these parts of your code, I cannot guarantee that I didn't make a mistake while deciphering them.




The implementation of the forward pass is also a little bit to complicated IMHO. Since you're only going through the network once, there is no need to store the activation and output of all layers. You only need the last one.



You also implement the forward pass twice, in it's own forward function and in train. If you stick to your original implementation, think about if you would like to return the other values as well. That would leave you with only one piece of code to fix if something is wrong. If you're concerned about usability since the application phase now sees all the internal values, you could implement an internal function , e.g. _forward which does the heavy-lifting and let forward just return the final output.




The comment and variable names at



# Convergence testing, according to the last N errors:
error_delta = sum(reversed(errors[-5:]))
if error_delta < 1.0e-6:
print("Error delta reached, ", epoch, " exiting.")
break


are also a little off. The termination criterion does seem to check if the sum of the last N (where N is hard coded to 5) is below your (arbitrarily chosen) threshold. Also, there is no need to reverse the list here since sum does not care about the order.



Since you are working in Python 3, you can also use the new f-string syntax for output formatting:



print(f"Error delta reached in epoch exiting.")



Speaking of comments, the methods of your class all lack user visible documentation. For that, Python programmers usually use so called """doc strings""" on their methods/classes/functions. As an example:



def train(self, training_data, labels):
"""Train the neural network with all the available data

The training continues until the maximum number of epochs is reached or
the termination criterion is hit.
"""
# your code here ...


Documentation written in this kind of format will be picked up by all major Python IDEs as well as by Python's built-in help(...) function.




I know there are common naming conventions in the neural network community, and when you implement it, you should stick to them as closely as possible which you mostly do. But bear in mind not to sacrifice the clarity and readability of your code. E.g. z could become activation and a could also be named layer_output (plurals might apply where they are used as list).






share|improve this answer











$endgroup$












  • $begingroup$
    Awesome points! Yeah, that those two loops are atrocious, your proposed solution is a lot cleaner. I'll update my code according to your recommendations and will post the updated solution. Also for that not so arbitrary, true. I don't want to create yet another public facing NN framework, there are already enough of them, this is for me learning what NNs actually do. How they behave for different activation functions etc etc.
    $endgroup$
    – Display name
    May 3 at 5:28













9












9








9





$begingroup$

The not so arbitrary Network



Your original claim was that your network is "arbitrary". From what I see, I would tend to say that it's not so arbitrary as one might expect.



Arbitrary:



  • number of neurons per layer (you call it "structure")

  • activation function

  • their derivatives (which means they are not totally arbitrary)

Not arbitrary:



  • learning rate (0.1)

  • network weight initialization

  • error function (Somehow set to half of the squared Euclidean distance? Maybe you wanted the Mean Squared Error here?)

  • number of epochs in training (10000)

  • training mode (sample vs. batch learning)

  • training termination condition (error_delta < 1.0e-6)

  • optimizer

  • ...

Those are all (more or less) important aspects one might like to tune without "monkey-patching" them into the original class (if possible). Some of these parameters could easily be set in the constructor or when calling train (for number of epochs etc.). The error function could also easily be set at initialization, especially since it's already a @staticmethod which does not rely on class internals.



I assume a flexible optimizer would be out of scope for what you want to do (and there are a plethora of frameworks that can do this).



The code itself



The initialization of the network weights and biases is using a while loop and is unnecessarily complicated. You don't need a while loop here since you know exactly how many iterations have to be done. So use a for loop instead, which would lead you to:



for i in range(len(structure)-1):
self.w.append(np.random.uniform(low=-1.0, high=1.0, size=(structure[i], structure[i+1])))
self.b.append(np.random.uniform(low=-1.0, high=1.0, size=(structure[i+1])))


Apart from not needing to keep tracking of the indexing variable, you also don't need indexing tricks with negatives indices and you are able to use .append(...) instead of inserting at the front.



Oh and while we are at initialization: there is no need to convert the functions/derivative tuples to lists. You can iterate over/index tuples the same way as over lists.




Speaking of iterations, this



layer_delta = []
iter_zi = iter(zip(reversed(range(len(z))), reversed(z)))
layer_delta.insert(0, (a[-1] - yi) * self.df[1](next(iter_zi)[1]))
for i, zi in iter_zi:
delta_i = np.multiply(layer_delta[0] @ self.w[i+1].T, self.df[i](z[i]))
layer_delta.insert(0, delta_i)


is madness™ IMHO (and there is also likely a bug with self.df[1]). See this SO post on how to reverse enumerate a Python list, if you really think you need to. I don't think you need to because you use the actual list value only once and the index in all other cases. That would bring that monster down to something like



layer_delta = [(a[-1] - yi) * self.df[-1](z[-1])]
for i in reversed(range(len(z)-1)):
delta_i = np.multiply(layer_delta[0] @ self.w[i+1].T, self.df[i](z[i]))
layer_delta.insert(0, delta_i)


You can and should adapt your other appearances of the monster above as well. Work carefully while refactoring these parts of your code, I cannot guarantee that I didn't make a mistake while deciphering them.




The implementation of the forward pass is also a little bit to complicated IMHO. Since you're only going through the network once, there is no need to store the activation and output of all layers. You only need the last one.



You also implement the forward pass twice, in it's own forward function and in train. If you stick to your original implementation, think about if you would like to return the other values as well. That would leave you with only one piece of code to fix if something is wrong. If you're concerned about usability since the application phase now sees all the internal values, you could implement an internal function , e.g. _forward which does the heavy-lifting and let forward just return the final output.




The comment and variable names at



# Convergence testing, according to the last N errors:
error_delta = sum(reversed(errors[-5:]))
if error_delta < 1.0e-6:
print("Error delta reached, ", epoch, " exiting.")
break


are also a little off. The termination criterion does seem to check if the sum of the last N (where N is hard coded to 5) is below your (arbitrarily chosen) threshold. Also, there is no need to reverse the list here since sum does not care about the order.



Since you are working in Python 3, you can also use the new f-string syntax for output formatting:



print(f"Error delta reached in epoch exiting.")



Speaking of comments, the methods of your class all lack user visible documentation. For that, Python programmers usually use so called """doc strings""" on their methods/classes/functions. As an example:



def train(self, training_data, labels):
"""Train the neural network with all the available data

The training continues until the maximum number of epochs is reached or
the termination criterion is hit.
"""
# your code here ...


Documentation written in this kind of format will be picked up by all major Python IDEs as well as by Python's built-in help(...) function.




I know there are common naming conventions in the neural network community, and when you implement it, you should stick to them as closely as possible which you mostly do. But bear in mind not to sacrifice the clarity and readability of your code. E.g. z could become activation and a could also be named layer_output (plurals might apply where they are used as list).






share|improve this answer











$endgroup$



The not so arbitrary Network



Your original claim was that your network is "arbitrary". From what I see, I would tend to say that it's not so arbitrary as one might expect.



Arbitrary:



  • number of neurons per layer (you call it "structure")

  • activation function

  • their derivatives (which means they are not totally arbitrary)

Not arbitrary:



  • learning rate (0.1)

  • network weight initialization

  • error function (Somehow set to half of the squared Euclidean distance? Maybe you wanted the Mean Squared Error here?)

  • number of epochs in training (10000)

  • training mode (sample vs. batch learning)

  • training termination condition (error_delta < 1.0e-6)

  • optimizer

  • ...

Those are all (more or less) important aspects one might like to tune without "monkey-patching" them into the original class (if possible). Some of these parameters could easily be set in the constructor or when calling train (for number of epochs etc.). The error function could also easily be set at initialization, especially since it's already a @staticmethod which does not rely on class internals.



I assume a flexible optimizer would be out of scope for what you want to do (and there are a plethora of frameworks that can do this).



The code itself



The initialization of the network weights and biases is using a while loop and is unnecessarily complicated. You don't need a while loop here since you know exactly how many iterations have to be done. So use a for loop instead, which would lead you to:



for i in range(len(structure)-1):
self.w.append(np.random.uniform(low=-1.0, high=1.0, size=(structure[i], structure[i+1])))
self.b.append(np.random.uniform(low=-1.0, high=1.0, size=(structure[i+1])))


Apart from not needing to keep tracking of the indexing variable, you also don't need indexing tricks with negatives indices and you are able to use .append(...) instead of inserting at the front.



Oh and while we are at initialization: there is no need to convert the functions/derivative tuples to lists. You can iterate over/index tuples the same way as over lists.




Speaking of iterations, this



layer_delta = []
iter_zi = iter(zip(reversed(range(len(z))), reversed(z)))
layer_delta.insert(0, (a[-1] - yi) * self.df[1](next(iter_zi)[1]))
for i, zi in iter_zi:
delta_i = np.multiply(layer_delta[0] @ self.w[i+1].T, self.df[i](z[i]))
layer_delta.insert(0, delta_i)


is madness™ IMHO (and there is also likely a bug with self.df[1]). See this SO post on how to reverse enumerate a Python list, if you really think you need to. I don't think you need to because you use the actual list value only once and the index in all other cases. That would bring that monster down to something like



layer_delta = [(a[-1] - yi) * self.df[-1](z[-1])]
for i in reversed(range(len(z)-1)):
delta_i = np.multiply(layer_delta[0] @ self.w[i+1].T, self.df[i](z[i]))
layer_delta.insert(0, delta_i)


You can and should adapt your other appearances of the monster above as well. Work carefully while refactoring these parts of your code, I cannot guarantee that I didn't make a mistake while deciphering them.




The implementation of the forward pass is also a little bit to complicated IMHO. Since you're only going through the network once, there is no need to store the activation and output of all layers. You only need the last one.



You also implement the forward pass twice, in it's own forward function and in train. If you stick to your original implementation, think about if you would like to return the other values as well. That would leave you with only one piece of code to fix if something is wrong. If you're concerned about usability since the application phase now sees all the internal values, you could implement an internal function , e.g. _forward which does the heavy-lifting and let forward just return the final output.




The comment and variable names at



# Convergence testing, according to the last N errors:
error_delta = sum(reversed(errors[-5:]))
if error_delta < 1.0e-6:
print("Error delta reached, ", epoch, " exiting.")
break


are also a little off. The termination criterion does seem to check if the sum of the last N (where N is hard coded to 5) is below your (arbitrarily chosen) threshold. Also, there is no need to reverse the list here since sum does not care about the order.



Since you are working in Python 3, you can also use the new f-string syntax for output formatting:



print(f"Error delta reached in epoch exiting.")



Speaking of comments, the methods of your class all lack user visible documentation. For that, Python programmers usually use so called """doc strings""" on their methods/classes/functions. As an example:



def train(self, training_data, labels):
"""Train the neural network with all the available data

The training continues until the maximum number of epochs is reached or
the termination criterion is hit.
"""
# your code here ...


Documentation written in this kind of format will be picked up by all major Python IDEs as well as by Python's built-in help(...) function.




I know there are common naming conventions in the neural network community, and when you implement it, you should stick to them as closely as possible which you mostly do. But bear in mind not to sacrifice the clarity and readability of your code. E.g. z could become activation and a could also be named layer_output (plurals might apply where they are used as list).







share|improve this answer














share|improve this answer



share|improve this answer








edited May 3 at 11:54

























answered May 2 at 16:18









AlexVAlexV

2,044721




2,044721











  • $begingroup$
    Awesome points! Yeah, that those two loops are atrocious, your proposed solution is a lot cleaner. I'll update my code according to your recommendations and will post the updated solution. Also for that not so arbitrary, true. I don't want to create yet another public facing NN framework, there are already enough of them, this is for me learning what NNs actually do. How they behave for different activation functions etc etc.
    $endgroup$
    – Display name
    May 3 at 5:28
















  • $begingroup$
    Awesome points! Yeah, that those two loops are atrocious, your proposed solution is a lot cleaner. I'll update my code according to your recommendations and will post the updated solution. Also for that not so arbitrary, true. I don't want to create yet another public facing NN framework, there are already enough of them, this is for me learning what NNs actually do. How they behave for different activation functions etc etc.
    $endgroup$
    – Display name
    May 3 at 5:28















$begingroup$
Awesome points! Yeah, that those two loops are atrocious, your proposed solution is a lot cleaner. I'll update my code according to your recommendations and will post the updated solution. Also for that not so arbitrary, true. I don't want to create yet another public facing NN framework, there are already enough of them, this is for me learning what NNs actually do. How they behave for different activation functions etc etc.
$endgroup$
– Display name
May 3 at 5:28




$begingroup$
Awesome points! Yeah, that those two loops are atrocious, your proposed solution is a lot cleaner. I'll update my code according to your recommendations and will post the updated solution. Also for that not so arbitrary, true. I don't want to create yet another public facing NN framework, there are already enough of them, this is for me learning what NNs actually do. How they behave for different activation functions etc etc.
$endgroup$
– Display name
May 3 at 5:28

















draft saved

draft discarded
















































Thanks for contributing an answer to Code Review Stack Exchange!


  • Please be sure to answer the question. Provide details and share your research!

But avoid


  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.

Use MathJax to format equations. MathJax reference.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f219583%2fdeep-neural-net-implementation-in-python3%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

How to write a 12-bar blues melodyI-IV-V blues progressionHow to play the bridges in a standard blues progressionHow does Gdim7 fit in C# minor?question on a certain chord progressionMusicology of Melody12 bar blues, spread rhythm: alternative to 6th chord to avoid finger stretchChord progressions/ Root key/ MelodiesHow to put chords (POP-EDM) under a given lead vocal melody (starting from a good knowledge in music theory)Are there “rules” for improvising with the minor pentatonic scale over 12-bar shuffle?Confusion about blues scale and chords

What if the end-user didn't have the required library?What is setup.py?What is a clean, pythonic way to have multiple constructors in Python?What does Ruby have that Python doesn't, and vice versa?What is the reason for having '//' in Python?How do I create a namespace package in Python?How to package shared objects that python modules depend on?setuptools vs. distutils: why is distutils still a thing?Navigation in Windows 10 vs code not going to virtualenv library when the same library is installed at user levelPython create package for local usePackaging a project that uses multiple python versionsWhy is permission denied on pip install except for when “--user” is included at end of command?

Esgonzo ibérico Índice Descrición Distribución Hábitat Ameazas Notas Véxase tamén "Acerca dos nomes dos anfibios e réptiles galegos""Chalcides bedriagai"Chalcides bedriagai en Carrascal, L. M. Salvador, A. (Eds). Enciclopedia virtual de los vertebrados españoles. Museo Nacional de Ciencias Naturales, Madrid. España.Fotos