用算法来预测下表的结果:
Input | Output |
0 0 1 | 0 |
1 1 1 | 1 |
1 0 1 | 1 |
0 1 1 | 0 |
上表就相当于是训练数据,Output相当于标签(Label)。
import numpy as np
def nonlin(x, deriv=False):
if True == deriv:
return x*(1-x)
return 1 / (1 + np.exp(-x))
train_data = np.array([[0, 0, 1],
[0, 1, 1],
[1, 0, 1],
[1, 1, 1]])
label = np.array([[0, 0, 1, 1]]).T
np.random.seed(1)
weights = np.random.random((3, 1))
weights = 2*weights- 1
for iter in range(1000):
l0 = train_data
l1 = nonlin(np.dot(l0, weights))
loss = label - l1
l1_delta = loss * nonlin(l1, True)
weights += np.dot(l0.T, l1_delta)
print(l1)
结果如下:
加大循环次数,可以更接近[0, 0, 1, 1]这个结果。
nonlin函数(sigmoid函数),参数deriv为True时,表示求导数,否则将一个实数转化为0到1间的一个数,也可以叫概率。
代码先随机初始化weights矩阵,这个矩阵中的每个元素表示了训练数据每组元素中的各个元素(如第一组(行)[0, 0, 1])与其结果[0](label矩阵的第一个元素)的权重关系。
代码详细解析 拿第一行数据为例:如下所示
下面对以上代码进行修改:
import numpy as np
def nonlin(x, deriv=False):
if True == deriv:
return x*(1-x)
return 1 / (1 + np.exp(-x))
train_data = np.array([[0, 0, 1],
[0, 1, 1],
[1, 0, 1],
[1, 1, 1]])
label = np.array([[0, 1, 1, 0]]).T # 仅修改此处
np.random.seed(1)
weights = np.random.random((3, 1))
weights = 2*weights- 1
for iter in range(1000):
l0 = train_data
l1 = nonlin(np.dot(l0, weights))
loss = label - l1
l1_delta = loss * nonlin(l1, True)
weights += np.dot(l0.T, l1_delta)
print(l1)
结果是什么呢?
期望[0, 1, 1, 0], 也就是我们的算法已经失去了效果。
为什么同样的代码(两层网络),仅修改了一点数据,就出现这么大的差异呢?
我们来对比一个前后两组数据。
Input | Output |
0 0 1 | 0 |
1 1 1 | 1 |
1 0 1 | 1 |
0 1 1 | 0 |
Input | Output |
0 0 1 | 0 |
1 1 1 | 0 |
1 0 1 | 1 |
0 1 1 | 1 |
其实可以理解为,第一组数据(Input与Output)是一个线性的关系, 所以我们才能用一个矩阵weights来计算三个权重,也就是说这三个权重对四组数据都是有效的,适用于四组数据。
而第二组数据就不是线性的关系了,其关系更为复杂(去掉第三列,然后一,二列异或运算),仅靠两层网络是处理不了的。
我们上面添加的那层仅能处理线性的关系。
解决方法就是再添加一层网络:代码如下:
import numpy as np
def nonlin(x, deriv=False):
if True == deriv:
return x*(1-x)
return 1 / (1 + np.exp(-x))
train_data = np.array([[0, 0, 1],
[0, 1, 1],
[1, 0, 1],
[1, 1, 1]])
label = np.array([[0], [1], [1], [0]])
np.random.seed(1)
weights1 = 2 * np.random.random((3, 4)) - 1
weights2 = 2 * np.random.random((4, 1)) - 1
for j in range(10000):
l0 = train_data # [4, 3]
l1 = nonlin(np.dot(l0, weights1)) # [4, 4]
l2 = nonlin(np.dot(l1, weights2)) # [4, 1]
l2_loss = label - l2 # [4, 1]
l2_delta = l2_loss * nonlin(l2, True) # 不是矩阵乘法
l1_loss = l2_delta.dot(weights2.T)
l1_delta = l1_loss * nonlin(l1, True)
weights1 += l0.T.dot(l1_delta)
weights2 += l1.T.dot(l2_delta)
print(l2)
结果如下:
符合预期。
代码重点应该是在加的l1 = nonlin(np.dot(l0, weights1)) # [4, 4]
这一层,这是一个4*4的矩阵。
我们先来看这个矩阵是怎么来的。
它是由train_data与weights1做矩阵乘法的结果。l1的每一个元素等于train_data的相应行(一组)与weights1的相应列,每个元素的权重点乘所得。
注意这里weights1是个3*4的矩阵,而文章开头的代码weights是3*1的。前面说过,前面的weights矩阵中的各个元素可以适用于所有四条数据(线性)。而这里weights1这个3*4的矩阵则是为每组训练数据都准备了一组权重(一共四组)。
也就是说,第二组数据虽然不是线性的关系,但是经过这一层处理后,其结果l1具备线性的关系,可以由我们文章开头的代码逻辑来处理了。可以看到,l2这层的处理与文章开头的代码基本是一样的,只不过l2是把l1作为输入的。
还有l1_loss计算那里,l1不像l2可以用label来计算损失,l1这一层在中间,是我们加上去的,它的损失值是根据l2的损失来计算的,l1是l2的前一层,l2的损失当然要l1来负责了,还要考虑l1与l2之间的权重,比如l1的a, b, c三个位置导致l2损失了0.1, 占比多的,多损失一点,不能a, b, c都损失0.1。
所以我们可以猜测,对于更加复杂的问题,n层网络处理不了,就再加一层,直到提取出来可以当作线性数据处理的结果,再交给类似上面代码总结输出,也就是深度学习(这里是我的猜测,没有查相关定义,可能不准确)。