簡單例子:
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的性能消耗本質仍是內存與速度的取捨,而非編譯語言。