[PYTHON] Utiliser Cython avec Jupyter Notebook

C'est une note parce que j'ai trouvé que je pouvais essayer Cython avec Jupyter Notebook (iPython Notebook) plus facilement que prévu. Cython accélère le traitement en compilant avant l'exécution et en tapant statiquement.

(Le fichier .ipynb de cet article a été téléchargé sur Github ici.)

</ i> Environnement

L'environnement que j'ai essayé est le suivant. J'essaye sur Mac et Anaconda. Si Anaconda est installé, aucune préparation spéciale n'est requise.

Python 3.5.1 |Anaconda custom (x86_64)| (default, Jun 15 2016, 16:14:02) 
[GCC 4.2.1 Compatible Apple LLVM 4.2 (clang-425.0.28)] on darwin 

IPython 5.0.0 

</ i> Essayons

Exécuter la commande magique pour la compilation Cython

#Autoriser la compilation des fichiers cython sur Jupyter Notebook
%load_ext Cython

Déclarer des fonctions Cython

Écrivez le code cython avec «%% cython» au début. L'exemple est utilisé dans Bases du didacticiel Cython.

# ↓ -n <nom de fichier>En ajoutant, il sera plus facile de vérifier le fichier ultérieurement.
%%cython -n test_cython_code
def fib(int n):
    cdef int i
    cdef double a=0.0, b=1.0

    for i in range(n):
        a, b = a+b, a
    return a

def primes(int kmax):
    cdef int n, k, i
    cdef int p[1000]
    result = []

    if kmax > 1000:
        kmax = 1000

    k = 0
    n = 2
    while k < kmax:
        i = 0
        while i < k and n % p[i] != 0:
            i += 1

        if i == k:
            p[k] = n
            k += 1
            result.append(n)
        n += 1
    return result

Essaie

print(fib(90))
print(primes(20))

out


2.880067194370816e+18
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71]

l'a fait!

</ i> Comparaison de vitesse avec Python brut

Écrivons le même processus en Python brut et comparons les temps d'exécution.

import numpy as np
#Fonction Python pour la comparaison des performances
def pyfib(n):
    a, b = 0.0, 1.0
    for i in range(n):
        a, b = a+b, a
    return a

def pyprimes(kmax):
    p = np.zeros(1000)
    result = []

    #Le nombre maximum est de 1000
    if kmax > 1000:
        kmax = 1000

    k = 0
    n = 2
    while k < kmax:
        i = 0
        while i < k and n % p[i] != 0:
            i += 1

        if i == k:
            p[k] = n
            k += 1
            result.append(n)
        n += 1
    return result

Numéro de Fibonacci

#Générer et mesurer à plusieurs reprises le 1000e nombre de Fibonacci
%timeit fib(1000)
%timeit pyfib(1000)

cython est environ 50 fois plus rapide!

out


1000000 loops, best of 3: 786 ns per loop
10000 loops, best of 3: 42.5 µs per loop

Extraire un nombre premier

%timeit primes(1000)
%timeit pyprimes(1000)

out


100 loops, best of 3: 2.12 ms per loop
1 loop, best of 3: 218 ms per loop

Ce calcul est environ 100 fois plus rapide!

</ i> Essayez de l'utiliser pour Pandas Apply

1000 entiers

df = pd.DataFrame(np.arange(1, 10**4), columns=['num'] )

Vous pouvez l'utiliser simplement en spécifiant la fonction dans la fonction apply: blush:

%timeit df['fib'] = df.num.apply(fib)
%timeit df['pyfib'] = df.num.apply(pyfib)

out


10 loops, best of 3: 39.2 ms per loop
1 loop, best of 3: 2.02 s per loop
print(df.head())

out


   num  fib  pyfib
0    1  1.0    1.0
1    2  1.0    1.0
2    3  2.0    2.0
3    4  3.0    3.0
4    5  5.0    5.0

Le fichier cython compilé est stocké dans ~ / .ipython / cython. Si un nom de fichier a été spécifié avec %% cython -n <nom de fichier> au moment de la compilation, il est stocké ici avec ce nom de fichier.

</ i> Gérer ndarray

#Créer des données
rd.seed(71)
n_data = 10**5
X = pd.DataFrame(rd.normal(size=3*n_data).reshape((n_data,3)), columns=["a", "b", "c"])
print(X.shape)
print(X.head())

out


(100000, 3)
          a         b         c
0 -0.430603 -1.193928 -0.444299
1  0.489412 -0.451557  0.585696
2  1.177320 -0.965009  0.218278
3 -0.866144 -0.323006  1.412919
4 -0.712651 -1.362191 -1.705966

Ecrire du code Cython qui prend ndarray comme argument

%%cython -n sample_calc 
import numpy as np
cimport numpy as np

cpdef np.ndarray[double] sample_calc(np.ndarray col_a, np.ndarray col_b, np.ndarray col_c):
    #Vérification de type pour chaque colonne
    assert (col_a.dtype == np.float and col_b.dtype == np.float and col_c.dtype == np.float)
    
    #Vérifiez que la taille de chaque colonne est la même
    cdef Py_ssize_t n = len(col_c)
    assert (len(col_a) == len(col_b) == n)
    cdef np.ndarray[double] res = np.empty(n)
    
    # (a-b)/Faites le calcul c
    for i in range(n):
        res[i] = (col_a[i] - col_b[i])/col_c[i]
    return res

Appel du côté Python

sample_calc(X.a.values, X.b.values, X.c.values)

out


array([-1.71804336,  1.60658332,  9.81468496, ..., -0.44683095,
        0.46970409, -0.28352272])
#En comparaison
def pysample_calc(col_a, col_b, col_c):
    #Vérification de type pour chaque colonne
    assert (col_a.dtype == np.float and col_b.dtype == np.float and col_c.dtype == np.float)
    
    #Vérifiez que la taille de chaque colonne est la même
    n = len(col_c)
    assert (len(col_a) == len(col_b) == n)
    res = np.empty(n)
    
    # (a-b)/Faites le calcul c
    for i in range(n):
        res[i] = (col_a[i] - col_b[i])/col_c[i]
    return res
%timeit sample_calc(X.a.values, X.b.values, X.c.values)
%timeit pysample_calc(X.a.values, X.b.values, X.c.values)

out


100 loops, best of 3: 16.7 ms per loop
10 loops, best of 3: 37.2 ms per loop

</ i> Calculer le rapport de circonférence par la méthode de Monte Carlo

#Génération de données
rd.seed(71)
n_data = 10**7
X2 = rd.random(size=(n_data,2)).astype(np.float)
X2.dtype

Définition de la fonction Cython

%%cython -n calc_pi
import numpy as np
cimport numpy as np

cpdef np.ndarray[long]  calc_pi(np.ndarray[double, ndim=2] data):
    cdef Py_ssize_t n = len(data)
    cdef np.ndarray[long] res = np.empty(n, dtype=np.int)
    
    for i in range(n):
        res[i] = 1 if (data[i,0]**2 + data[i,1]**2) < 1 else 0
    return res

Fonction Python pour comparaison

#Fonction Python pour comparaison
def pycalc_pi(data):
    n = len(data)
    res = [1 if (data[i,0]**2 + data[i,1]**2) < 1 else 0 for i in range(n)]
    return res

Je vais le mesurer.

%time calc_pi(X2)
%time pycalc_pi(X2)

out


CPU times: user 25.2 ms, sys: 5.98 ms, total: 31.2 ms
Wall time: 31.1 ms
CPU times: user 7.7 s, sys: 46.1 ms, total: 7.75 s
Wall time: 7.75 s

Cython est beaucoup plus rapide!

#Vérifiez si les résultats sont les mêmes
np.all(res == respy)

correct!

out


True
#Calcul du rapport de circonférence
np.sum(res)/n_data*4

out


3.1413555999999998

Essayez de dessiner.

import matplotlib.pyplot as plt
%matplotlib inline
import seaborn as sns
from matplotlib.colors import LinearSegmentedColormap

sns.set(style="darkgrid", palette="muted", color_codes=True)
#dessiner
n_plot = 10**4  #Nombre de points à dessiner
plt.figure(figsize=(8,8))
plt.scatter(X2[:n_plot,0], X2[:n_plot,1], c=res[:n_plot], s=10)

plot.png

Vous jugez correctement l'intérieur et l'extérieur du cercle.

référence

Principes de base du didacticiel Cython http://omake.accense.com/static/doc-ja/cython/src/userguide/tutorial.html

O'Reilly "Cython" https://www.oreilly.co.jp/books/9784873117270/

pandas 0.18.1 documentation Enhancing Performance http://pandas.pydata.org/pandas-docs/stable/enhancingperf.html

Recommended Posts