这是利用Python做的第二个实践,第一个实践是2015年暑假在宁波智能制造产业研究院做Intern的时候写过一个八叉树的结构。问题描述如下:

P


代码基于Python,利用numpy做计算结构,matplotlib做绘图包,自己实现BP算法。根据问题描述,拟构建一个1xNx1的神经网络结构。在调研之后,基于Python的实现有keras,pybrain等,但是要求要自己Coding出BP模块。

为了充分利用numpy,将问题向量化,然后采用向量编程的想法来做,简单(统一的数据结构)且高效(numpy的执行效率)。很自然的,在Coding中选择的是Batch Learning方式,很多基于Matlab做的同学用的是Online Learning的方式。复盘之后的几个问题如下(是的,这篇复盘尽量不讨论关于BP的问题,仅仅是Coding问题)。

1. numpy和matplotlib的极简使用?

list和array之间的关系。例如,list和array之间的互相转换。这个问题中,需要认识到ndarray就是matrix,在C语言课程中,提到过的多维数组就是这个概念。很自然的,接下来就需要知道matrix相关的一些操作,包括行列操作,转置操作,范数操作等。在基本操作的基础上定义运算,如点积。关于运算,需要知道操作对象的属性,同样需要清楚针对对象中元素的运算过程,这是numpy的简单高效之处的体现之一。

关于array的合并与拆分。在给定问题中,样本的输入是list结构,显然任务中存在着这样的需求,建立input和target的KV关系,因为这涉及到sampling操作。利用vstack合并操作可以实现我的目的,在上段内容中提到的行列操作则可以服务于split操作。

shuffle的使用。在给定问题中,随机模块的使用需求有两个地方。第一个地方在于原来input是递增序列,在进行交叉集划分的时候需要shuffle。第二个地方在于同一个model,交叉验证的时候要对输入样本shuffle操作。通常算法中的shuffle操作可以从概率算法的角度给出一个解释,而结合神经网络模型阿狸说,每次shuffle都有自己的意义,此处不做讨论。

ndarray与matrix的关系。在一些必要的时候,如果想利用numpy中的实现,特别是需要指定axis的时候,需要将我的数据转换为matrix格式,也就是np.mat()函数。利用在我的数据处理中,归一化的操作,利用MinMax操作,需要找到min和max值。

关于绘图。对于绘图做了一个简单的封装,如下:

def show(dat,xlabel,ylabel,title):
''' Showing one line'''
    plt.figure
    plt.plot(dat)
    plt.xlabel(xlabel)
    plt.ylabel(ylabel)
    plt.show()
    plt.close()

def showLines(dat1,label1,dat2,label2,xlabel,ylabel,title):
''' Showing two lines'''
    plt.figure
    plt.plot(dat1, label=label1)
    plt.plot(dat2,label=label2)
    plt.xlabel(xlabel)
    plt.ylabel(ylabel)
    plt.title(title)
    plt.legend()
    plt.show()
    plt.close()

在给定任务中,需要绘制三张图片,最简单的绘图方式应该就是这样了。

2. 科学计算任务下,接口参数如何定义?

在给出理想的一个相对较粗的代码框架之前,来看几个细节。

一.关于*args**kwargs

二者都是可变参数,*args(arguments)表示任何多个无名参数,结构为tuple;

**kwargs(key words arguments)表示任意多个关键字参数,结构为dict;

举个栗子:

def foo(*args,**kwargs):
    print 'args=',args
    print 'kwargs=',kwargs
if __name__ == '__main__':
    foo(1,2,3,a = 1,b = 2) 结果为:

args=(1,2,3)
kwargs={'a':1,'b':2}

吐槽时刻,读者可略过…

Python中有这样的一个用法,将函数作为另一个函数的参数传入并调用。就是这样的:

def func_a(*args):
    return args
def func_b(func,*args):
    print(func(*args))
if __name__ == '__main__':
    func_b(func_a,1,2,3)

与其那样,不如这样:

def func_a(*args):
    return args
def func_b(*args):
    print(func_a(*args))
if __name__ == '__main__':
    func_b(1,2,3) 如果真的是这样,上面还有什么意义呢?这样的好处是显而易见的,同样的输入参数,调用不同的函数结果不同(说了句废话...)。

二.函数参数传递

举两个栗子,我就可以什么也不用说了。

def func(x,y = 2,*p,**q):
    print 'x=',x
    print 'y=',y
    print 'p=',p
    print 'q=',q
if __name__ == '__main__':
    func(20,10,1,2,3,a = 4,b = 5)

输出结果为:

x=20
y=10
p=(1,2,3)
q={'a':4,'b':5}

第二个栗子:

def func(x,y = 2)
    print 'x=',x
    print 'y=',y
if __name__ = '__main__':
    func(y = 10,x = 4)

输出结果为:

x=4
y=10

所以,或许在传递参数的时候采用参数名加参数值的方式比较好,代码的可读性增强了。但是,并不意味着在函数定义的时候给定默认参数值不是一种好的方式,可能有的参数被码农同学经常使用的概率很小,但是作为程序友好,还是开放给码农同学。此外,函数实现之前的参数校验部分也是节省了一部分工作,比如参数类型和参数是否为空校验。

借此安利两个小习惯(不管你信不信,这两个习惯会让你的效率大大增加,你想不到的增速):

1. 读代码前,弄明白变量的意义。

2. 读数学证明前,弄明白notation的意义。

下边给出一个比较粗的代码框架(重在结构和参数):

class NeuralNetwork:
    def __init__(self,layers,activation='tanh'):
        if activation == 'logistic':
            self.activation = self.__logistic
        elif activation == 'tanh':
            self.activation = self.__tanh
        Initialize weights and biases.
    def __logistic(x):
        Code here
    def __tanh(x):
        Code here
    def fit(self,x,y,learning_rate=0.2,epoches = 1000):
        Code here
    def predict(self,x):
        Code here

比如说,这是一种可能的调用方式:

import numpy as np
from BP import NeuralNetwork
nn = NeuralNetwork(layers=[2,4,1],activation='tanh')
x = np.array([[1,2],[3,2],[5,2]])
y = np.array([1,0,1,1])
nn.fit(x,y,learning_rate=0.1,epoches=400)
test = np.array([[3,4],[2,1],[5,4]])
for i in test:
    print(i,nn.predict(i))

在NeuralNetwork的定义中,将各种类型的激活函数定义为私有函数,因为激活函数只在训练阶段被使用。整理的过程分为初始化,训练或者成为拟合,预测三个过程。实际上learning_rate和epoches两个参数也可以放在初始化参数列表中,也就是类的构造函数中。但是如果放在构造函数中,实例化参数列表就会变得冗长。不管在哪个地方,这两个参数总要显式传入函数。

总结:神经网络的拟合效果好坏依赖于tuning,对于强经验依赖,没有严格数学理论支撑的参数似乎不是太好,期待有人能够从理论上给参数一个合理的解释。虽然这样,但是从大家把神经网络用于实际问题的反响来看,它是work的,因此并不能说它不漂亮。