LoginSignup
4
1

More than 5 years have passed since last update.

argsortの逆関数

Posted at

概要

numpy.argsort関数の「逆関数」みたいな操作をしたいと思うことがあり、ネットを調べてみても、すぐには出てこなさそうだったので、自分で考えてみました。
いろいろ試した結論としては、numpy.argsort( numpy.argsort( ARRAY_INPUT ) ) とするとよさそうです。

・・・という記事です。

本当?

と思われる方は、以下本文を参照ください・・・

なぜそんなことを考えたのか?

機械学習の仕事をしていて、学習モデルが出力する予測値を変換してからスコア算出する、ということをしたいことがありました。
例えば、学習モデルが以下のような予測値の配列を出力したとします。

INDEX 0 1 2 3 4 5 6 7 8 9
VALUE 0.15 0.04 0.20 0.08 0.05 0.30 0.12 0.01 0.03 0.02

これは0-9の各クラスのいずれかに属するかを示す確信度(1に近いほど高確率)を表しているものとします。このままでもいいのかもしれないですが、トップ3だけに、順に10点、5点、1点のスコアを与え、あとは0点にしてスコア計算したいという要求がありました。
(そういうスコアの与え方でGridSearchしたかったのです。)

つまり、上記の例でいうと、以下のような配列に変換してから、スコア計算したかったわけです。

INDEX 0 1 2 3 4 5 6 7 8 9
VALUE 1 0 5 0 0 10 0 0 0 0

この配列をどうやったら作れるんだろう?
numpy.argsort関数の逆関数、例えばinv_argsortという名前の関数があって、以下のようなことができたらうれしいわけです。

In [1]: prediction
Out[1]: [0.15, 0.04, 0.2, 0.08, 0.05, 0.3, 0.12, 0.01, 0.03, 0.02]

In [2]: score = [0, 0, 0, 0, 0, 0, 0, 1, 5, 10]

In [3]: prediction_scored = numpy.take(score, inv_argsort(prediction))

In [4]: prediction_scored
Out[4]: array([ 1,  0,  5,  0,  0, 10,  0,  0,  0,  0])

inv_argsortの実装

概要にも書きましたが、以下の通り、実装自体は簡単です。

def inv_argsort(array_input):
    return numpy.argsort(numpy.argsort(array_input))

でも本当にそれでいいの?という気もしますので、以下で簡単な証明をしておこうと思います。

証明

1回目のargsort

${ n }$個の要素を持つ配列 ${ v }$ が学習モデルの直接の出力とします。

INDEX 0 1 2 ${\dots}$ ${n-1}$
${ v }$の値 ${ v_0 }$ ${ v_1 }$ ${ v_2 }$ ${ \dots }$ ${ v_{n-1} }$

これを numpy.argsort にかけた結果を ${ u }$ とします。

INDEX 0 1 2 ${\dots}$ ${n-1}$
${ u }$の値 ${ u_0 }$ ${ u_1 }$ ${ u_2 }$ ${ \dots }$ ${ u_{n-1} }$
${ v }$の値 ${ v_{u_0} }$ ${ v_{u_1} }$ ${ v_{u_2} }$ ${ \dots }$ ${ v_{u_{n-1}} }$

ここで、argsortの定義により、

$$ v_{u_0} \leq v_{u_1} \leq v_{u_2} \leq \dots \leq v_{u_{n-1}} $$

であるとわかります。
つまり、INDEXの $0, 1, 2,\dots, n-1$ と大小関係が一致するわけです。
ここでこの表の最下行に、付与したいスコアもくっつけてみます。

INDEX 0 1 2 ${\dots}$ ${n-1}$
${ u }$の値 ${ u_0 }$ ${ u_1 }$ ${ u_2 }$ ${ \dots }$ ${ u_{n-1} }$
${ v }$の値 ${ v_{u_0} }$ ${ v_{u_1} }$ ${ v_{u_2} }$ ${ \dots }$ ${ v_{u_{n-1}} }$
SCORE ${ s_0 }$ ${ s_1 }$ ${ s_2 }$ ${ \dots }$ ${ s_{n-1} }$

概要の例でいえば、${ s_9 = 10, s_8 = 5, s_7 = 1 }$ で残りは 0 ということになります。
$v$ の昇順に並んでいるので、「トップ3に順に 10点、5点、1点を割り当てる」という対応関係が成立しています。

2回目のargsort

$u$ を numpy.argsort にかけた結果を ${ w }$ とします。

INDEX 0 1 2 ${\dots}$ ${n-1}$
${ w }$の値 ${ w_0 }$ ${ w_1 }$ ${ w_2 }$ ${ \dots }$ ${ w_{n-1} }$
${ u }$の値 ${ u_{w_0} }$ ${ u_{w_1} }$ ${ u_{w_2} }$ ${ \dots }$ ${ u_{w_{n-1}} }$
${ v }$の値 ${ v_{u_{w_0}} }$ ${ v_{u_{w_1}} }$ ${ v_{u_{w_2}} }$ ${ \dots }$ ${ v_{u_{w_{n-1}}} }$
SCORE ${ s_{w_0} }$ ${ s_{w_1} }$ ${ s_{w_2} }$ ${ \dots }$ ${ s_{w_{n-1}}} $

ここで、あることに気が付きます。
$$ u_{w_i} \in [0, 1, 2, \dots, n-1], \hspace{12pt} u_{w_0} \lt u_{w_1} \lt u_{w_2} \lt \dots \lt u_{w_{n-1}}$$
という事実から、
$$ u_{w_0}=0, u_{w_1}=1, u_{w_2}=2, \dots , u_{w_{n-1}}=n-1 $$
であるとわかります。
これをもとに表を書き換えておきましょう。

INDEX 0 1 2 ${\dots}$ ${n-1}$
${ w }$の値 ${ w_0 }$ ${ w_1 }$ ${ w_2 }$ ${ \dots }$ ${ w_{n-1} }$
${ u }$の値 ${ 0 }$ ${ 1 }$ ${ 2 }$ ${ \dots }$ ${ n-1 }$
${ v }$の値 ${ v_0 }$ ${ v_1 }$ ${ v_2 }$ ${ \dots }$ ${ v_{n-1} }$
SCORE ${ s_{w_0} }$ ${ s_{w_1} }$ ${ s_{w_2} }$ ${ \dots }$ ${ s_{w_{n-1}}} $

・・・おや、$v$ の並び順がもともとの並び順に戻っていますね。
これはつまり、学習モデルが出力したままの順序に戻ったわけです。
それに対応するスコア配列の並びは、 $w$ に記録されています。

つまり、求めるスコア配列(prediction_scored)は、以下で求めることができるということです。

v = prediction
u = numpy.argsort( v )
w = numpy.argsort( u )
score = [0, 0, 0, 0, 0, 0, 0, 1, 5, 10]
prediction_scored = numpy.take(score, w)

[証明終了]

4
1
0

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
4
1