前馈神经网络(Feedforward Neural Network)是一种最简单的神经网络模型,也是深度学习中最基础的模型之一。其基本思想是将输入数据传递给网络的输入层,逐层进行非线性转换,并终究输出猜测成果,如下图所示:
图中,Input Layuer 表明输入数据,Hidden Layer 表明躲藏层的输出,Output Layer 表明输出层的输出。躲藏层和输出层都是由若干个神经元组成的。每个神经元都有一个权重向量和一个偏置项,用于对输入数据进行非线性转换。详细地,关于第i个躲藏层神经元的输出,能够表明为:
hi=f(∑j=1nwijxj+bi)h_i = f(\sum_{j=1}^{n}w_{ij}x_j + b_i)
其间,f为激活函数,w为权重向量,b为偏置项,n为输入数据的维度。常见的激活函数包括sigmoid、ReLU、tanh等。
输出层的核算方式与躲藏层类似,即:
yk=g(∑j=1mwkjhj+bk)y_k = g(\sum_{j=1}^{m}w_{kj}h_j + b_k)
其间,g为输出层激活函数,k为输出数据的维度。
关于前馈神经网络的练习,一般选用反向传达算法。其基本思想是核算网络输出的差错,并将差错沿着网络反向传递,以调整各个神经元的权重和偏置项,然后使得输出成果愈加挨近目标成果。
下面是一个运用Python完成前馈神经网络的示例代码:
import numpy as np
class FeedforwardNeuralNetwork:
def __init__(self, num_inputs, num_hidden, num_outputs, learning_rate=0.1):
self.num_inputs = num_inputs
self.num_hidden = num_hidden
self.num_outputs = num_outputs
self.learning_rate = learning_rate
self.weights_hidden = np.random.randn(num_inputs, num_hidden)
self.bias_hidden = np.zeros((1, num_hidden))
self.weights_output = np.random.randn(num_hidden, num_outputs)
self.bias_output = np.zeros((1, num_outputs))
def sigmoid(self, x):
return 1 / (1 + np.exp(-x))
def sigmoid_derivative(self, x):
return x * (1 - x)
def forward(self, X):
self.hidden_layer = np.dot(X, self.weights_hidden) + self.bias_hidden
self.hidden_activation = self.sigmoid(self.hidden_layer)
self.output_layer = np.dot(self.hidden_activation, self.weights_output) + self.bias_output
self.output_activation = self.sigmoid(self.output_layer)
return self.output_activation
def backward(self, X, y, output):
output_error = y - output
output_delta = output_error * self.sigmoid_derivative(output)
hidden_error = np.dot(output_delta, self.weights_output.T)
hidden_delta = hidden_error * self.sigmoid_derivative(self.hidden_activation)
self.weights_output += self.hidden_activation
self.learning_rate * output_delta.T
self.bias_output += np.sum(output_delta, axis=0, keepdims=True) * self.learning_rate
self.weights_hidden += np.dot(X.T, hidden_delta) * self.learning_rate
self.bias_hidden += np.sum(hidden_delta, axis=0, keepdims=True) * self.learning_rate
def train(self, X, y, num_epochs):
for i in range(num_epochs):
output = self.forward(X)
self.backward(X, y, output)
def predict(self, X):
return self.forward(X)
# 示例
X = np.array([[0, 0, 1], [0, 1, 1], [1, 0, 1], [1, 1, 1]])
y = np.array([[0], [1], [1], [0]])
model = FeedforwardNeuralNetwork(num_inputs=3, num_hidden=4, num_outputs=1, learning_rate=0.1)
model.train(X, y, num_epochs=10000)
print(model.predict(X)) # 输出猜测成果
在示例代码中,咱们界说了一个名为FeedforwardNeuralNetwork
的类,它包括了网络的各种属性和办法,如网络输入维度、躲藏层维度、输出层维度、学习率、权重向量、偏置项、激活函数、反向传达算法等。其间,forward
办法用于核算网络的前向传达成果,backward
办法用于核算网络的反向传达成果,train
办法用于练习网络,predict
办法用于输出猜测成果。
在示例中,咱们运用一个包括4个样本、每个样本有3个特征的数据集X,以及一个包括4个样本、每个样本有1个标签的数据集y,来练习一个包括1个躲藏层(4个神经元)的前馈神经网络。网络的学习率设置为0.1,练习过程中迭代了10000次。最后,咱们运用predict
办法对数据集X进行猜测,并输出猜测成果。
需求留意的是,前馈神经网络的简单性也带来了一些限制。例如,它只能处理固定维度的输入和输出,无法处理变长的序列数据;一起,它也无法处理一些比较杂乱的形式,如自然语言处理、图画处理等。因而,在实践运用中,需求依据详细状况挑选合适的模型来进行建模。
适用于小型数据集
前馈神经网络适用于小型数据集的原因主要是由于其模型参数相对较少,练习和猜测的速度比较快,不会呈现过拟合的问题。
详细来说,当练习数据集规划较小时,前馈神经网络能够很好地拟合数据,而不会呈现过度拟合的状况。这是由于,前馈神经网络的模型参数相对较少,它的拟合才干较为有限,因而不会将练习数据集中的噪声也纳入到模型中,然后防止了过度拟合的问题。
别的,前馈神经网络的练习和猜测速度较快,由于前向传达过程中没有循环操作,而且反向传达算法中的梯度核算能够通过矩阵运算进行高效地核算。
下面是一个用于二分类使命的前馈神经网络示例代码:
import numpy as np
class FeedForwardNeuralNetwork:
def __init__(self, num_inputs, num_hidden, num_outputs, learning_rate):
self.num_inputs = num_inputs
self.num_hidden = num_hidden
self.num_outputs = num_outputs
self.learning_rate = learning_rate
# 初始化权重和偏置项
self.weights_hidden = np.random.randn(num_inputs, num_hidden)
self.bias_hidden = np.zeros((1, num_hidden))
self.weights_output = np.random.randn(num_hidden, num_outputs)
self.bias_output = np.zeros((1, num_outputs))
def sigmoid(self, z):
return 1 / (1 + np.exp(-z))
def forward(self, X):
hidden = self.sigmoid(np.dot(X, self.weights_hidden) + self.bias_hidden)
output = self.sigmoid(np.dot(hidden, self.weights_output) + self.bias_output)
return output
def backward(self, X, y, output):
output_error = y - output
output_delta = output_error * output * (1 - output)
hidden_error = np.dot(output_delta, self.weights_output.T)
hidden_delta = hidden_error * (1 - self.sigmoid(np.dot(X, self.weights_hidden) + self.bias_hidden)) * self.sigmoid(np.dot(X, self.weights_hidden) + self.bias_hidden)
self.weights_output += np.dot(self.sigmoid(np.dot(X, self.weights_hidden) + self.bias_hidden).T, output_delta) * self.learning_rate
self.bias_output += np.sum(output_delta, axis=0, keepdims=True) * self.learning_rate
self.weights_hidden += np.dot(X.T, hidden_delta) * self.learning_rate
self.bias_hidden += np.sum(hidden_delta, axis=0, keepdims=True) * self.learning_rate
def train(self, X, y, num_epochs):
for i in range(num_epochs):
output = self.forward(X)
self.backward(X, y, output)
def predict(self, X):
return np.round(self.forward(X))
# 示例
X = np.array([[0, 0, 1], [0, 1, 1], [1, 0, 1], [1, 1, 1]])
y = np.array([[0, 1, 1, 0]]).T
# 创建前馈神经网络
nn = FeedForwardNeuralNetwork(num_inputs=3, num_hidden=4, num_outputs=1, learning_rate=0.1)
# 练习网络
nn.train(X, y, num_epochs=10000)
# 猜测成果
print(nn.predict(X))
在上述示例代码中,咱们首要界说了一个FeedForwardNeuralNetwork
类,其间包括了前向传达和反向传达算法的完成。在练习过程中,咱们将练习数据集X
和标签y
作为输入,通过调用train()
办法进行练习。在练习完成后,咱们能够通过调用predict()
办法对新的数据进行分类猜测。
需求留意的是,上述示例代码仅用于二分类使命,假如要进行更杂乱的使命,需求相应地修改网络结构和参数设置。
无法处理序列数据
前馈神经网络无法处理序列数据的主要原因在于其缺乏回忆才干,即无法对前一次输入的状况进行回忆和处理。在前馈神经网络中,每个神经元的输入只依赖于上一层的输出,与时间和序列无关。因而,前馈神经网络无法处理序列数据。
举个例子,假如咱们要将一段文字进行情感分析,那么这段文字便是一个序列数据。前馈神经网络无法有效地处理这样的序列数据,由于它无法将前一时间的输入和输出作为当时时间的输入,也就无法考虑前一时间的上下文信息。
相比之下,循环神经网络(Recurrent Neural Network,RNN)是一种能够处理序列数据的神经网络模型。循环神经网络在处理序列数据时,会引入一个循环结构,使得每个时间的输入除了依赖于上一层的输出,还依赖于上一时间的输出,这样就能够处理序列数据,完成对序列中的上下文信息的建模。
以下是一个简单的前馈神经网络代码示例,其间咱们运用前馈神经网络对MNIST手写数字数据集进行分类。能够看到,该模型的输入是一个固定长度的向量,输出也是一个固定长度的向量,无法处理变长的序列数据。
import numpy as np
from keras.datasets import mnist
# 加载MNIST数据集
(X_train, y_train), (X_test, y_test) = mnist.load_data()
# 对数据进行预处理
X_train = X_train.reshape(X_train.shape[0], -1) / 255.0
X_test = X_test.reshape(X_test.shape[0], -1) / 255.0
y_train = np.eye(10)[y_train]
y_test = np.eye(10)[y_test]
# 界说前馈神经网络
class FeedforwardNeuralNetwork:
def __init__(self, num_inputs, num_hidden, num_outputs):
self.num_inputs = num_inputs
self.num_hidden = num_hidden
self.num_outputs = num_outputs
# 初始化权重和偏置项
self.weights1 = np.random.randn(self.num_inputs, self.num_hidden)
self.bias1 = np.zeros((1, self.num_hidden))
self.weights2 = np.random.randn(self.num_hidden, self.num_outputs)
self.bias2 = np.zeros((1, self.num_outputs))
def sigmoid(self, x):
return 1 / (1 + np.exp(-x))
def forward(self, X):
# 核算躲藏层输出
self.hidden = self.sigmoid(np.dot(X, self.weights1) + self.bias1)
# 核算输出层输出
self.output = self.sigmoid(np.dot(self.hidden, self.weights2) + self.bias2)
return self.output
def backward(self, X, y, output, learning_rate):
# 核算输出层差错
delta_output = (output - y) * output * (1 - output)
# 核算躲藏层差错
delta_hidden = np.dot(delta_output, self.weights2.T) * self.hidden * (1 - self.hidden)
# 更新权重和偏置项
self.weights2 -= learning_rate * np.dot(self.hidden.T, delta_output)
self.bias2 -= learning_rate * np.sum(delta_output, axis=0, keepdims=True)
self.weights1 -= learning_rate * np.dot(X.T, delta_hidden)
self.bias1 -= learning_rate * np.sum(delta_hidden, axis=0, keepdims=True)
def train(self, X, y, learning_rate=0.1, num_epochs=10):
for epoch in range(num_epochs):
# 前向传达
output = self.forward(X)
# 反向传达
self.backward(X, y, output, learning_rate)
def predict(self, X):
# 对输入进行猜测
return np.argmax(self.forward(X), axis=1)
# 创建前馈神经网络模型
model = FeedforwardNeuralNetwork(num_inputs=784, num_hidden=128, num_outputs=10)
# 练习模型
model.train(X_train, y_train, learning_rate=0.1, num_epochs=10)
# 猜测测验集
y_pred = model.predict(X_test)
# 核算模型准确率
accuracy = np.mean(y_pred == np.argmax(y_test, axis=1))
print("Accuracy:", accuracy)
在这个示例中,咱们界说了一个具有一个躲藏层的前馈神经网络模型,用于对MNIST手写数字数据集进行分类。在模型的练习过程中,咱们运用反向传达算法更新模型的权重和偏置项。终究,咱们核算模型在测验集上的准确率。
需求留意的是,在处理序列数据时,咱们应该运用循环神经网络,而不是前馈神经网络。循环神经网络在处理序列数据时,引入了一个循环结构,能够对序列中的上下文信息进行建模,因而能够更好地处理序列数据。
容易堕入部分最优解
容易堕入部分最优解是前馈神经网络的一个劣势,这是由于前馈神经网络的丢失函数对错凸函数,存在多个部分最优解。假如网络的初始参数设置不合理或许学习率设置不合适,就或许堕入到一个部分最优解中,无法得到全局最优解。下面给出一个简单的示例来阐明这个问题。
咱们运用一个单躲藏层的前馈神经网络对非线性函数y=sin(x)进行回归,丢失函数为均方差错(MSE),代码如下所示:
import numpy as np
import matplotlib.pyplot as plt
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def feedforward(X, w1, b1, w2, b2):
hidden = sigmoid(np.dot(X, w1) + b1)
output = np.dot(hidden, w2) + b2
return hidden, output
def backward(X, y, hidden, output, w2):
output_delta = output - y
hidden_delta = np.dot(output_delta, w2.T) * hidden * (1 - hidden)
return output_delta, hidden_delta
def train(X, y, num_hidden, learning_rate, num_epochs):
num_inputs = X.shape[1]
num_outputs = y.shape[1]
w1 = np.random.randn(num_inputs, num_hidden)
b1 = np.zeros((1, num_hidden))
w2 = np.random.randn(num_hidden, num_outputs)
b2 = np.zeros((1, num_outputs))
for i in range(num_epochs):
hidden, output = feedforward(X, w1, b1, w2, b2)
output_delta, hidden_delta = backward(X, y, hidden, output, w2)
w2 -= learning_rate * np.dot(hidden.T, output_delta)
b2 -= learning_rate * np.sum(output_delta, axis=0, keepdims=True)
w1 -= learning_rate * np.dot(X.T, hidden_delta)
b1 -= learning_rate * np.sum(hidden_delta, axis=0, keepdims=True)
if i % 1000 == 0:
loss = np.mean((output - y) ** 2)
print(f"Epoch {i}: loss = {loss:.4f}")
return w1, b1, w2, b2
# 生成练习数据
X = np.linspace(-5, 5, 200).reshape(-1, 1)
y = np.sin(X)
# 练习模型
w1, b1, w2, b2 = train(X, y, num_hidden=10, learning_rate=0.01, num_epochs=10000)
# 制作成果
hidden, output = feedforward(X, w1, b1, w2, b2)
plt.plot(X, y, label="Ground truth")
plt.plot(X, output, label="Predicted")
plt.legend()
plt.show()
在上述代码中,咱们界说了一个包括10个躲藏神经元的前馈神经网络,并运用均方差错作为丢失函数进行练习。咱们运用正弦函数作为练习数据,并在练习过程中输出丢失函数的值以及制作练习成果。运转代码后,咱们能够得到以下成果:
Epoch 0: loss = 1.2486
Epoch 1000: loss = 0.0061
Epoch 2000: loss = 0.0059
Epoch 3000: loss = 0.0058
Epoch 4000: loss = 0.0056
Epoch 5000: loss = 0.0055
Epoch 6000: loss = 0.0053
Epoch 7000: loss = 0.0051
Epoch 8000: loss = 0.0049
Epoch 9000: loss = 0.0046
能够看到,在练习的过程中,丢失函数逐渐下降,但是终究的练习成果并不是很理想,猜测成果与实在值有很大的差错。这是由于,由于初始参数随机初始化,网络或许堕入了一个部分最优解,无法得到全局最优解。
为了阐明这个问题,咱们将躲藏神经元的数量从10调整到2,再次运转代码。此时,网络的拟合才干显着下降,能够看到,猜测成果与实在值之间的差距更大了。咱们能够尝试屡次运转代码,能够发现有些运转成果表现比较好,有些则比较差,这便是部分最优解导致的问题。
因而,在实践运用中,咱们需求选用一些办法来防止部分最优解的问题,例如运用不同的初始化办法、改变网络结构、运用正则化等办法。一起,也能够选用基于遗传算法、粒子群算法等优化算法来优化神经网络的参数,以得到更好的成果。
对超参数灵敏
超参数是指在模型练习过程中需求手动设置的参数,例如学习率、正则化系数、迭代次数等。这些超参数的挑选对模型功能有很大的影响,因而需求进行重复试验来调整超参数,以获得最佳的模型功能。
对超参数灵敏是指,超参数的挑选对模型功能有很大的影响,即使是微小的超参数调整也或许会导致模型功能的明显变化。例如,在神经网络中,假如学习率设置过大,或许会导致模型不收敛,而假如学习率设置过小,则或许需求更多的迭代次数才干收敛。因而,超参数的挑选需求依据详细的问题进行调整,不能盲目地挑选默认值。
以下是一个运用前馈神经网络进行手写数字识别的示例,其间包括了几个常用的超参数,如学习率、躲藏层巨细、迭代次数等。能够通过调整这些超参数,来观察它们对模型功能的影响。
import numpy as np
from sklearn.datasets import load_digits
from sklearn.preprocessing import LabelBinarizer
from sklearn.model_selection import train_test_split
# 加载数据集
digits = load_digits()
X = digits.data
y = digits.target
y_onehot = LabelBinarizer().fit_transform(y)
X_train, X_test, y_train, y_test = train_test_split(X, y_onehot, test_size=0.3, random_state=42)
# 界说前馈神经网络
class FeedforwardNeuralNetwork:
def __init__(self, num_inputs, num_hidden, num_outputs, learning_rate):
self.num_inputs = num_inputs
self.num_hidden = num_hidden
self.num_outputs = num_outputs
self.learning_rate = learning_rate
# 初始化权重和偏置项
self.weights_hidden = np.random.randn(num_inputs, num_hidden)
self.bias_hidden = np.zeros((1, num_hidden))
self.weights_output = np.random.randn(num_hidden, num_outputs)
self.bias_output = np.zeros((1, num_outputs))
def sigmoid(self, X):
return 1 / (1 + np.exp(-X))
def sigmoid_derivative(self, X):
return X * (1 - X)
def forward(self, X):
# 核算躲藏层输出
hidden_layer_input = np.dot(X, self.weights_hidden) + self.bias_hidden
hidden_layer_output = self.sigmoid(hidden_layer_input)
# 核算输出层输出
output_layer_input = np.dot(hidden_layer_output, self.weights_output) + self.bias_output
output_layer_output = self.sigmoid(output_layer_input)
return hidden_layer_output, output_layer_output
def backward(self, X, y, output):
hidden_layer_output, output_layer_output = output
# 核算输出层的差错
output_error = y - output_layer_output
output_delta = output_error * self.sigmoid_derivative(output_layer_output)
# 核算躲藏层的差错
hidden_error = np.dot(output_delta, self.weights_output.T)
hidden_delta = hidden_error * self.sigmoid_derivative(hidden_layer_output)
# 更新权重和偏置项
self.weights_output += self.learning_rate * np.dot(hidden_layer_output.T, output_delta)
self.bias_output += self.learning_rate * np.sum(output_delta, axis=0, keepdims=True)
self.weights_hidden += self.learning_rate * np.dot(X.T, hidden_delta)
self.bias_hidden += self.learning_rate * np.sum(hidden_delta, axis=0, keepdims=True)
def train(self, X, y, num_epochs):
for epoch in range(num_epochs):
hidden_layer_output, output_layer_output = self.forward(X)
self.backward(X, y, (hidden_layer_output, output_layer_output))
def predict(self, X):
_, output_layer_output = self.forward(X)
return np.argmax(output_layer_output, axis=1)
# 创建模型并练习
model = FeedforwardNeuralNetwork(num_inputs=64, num_hidden=100, num_outputs=10, learning_rate=0.1)
model.train(X_train, y_train, num_epochs=1000)
# 在测验集上测验模型
y_pred = model.predict(X_test)
accuracy = np.mean(y_pred == np.argmax(y_test, axis=1))
print(f"Accuracy: {accuracy}")
在上面的示例中,能够调整超参数 num_hidden
和 learning_rate
来观察它们对模型功能的影响。假如 num_hidden
设置得过小,或许会导致模型欠拟合,而假如设置得过大,则或许会导致模型过拟合。而假如 learning_rate
设置得过小,则或许需求更多的迭代次数才干收敛,而假如设置得过大,则或许会导致模型不收敛或许呈现震动。因而,超参数的挑选需求进行重复试验和调整,以获得最佳的模型功能。