【问题标题】:Unique Computational value for an array数组的唯一计算值
【发布时间】:2014-04-16 01:53:08
【问题描述】:

我一直在想它,但已经没有想法了。我有 10 个长度为 18 的数组,其中有 18 个双精度值。这 18 个值是图像的特征。现在我必须对它们应用 k-means 聚类。

为了实现 k-means 聚类,我需要为每个数组设置一个唯一的计算值。是否有任何数学或统计或任何逻辑可以帮助我为每个数组创建一个计算值,根据其中的值对它来说是独一无二的。提前致谢。

这是我的数组示例。还有10个

[0.07518284315321135    
0.002987851573676068    
0.002963866526639678    
0.002526139418225552    
0.07444872939213325 
0.0037219653347541617   
0.0036979802877177715   
0.0017920256571474585   
0.07499695903867931 
0.003477831820276616    
0.003477831820276616    
0.002036159171625004    
0.07383539747505984 
0.004311312204791184    
0.0043352972518275745   
0.0011786937400740452   
0.07353130134299131 
0.004339580295941216]

【问题讨论】:

  • @Octopus 检查,它适用于单个值,我有 10 个这样的数组,我必须用于集群。 1 个数组 = 单个图像特征。简而言之,我必须创建类似图像的集群
  • 第一种方法可以基于Arrays.hashCode(doubleArray)。这对于数组来说不是唯一的,但是......无论如何,没有比 18 个双精度值本身小得多的 18 个双精度值的唯一表示。对于 10 个数组,两个数组具有相同 hashCode 值的可能性应该已经非常非常低,并且可以手动验证和处理。但这一切可能对您没有多大帮助:如果您打算对这些“ID”进行聚类,那么这些 ID 必须保持相似性 - 这是正确的吗?
  • @Marco13 是的,我打算保留那个唯一计算值的相似性。哈希码会保留它们吗?我现在只拍了 10 张图片,数据库将包含数千张图片
  • 开发一种算法,将这些数字转换为基数 18 或基数 36 或基数 72(或更多)char 表示。它是否需要是数字,如果是,为什么?如上所述,您不能仅使用 10 (0-9) 个整数位和长度更小的双精度数来唯一地表示这些双精度数('Real')。但是当我看到你的数据集时,你可以安全地从你的实数中删除第一个 0 、点和后面的 0 ,并将它们表示为整数,但在将它们转换为整数表示时注意前导零.

标签: java arrays k-means


【解决方案1】:

我将建议三种方法,我将概述它们各自的优缺点。

  1. 哈希码 这是显而易见的“解决方案”,尽管已正确指出它不是唯一的。但是,任何两个数组都不太可能具有相同的值。

  2. 加权和 您的元素似乎是有界的;也许它们的范围从最小值 0 到最大值 1。如果是这种情况,您可以将第一个数字乘以 N^0,第二个乘以 N^1,第三个乘以 N^2,依此类推,其中 N是一个很大的数字(理想情况下是您的精度的倒数)。这很容易实现,特别是如果您使用矩阵包,而且速度非常快。如果我们愿意,我们可以让它变得独一无二。

  3. 欧式距离均值 从每个数组中减去数组的平均值,对结果求平方,对平方求和。如果你有一个预期的平均值,你可以使用它。同样,不是唯一的,会有冲突,但你(几乎)无法避免。

唯一性的难度

已经解释过,散列不会给你一个唯一的解决方案。理论上,唯一的数字可能的,使用加权和,但我们必须使用 非常 大尺寸的数字。假设您的数字在内存中是 64 位。这意味着它们可以表示 2^64 个可能的数字(使用浮点数略少)。数组中的 18 个这样的数字可以代表 2^(64*18) 个不同的数字。那是巨大的。如果你少用任何东西,由于鸽洞原理,你将无法保证唯一性。

让我们看一个简单的例子。如果您有四个字母,a、b、c 和 d,并且您必须使用数字 1 到 3 对它们进行唯一编号,那么您不能。这就是鸽巢原理。您有 2^(18*64) 个可能的数字。您不能用少于 2^(18*64) 的数字对它们进行唯一编号,而散列不会给您这样的编号。

如果您使用 BigDecimal,您可以表示(几乎)任意大的数字。如果你能得到的最大元素是 1,最小的元素是 0,那么你可以设置 N = 1/(precision) 并应用上面提到的加权和。这将保证唯一性。 Java 中双精度的精度是 Double.MIN_VALUE。请注意,权重数组需要存储在 _Big Decimal_s 中!

这满足了你的这部分问题:

为每个数组创建一个计算值,它是唯一的 基于里面的值

但是,有一个问题:

1 和 2 不适合 K 均值

根据您与 Marco 13 的讨论,我假设您正在对单个值而不是长度为 18 的数组执行聚类。正如 Marco 已经提到的,散列对于 K 均值来说很糟糕。整个想法是,数据的最小变化将导致哈希值的巨大变化。这意味着两个相似的图像会产生两个非常相似的数组,产生两个非常不同“唯一”的数字。 不保留相似性。结果将是伪随机的!!!

加权和更好,但仍然很差。它基本上会忽略除最后一个之外的所有元素,除非最后一个元素相同。只有这样,它才会查看倒数第二个,依此类推。相似性并未真正保留。

与平均值(或至少某个点)的欧几里得距离至少会以一种明智的方式将事物组合在一起。方向将被忽略,但至少远离平均值的事物不会与接近的事物分组。保留一个特征的相似性,而丢失其他特征。

总结

1 非常简单,但不是唯一的,并且不保留相似性。

2 很简单,可以是唯一并且不保留相似性。

3 很简单,但不是唯一的,并且保留了一些相似性。

加权和的实现。没有真正测试过。

public class Array2UniqueID {

private final double min;
private final double max;
private final double prec;
private final int length;

/**
 * Used to provide a {@code BigInteger} that is unique to the given array.
 * <p>
 * This uses weighted sum to guarantee that two IDs match if and only if
 * every element of the array also matches. Similarity is not preserved.
 *
 * @param min smallest value an array element can possibly take
 * @param max largest value an array element can possibly take
 * @param prec smallest difference possible between two array elements
 * @param length length of each array
 */
public Array2UniqueID(double min, double max, double prec, int length) {
    this.min = min;
    this.max = max;
    this.prec = prec;
    this.length = length;
}

/**
 * A convenience constructor which assumes the array consists of doubles of
 * full range.
 * <p>
 * This will result in very large IDs being returned.
 *
 * @see Array2UniqueID#Array2UniqueID(double, double, double, int)
 * @param length
 */
public Array2UniqueID(int length) {
    this(-Double.MAX_VALUE, Double.MAX_VALUE, Double.MIN_VALUE, length);
}

public BigDecimal createUniqueID(double[] array) {
    // Validate the data
    if (array.length != length) {
        throw new IllegalArgumentException("Array length must be "
                + length + " but was " + array.length);
    }
    for (double d : array) {
        if (d < min || d > max) {
            throw new IllegalArgumentException("Each element of the array"
                    + " must be in the range [" + min + ", " + max + "]");
        }
    }

    double range = max - min;

    /* maxNums is the maximum number of numbers that could possibly exist
     * between max and min.
     * The ID will be in the range 0 to maxNums^length.
     * maxNums = range / prec + 1
     * Stored as a BigDecimal for convenience, but is an integer
     */
    BigDecimal maxNums = BigDecimal.valueOf(range)
            .divide(BigDecimal.valueOf(prec))
            .add(BigDecimal.ONE);
    // For convenience

    BigDecimal id = BigDecimal.valueOf(0);

    // 2^[ (el-1)*length + i ]
    for (int i = 0; i < array.length; i++) {
        BigDecimal num = BigDecimal.valueOf(array[i])
                .divide(BigDecimal.valueOf(prec))
                .multiply(maxNums).pow(i);

        id = id.add(num);
    }

    return id;

}

【讨论】:

  • 这是一个相当合理的解释。谢谢你;)
  • 考虑到平均值的欧几里得距离,如果我通过乘以值而不是索引位置来计算平均值,它会保持相似性吗?我认为它会..对吗?
  • 乘以索引位置将为您提供类似于加权和的结果。其实就是一种加权求和(以索引位置为权重)。这并不能保证唯一性,也不能保持相似性,尽管它也不会破坏所有相似性。
  • 还有另一种选择,即 2 和 3 之间的折衷方案,将保留一些相似性并保证唯一性;使用交错。我会尝试编辑,也许会放一些代码来演示。
  • 好的,我已经研究过使用交错,但这并不容易。虽然数学在纸上很好,但代码有问题;您必须将 BigInteger 提升到另一个 BigInteger 的幂。没有内置的方法。
【解决方案2】:

您检查过 Java 7 中的 Arrays.hashcode 吗?

 /**
 * Returns a hash code based on the contents of the specified array.
 * For any two <tt>double</tt> arrays <tt>a</tt> and <tt>b</tt>
 * such that <tt>Arrays.equals(a, b)</tt>, it is also the case that
 * <tt>Arrays.hashCode(a) == Arrays.hashCode(b)</tt>.
 *
 * <p>The value returned by this method is the same value that would be
 * obtained by invoking the {@link List#hashCode() <tt>hashCode</tt>}
 * method on a {@link List} containing a sequence of {@link Double}
 * instances representing the elements of <tt>a</tt> in the same order.
 * If <tt>a</tt> is <tt>null</tt>, this method returns 0.
 *
 * @param a the array whose hash value to compute
 * @return a content-based hash code for <tt>a</tt>
 * @since 1.5
 */
public static int hashCode(double a[]) {
    if (a == null)
        return 0;

    int result = 1;
    for (double element : a) {
        long bits = Double.doubleToLongBits(element);
        result = 31 * result + (int)(bits ^ (bits >>> 32));
    }
    return result;
}

我不明白为什么@Marco13 提到“这不是为数组返回 unquie”。

更新

见@Macro13 评论the reason 为什么它不能是 unquie..


更新

如果我们使用您的输入点绘制图表,(18 个元素)有一个峰值和 3 个低值,并且模式会出现...... 如果这是真的.. 你可以找到你的峰值 (1, 4, 8,12,16) 的平均值并从剩余值中找到低平均值。

这样你就会有 Peak mean 和 Low mean 。并且您发现表示这两个的 unquie 数还使用here中描述的双射算法保留值

此 Alogirthm 还提供了反转公式,即从 unquie 值中获取峰值和低均值。

寻找唯一对&lt; x; y &gt;= x + (y + ( (( x +1 ) /2) * (( x +1 ) /2) ) )

另请参阅 pdf 第 2 页中的练习 1 以反转 x 和 y。

用于求均值并求配对值。

public static double mean(double[] array){
    double peakMean = 0;
    double lowMean = 0;
    for (int i = 0; i < array.length; i++) {
        if ( (i+1) % 4 == 0 || i == 0){
            peakMean = peakMean + array[i];
        }else{
            lowMean = lowMean + array[i];
        }
    }
    peakMean = peakMean / 5;
    lowMean = lowMean / 13;
    return bijective(lowMean, peakMean);
}



public static double bijective(double x,double y){
    double tmp = ( y +  ((x+1)/2));
    return x +  ( tmp * tmp);
}

用于测试

public static void main(String[] args) {
    double[] arrays = {0.07518284315321135,0.002963866526639678,0.002526139418225552,0.07444872939213325,0.0037219653347541617,0.0036979802877177715,0.0017920256571474585,0.07499695903867931,0.003477831820276616,0.003477831820276616,0.002036159171625004,0.07383539747505984,0.004311312204791184,0.0043352972518275745,0.0011786937400740452,0.07353130134299131,0.004339580295941216};
    System.out.println(mean(arrays));
}

您可以使用此峰值和低值来查找相似的图像。

【讨论】:

  • 您更新的答案因某种模式而被卡住,但数组不遵循像 1 个尖峰和 3 个低值这样的模式,我得到一些带有 2 个尖峰或 3 个尖峰的数组 .. 它是随机的..但是使用双射确实是对您的答案的补充。数组可以按降序排序,并且可以计算中间值以找到峰值均值和低均值,但值的位置反映了相似性。那会受到影响。但如果我有类似的模式,这将是一个完美的答案:)
【解决方案3】:

根据数组生成唯一结果的可靠方法是将其转换为一个大字符串,然后将其用于计算值。

  • 它可能会很慢,但它会根据数组的值是唯一的。

实现示例: Best way to convert an ArrayList to a string

【讨论】:

    【解决方案4】:

    首先,让我们试着从数学上理解你需要什么:

    m 实数数组唯一映射到单个数字实际上是R^mR 之间的双射,或者至少是N

    由于浮点实际上是有理数,你的问题是找到Q^mN 之间的双射,它可以转换为N^nN,因为你知道你的值总是更大大于 0(只需将您的值乘以精度)。

    因此您需要将N^m 映射到N。看看Cantor Pairing Function 了解一些想法

    【讨论】:

      【解决方案5】:

      嗯,这是一种适用于任意数量的双打的方法。

      public BigInteger uniqueID(double[] array) {
          final BigInteger twoToTheSixtyFour = 
                  BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE);
      
          BigInteger count = BigInteger.ZERO;
          for (double d : array) {
              long bitRepresentation = Double.doubleToRawLongBits(d);
              count = count.multiply(twoToTheSixtyFour);
              count = count.add(BigInteger.valueOf(bitRepresentation));
          }
          return count;
      }
      

      说明

      每个double 都是一个 64 位的值,这意味着有 2^64 个不同的可能双精度值。由于long 更容易处理这类事情,而且它的位数相同,我们可以使用Double.doubleToRawLongBits(double) 获得从双精度数到长整数的一对一映射。

      这太棒了,因为现在我们可以将其视为一个简单的组合问题。你知道你怎么知道 1234 是一个唯一的数字吗?没有其他数字具有相同的值。这是因为我们可以像这样按数字分解它:

      1234 = 1 * 10^3 + 2 * 10^2 + 3 * 10^1 + 4 * 10^0
      

      如果您了解线性代数,那么 10 的幂将是基数为 10 的编号系统的“基”元素。这样,以 10 为基数的数字就像仅由 0 到 9 (含)的值组成的数组。

      如果我们想对双精度数组进行类似的处理,我们可以讨论 base-(2^64) 编号系统。每个双精度值都将是一个值的基数(2^64)表示中的一个数字。如果有 18 位数字,则长度为 18 的 double[] 有 (2^64)^18 个唯一值。

      这个数字很大,所以我们需要用BigInteger 数据结构而不是原始数字来表示它。这个数字有多大?

      (2 ^ 64)^ 18 = 61172327492847069472032393719205726809135813743440799050195397570919697796091958321786863938157971792315844506873509046544459008355036150650333616890210625686064472971480622053109783197015954399612052812141827922088117778074833698589048132156300022844899841969874763871624802603515651998113045708569927237462546233168834543264678118409417047146496 P>

      18 长双精度数组有很多独特的配置,这段代码可以让您唯一地描述它们。

      【讨论】:

        【解决方案6】:

        据我了解,您将根据双精度值进行 k 聚类。

        为什么不用数组和位置标识符将双精度值包装在一个对象中,这样您就可以知道它在哪个集群中结束了?

        类似:

         public class Element {
             final public double value;
             final public int array;
             final public int position;
             public Element(double value, int array, int position) {
                 this.value = value;
                 this.array = array;
                 this.position = position;
             }
         }
        

        如果需要将数组作为一个整体进行聚类,

        1. 您可以将长度为 18 的原始数组转换为长度为 19 的数组,其中最后一个或第一个元素是唯一的 id,在聚类期间您将忽略它,但是,您可以在聚类完成后引用它。这样,它的内存占用量很小——一个数组需要 8 个额外的字节,并且很容易与原始值关联。
        2. 如果空间绝对是个问题,并且你有一个数组的所有值都小于 1,你可以为每个数组添加唯一的 id,大于或等于 1,并根据除以 1 的提示进行聚类,0.07518284315321135 停留第一个为 0.07518284315321135,第二个为 0.07518284315321135 变为 1.07518284315321135,尽管这会增加聚类期间的计算复杂度。

        【讨论】:

        • 我想对数组的双精度值执行 k 聚类,而不是数组中的所有双精度值。这会为 k 聚类添加额外的对象创建,这在数千个数组上是不可行的。
        • 添加具有唯一 id 的额外元素作为数组的头部,这将被集群忽略,这样它只有 1 个额外的元素开销。
        • 你能详细说明你的想法吗?
        • 好的,你的答案更多地集中在如何对数组进行聚类,而我的问题更多地集中在如何为数组获取唯一的单个值,以便我可以在一维中执行 k-means。正如@xlm 提到的,k-means 在 n 空间中工作,但我想让它在 n 数组 wid n 值的单个空间中工作..
        • 如果您要基于单个唯一值进行聚类,那没有多大意义,因为您的结果将取决于您选择生成此唯一值的函数,因此它们可以不能依赖。如果您已经有一个函数,它将您的数组转换为单个值,那么您已经有了唯一标识符。
        【解决方案7】:

        您可以简单地对这些值求和,使用双精度,结果值大多数次都是唯一的。另一方面,如果值位置相关,则可以使用索引作为乘数来应用总和。

        代码可以很简单:

        public static double sum(double[] values) {
            double val = 0.0;
            for (double d : values) {
                val += d;
            }
            return val;
        }
        
        public static double hash_w_order(double[] values) {
            double val = 0.0;
            for (int i = 0; i < values.length; i++) {
                val += values[i] * (i + 1);
            }
            return val;
        }
        
        public static void main(String[] args) {
            double[] myvals =
                { 0.07518284315321135, 0.002987851573676068, 0.002963866526639678, 0.002526139418225552, 0.07444872939213325, 0.0037219653347541617, 0.0036979802877177715, 0.0017920256571474585, 0.07499695903867931, 0.003477831820276616,
                        0.003477831820276616, 0.002036159171625004, 0.07383539747505984, 0.004311312204791184, 0.0043352972518275745, 0.0011786937400740452, 0.07353130134299131, 0.004339580295941216 };
        
            System.out.println("Computed value based on sum: " + sum(myvals));
            System.out.println("Computed value based on values and its position: " + hash_w_order(myvals));
        }
        

        该代码的输出,使用您的值列表是:

        Computed value based on sum: 0.41284176550504803
        Computed value based on values and its position: 3.7396448842464496
        

        【讨论】:

        • Sum 不会起作用,也不意味着.. 但是将索引位置与值相乘的想法似乎很好。将不得不检查,它们将如何影响这个想法非常有用的结果..
        猜你喜欢
        • 2018-04-01
        • 2012-02-10
        • 1970-01-01
        • 2019-06-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-02-04
        相关资源
        最近更新 更多