CMAC是cerebellar model articulation controller,一种模拟小脑的模型。小脑对肢体动作的操作是近似“反射”的,CMAC的理念就是让训练后的网络在应用时对输入的计算越快越好。很容易想到,最快的方法自然是把所有可能的输入情况和它们对应的输出存在表里,在使用时直接查表。而且为了更快的速度,即使是平衡搜索树也不够,哈希散列技术是一种更有效的技术,只需要预留足够的内存空间,并设计优秀的散列函数就能只花费散列函数计算的时间就查询到结果。
如上图所示,首先对输入的模拟信号进行量化分级,分级的数目根据自己设置的采样率而定。每个级别的量化信号会激活一个区间内的感知器,被激活的感知器把自带的权值做加和成为输出。
其中为了减少感知机的数目,一般使用随机的哈希映射,把量化激活的感知机虚地址映射到实地址。这一点可以有效节约感知器数目,在硬件实现时至关重要。
综上,CMAC实际上是一种智能查表技术。使用简单的局部性原理,用多个超平面拟合输出超曲面。虽然CMAC做不到RBF和BPN的完全非线性逼近,但CMAC胜在前馈计算速度快,适合智能应用中需要即时反射的场景,而且网络模型更简单,参数更少,计算速度更快。
CMAC最早的出现是为了解决一个机器人手臂协调的问题,机器人的传感器会返回速度、角度和加速度等信号,它们和力矩满足一个方程,而想从这些信号中计算出应该施加的力矩需要求解反函数,但这个反函数是未知的。为了学习这个反函数,并能满足机器人需要的快速反射,就设计了这种查表型的算法。
这里我们也用CMAC来学习一个二维输入一维输出的函数,y = sin(x1)cos(x2), 验证本算法的性能。
import numpy as np
import random
import matplotlib.pyplot as plt
def prime(x):
top = int(x**0.5)+1
for i in range(2,top):
if x%i==0:
return False
return True
def getprime(x):
for i in range(x,x**2):
if prime(i):
return i
return False
k = getprime(random.randint(0,1000))
b = getprime(random.randint(0,1000))
def hashing(x,size):
return (k*x+b)%size
class CMAC:
def __init__(self,resolution,steps,bottom,C,mem):
'''
resolution:模型在各个维度的分辨率
steps:模型在各个维度采样的步长
bottom:模型在各个维度采样的起点
C:模型的泛化范围
mem:模型被分配的实地址数目,也是神经元数目
为了哈希函数的应用,会在给定的mem向上取到质数
'''
self.dim = len(resolution)
self.C = C
self.resolution = resolution
self.steps = steps
self.bottom = bottom
#向上取到质数
self.size = getprime(mem)
#设置size个权值,用于描述结果
self.W = np.zeros(self.size)
#为每个维度的每个可能的量化取值,计算感知器激活情况
L = max(resolution)
self.activate = np.zeros((self.dim,L,C))
for d in range(self.dim):
self.activate[d][0] = np.array([x for x in range(C)])
for i in range(1,resolution[d]):
self.activate[d][i] = self.activate[d][i-1]
self.activate[d][i][(i-1)%C]+=C
def forward(self, x):
#首先解析x,确定每个维度的虚地址
assert len(x)==self.dim
dim = self.dim
address = np.zeros(self.C)#被激活的C个神经元的虚地址
radix = 1# 由于地址是直接拼接的,每上升一个维度进制要乘以一个值
for d in range(dim):
#根据采样步长得到x在该维度的量化取值
num = int((x[d]-self.bottom[d])//self.steps[d])
assert num<self.resolution[d]
#查表找到量化取值num对应的感知器激活情况
acti = self.activate[d][num]
address += radix*acti
radix *= self.resolution[d]+self.C-1
#使用哈希函数,虚地址转实地址
address = hashing(address,self.size)
self.work = address.astype(np.int)
#实地址对应的神经元权值相加
self.out = sum(self.W[i] for i in self.work)
return self.out
def backward(self, yhat, lr):
total = len(self.work)
delta = yhat-self.out
for i in self.work:
self.W[i] += delta*lr/total
return
def fit(self, X, Y, lr=0.1, iterations = 1000):
n = len(X)
for t in range(iterations):
i = random.randint(0,n-1)
x,y = X[i],Y[i]
self.forward(x)
self.backward(y,lr)
return
首先我们定义CMAC的基本架构,代码的注释比较详细,需要知道的都在里面。最上面的随机数生成和质数判断是用于哈希散列的,因为如果使用随机质数系数的线性函数和取模来散列能取得比较好的效果。另外,哈希表的长度,也就是网络里神经元的数目也需要是质数。
#希望模型学习一个函数, f(x1,x2) = sin(x1)cos(x2)
#生成伪数据集
row = np.linspace(0,6.2,300)
col = np.linspace(0,6.2,300)
x1,x2 = np.meshgrid(row,col)
X = np.concatenate((x1.reshape(1,-1),x2.reshape(1,-1)),axis=0).T
Y = np.zeros(90000)
for i in range(len(X)):
Y[i] = np.sin(X[i][0])*np.cos(X[i][1])
我们在三维空间上制造九万个点用于训练。
model = CMAC([65,65],[0.1,0.1],[0,0],5,5000)
model.fit(X,Y,iterations=20000)
#用3dplot打印model生成的曲面
from mpl_toolkits.mplot3d.axes3d import Axes3D
from matplotlib import pyplot as plt
xx = np.linspace(0,6,101)
yy = np.linspace(0,6,101)
X,Y = np.meshgrid(xx,yy)
Z = np.zeros(X.shape)
for i in range(len(X)):
for j in range(len(X[0])):
x = np.array((X[i][j],Y[i][j]))
Z[i][j] = model.forward(x)
fig = plt.figure()
axes3d = Axes3D(fig)
axes3d.plot_surface(X,Y,Z,color='grey')
这里建立一个0.1步长,泛化范围(感知器的激活区间)为5,总存储单元5000(会自动向上取到质数)的CMAC网络,经过20000次随机学习,打印出模型学习到的曲面如下。
曲面是不光滑的,因为模型实质上是用很多平面去近似目标函数的曲面。这也是它的最大特点,局部近似。
CMAC是一种智能查表技术,说它是神经网络的一种并没错,但似乎有些勉强。
CMAC和前馈网络,RBF径向基网络一样可以逼近非线性函数,但是它是局部逼近。
CMAC的训练速度和响应速度都很快,因为CMAC每次训练只需要更新被激活的几个神经元的权值。而在前馈响应时,只需要计算一下需要激活的神经元的地址,做一个哈希映射,再求和就能得到输出。比起其他深度网络,它需要的计算量非常小,适合用于机器人、证券等对响应时间要求高的领域。