Cython 初探及关于性能提升的初步讨论

Cython 可以通过强类型 大大加快Python代码的运行效率:
简单例子:
pure python
def f(x):
return x ** 2 - x 


def intergrate_f(a, b, N):
s = 0
dx = (b - a)/ N
for i in range(N):
s += f(a + i * dx)


return s * dx
 
print intergrate_f(-2.5, 10.5, 10000000)



Cython:
def f(double x):
return x ** 2 - x 


def intergrate_f(double a, double b, int N):
cdef int i 
cdef double s, dx 
s = 0 
dx = (b-a)/N 
for i in range(N):
s += f(a + i * dx)
return s * dx




将上述代码用Setup编译成Python包后 调用执行,
当使用pure Python 时耗时4.5s Cython耗时0.96s


这里cdef 是c相应类型的定义
这里还提供了对于异常的问题,当定义强类型函数时,一般需要指定exception,
值 当返回相应的exception值时说明产生了异常,否则Cython对于特殊的cdef返回值函数
是不具备异常提醒机制的。


Cython 用于提升np.ndarray相关的性能的方法在于指定数据类型
cdef np.ndarray 及对ndarray下标访问时 如指定下标cdef int i
应该使用转型<unsigned int> i 代替i进行下标访问。


使用Cython 可以大大加快Python代码运行
这一般限于对于底层的一些C类型,而不是所有情况,例如对于一般的np.ndarray
这种已经使用C进行了较大优化的数组,这种情况是有限的,
而且对于诸如一些for循环, 当循环次数不够多时,Cython的加速也是捉襟见肘。
而且由于引入了新的类型及变量会导致运行时间更长,
有时候速度与内存是不可兼得的,
比如要使用一个Bagging算法(统计中的BootStrap)需要初始化抽样数组,
这时如果利用生成器对于每一个样本逐个生成,并进行后续处理,
会在占用较小内存的情况下完成任务,但速度不佳,
但也可以选择先生成所有抽样下标样本,这样会消耗较多的内存,但速度应当是更快的。


举一个例子,下面的代码是用来产生Bagging样本的pure python代码:
def generate_bagging_sample_index(X, sample_num, sample_size):
ListRequire = []
for i in range(sample_num):
ListRequire.append(np.random.randint(X.shape[1], size = sample_size))


return np.array(ListRequire)


def generate_bagging_sample_array(X, sample_num, sample_size):
sample_indexs_array = generate_bagging_sample_index(X, sample_num, sample_size)
for sample_indexs in sample_indexs_array:
yield X[sample_indexs].copy()


def Bagging_Test_Simple_Func(X, factor_max_bound = 20, sample_num = 10, sample_size = 100, disp = False, Type = "forward_stepwise", itertimes = None):
factor_dict = dict()


def change_eigen_ratio_sample_order(eigen_ratio_sample):
array_require_T = np.empty(shape = eigen_ratio_sample.T.shape)
for i in range(eigen_ratio_sample.T.shape[0]):
array_require_T[i] = list(reversed(list(eigen_ratio_sample.T[i])))


return (array_require_T.T).copy()


def generate_forward_stepwise_eigen_ratio_sample(X_bagging_sample):
eigen_ratio_sample = None


for i in range(factor_max_bound ,sample_size):
eigen_v = np.abs(list(reversed(list(eigvalsh(np.dot(X_bagging_sample[ :i + 1], X_bagging_sample[ :i + 1].T)))))) ** 0.5


if disp:
print (eigen_v[:-1] / eigen_v[1:])[: factor_max_bound]
print np.argmax((eigen_v[:-1] / eigen_v[1:])[: factor_max_bound])


if type(eigen_ratio_sample) == type(None):
eigen_ratio_sample = np.array((eigen_v[:-1] / eigen_v[1:])[: factor_max_bound]).reshape(1, -1)
else:
eigen_ratio_sample = np.append(eigen_ratio_sample, np.array((eigen_v[:-1] / eigen_v[1:])[: factor_max_bound]).reshape(1, -1), axis = 0)


return eigen_ratio_sample




这些函数的作用仅仅是生成样本及计算特征值的比,但当考虑性能采用Cython进行改进
会得到一个非常不具有可读性的版本:
def generate_bagging_sample_index(np.ndarray X, int sample_num, int sample_size):
cdef np.ndarray ListRequire = np.empty(shape = (sample_num, sample_size)), random_list


cdef int i, j


for i in range(sample_num):
random_list = np.random.randint(X.shape[1], size = sample_size)
for j in range(sample_size):
ListRequire[<unsigned int>i][<unsigned int>j] = random_list[<unsigned int>j]


return ListRequire


def generate_bagging_sample_array(np.ndarray X, int sample_num, int sample_size):
cdef np.ndarray sample_indexs_array = generate_bagging_sample_index(X, sample_num, sample_size)
cdef int i


for i in range(sample_indexs_array.shape[0]):
yield X[np.array(sample_indexs_array[<unsigned int>i], dtype = np.int)].copy()


def Bagging_Test_Simple_C_Func(np.ndarray X, int factor_max_bound = 20, int sample_num = 10, int sample_size = 100, disp = False, Type = "forward_stepwise", int itertimes = 0):
factor_dict = dict()


def change_eigen_ratio_sample_order(eigen_ratio_sample):
cdef np.ndarray array_require_T = np.empty(shape = eigen_ratio_sample.T.shape)


cdef int i


for i in range(eigen_ratio_sample.T.shape[0]):
array_require_T[<unsigned int>i] = list(reversed(list(eigen_ratio_sample.T[<unsigned int>i])))


return (array_require_T.T).copy()


def generate_forward_stepwise_eigen_ratio_sample(X_bagging_sample):
cdef np.ndarray eigen_ratio_sample = np.empty(shape = (sample_size - factor_max_bound, factor_max_bound)), ratio_list, eigen_v


cdef int i, j


for i in range(factor_max_bound ,sample_size):
eigen_v = np.abs(list(reversed(list(eigvalsh(np.dot(X_bagging_sample[ :<unsigned int>i + 1], X_bagging_sample[ :<unsigned int>i + 1].T)))))) ** 0.5


ratio_list = np.array((eigen_v[:-1] / eigen_v[1:])[: factor_max_bound])
for j in range(ratio_list.shape[0]):
eigen_ratio_sample[<unsigned int>i - factor_max_bound][<unsigned int>j] = ratio_list[<unsigned int>j]


return eigen_ratio_sample




在测试这两个程序时会发现,Cython比前者慢(不会慢太多),这与直接将前者进行Cython编译
的结果是近乎相同的,当数据量不够大时后者完败。
所以Bagging的性能消耗本质仍是内存与速度的取舍,而非编译语言。








评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值