用Numpy写一个简洁的神经网络

最近想实现一个简单的神经网络,之前用C语言写过一次,只觉得很繁琐,最近看到一个非常简洁的神经网络实现,代码如下

X = np.array([ [0,0,1],[0,1,1],[1,0,1],[1,1,1] ])
y = np.array([[0,1,1,0]]).T
syn0 = 2*np.random.random((3,4)) - 1
syn1 = 2*np.random.random((4,1)) - 1
for j in xrange(60000):
    l1 = 1/(1+np.exp(-(np.dot(X,syn0))))
    l2 = 1/(1+np.exp(-(np.dot(l1,syn1))))
    l2_delta = (l2 - y)*(l2*(1-l2))  #此处原链接中取负号,后面梯度计算取加号,有很大误导性
    l1_delta = l2_delta.dot(syn1.T) * (l1 * (1-l1))
    syn1 -= l1.T.dot(l2_delta)
    syn0 -= X.T.dot(l1_delta)

具体的解释见原链接。

非常简洁,利用numpy中便捷的矩阵运算代替了我之前所使用的多重循环,运算速度也快了很多。因此我想学习一下这种pythonic的方式来实现一个简单的径向基网络。

首先需要了解的是numpy。np.array是一种很方便的数据类型,既是多维数组,也是矩阵。当它作为矩阵时有两种运算:

  1. 如 + - * 普通运算
  2. 点乘等矩阵运算

当执行简单运算的时候对于两个操作数有一些要求:

比如,一个 n*m 的矩阵可以加一个 1*m的矩阵,这等于是每一列都加上一个1*m的矩阵,或者是加上一个 n*1的矩阵,这也就等于每一行都加上一个n*1的矩阵。减法和乘法同理。

当一侧是一个数字的时候,这就等于对矩阵中的每一个元素执行该操作,还有就是 n**2 或者 n*n表示每一个元素都取平方,而不是矩阵乘法。

真正的矩阵乘法需要使用dot,如x.dot(y)或者np.dot(x,y),遵循矩阵运算法则。

好的,现在我们来写一个RBF网络。

我们假设网络为3层,节点数分别为i=2,j=5,k=1,样本数量为n=4

X = np.array([ [0,0],[0,1],[1,0],[1,1] ])
y = np.array([[0,1,1,0]]).T
c = 2*np.random.random((2,5)) - 1
b =  2*np.random.random((1,5)) - 1
w =  2*np.random.random((1,5)) - 1

def euclidDistance(X, Y):  #1*n
    Z = X - Y
    return Z.dot(Z.T)

def rbf(X, c, b):  # X:n*i  c:i*j  b:1*j  w:1*j
    N = len(X)
    J = len(c.T)
    r = np.zeros((N,J))
    for i in range(N):
        for j in range(J):
            r[i][j] = euclidDistance(X[i],c.T[j])
    return r  # return n*j
    
for u in range(6000):
    r = rbf(X,c,b)
    l1 = np.exp(-1*b*r)
    l2 = l1.dot(w.T)
    delta_w = (l2-y) * l1
    delta_b = (l2-y) * w * l1* -1 * r
    w -= 0.1*np.sum(delta_w, 0) //此处其实是累积bp算法,将n个样本的梯度一起计算了
    b -= 0.1*np.sum(delta_b, 0)

公式为:

$$ φ(x)=∑w_j p(x,c_j) $$
$$ \rho (x,c_j)=w−β_j||x−c_j||^2 $$
反向求导之后
$$ \Delta w = (o - y)\rho $$
$$ \Delta \beta = -(o - y )w_j\rho ||x−c_j||^2 $$

可以与代码中对应,但是需要注意的是程序中进行的都是矩阵运算,在看的过程中需要十分注意。
(之所以代码长度比普通bp网络长是因为bp网络中的线性运算可以非常流畅地转换成矩阵运算,基本上一次dot点乘就可以省略一个循环。而欧几里得距离的计算方式我没有想到什么好方法表示...只有用一个双重循环了)