使用python编写一个简单的循环神经网络拟合一个退位减法的操作
1.定义基本函数
首先手动写一个sigmoid函数机器导数
import copy, numpy as np
np.random.seed(0) #固定随机生成器的种子,可以每次得到一样的值
def sigmoid(x): #激活函数
output = 1/(1+np.exp(-x))
return output
def sigmoid_output_to_derivative(output):#激活函数的倒数
return output*(1-output)
2.建立二进制映射
定义的减法最大限制在256之内,即8位二进制的减法,定义int与二进制之间的映射数组int2binary。
int2binary = {}#整数到其二进制的映射
binary_dim = 8#暂时制作256以内的减法
#计算0~256的二进制表示
largest_number = pow(2, binary_dim)
binary = np.unpackbits(np.array([range(largest_number)], dtype = np.uint8).T, axis = 1)
for i in range(largest_number):
int2binary[i] = binary[i]
3.定义参数
定义学习参数:隐藏层的权重synase_0、循环节点的权重synapse_h(输入节点16,输出节点16)、输出层的权重synapse_1(输入节点16,输出节点1)。为了减小复杂度,这里只设权重w,b被忽略。
#定义参数
alpha = 0.9#学习速率
input_dim = 2#输入的我的维度是2,减数和被减数
hidden_dim = 16
output_dim = 1#输出维度为1
#初始化网络
synapse_0 = (2*np.random.random((input_dim, hidden_dim))-1)*0.05#维度为2*16,2是输入维度,16是隐藏层维度
synapse_1 = (2*np.random.random((hidden_dim, output_dim))-1)*0.05
synapse_h = (2*np.random.random((hidden_dim, hidden_dim))-1)*0.05 #=>[-0.05, 0.05]
#用于存放反向传播的权重你更新值
synapse_0_update = np.zeros_like(synapse_0)
synapse_1_update = np.zeros_like(synapse_1)
synapse_h_update = np.zeros_like(synapse_h)
4.准备样本数据
(1)建立循环生成样本的数据,先生成两个数a和b。如果a小于b就变换位置,保证被减数大。
(2)计算出相间的结果c。
(3)将三个数字转换成二进制,为模型计算做准备。
将上面过程一一实现,代码如下:
#开始训练
for j in range(10000):
#生成一个数字a
a_int = np.random.randint(largest_number)
#生成一个数字b,b的最大去只是largest_number/2,作为被减数,让它小一点
b_int = np.random.randint(largest_number/2)
#如果生成的大于b了,那么交换一下
if a_int < b_int:
tt = b_int
b_int = a_int
a_int = tt
a = int2binary[a_int] #二进制编码
b = int2binary[b_int] #二进制编码
#正确的答案
c_int = a_int - b_int
c = int2binary[c_int]
5.模型初始化
初始化输出值为0,初始化总误差为0,定义layer_2_delats存储反向传播过程中的循环层的误差,layer_1_values为隐藏层的输出值,由于第一个数据传入时,没有前面的隐藏层输出值作为本次的输入,所以要为其定义一个初始值,这里定义为0.1。
#存储神经网络的预测值
d = np.zeros_like(c)
overallError = 0 #每次把总误差清零
layer_2_deltas = list() #存储每个时间点输出层的误差
layer_1_values = list() #存储每个时间点隐藏层的值
layer_1_values.append(np.ones(hidden_dim)*0.1) #一开始没有隐藏层,所以初始化一下原始值为0.1
6.正向传播
循环遍历每个二进制位,从各位开始一次相减,并将中间层隐藏层的输出传入下一位的计算,吧每一个时间点的误差导数都记录下来,同时统计总误差,为输出做准备。
#正向传播
for position in range(binary_dim):
#生成输入和输出
X = np.array([[a[binary_dim - position - 1], b[binary_dim - position - 1]]]) #从右到左,每次取两个输入数字的一个bit位
y = np.array([[c[binary_dim - position - 1]]]).T #正确答案
layer_1 = sigmoid(np.dot(X, synapse_0) + np.dot(layer_1_values[-1], synapse_h))
layer_2 = sigmoid(np.dot(layer_1, synapse_1)) #隐藏层*隐藏层到输出层的转化矩阵synapse_1->输出层
layer_2_error = y - layer_2 #预测误差
layer_2_deltas.append((layer_2_error)*sigmoid_output_to_derivative(layer_2)) #把每一个时间点的误差倒数都记录下来
overallError += np.abs(layer_2_error[0]) #总误差
d[binary_dim - position -1] = np.round(layer_2[0][0]) #记录每一个预测bit位
#将隐藏层保存起来。下个时间序列便可以使用
layer_1_values.append(copy.deepcopy(layer_1)) #记录下隐藏层的值,在下一个时间点用
future_layer_1_delta = np.zeros(hidden_dim)
7.反向训练
初始化之后,开始从高位往回遍历,一次对每一位的所有层计算误差,并根据每层误差对权重求偏导,的到其调整值,最终将每一位算出的各层权重的调整值加在一起乘以学习率,来更新各层的权重,完成一次优化训练。
#反向传播,从最后一个时间点到第一个时间点
for position in range(binary_dim):
X = np.array([[a[position], b[position]]]) #最后一次的两个输入
layer_1 = layer_1_values[-position - 1] #当前时间点的隐藏层
prev_layer_1 = layer_1_values[-position - 2]#前一个时间点的隐藏
layer_2_delta = layer_2_deltas[-position - 1]#当前时间点输出层导数
#通过后一个时间点(因为是反向传播)的隐藏层误差和当前时间点的输出层误差,计算当前时间点的隐藏层误差
layer_1_delta = (future_layer_1_delta.dot(synapse_h.T)+layer_2_delta.dot(synapse_1.T))*sigmoid_output_to_derivative(layer_1)
#等完成所有反向传播误差的计算,才会更新权重矩阵,西安暂时把更新的矩阵存起来
synapse_1_update += np.atleast_2d(layer_1).T.dot(layer_2_delta)
synapse_h_update += np.atleast_2d(prev_layer_1).T.dot(layer_1_delta)
synapse_0_update += X.T.dot(layer_1_delta)
future_layer_1_delta = layer_1_delta
#完成所有反省传播之后,更新权重矩阵。并把矩阵变量清零
synapse_0 += synapse_0_update*alpha
synapse_1 += synapse_1_update*alpha
synapse_h += synapse_h_update*alpha
synapse_0_update *= 0
synapse_1_update *= 0
synapse_h_update *= 0
8.输出结果
每运行800次将结果输出,代码如下:
#打印输出过程
if(j%800 == 0):
print("总误差:" +str(overallError))
print("Pred:" +str(d))
print("True:" +str(c))
out = 0
for index, x in enumerate(reversed(d)):
out += x*pow(2, index)
print(str(a_int) + "-" +str(b_int) + "=" + str(out))
print("=====================================================================")
运行代码,输结果如下:
总误差:[3.99972855]
Pred:[0 0 0 0 0 0 0 0]
True:[0 0 1 1 0 0 1 1]
60-9=0
=====================================================================
总误差:[2.486562]
Pred:[0 0 0 0 0 0 0 0]
True:[0 0 0 1 0 0 0 1]
17-0=0
=====================================================================
总误差:[3.51869416]
Pred:[0 0 1 0 0 1 1 0]
True:[0 0 0 1 1 1 1 0]
89-59=38
=====================================================================
总误差:[0.18361106]
Pred:[0 0 0 1 1 0 0 0]
True:[0 0 0 1 1 0 0 0]
43-19=24
=====================================================================
总误差:[0.1709148]
Pred:[0 0 0 0 0 0 1 0]
True:[0 0 0 0 0 0 1 0]
73-71=2
=====================================================================
总误差:[0.13827615]
Pred:[0 0 1 1 1 1 0 0]
True:[0 0 1 1 1 1 0 0]
71-11=60
=====================================================================
总误差:[0.08982648]
Pred:[1 0 0 0 0 0 0 0]
True:[1 0 0 0 0 0 0 0]
230-102=128
=====================================================================
总误差:[0.17024705]
Pred:[0 1 1 1 0 0 0 1]
True:[0 1 1 1 0 0 0 1]
160-47=113
=====================================================================
总误差:[0.06442929]
Pred:[0 1 0 1 1 0 0 1]
True:[0 1 0 1 1 0 0 1]
92-3=89
=====================================================================
总误差:[0.04940924]
Pred:[0 0 0 1 1 0 1 1]
True:[0 0 0 1 1 0 1 1]
44-17=27
=====================================================================
总误差:[0.04009697]
Pred:[1 0 0 1 0 1 1 0]
True:[1 0 0 1 0 1 1 0]
167-17=150
=====================================================================
总误差:[0.06397785]
Pred:[1 0 0 1 1 0 0 0]
True:[1 0 0 1 1 0 0 0]
204-52=152
=====================================================================
总误差:[0.02595276]
Pred:[1 1 0 0 0 0 0 0]
True:[1 1 0 0 0 0 0 0]
209-17=192
=====================================================================
Process finished with exit code 0
可以看到,刚开始还不准,随着迭代次数的增加,到后来已经可以完全拟合退位减法了。