【问题标题】:Python scipy.optimize.leastsq to Java org.apache.commons.math3.fitting.leastsquaresPython scipy.optimize.leastsq 到 Java org.apache.commons.math3.fitting.leastsquares
【发布时间】:2017-12-15 12:37:47
【问题描述】:

我尝试模仿这个algorithm,它是用 Python 开发的,它根据看到的 Wifi 站位置计算地理位置,它本身基于这个idea

该算法首先使用 Numpy 函数来计算观测到的纬度和经度的基本加权平均值。为了尽量减少可能的 Wifi 位置错误的影响,它还使用“scipy.optimize.leastsq”方法以统计方式计算,如果可能,更精确的位置。

我想在 Java Android 平台上实现同样的行为。

对于所有其他计算,我成功地依赖于 org.apache.commons.math3。 所以对于最小二乘问题,我在逻辑上尝试依赖https://commons.apache.org/proper/commons-math/userguide/leastsquares.html

如果我很好理解的话,我的问题是 Scipy 为我管理了雅可比函数定义的复杂性,而我糟糕的数学技能不允许我正确定义 LeastSquaresProblem 的模型。我尝试了一些基于此example 的实验,这似乎与我所需要的很接近,但结果并不好,因为我不知道如何处理“雅可比”部分。

就像有人为this 发帖所做的那样,有人可以为我做同样的事情并尝试用简单的方式解释吗?

有关 Python 部分如何工作的更多详细信息:

使用的“scipy.optimize.leastsq”语句是:

(lat, lon), cov_x, info, mesg, ier = 
scipy.optimize.leastsq(func, initial, args=data, full_output=True)

其中数据为:纬度/经度/年龄(毫秒)/信号强度,例如:data = numpy.array([(43.48932915, 1.66561772, 1000, -20), (43.48849093, 1.6648176, 2000, -10), (43.48818612, 1.66615113, 3000, -50)])

初始是计算加权平均纬度/经度,在本例中:initial = 43.48864654, 1.66550075

功能是

  def func(initial, data):
        return numpy.array([
            geographic distance((float(point[latitude]), float(point[longitude])), (initial[latitude], initial[longitude])).meters * min(math.sqrt(2000.0 / float(point[age])), 1.0) / math.pow(float(point[signal strength]), 2)

结果是:43.4885401095, 1.6648660983

我在 Java 中的实验,我已经替换了数据值并改变了“modelI”的计算方式。我简化了信号强度和年龄值。但这是一个事实,结果表明,这还不够。

double modelI = calculateVincentyDistance(o.getY(), o.getX(), center.getY(), center.getX())* Math.min(Math.sqrt(2000.0/1000.0), 1.0) / Math.pow(-10, 2);

我也打算试试https://github.com/odinsbane/least-squares-in-java,但我不确定是否正确使用它,因为我不掌握它的工作方式。

仅供参考,我使用 Vincenty 距离计算,例如可以用 Haversine 或 Euclidean 代替。

感谢您的帮助!

【问题讨论】:

标签: java math scipy least-squares


【解决方案1】:

代码不容易移植,因为 SciPy 提供了更通用的Least-squares minimization 接口,而 Apache Commons Math 提供了curve fitting。仍然有许多优化问题可以重新表述为曲线拟合。在 Python 代码中你最小化

F(current_point) = Sum{ (distance(known_point[i], current_point) * weight[i])^2 } -> min

Java曲线拟合问题有点不同:

F(current_point) = Sum{ (target_value[i] - model[i](current_point))^2  } -> min

因此可以通过将所有target_values 分配为0 并让model[i] 计算从current_pointknown_point[i] 的加权距离来创建等效拟合问题。

在一般情况下,此类问题没有使用公式的精确解决方案,而是使用了一些数值优化方法。这里还有另一个区别:Java 实现明确要求您为优化器提供计算被优化函数的导数的方法。如果未提供Dfun,Python 代码似乎会使用某种差异区分器。您可以在 Java 中手动或使用 FiniteDifferencesDifferentiator 执行此类操作,但对于简单的公式,使用 DerivativeStructure 显式编码它们可能更容易

static class PositionInfo {
    public final double latitude;
    public final double longitude;
    public final int ageMs;
    public final int strength;

    public PositionInfo(double latitude, double longitude, int ageMs, int strength) {
        this.latitude = latitude;
        this.longitude = longitude;
        this.ageMs = ageMs;
        this.strength = strength;
    }

    public double getWeight() {
        return Math.min(1.0, Math.sqrt(2000.0 / ageMs)) / (strength * strength);
    }
}


static DerivativeStructure getWeightedEuclideanDistance(double tgtLat, double tgtLong, PositionInfo knownPos) {
    DerivativeStructure varLat = new DerivativeStructure(2, 1, 0, tgtLat); // latitude is 0-th variable of 2 for derivatives up to 1
    DerivativeStructure varLong = new DerivativeStructure(2, 1, 1, tgtLong); // longitude is 1-st variable of 2 for derivatives up to 1
    DerivativeStructure latDif = varLat.subtract(knownPos.latitude);
    DerivativeStructure longDif = varLong.subtract(knownPos.longitude);
    DerivativeStructure latDif2 = latDif.pow(2);
    DerivativeStructure longDif2 = longDif.pow(2);
    DerivativeStructure dist2 = latDif2.add(longDif2);
    DerivativeStructure dist = dist2.sqrt();
    return dist.multiply(knownPos.getWeight());
}

// as in https://en.wikipedia.org/wiki/Haversine_formula
static DerivativeStructure getWeightedHaversineDistance(double tgtLat, double tgtLong, PositionInfo knownPos) {
    DerivativeStructure varLat = new DerivativeStructure(2, 1, 0, tgtLat);
    DerivativeStructure varLong = new DerivativeStructure(2, 1, 1, tgtLong);
    DerivativeStructure varLatRad = varLat.toRadians();
    DerivativeStructure varLongRad = varLong.toRadians();
    DerivativeStructure latDifRad2 = varLat.subtract(knownPos.latitude).toRadians().divide(2);
    DerivativeStructure longDifRad2 = varLong.subtract(knownPos.longitude).toRadians().divide(2);
    DerivativeStructure sinLat2 = latDifRad2.sin().pow(2);
    DerivativeStructure sinLong2 = longDifRad2.sin().pow(2);
    DerivativeStructure summand2 = varLatRad.cos().multiply(varLongRad.cos()).multiply(sinLong2);
    DerivativeStructure sum = sinLat2.add(summand2);
    DerivativeStructure dist = sum.sqrt().asin();
    return dist.multiply(knownPos.getWeight());
}

使用这样的准备,你可以做这样的事情:

public static void main(String[] args) {

    // latitude/longitude/age in milliseconds/signal strength
    final PositionInfo[] data = new PositionInfo[]{
            new PositionInfo(43.48932915, 1.66561772, 1000, -20),
            new PositionInfo(43.48849093, 1.6648176, 2000, -10),
            new PositionInfo(43.48818612, 1.66615113, 3000, -50)
    };


    double[] target = new double[data.length];
    Arrays.fill(target, 0.0);

    double[] start = new double[2];

    for (PositionInfo row : data) {
        start[0] += row.latitude;
        start[1] += row.longitude;
    }
    start[0] /= data.length;
    start[1] /= data.length;

    MultivariateJacobianFunction distancesModel = new MultivariateJacobianFunction() {
        @Override
        public Pair<RealVector, RealMatrix> value(final RealVector point) {
            double tgtLat = point.getEntry(0);
            double tgtLong = point.getEntry(1);

            RealVector value = new ArrayRealVector(data.length);
            RealMatrix jacobian = new Array2DRowRealMatrix(data.length, 2);
            for (int i = 0; i < data.length; i++) {
                DerivativeStructure distance = getWeightedEuclideanDistance(tgtLat, tgtLong, data[i]);
                //DerivativeStructure distance = getWeightedHaversineDistance(tgtLat, tgtLong, data[i]);
                value.setEntry(i, distance.getValue());
                jacobian.setEntry(i, 0, distance.getPartialDerivative(1, 0));
                jacobian.setEntry(i, 1, distance.getPartialDerivative(0, 1));
            }

            return new Pair<RealVector, RealMatrix>(value, jacobian);
        }
    };


    LeastSquaresProblem problem = new LeastSquaresBuilder()
            .start(start)
            .model(distancesModel)
            .target(target)
            .lazyEvaluation(false)
            .maxEvaluations(1000)
            .maxIterations(1000)
            .build();

    LeastSquaresOptimizer optimizer = new LevenbergMarquardtOptimizer().
            withCostRelativeTolerance(1.0e-12).
            withParameterRelativeTolerance(1.0e-12);

    LeastSquaresOptimizer.Optimum optimum = optimizer.optimize(problem);
    RealVector point = optimum.getPoint();
    System.out.println("Start = " + Arrays.toString(start));
    System.out.println("Solve = " + point);
}

附:重量的逻辑对我来说似乎很可疑。在您引用的问题中,OP 对半径进行了一些估计,然后它是一个明显的权重。使用以对数 dBm 测量的信号强度的反向平方对我来说似乎很奇怪。

【讨论】:

  • 感谢 SergGr 的回答!它工作正常。我现在必须测试更复杂和更多的测试用例。您的解释和代码 cmets 很清楚,但对我来说有点棘手。由于没有很多迭代,我可以对该过程进行更深入的分析。在回复您的评论时,在进行观测但未使用时计算站点位置的半径。此代码的作者更喜欢使用聚类来对相似位置进行分组并计算它们的中心。在您最后的帮助下,我现在可以对整个解决方案进行更深入的分析,并尝试找到自己的方法。
  • 您的解决方案适用于欧几里得距离和哈弗辛距离计算。 Vincenty 方法更复杂。你认为它可以用 DerivativeStructure 来实现吗?会有效率吗?我读过this 鼓励使用FiniteDifferencesDifferentiator。但是我不掌握基础,也不了解 FiniteDifferencesDifferentiator 参数值的选择以及我可以在 UnivariateFunction 中放入什么。请你给我更多关于如何做的建议吗?谢谢
  • 最后,我想我会使用纬度/经度投影和欧几里得距离。感谢您的帮助。
  • @chrisail,AFAIU Vincenty 基本上是迭代的,所以DerivativeStructure 不适合。至于FiniteDifferencesDifferentiator,我还没有真正研究过,所以不能给你建议。我认为如果你想使用它,你应该为你的特定任务使用参数,看看什么对你有用。
猜你喜欢
  • 2022-01-08
  • 1970-01-01
  • 2020-10-23
  • 2012-04-10
  • 2019-08-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-02
相关资源
最近更新 更多