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

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

新規登録して質問してみよう
ただいま回答率
85.35%
NumPy

NumPyはPythonのプログラミング言語の科学的と数学的なコンピューティングに関する拡張モジュールです。

アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

ループ

ループとは、プログラミングにおいて、条件に合致している間、複数回繰り返し実行される箇所や、その制御構造を指します

コードレビュー

コードレビューは、ソフトウェア開発の一工程で、 ソースコードの検査を行い、開発工程で見過ごされた誤りを検出する事で、 ソフトウェア品質を高めるためのものです。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

3回答

1344閲覧

numpyでforで計算している部分を高速化したい。

Luna_rab

総合スコア17

NumPy

NumPyはPythonのプログラミング言語の科学的と数学的なコンピューティングに関する拡張モジュールです。

アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

ループ

ループとは、プログラミングにおいて、条件に合致している間、複数回繰り返し実行される箇所や、その制御構造を指します

コードレビュー

コードレビューは、ソフトウェア開発の一工程で、 ソースコードの検査を行い、開発工程で見過ごされた誤りを検出する事で、 ソフトウェア品質を高めるためのものです。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2021/12/15 11:08

編集2021/12/15 12:24

やりたいこと

現在forループで実装している部分を、numpyのメソッドを使うなどして高速化したいと考えております。
(初めにお伝えしますが、本文中に何度かLightGBMに関する話題が登場しますが、実装したいこと自体にはLightGBMは関係なく本ページ下部の「やったこと」章にあるコードのforループを高速化したいだけです。LightGBMに詳しくない方もブラウザバックせずにご一読いただければ嬉しいです。)

具体的には、LightGBMのrambdarankでグループ内での予測順1位かつ正解順1位のaccuracyを求めるカスタムメトリックを作成したいです。
しかし、自分で作成したメトリックはfor文を使っているため学習が非常に遅くなってしまいます。
例えば、以下のようにpredslabelgroupを与えられます。

  • grouppredslabelの要素が何個づつグループに所属しているかを示しています。
  • predsはLightGBMから渡される予測値です。大きいほうがより高い予測順位となります。
  • labelは学習に使用する正解順位のラベルです。0,1,2のいずれかの値を持ち、最大値の2は各グループで必ず1回だけ登場します。大きいほうがより高い正解順位となります。
preds = [-0.112,0.025,0.998,1.112,1.226,0.520,2.112,-0.220,2.174,1.552] label = [0,0,1,2,0,2,1,0,2,1] group = [4,3,3]

グループ内で一番大きなpredsを持っている要素のlabelが2であれば正しく予測できている、それ以外だと正しく予測できていないことになります。

文字で説明しても分かりづらいと思いますので表で表現してみます。

  • index列はinputlabelのインデックスです。
  • preds列はpredsの値です。
  • label列はlabelの値です。
  • 「----」となっている行でgroupに基づくグループ分けを行い、predslabelの要素を区切ります。

この場合ですと、indexが03、46、7~9の3グループに分けられ、それぞれのグループの中で最大のpredsを持つ行のlabelが2であれば、そのグループは正しく予測できている(TorF = true)、それ以外だと正しく予測できていない(TorF = false)となります。
3グループのうち正しく予測しているのは2グループなので、求めたい値(accuracy)は2/3=0.67となります。

|index|preds|label|T or F|
|:--|:--|||
|0|-0.112|0||
|1|0.025|0||
|2|0.998|1||

31.1122true
41.2260
50.5202
62.1121false
----------------
7-0.2200
82.1742true
91.5521

やったこと

次のようなコードで実装しました。

Python3

1def accuracy(preds, data): 2 BEST_LABEL = 2 3 label = data.get_label() 4 group = data.get_group() 5 6 i = 0 7 acc = 0 8 for n in group: 9 max = preds[i:i+n].argmax() 10 acc += (label[i+max] == BEST_LABEL) 11 i += n 12 acc = acc/len(group) 13 return "accuracy", acc, True

しかしこのコードだとforで回しており学習が非常に遅くなってしまうので、numpyのメソッドを使用するなどで高速化を図りたいです。
自分では良い実装が思いつかなかったため、皆様のお力をお借りしたいと考えました。よろしくおねがいします。

環境

Windows10
Python 3.10.0

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

bsdfan

2021/12/15 13:25

各groupの値とか、groupの総数(len(group))は、実際はどれぐらいなのでしょうか?
Luna_rab

2021/12/15 14:20 編集

実際は各groupの値は10~20程度、len(group)は学習に使用するデータの量にもよりますが10000~30000程度を想定しています。 また、1回の学習でおよそ500~1000回ほどaccuracy関数が呼ばれます。
guest

回答3

0

データはダミーですが、比較用に測定結果を貼っておきます。solve1が(ほぼ)質問者さんのコード、solve2がppaulさんのコードです。

私の環境では、

  • solve1: 0.0002551000000000636秒
  • solve2: 0.017054800000000037秒
  • solve2(2回目): 0.01655569999999995秒

でした。

python

1import pandas as pd 2from time import perf_counter 3import numpy as np 4 5preds = [7,8,9,10,8,7,9,7,9,-10] * 100 6label = [0,0,1,2,0,2,1,0,2,1] * 100 7group = [4,3,3] * 100 8 9 10def solve1(preds, label, group): 11 preds = np.array(preds) 12 BEST_LABEL = 2 13 i = 0 14 acc = 0 15 for n in group: 16 max = preds[i:i+n].argmax() 17 acc += (label[i+max] == BEST_LABEL) 18 i += n 19 return acc/len(group) 20 21 22def solve2(preds, label, group): 23 group2 = sum([[i]*g for i,g in enumerate(group)], []) 24 df = pd.DataFrame({'preds':preds, 'label':label, 'group':group2}) 25 return df.sort_values('preds').groupby('group').apply(lambda d: d.iloc[-1]['label']==2).mean() 26 27 28st = perf_counter() 29res1 = solve1(preds, label, group) 30ed = perf_counter() 31print(ed - st, res1) 32 33st = perf_counter() 34res2 = solve2(preds, label, group) 35ed = perf_counter() 36print(ed - st, res2) 37 38st = perf_counter() 39res2 = solve2(preds, label, group) # 2回目 40ed = perf_counter() 41print(ed - st, res2) 42

投稿2021/12/15 12:47

編集2021/12/15 13:51
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Luna_rab

2021/12/15 12:57 編集

検証していただきありがとうございます。 やはりpandasの方が遅くなってしまいますか...
ppaul

2021/12/15 13:44

solve2を一回実行した後で2 回目の実行の時間を計るとどうなるかやってみてもらえませんか。 pandasのようなライブラリでは、最初に実行するときに追加のライブラリを読み込むことがあるので、そのための時間も見かけ上の実行時間となることがあります。
ppaul

2021/12/15 13:52

また、pandasの中身(values)はnumpy.ndarrayですので、ライブラリ読み込みを除いてpandasでやって遅いものはnumpyでやっても遅くなる可能性が高いです。 それぞれのグループの長さが4程度だとあまり速くないとは思います。numpyが使っているSIMD長は最近のCPUだと512ビットですのでint32の場合で10を超えるともっと速くなるでしょう。
退会済みユーザー

退会済みユーザー

2021/12/15 13:52

2回目の計測コードを入れてみましたが、0.017 -> 0.0165のように微妙に改善されただけでした。
Luna_rab

2021/12/16 01:40

pandasの中身がndarrayとのことですが、それは列方向に対してだったと記憶しています。ppaulさんのコードではapplyを使用しているのでベクトル処理は行われていないのではないでしょうか?(pandasの処理でapplyを使うと遅くなるのは有名な話だったと思います。)
guest

0

ベストアンサー

下記でどうでしょう。

numpy.maximum.reduceat() で、各グループ毎の最大値を求めて、
その最大値に等しいかつ、labelが2の場所を数えています。

python

1def accuracy(preds, data): 2 BEST_LABEL = 2 3 label = data.get_label() 4 group = data.get_group() 5 6 group_max = np.maximum.reduceat(preds, [0, *np.cumsum(group[:-1])]) 7 acc = np.sum((preds == np.repeat(group_max, group)) & (label == BEST_LABEL)) 8 acc = acc/len(group) 9 return "accuracy", acc, True

手元のテストコードで、元のより1.5倍ちょっと早くなる程度でした。
([0, *np.cumsum(group[:-1])]が固定で、毎回計算する必要がないなら、この部分を外に出すともう少し速くできそうです)

こういうのの高速化はnumbaのほうが良さそうなので、そちらも試してみましたが2倍程度しか速くなりませんでした。
参考までにコードは下記の通りで、元のコードにjitをつけただけで試しています。(引数はndarrayである前提)
もともと、それほど遅くなるようなforの使い方でもないように思います。

python

1from numba import jit 2 3@jit 4def _accuracy(preds, label, group): 5 BEST_LABEL = 2 6 i = 0 7 acc = 0 8 for n in group: 9 max = preds[i:i+n].argmax() 10 acc += (label[i+max] == BEST_LABEL) 11 i += n 12 return acc/len(group) 13 14 15def accuracy(preds, data): 16 label = data.get_label() 17 group = data.get_group() 18 19 return "accuracy", _accuracy(preds, label, group), True

投稿2021/12/16 02:02

編集2021/12/16 03:37
bsdfan

総合スコア4794

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Luna_rab

2021/12/16 02:54

http://numba.pydata.org/numba-doc/latest/user/5minguide.html Numbaについて上記ガイドでざっくり調べてみましたが、まさにNumbaで速くできそうな感じがしますので一度それで実装してみたいと思います。 numbaでもあまり早くならなかったとのことですが、初回呼び出し時にコンパイルしているようなので繰り返し関数を呼ぶような用途なら、実際はもう少し高速化が狙えるのではないでしょうか。
bsdfan

2021/12/16 03:15

jitコンパイルの時間を除いて、こちらの環境とテストデータで2倍程度でした。 データの大きさによって変わると思いますし、実際の環境で試してみる価値はあります。
Luna_rab

2021/12/16 03:40

既にコンパイル時間を除いて計算していただいていましたか。ありがとうございます。
Luna_rab

2021/12/16 16:46

numbaで計算したところ、bsdfanさんの試算通り計算時間がおおよそ半分になりました。accuracyメトリックなしでの計算時間とaccuracyメトリック込みでの計算時間で差がほとんどなくなったため、当初の目的は達成できました。ありがとうございます。
guest

0

numpyよりはpandasの方が向いています。

pandasを使うと以下です。

python

1import pandas as pd 2preds = [7,8,9,10,8,7,9,7,9,-10] 3label = [0,0,1,2,0,2,1,0,2,1] 4group = [4,3,3] 5 6group2 = sum([[i]*g for i,g in enumerate(group)], []) 7df = pd.DataFrame({'preds':preds, 'label':label, 'group':group2}) 8accuracy = df.sort_values('preds').groupby('group').apply(lambda d: d.iloc[-1]['label']==2).mean() 9print(accuracy)

実行結果

python

1>>> print(accuracy) 20.6666666666666666

投稿2021/12/15 11:34

ppaul

総合スコア24670

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2021/12/15 12:33

この手の処理でpandasを使うと遅くないですか?
Luna_rab

2021/12/15 12:40

実測したわけではないのでわからないですが、それは自分も気になっていました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問