1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

それ,numpy で書かない?-- 1 --

Last updated at Posted at 2023-12-06

それ,numpy で書かない?

Python ではリストがよく使われる。また,for ループが遅いのでリストに特化したリスト内包表記も推奨されることが多い。

それなりの根拠があるからではあるが...

課題:$n$ 個の整数を要素とするリストが 2 対ある。対応する要素が等しいものと等しくないものの個数を求めよ。

テストデータとして以下のような,0 ~ 999 の整数要素からなる,2 個のリストを作る。

from time import time
import random
n = 10000000
random.seed(123)
expected = [random.randint(0, 999) for _ in range(n)]
predicted = [random.randint(0, 999) for _ in range(n)]

for を使うとたしかに遅い。

s = time()
correct = 0.0
wrong = 0.0
for i in range(n):
    if expected[i] == predicted[i]:
        correct += 1
    else:
        wrong += 1
print("@1", correct, wrong, time() - s)
@1 10026.0 9989974.0 1.076805830001831

遅いのは遅いが,ちょっと無駄がある。同じものと違うものを別々に数えているが,これはどちらか一方を数えるだけで十分だ。

s = time()
correct = 0.0
wrong = 0.0
for i in range(n):
    if expected[i] == predicted[i]:
        correct += 1
wrong = n - correct
print("@2", correct, wrong, time() - s)
@2 10026.0 9989974.0 0.7036769390106201

実行速度は倍速になるかと思いきや,そうはいかない。

for が遅いのを,リスト内包表記でカバーするのが Python 流。
書き方は何通りかあるので,遅い順に挙げてみる。

まずは,for の前に判定条件を置く場合。判定結果は長さが同じ True/False からなるリストなので,同じものの個数は sum() で得られる。
最初のものに比べるとほぼ倍速になる。

s = time()
correct = sum([expected[_] == predicted[_] for _ in range(n)])
wrong = n - correct
print("@3", correct, wrong, time() - s)
@3 10026 9989974 0.49999427795410156

次は,for のあとに if を置く場合。結果は等しいものだけなので,同じものの個数は len() で得られる。
前のものに比べて少し速くなる。

s = time()
correct = len([_ for _ in range(n) if expected[_] == predicted[_]])
wrong = n - correct
print("@4", correct, wrong, time() - s)
@4 10026 9989974 0.43302106857299805

次は,イテレータに zip() を使う方法。
更に速くなる。

s = time()
correct = sum([_1 == _2 for _1, _2 in zip(expected, predicted)])
wrong = n - correct
print("@5", correct, wrong, time() - s)
@5 10026 9989974 0.2798469066619873

次は,イテレータに zip() を使う方法。
更に速くなる。最初の for を使った場合に比べてほぼ 4 倍速である。

s = time()
correct = len([_1 for _1, _2 in zip(expected, predicted) if _1 == _2])
wrong = n - correct
print("@6", correct, wrong, time() - s)
@6 10026 9989974 0.22952604293823242

ここまでにしようかとも思うが,観点を変えて numpy を使った場合を見てみる。

リスト内包表記を使った最後のプログラムは 1 行で書けるとはいえ,決して簡潔ではない。
numpy で書くと,簡潔に書くことができる(もちろん 1 行で)。
速度は,ちょっと遅い。

以下ではリストを numpy 配列にする処理を挟むが,最初から numpy で書けば,余分な処理をする必要はない。

import numpy as np
expected2 = np.ravel(expected)
predicted2 = np.ravel(predicted)
s = time()
correct = sum(expected2 == predicted2)
wrong = n - correct
print("@7", correct, wrong, time() - s)
@7 10026 9989974 0.6402859687805176

内包表記で書いた場合の 2 倍ぐらいかかる。
ならば,内包表記で書く...

いやいや,待って。
長さが 1千万のリストで 0.27 秒かかるのが 0.51 なのだから,体感的にはそんなに差がないともいえる。

numpy を使うメリット,局面は他にもあるので,今回の課題にはこのやり方で十分だろう...
個人の感想です。

1
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?