Пытаясь понять полностью связанные ANN, я начинаю с простого примера 2-D линейной регрессии.
Моя сеть тривиальна - один входной слой и выходной слой с набором весов между ними.Если мое понимание правильное, вес должен, по сути, учиться наклона линии наилучшего соответствия моих данных.
Мои тренировочные данные - слегка шумная линия, как показано ниже.Это просто нечеткая линия с наклоном m = .5.Мой код тянет одну точку, распространяет ее через мою сеть и возвращает обратно, чтобы обновить веса.Обновление веса происходит либо каждые данные, либо усредняется через каждые 5, 10 или 20 баллов.
Мои веса рандомизированы, но я исправил зерно для здравомыслия во время отладки.Когда я строю квадратную ошибку из каждого учебного примера, я получаю следующий горб, оседание, затем взрыв.
В соответствии с уменьшением квадратной ошибкиМой алгоритм находит наклон линейных данных ... затем яростно отвергает их, смеется.
Сначала я думал, что я закодировал колебанияв мои тренировочные данные были слишком дикими и могли выкинуть решение из локальных минимумов.Но затем я сузил разброс вокруг y = .5x в моих тренировочных данных без особого эффекта.Чтобы также попытаться сгладить эти эффекты, я реализовал усредненные стохастические обновления веса, чтобы они обновлялись только после нескольких тренировочных образцов.Никакой любви.
Я также использую очень маленькую скорость обучения (.0005), так как я думал, что шум мог заставить меня колебаться вниз по склону градиента.Это изначально помогло, но цифры являются результатом альфа = .005.
Какие-либо предложения о том, что мне не хватает?Я хотел бы справиться с этой ситуацией, чтобы я мог приблизиться к многовариантной регрессии.
import random
import matplotlib.pyplot as plt
import numpy as np
random.seed(0)
def gen_linear_regression_data(num_points, slope=.5, var=1.0, plot=False, seed=None):
if seed is not None:
np.random.seed(seed)
data = [idx for idx in range(num_points)]
labels = [data[idx] * slope for idx in range(num_points)]
# add noise
labels = [l + np.random.uniform(-var, var) for l in labels]
if plot:
plt.scatter(data, labels)
plt.show()
return data, labels
class Sigmoid():
def activate(self, x):
return 1 / (1 + np.exp(-x))
def backtivate(self, x):
return np.multiply(x, (1 - x))
class Passive():
def activate(self, x):
return x
def backtivate(self, x):
return 1
class Layer():
def __init__(self, values, activation="logistic"):
if not (isinstance(values, list) or isinstance(values, np.ndarray)):
values = [values]
self.values = np.matrix(values)
if self.values.shape[-1] > self.values.shape[0]:
self.values = self.values.reshape((self.values.shape[-1], 1))
self.set_activation(activation_str=activation)
def __getitem__(self, item):
return self.values[item]
def __setitem__(self, key, value):
if not (isinstance(value, int) or isinstance(value, float)):
raise TypeError("Layer values must be int or float.")
self.values[key] = value
def __len__(self):
return self.values.shape[0]
def __str__(self):
return "\n".join([str(val) for val in self.values])
def __mul__(self, other):
return np.dot(other, self.values)
def set_activation(self, activation_str):
if activation_str == "logistic":
self.activation = Sigmoid()
elif activation_str == "passive":
self.activation = Passive()
def transpose(self):
return self.values.reshape(len(self), 1)
def activate(self):
return self.activation.activate(self.values)
def backtivate(self):
return self.activation.backtivate(self.values)
class DataSet():
def __init__(self, data, labels):
self.data = data
self.labels = labels
self.data_dict = [{"data": d, "label": l} for (d, l) in zip(self.data, self.labels)]
def __getitem__(self, item):
return self.data_dict[item]
class Weights():
def __init__(self, weights):
if not isinstance(weights, list) and not all([isinstance(weight, np.ndarray) for weight in weights]):
raise TypeError("Blah.")
self.data = weights
def __len__(self):
return sum([w.shape[0] * w.shape[1] for w in self.data])
def __getitem__(self, item):
weight_idx = np.cumsum([w.shape[0] * w.shape[1] - 1 for w in self.data])
desired_idx = 0
for idx, w_idx in enumerate(weight_idx):
if item < w_idx:
desired_idx = idx
break
if idx > 0:
cs = np.cumsum(weight_idx)
another_idx = item - cs[idx - 1]
else:
another_idx = item
self.data[desired_idx][another_idx]
class Network():
def __init__(self, network_config,
first_layer=None,
random_weights=True,
learning_rate=.0005):
self.network_config = network_config
if first_layer is None:
first_layer = Layer(np.zeros(network_config[0]["layers"]))
first_layer.set_activation(network_config[0]["activation"])
# Initialize layers
self.depth = len(network_config)
self.layers = [first_layer]
self.layers.extend(
[Layer(np.zeros(config["layers"]),
activation=config["activation"]) for config in network_config[1:]])
# Initialize learning rate
self.learning_rate = learning_rate
# Initialize weights
self.weights = []
for layer_idx in range(self.depth - 1):
if random_weights:
self.weights.append(2 * (np.random.rand(len(self[layer_idx + 1]), len(self[layer_idx])) - .5))
else:
self.weights.append(np.ones((len(self[layer_idx + 1]), len(self[layer_idx]))))
def __getitem__(self, item):
return self.layers[item]
def __str__(self):
max_elems = np.max([len(layer) for layer in self.layers])
matrix = [[str(layer[elem]) if elem < len(layer) else None for layer in self.layers] for elem in
range(max_elems)]
net_str = "\n".join([str(layer) for layer in matrix])
weight_str = str(self.weights)
try:
deltas_str = " ".join([str(lay.delta) for lay in self.layers])
except:
deltas_str = ""
return "Net:\n%s\n\nWeights:\n%s\n\nDeltas:\n%s" % (net_str, weight_str, deltas_str)
def forward_prop(self, debug=False):
for layer_idx in range(1, self.depth):
ww = self.weights[layer_idx - 1]
layer = self.layers[layer_idx - 1]
weighted_input = np.dot(ww, layer.values)
self.layers[layer_idx].values = weighted_input
self.layers[layer_idx].values = self.layers[layer_idx].activate()
if debug:
print("-------------")
print(self)
return self.layers[-1]
def back_prop(self, answer, debug=False):
def calc_deltas():
for layer_idx, layer in enumerate(reversed(self.layers)):
if layer_idx == 0:
# Calculate dE for Squared Error
outputs = self.layers[-1]
dE = self.layers[-1][0] - answer
square_error = dE ** 2
a = dE
else:
a = np.dot(self.weights[-layer_idx].T,
self.layers[-layer_idx].delta)
b = layer.backtivate()
layer_delta = np.multiply(a, b)
layer.delta = layer_delta
return square_error
def calc_dws():
dws = []
deltas = [l.delta for l in self.layers]
values = [l.values for l in self.layers]
for layer_idx, layer in enumerate(self.layers[:-1]):
dws.append(np.multiply(deltas[layer_idx + 1], values[layer_idx].T))
return dws
print("Answer:\n%f" % answer)
square_error = calc_deltas()
dws = calc_dws()
return dws, square_error
def set_inputs(self, layer):
self.layers[0] = layer
self.layers[0].set_activation(self.network_config[0]["activation"])
def build_network_config(layers):
network_config = []
for n_idx, neurons in enumerate(layers):
if n_idx != len(layers) - 1:
network_config.append({"layers": neurons, "activation": "passive"})
else:
network_config.append({"layers": neurons, "activation": "passive"})
return network_config
disp_el = 200
batch_size = 1
samples = 300
learning_rate = .00005
# Setup the network
network_config = build_network_config([1, 1])
net = Network(network_config=network_config,
random_weights=True,
learning_rate=learning_rate)
# Pull in the labeled data set.
dataset = DataSet(*gen_linear_regression_data(samples, seed=0, plot=True))
errs = []
cum_sum_dws = np.zeros_like(net.weights)
weights = []
for idx, dset in enumerate(dataset):
initial_layer = Layer(dset["data"])
net.set_inputs(initial_layer)
# Feed forward
net.forward_prop(True)
# Back propagate error
dws, sq_err = net.back_prop(dset["label"], debug=True)
errs.append(sq_err)
# Update weights
if idx % batch_size == 0 and idx != 0:
cum_sum_dws += dws
cum_sum_dws /= batch_size
new_weights = [net.weights[idx] - net.learning_rate * cum_sum_dws[idx] for idx in range(len(cum_sum_dws))]
net.weights = new_weights
weights.append(new_weights)
print("dws:\n%s" % str([-net.learning_rate * cum_sum_dws[idx] for idx in range(len(dws))]))
cum_sum_dws = np.zeros_like(dws)
else:
cum_sum_dws += dws
plt.scatter(range(len(errs[:disp_el])), errs[:disp_el])
plt.show()
plt.scatter(range(len(weights[:disp_el])), weights[:disp_el])
plt.show()
ww = Weights(net.weights)
# Validate data set
dataset = DataSet(*gen_linear_regression_data(5, seed=0))
for dset in dataset:
initial_layer = Layer(dset["data"])
net.set_inputs(initial_layer)
prediction = net.forward_prop()
print("data: %s, prediction: %s" % (str(dset["data"]), prediction))