特にあなたが説明している規模では、これは非常に難しい最適化問題であることに同意します。各目的関数の評価では、次元 m の n 個の点に対して O(nm + n^2) の作業が必要です。つまり、各点から新しい点までの距離を計算するのに O(nm)、距離が与えられた目的を計算するのに O(n^2) が必要です。 . m=300 で n=3M の場合、これはかなり恐ろしいことです。したがって、完全な最適化問題を解くことは言うまでもなく、1 つの関数評価でさえおそらく扱いにくいものです。
他の回答で言及されている1つのアプローチは、ポイントの重心を効率的に計算できるO(nm)を取ることです。このアプローチの欠点は、提案された目的に対して非常にうまくいく可能性があることです。たとえば、値 1 の 300 万点と値 0 の 1 点がある 1 次元空間の状況を考えてみましょう。調べると、最適解は v=0.5 で目的値 0 (すべての点から等距離) ですが、重心はv=1 (まあ、それより少し小さい) を選択し、目的値は 300 万です。
重心よりもうまくいくと私が考えるアプローチは、各次元を個別に最適化することです (他の次元の存在を無視します)。この場合、目的関数の計算には依然としてコストがかかりますが、少し代数を考えると、目的関数の微分は非常に簡単に計算できることがわかります。これは、値 4*((vi)+(vj)) の i < v および j > v であるすべてのペア (i, j) の合計です。点 i と j が v と同様に 1 次元になるように、1 次元を最適化していることを思い出してください。したがって、各次元について、データ (O(n lg n)) を並べ替え、値 v の導関数を計算できます。二分探索と基本代数を使用した O(n) 時間。次に、使用できますscipy.optimize.newton
その次元の最適値となる導関数のゼロを見つけます。すべての次元を反復すると、問題の近似解が得られます。
最初に、1 次元のデータ ポイント {0, 3, 3} を使用して、単純な設定で提案されたアプローチと重心法を比較します。
import bisect
import scipy.optimize
def fulldist(x, data):
dists = [sum([(x[i]-d[i])*(x[i]-d[i]) for i in range(len(x))])**0.5 for d in data]
obj = 0.0
for i in range(len(data)-1):
for j in range(i+1, len(data)):
obj += (dists[i]-dists[j]) * (dists[i]-dists[j])
return obj
def f1p(x, d):
lownum = bisect.bisect_left(d, x)
highnum = len(d) - lownum
lowsum = highnum * (x*lownum - sum([d[i] for i in range(lownum)]))
highsum = lownum * (x*highnum - sum([d[i] for i in range(lownum, len(d))]))
return 4.0 * (lowsum + highsum)
data = [(0.0,), (3.0,), (3.0,)]
opt = []
centroid = []
for d in range(len(data[0])):
thisdim = [x[d] for x in data]
meanval = sum(thisdim) / len(thisdim)
centroid.append(meanval)
thisdim.sort()
opt.append(scipy.optimize.newton(f1p, meanval, args=(thisdim,)))
print "Proposed", opt, "objective", fulldist(opt, data)
# Proposed [1.5] objective 0.0
print "Centroid", centroid, "objective", fulldist(centroid, data)
# Centroid [2.0] objective 2.0
提案されたアプローチは正確な最適解を見つけますが、重心法は少しミスします。
次元 300 の 1000 個の点があり、各点がガウス混合から引き出された少し大きな例を考えてみましょう。各ポイントの値は、確率 0.1 で平均 0 と分散 1 の正規分布であり、確率 0.9 で平均 100 と分散 1 の正規分布です。
data = []
for n in range(1000):
d = []
for m in range(300):
if random.random() <= 0.1:
d.append(random.normalvariate(0.0, 1.0))
else:
d.append(random.normalvariate(100.0, 1.0))
data.append(d)
結果の目的値は、提案されたアプローチでは 1.1e6、重心アプローチでは 1.6e9 でした。これは、提案されたアプローチが目的を 99.9% 以上減少させたことを意味します。明らかに、客観的な値の違いは、ポイントの分布によって大きく影響されます。
最後に、スケーリングをテストするために (最終的な目標値の計算を削除します。これは一般に扱いにくいためです)、m=300 で次のスケーリングを取得します: 1,000 ポイントで 0.9 秒、10,000 ポイントで 7.1 秒、100,000 ポイントで 122.3 秒ポイント。したがって、300 万ポイントの完全なデータセットの場合、これには約 1 ~ 2 時間かかるはずです。