您描述的一般问题没有解决方案,因为您正在尝试制作从 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,它指定了圆上点的距离与二维排列的距离不匹配的惩罚。改变这一点以增加大错误的成本(比如二次方),或者强调大距离正确的成本,比如d_target*(abs(d_actual-d_target))等,可能会有所帮助。
此外,相对于 2D 数据的大小更改圆的大小会大大改变它的外观,您可能希望圆比数据小一些,就像我在这里所做的那样,这将更多地分散在圆圈周围的点。 (这里的圆圈 R = 1,所以只需适当地缩放数据。)还要注意,这将使成本的定量评估不是很有意义,因为最好的安排永远不会得到非常低的成本,因为某些地区永远不可能与 2D 数据中的距离一样远。
运行多个随机开始的关键在于,不断演变的排列可能会陷入局部最小值。这种技术似乎很有用:解决有助于使距离正确并降低成本(图#3,蓝点=初始随机,菱形=局部最小值)并且它比其他方法更能帮助一些初始安排,所以尝试一下很好多个初始安排。此外,由于其中一些似乎稳定在 15 左右,这让人相信这种安排可能具有代表性。