質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.37%

Numpyの計算スピードの検証

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 4
  • VIEW 1,435

score 120

やっていること

NumPyを使った計算高速化について体感、理解するため、以下サイトを参考に勉強中です。
https://www.labri.fr/perso/nrougier/from-python-to-numpy/

サイトの事例

ここに高速化の事例として、以下のようなコードが提示されています。
イメージ説明

これら2つの関数の実行時間を比較した結果が以下だそうで、ケタ違いにNumpyが速いと言っています。
イメージ説明

自分でやってみた結果

def add_python(Z1, Z2):
    return [z1 + z2 for (z1, z2) in zip(Z1, Z2)]

def add_numpy(Z1, Z2):
    return np.add(Z1, Z2)

Z1 = random.sample(range(1000), 100)
Z2 = random.sample(range(1000), 100)

timeitの書き方を変えて2パターン

  1. Numpyがちょっとだけ早い
%timeit ("add_python(Z1, Z2)", globals())
%timeit ("add_numpy(Z1, Z2)", globals())

89.7 ns ± 3.92 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
87.4 ns ± 0.848 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
  1. Numpyが遅い
%timeit add_python(Z1, Z2)
%timeit add_numpy(Z1, Z2)

8.06 µs ± 80.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
15.1 µs ± 288 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

知りたいこと

A. なぜNumpyの方が遅くなってしまうのでしょうか。
B. おそらく、実際はNumpyの方が速いのではと思いますが、どなたか同じ方法で検証してみて頂けないでしょうか。
C. ついでに、timeitにglobals()を付けると結果が大きく変わるのはなぜでしょうか。

ちなみに...

本件と直接関係ありませんが、他のコードにて、
np.linalg.normが異常に遅く、普通のpythonのコードに書き換えたことがあります。
これはありえない?

a1 = np.random.randint(1,10, (2))
a2 = np.random.randint(1,10, (2))
l1 = [random.randint(1,10) for i in range(2)]
l2 = [random.randint(1,10) for i in range(2)]

#a1=[8 9], <class 'numpy.ndarray'>
#a2=[9 1], <class 'numpy.ndarray'>
#l1=[1, 7], <class 'list'>
#l2=[9, 8], <class 'list'>

%%timeit
length1 = ((l2[0] - l1[0]) ** 2 + (l2[1] - l1[1]) ** 2) ** 0.5

#1.14 µs ± 18.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%%timeit
length2 = np.linalg.norm(a2 - a1)
length2

#4.99 µs ± 301 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

環境

Python 3.7.1
Numpy 1.16.4
windows10
anaconda
jupyter notebook 5.7.4

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

+3

簡単には

Python3では最適化を頑張ったのでforループが速くなった

だけです。

Python2

In [1]: import numpy as np

In [2]: import random

In [3]: def add_python(Z1, Z2):
      :     return [z1 + z2 for (z1, z2) in zip(Z1, Z2)]
      :
      : def add_numpy(Z1, Z2):
      :     return np.add(Z1, Z2)
      :
      : Z1 = random.sample(range(10000000), 1000000)
      : Z2 = random.sample(range(10000000), 1000000)

In [4]: %timeit add_python(Z1, Z2)
1 loop, best of 3: 616 ms per loop

In [5]: %timeit add_numpy(Z1, Z2)
1 loop, best of 3: 454 ms per loop

Python3

In [1]: import numpy as np

In [2]: import random

In [3]: def add_python(Z1, Z2):
      :     return [z1 + z2 for (z1, z2) in zip(Z1, Z2)]
      :
      : def add_numpy(Z1, Z2):
      :     return np.add(Z1, Z2)
      :
      : Z1 = random.sample(range(10000000), 1000000)
      : Z2 = random.sample(range(10000000), 1000000)

In [4]: %timeit add_python(Z1, Z2)
200 ms ± 4.91 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [5]: %timeit add_numpy(Z1, Z2)
304 ms ± 6.25 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Rob Pikeの言うところの"計測すべし"の典型例ですね。
Python3 になって、"Python2ではこっちが速かった"みたいな話はまったく当てになりません。


ではなんでPython3でnumpyが負けるのかというと、numpy.addにリストを渡しているので、中でリストをnumpy.ndarrayに変換する処理が余計に入るからだと思います。
Python2のころは、そこに余計な処理をかけてもまだ、素直なforループに比べて速かったというだけかと。

numpy.addの速度と、numpy.arrayへの変換の速度をざっくり測ると以下の様になります。(これはPython3)

In [6]: N1 = np.array(Z1)

In [7]: N2 = np.array(Z2)

In [8]: %timeit add_numpy(N1, N2)
2.65 ms ± 332 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [9]: %timeit N1 = np.array(Z1)
72.1 ms ± 9.19 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/09/25 13:15

    どうもありがとうございます。Python3は2に比べて改善されているのですね。
    ただ、まだ気になるのは、参考サイトの事例では、
    ・python3.6.0を使っている
    ・add_python, add_numpyの両方とも引数はリスト
    なのにnumpyが圧倒的に早いということです。なにか裏があるのかな。。。
    まあ、quiquiさんの結果も私と同様のようですし、まずは安心しました。
    「ちなみに...」にも追記しましたが、「高速化のためにnumpy」はケースバイケースで考えないと危険ですね。

    キャンセル

+2

numpyの計算速度の恩恵を受けたい場合、組み込み型のlistではなくnumpy配列としてデータを作成してください。

import random
import timeit
import numpy as np

def add_python(Z1, Z2):
    return [z1 + z2 for (z1, z2) in zip(Z1, Z2)]

def add_numpy(Z1, Z2):
    return np.add(Z1, Z2)

Z1 = random.sample(range(1000), 100)
Z2 = random.sample(range(1000), 100)
Z1np = np.array(Z1, dtype=np.int64)
Z2np = np.array(Z2, dtype=np.int64)

for func in [add_python, add_numpy]:
    print("func:", func.__name__)
    t = timeit.timeit(lambda : func(Z1, Z2), number=1000) / 1000
    print("list:")
    print("{:.8f}".format(t))
    t = timeit.timeit(lambda : func(Z1np, Z2np), number=1000) / 1000
    print("numpy array:")
    print("{:.8f}".format(t))
""" =>
func: add_python
list:
0.00001097
numpy array:
0.00002730
func: add_numpy
list:
0.00002006
numpy array:
0.00000120
"""

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/09/25 14:14

    おっしゃる通り、著者に聞くしかないです。。。
    ただ、事例がちょっと怪しいかもということ、私の計算結果でも感覚的に間違ってはいないことが判ったので安心しました。
    ありがとうございました。

    キャンセル

  • 2019/09/25 16:10

    著者に聞いてみたところ、バージョンが違うからじゃないかとのことでした(ホントに返事が来ると思いませんでした)。
    That’s interesting and I suspect some optimizations have been made from Python 3.5 and Python 3.7 that would explain the absence of difference (when I tested 2 years ago, there were some differences). If you have access to an earlier version of Python, maybe you can test if it is the case.

    キャンセル

  • 2019/09/25 17:27

    試すならnumpyのバージョンもあわせてやってみるといいかと思いました。

    キャンセル

0

numpy を使うことによる効果が出ないほどに、Z1とZ2の要素の数が少ないのでは、と思われます。
Z1とZ1がそれぞれ100万要素程度で計測してみてください。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/09/25 11:51

    100万でやってみましたが、結果はあまり変わりませんでした。

    add_python: 156 ms ± 3.57 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
    add_numpy: 167 ms ± 2.39 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

    add_python: 85.8 ns ± 1.25 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
    add_numpy: 85.1 ns ± 0.518 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

    キャンセル

  • 2019/09/25 12:04

    ん???1個は判ったかも
    %timeit ("add_python(Z1, Z2)", globals())
    %timeit ("add_numpy(Z1, Z2)", globals())

    89.7 ns ± 3.92 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
    87.4 ns ± 0.848 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

    の方で差が出ないのは、余分な"が有るからのような気がします。
    timeit (add_python(Z1, Z2), globals())
    timeit (add_numpy(Z1, Z2), globals())
    で測定してみてください。

    キャンセル

  • 2019/09/25 12:24

    ダメです。。。
    add_python: 155 ms ± 1.34 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
    add_numpy: 167 ms ± 2.66 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

    やっぱり普通はnumpyの方が速いですか?codeコピーして、hiro-kさんの環境でやってみることはできますか?厚かましいお願いですが。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.37%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る