Общая проблема, которую вы описываете, не имеет решения, потому что вы пытаетесь сделать карту из 2D-поверхности в 1D-линию, которая сохраняет все расстояния, а это невозможно. Если существует определенный регион, который вы хотите сравнить со всеми остальными, вы можете поместить все остальные вокруг круга так, чтобы их расстояние соответствовало расстоянию до этого региона (но тогда расстояние между этими другими регионами будет искажено).
Но вы, конечно, можете сделать лучше, чем просто случайное приближение расстояния. Вот подход: первым шагом было бы сделать несколько случайных расположений, а затем выбрать лучшее из них. Следующим улучшением будет оптимизация каждой из этих схем в зависимости от некоторой функции затрат, перемещая регионы небольшими шагами до тех пор, пока они не достигнут локального минимума, а затем выберите лучший из этих локальных минимумов. Результаты этого показаны на графиках ниже, а код Python находится ниже.
import pylab as px
import numpy as nx
import numpy.random as rand
rand.seed(1)
rt2 = nx.sqrt(2)
N = 10 # number of brain regions
# make brain region locations r=1
regions = []
s = 2.
while len(regions)<N:
p = 2*s*rand.rand(2)-s
if nx.sqrt(px.dot(p,p))<s:
regions.append(p)
regions = nx.array(regions)
#px.figure()
px.subplot(2,2,1)
for i in range(len(regions)):
px.text(regions[i,0], regions[i,1], `i`, fontsize=15)
px.xlim(-1.1*s, 1.1*s)
px.ylim(-1.1*s, 1.1*s)
px.title("inital positions")
# precalc distance matrix for future comparisons
dm = nx.zeros((N,N), dtype=nx.float)
for i in range(N):
for j in range(N):
dm[i,j] = nx.sqrt(nx.sum((regions[i,:]-regions[j,:])**2))
def randomize_on_circle(n):
"""return array of n random angles"""
return 2*nx.pi*rand.rand(n)
def cost_fcn(d_target, d_actual): # cost for distances not matching
return abs(d_target-d_actual)
def calc_cost(angles):
"""calc cost for the given arrangement """
c = 0.
for i in range(N-1):
for j in range(i, N):
# sqrt(...) is distance between two pts on a circle (I think)
c += cost_fcn(dm[j, i], rt2*nx.sqrt(1-nx.cos(angles[i]-angles[j])))
return c
def optimize_step(a, shift=2*nx.pi/360):
"""try shifting all points a bit cw and ccw, and return the most beneficial"""
max_benefit, ref_cost = None, None
best_i, best_shift = None, None
for imove in range(N): # loop through the regions and try moving each one
cost0 = calc_cost(a)
for da in (shift, -shift):
a_temp = nx.array(a)
a_temp[imove] += da
cost = calc_cost(a_temp)
benefit = cost0 - cost # benefit if moving lowers the cost
if max_benefit is None or benefit > max_benefit:
max_benefit, best_i, best_shift, ref_cost = benefit, imove, da, cost
return max_benefit, best_i, best_shift, ref_cost
lowest_cost, best_angles = None, None
cost_initials, cost_plateaus = [], []
for i in range(30): # loop though 20 randomized placements on the circle
angles = randomize_on_circle(N)
costs = []
benefits = []
# optimize each original arrangement by shifting placements one-by-one in small steps
count_benefits_neg = 0
count_total, max_total = 0, 2000
while count_benefits_neg < 10: # better to do a variable step size
b, i, s, c = optimize_step(angles)
angles[i] += s
costs.append(c)
benefits.append(b)
if b < 0:
count_benefits_neg += 1
count_total += 1
if count_total > max_total:
print count_total, b, costs[-20:], benefits[-20]
raise "not finding an equilibrium"
if lowest_cost is None or c < lowest_cost:
lowest_cost = c
best_angles = nx.array(angles)
cost_graph = costs[:]
benefit_graph = nx.array(benefits)
cost_plateaus.append(c)
cost_initials.append(costs[0])
px.subplot(2, 2, 2)
px.plot(cost_graph, 'o') # make sure the cost is leveling off
px.title("cost evoloution of best")
px.subplot(2, 2, 3)
px.plot(cost_initials, 'o')
px.plot(cost_plateaus, 'd')
px.title("initial and final costs")
px.subplot(2, 2, 4)
for i in range(len(best_angles)):
px.text(nx.cos(best_angles[i]), nx.sin(best_angles[i]), `i`, fontsize=15)
px.xlim(-1.2, 1.2)
px.ylim(-1.2, 1.2)
px.title("positioned on circle")
px.show()
Интересно, что это, кажется, привело к тому, что далекие вещи были далекими, а близкие - близкими, но с испорченными средними приказами, так что, возможно, это будет делать то, что вы хотите? (Это также иллюстрирует фундаментальную проблему при переходе от 2D к 1D. Например, на окружности 4 хочет быть дальше от 9, но это не может быть сделано без приближения к другим числам, тогда как в 2D это может просто выйди в сторону.)
Возможно, вы захотите изменить cost_fnc
, который определяет штраф за то, что расстояния между точками на окружности не совпадают с расстоянием от 2D-схемы. Это может помочь изменить затраты на большие ошибки (скажем, на квадратичные) или подчеркнуть стоимость правильного большого расстояния, скажем, d_target*(abs(d_actual-d_target))
и т. Д., Может помочь.
Кроме того, изменение размера круга относительно размера двумерных данных сильно изменит внешний вид, и вы, вероятно, захотите обвести круг немного меньше, чем данные, как я сделал здесь, так как это расширит точки вокруг круга больше. (Здесь кружок имеет R = 1, поэтому просто масштабируйте данные соответствующим образом.) Также обратите внимание, что количественная оценка стоимости будет не очень значимой, поскольку лучшие схемы никогда не достигают очень низких затрат, поскольку некоторые регионы никогда не могут быть так далеко друг от друга, как в 2D данных.
Точка запуска нескольких случайных запусков заключается в том, что развивающееся устройство может застрять в локальных минимумах. Этот метод кажется полезным: расчеты помогают правильно определить расстояние и снизить затраты (график № 3, синие точки = начальная случайная величина, ромбы = локальный минимум), и некоторые первоначальные схемы помогают гораздо больше, чем другие, поэтому стоит попробовать несколько начальных договоренностей. Кроме того, поскольку некоторые из них, по-видимому, достигают 15, это дает некоторую уверенность в том, что эта схема может быть репрезентативной.