神经网络
最近学习神经网络的时候,发现大多介绍神经网络的文章都是先大篇幅的描述概念,然后莫名其妙就丢出很多晦涩难懂的数学公式,就结束了,让人看得一头雾水。
直到最近看了一篇文章,它通过实例和概念相结合的方式介绍神经网络,对初学者十分友好,于是我将其与其他文章整合,加上自己的理解,写出这篇文章。
什么是神经网络
简单介绍一下神经网络,
以房价预测的来说明,把房屋的面积作为神经网络的输入(我们称之为𝑥),通过一个节点(一个小圆圈),最终输出了价格(我们用𝑦表示),那么这个节点就是神经元,而无数个神经元就组成了神经网络
神经网络的一般结构是由输入层、隐藏层(神经元)、输出层构成的。隐藏层可以是1层或者多层叠加,层与层之间是相互连接的,如下图所示。

一般说到神经网络的层数是这样计算的,输入层不算,从隐藏层开始一直到输出层,一共有几层就代表着这是一个几层的神经网络,例如上图就是一个三层结构的神经网络。
已经知道了神经网络的概念,接下来开始尝试一下通过代码来理解和实现一个神经网络吧!
构建神经元
“千里之行,始于足下”,我们先从一个神经元开始构建。
神经元接受输入,做一些数学运算,然后产生一个输出。下图是一个2输入神经元
神经元中,输入的数据总共经历了三步数学运算。
首先将输入乘上权重:
x_2 \rightarrow x_2*w_2 **接下来将结果相加,再加上一个偏移值(bias):** (x_1*w_1)+(x_2*w_2)+b **最后,将结果放入激活函数中(activation function):** y=f(x_1*w_1+x_2*w_2+b) 那么,什么是激活函数呢? **激活函数**的作用就是将输入转化为可预测的输出,常用的激活函数是**sigmoid**函数 S(x)=\frac{1}{1+e^{-x}} <img src="https://victorzhou.com/static/dd5a39500acbef371d8d791d2cd381e0/39600/sigmoid.png" alt="sigmoid" style="zoom:50%;" /> sigmoid函数的输出介于0~1之间,可以理解为sigmoid将(−∞,+∞) 的数压缩到了(0, 1)这个区间。 当然还有其他的激活函数: * **tanh(双曲正切)函数**:$\ g(z)=\frac{e^z−e^{−z}}{e^z+e^{−z}},g(z)'=1−(tanh(z))^2$ * **ReLu(修正线性单元)函数:** 只要𝑧是正值的情况下,导数恒等于 1,当𝑧是负值的时候,导数恒等于 0。$\ a=max(0,z)$ **激活函数优缺点:** - 在𝑧的区间变动很大的情况下,激活函数的导数或者激活函数的斜率都会远大于0,在程序实现就是一个 **if-else** 语句,而 **sigmoid** 函数需要进行浮点四则运算,在实践中,使用 **ReLu** 激活函数神经网络通常会比使用 **sigmoid** 或者 **tanh** 激活函数学习的更快。weight=[0,1]\
bias=4
(w⋅x)+b\=((x_1w_1)+(x_2w_2))+b\
=02+13+4\
=7\
Then\ put\ in \ activation\ function:
\output=f((w⋅x)+b)\ =0.999
如果输入的值为x=[2,3],那么我们可以计算出神经网络中所代表的值
因此在这个神经网络中,在输入x=[2,3]时,输出为0.7216
神经网络可以有任意数量的层,在这些层中有任意数量的神经元。
但是其基本的思想是一样的:通过网络中的神经元向前输入,最终得到输出。
实现代码
import numpy as np
class Neuron:
def __init__(self, weights, bias):
self.weights = weights
self.bias = bias
def feedforward(self, inputs):
return sigmoid(np.dot(self.weights, inputs) + self.bias)
class OurNeuralNetwork:
def __init__(self):
weights = np.array([0, 1])
bias = 0
self.h1 = Neuron(weights, bias)
self.h2 = Neuron(weights, bias)
self.o1 = Neuron(weights, bias)
def feedforward(self, x):
out_h1 = self.h1.feedforward(x)
out_h2 = self.h2.feedforward(x)
out_o1 = self.o1.feedforward(np.array([out_h1, out_h2]))
return out_o1
if __name__ == "__main__":
network = OurNeuralNetwork()
x = np.array([2, 3])
print(network.feedforward(x))
0.7216
训练神经网络
我们有如下数据集:
| Name | Weight | Height | Gender |
|---|---|---|---|
| Alice | 54.5 | 165 | 女 |
| Bob | 66 | 170 | 男 |
| Charlie | 48 | 158 | 女 |
| Diana | 65 | 171 | 女 |
| Jack | 70.5 | 181.3 | 男 |
| Loony | 58 | 172 | 男 |
接下来让我们通过数据集来训练神经网络,使其能通过给定身高体重来预测一个人的性别吧!
第一步
我们首先通过0和1来分别代表女性和男性,同时为了能够方便表示数据,我们将身高和体重分别减去170和60
| Name | Weight | Height | Gender |
|---|---|---|---|
| Alice | -5.5 | -5 | 1 |
| Bob | 6 | 0 | 0 |
| Charlie | -12 | -12 | 1 |
| Diana | 5 | 1 | 1 |
| Jack | 10.5 | 11.3 | 0 |
| Loony | -2 | 2 | 0 |
在训练神经网络之前,我们首先需要有一种能知道神经网络中输出值是否表现得“好”与“坏”的方法,这种方法就称作 “损失函数”
常用的损失函数为MSE(mean squared error),也就是均方差
其中,n代表训练样本的数量,代表训练样本中的真实值,代表神经网络中输出的预测值
换句话说:
训练神经网络=尝试寻找损失函数最小值
举个例子
| Name | |||
|---|---|---|---|
| Alice | 1 | 0 | 1 |
| Bob | 0 | 0 | 0 |
| Charlie | 1 | 0 | 1 |
| Diana | 1 | 0 | 1 |
| Jack | 0 | 0 | 0 |
| Loony | 0 | 0 | 0 |
接下来,我们探究一下权重和偏移值对损失函数的具体影响:
我们可以把损失函数写成一个多变量函数
如果调整一下,损失函数是会变大还是变小?我们需要知道偏导数是正是负才能回答这个问题。
首先,根据链式求导法则
由于,因此:
接下来我们要想办法获得和的关系,我们已经知道神经元的数学运算规则:
实际上只有神经元中包含权重,所以我们再次运用链式求导法则:
同理:
\\h_1=f(w_1x_1+w_2x_2+b_1) \frac{∂h_1}{∂w_1}=x_1*f′(w_1x_1+w_2x_2+b_1)……③ 我们在上面的计算中遇到了2次激活函数sigmoid的导数f′(x),sigmoid函数的导数很容易求得: f(x)= \frac{1}{1+e^{-x}} f'(x)=\frac{e^{-x}}{(1+e^{-x})^2}=f(x)*(1-f(x)) 总的链式求导公式为①②③ \frac{∂L}{∂w_1}= \frac{∂L}{∂y_{pred}}*\frac{∂y_{pred}}{∂h_1}*\frac{∂h_1}{∂w_1} 这种通过求偏导而从后向前进行计算的,我们称为**反向传播** <hr>  | Name | Weight | Height | Gender | | Alice | -5.5 | -5 | 1 | 接下来我们通过Alice样本,并假设所有权重为1,所有偏移值为0,带入来计算一下神经网络中的各个数值 h_1=f(w_1x_1+w_2x_2+b_1) =0.0000275 h2=f(w_3x_1+w_4x_2+b_2)=0.0000275 o_1=f(w_5h_1+w_6h_2+b_3)\\ \\=0.50 神经网络的输出$\ y_{pred}=0.5$,没有显示出强烈的是女(1)是男(0)的证据。现在的预测效果还很不好。 我们再计算一下当前网络的偏导数$\ \frac{∂L}{∂w1}$: \frac{∂L}{∂w_1}= \frac{∂L}{∂y_{pred}}*\frac{∂y_{pred}}{∂h_1}*\frac{∂h_1}{∂w_1} \frac{\partial L}{\partial y_{pred}} =-2(1-y_{pred})\\ \frac{∂y_{pred}}{h_1}=w_5*f′(w_5h_1+w_6h_2+b_3)\\ =f(0.000055)*(1-f(0.000055)) \\ =-5.5*f'(-10.5) \\ =0.0000375 由此得出,当我们增加$\ w_1,L$ 也会略微的增加。 ### 第三步 下面将使用一种称为**随机梯度下降**(SGD)的优化算法,来训练神经网络的参数。 经过前面的运算,我们已经有了训练神经网络所有数据。但是该如何操作呢? SGD定义了改变权重和偏置的方法: w=w-n\frac{∂L}{∂w_1} 其中$\ n$是一个常数,称为**学习率**(learning rate),它决定了我们训练网络速率的快慢。将w减去$\ η·\frac{∂L}{∂w1}$,就等到了新的权重w。 * If$\ \frac{\partial L}{\partial w_1}$是正数,那么w就会减少,这将会使损失函数L降低. * If$\ \frac{\partial L}{\partial w_1}$是负数,那么w就会增加,这将会使损失函数L降低. 如果我们用这种方法去逐步改变网络的权重w和偏置b,损失函数会缓慢地降低,从而改进我们的神经网络。 ### 训练流程如下: 1、从数据集中选择一个样本; 3、使用更新公式更新每个权重和偏置; | ------- | ------ | ------ | ------ | | Bob | 6 | 0 | 0 | | Diana | 5 | 1 | 1 | | Loony | -2 | 2 | 0 |  ```python def sigmoid(x): def diff_sigmoid(x): return fx * (1 - fx) res = 0 res += (i - j) ** 2 class Neuron: self.weights = weights total = np.dot(self.weights, inputs) + self.bias class NeuronNetworks: self.w1 = weight[0] self.w3 = weight[2] self.w5 = weight[4] self.b1 = bias[0] self.b3 = bias[2] def calculation(self, x): h2 = sigmoid(self.w3 * float(x[1]) + self.w4 * float(x[2]) + self.b2) return o1 def train(self, dataSets): y_T = [int(i[3]) for i in dataSets] for x,y_true in zip(dataSets,y_T): sum_h2=float(x[1]) * self.w1 + int(x[2]) * self.w2 + self.b1 h2 = sigmoid(float(x[1]) * self.w3 + float(x[2]) * self.w4 + self.b2) y_pre = sigmoid(h1 * self.w5 + h2 * self.w6 + self.b3) Diff_pre_Diff_h1 = self.w5 * diff_sigmoid(pre) Diff_pre_Diff_w5 = h1 * diff_sigmoid(pre) Diff_pre_Diff_b3 = diff_sigmoid(pre) Diff_h1_Diff_w2 = float(x[2]) * diff_sigmoid(sum_h1) Diff_h2_Diff_w4 = float(x[2]) * diff_sigmoid(sum_h2) Diff_h2_Diff_b2 = diff_sigmoid(sum_h2) self.w2 -= n * Diff_output_Diff_pre * Diff_pre_Diff_h1 * Diff_h1_Diff_w2 self.w4 -= n * Diff_output_Diff_pre * Diff_pre_Diff_h2 * Diff_h2_Diff_w4 self.w6 -= n * Diff_output_Diff_pre * Diff_pre_Diff_w6 self.b2 -= n * Diff_output_Diff_pre * Diff_pre_Diff_h2 * Diff_h2_Diff_b2 if epoch % 10 == 0: for x in dataSets: print(f"Epoch is {epoch} and loss is:{lossFunction(y_T, y_pre)}") ['Alice', 54.5, 165, 1], ['charlie', 48, 158, 1], ['jack', 70.5, 181.3, 0], ] weights = np.array( np.random.normal()]) nn = NeuronNetworks(weights, bias) DataSet[i][1]-=60 nn.train(DataSet) ``` 随着迭代次数的增加,损失函数逐步减少最终趋向稳定  现在我们可以用它来推测出每个人的性别了: ```python print(round(nn.calculation(['bamba',75-60, 178-170]))) #0->男性 >* [【深度学习】神经网络](http://mantchs.com/2019/08/04/DL/Neural%20Network/ )