Абсолютная строковая метрика

У меня есть огромный (но конечный) набор строк на естественном языке.

Мне нужен способ преобразовать каждую строку в числовое значение. Для любой данной строки значение должно быть каждый раз одинаковым.

Чем более "разные" две данные строки, тем больше должны быть разных двух соответствующих значений. Чем больше они «похожи», тем меньше должно быть разных значений.

Я пока не знаю, какое точное определение разницы между строками мне нужно. В любом случае никакого анализа естественного языка. Вероятно, это должно быть что-то вроде Левенштейна (но Левенштейн относителен, и мне нужна абсолютная метрика). Начнем с чего-нибудь простого.

Обновить размеры

Я буду счастлив довольствоваться многомерным (лучше всего трехмерным) вектором вместо одного числового значения.

Обновление о правильности ожидаемого результата

Как правильно было отмечено, здесь и здесь расстояние от одной строки до другой представляет собой вектор с MAX(firstStringLength, secondStringLength) размерами. Как правило, невозможно уменьшить количество измерений без потери информации.

Однако мне не нужно абсолютное решение. Я бы согласился на любое «достаточно хорошее» преобразование из пространства N-мерных строк в мое трехмерное пространство.

Также обратите внимание, что у меня есть конечное количество строк конечной длины. (Количество строк довольно велико, около 80 миллионов (10 ГБ), поэтому мне лучше выбрать какой-нибудь однопроходный алгоритм без состояния.)

При сканировании ссылок у меня сложилось впечатление, что здесь мне может помочь кривая заполнения гильбертова пространства. Похоже на В статье Анализ кластеризационных свойств кривой заполнения гильбертова пространства обсуждается нечто близкое к моей проблеме ...

Обновленная информация о подходе с использованием кривой Гильберта

  1. Мы сопоставляем каждую строку с точкой в ​​N-мерном пространстве, где N - максимальная длина строки в наборе. Кстати, можно ли использовать код i-го символа из строки в качестве значения i-й координаты здесь?
  2. Мы проводим кривую Гильберта через это N-мерное пространство.
  3. Для каждой струны берем точку на кривой, ближайшую к координатам струны. Значение Гильберта этой точки (длина от начала кривой) - это одномерное значение, которое я ищу.
  4. Если нам нужно 3D-значение, мы строим кривую Гильберта в 3D и выбираем точки, соответствующие значениям Гильберта, вычисленным выше.

Выглядит правильно? Какие здесь будут вычислительные затраты?


person Alexander Gladysh    schedule 30.01.2009    source источник


Ответы (8)


Я не думаю, что это возможно. Начните с простой строки и присвойте ей ноль (на самом деле не имеет значения, какой номер)

  • «Привет, мир» = 0

На расстоянии 2 от него находятся следующие струны:

  • «XXllo World» = а
  • «HeXXo World» = b
  • "Привет, XXrld" = c
  • "Hello WorXX" = d

Тем не менее, каждая из этих струн находится на расстоянии 4 друг от друга. Невозможно отсортировать числа, чтобы заставить его работать, в следующем случае:

a = 1, b = -1, c = 2, d = -2

Считайте, что от c до 0 равно 2, а от c до a равно 1, а 0 ближе, чем a.

И это просто случай.

person FryGuy    schedule 30.01.2009

Я думаю, вам придется более четко обозначить вашу проблему, чего именно вы пытаетесь достичь с помощью этой метрики?

Я говорю это, потому что Левенштейн работает, поскольку он отображает пары строк в метрику, которая может сохранить размерность пространства строк. Что произойдет, если вы попытаетесь сопоставить строки с числами, так это большая потеря размерной информации. Например, скажем, у меня есть строка «кошка», я бы хотел, чтобы «летучая мышь», «шляпа», «крыса», «может», «кроватка» и т. Д. Были достаточно близки к этому. При большом количестве слов в результате получается, что разнородные слова встречаются довольно часто, например «летучая мышь» и «кроватка» могут быть близки, потому что они оба находятся на одинаковом расстоянии от «кошки» с положительной стороны. Это похоже на проблему того, что происходит, когда вы пытаетесь сопоставить плоскость с линией, трудно выполнить ограничение, согласно которому точки, расположенные далеко в плоскости, остаются далеко на линии. Таким образом, результатом этого является то, что требование «Чем больше« разных »двух заданных строк, тем больше должно быть разных двух соответствующих значений», является трудным.

Итак, мое первое предложение: вам действительно нужно что-то, что делает это, будет ли достаточно простого хеш-кода, чтобы дать вам уникальные значения, или, возможно, вы все-таки можете использовать Левенштейна и игнорировать значения для отдельных строк? Если ничего из этого недостаточно, возможно, вы можете использовать значение многомерной функции, то есть сопоставить строки в парах, тройках или другом небольшом кортеже чисел. Предоставляемая таким образом дополнительная размерность даст вам гораздо лучшие результаты.

Примером может быть кодирование строки как тройка: длина, сумма значений букв в строке, переменная сумма значений букв, например. f ("кошка") = (3, 3 + 1 + 20, 3 - 1 + 20) = (3, 24, 22). Это будет иметь некоторые из желаемых вами свойств, но, вероятно, не оптимально. Попробуйте найти ортогональные особенности строки, чтобы выполнить эту кодировку, или, что еще лучше, если у вас есть большой тестовый набор строк, существуют существующие библиотеки для сопоставления такого рода данных с низкими измерениями при сохранении метрик (например, метрики Левенштейна), и вы можете обучить вашу функцию на этом. Я помню, что язык S поддерживал подобные вещи.

person Daniel Nadasi    schedule 30.01.2009
comment
Хм. Похоже, ты прав. Я пытаюсь сопоставить набор строк с набором цветов. Так что я полностью за значение многомерной функции. См. stackoverflow.com/questions/495662/ < / а> - person Alexander Gladysh; 31.01.2009

Итак, здесь я надеюсь показать основную проблему и решение.

Проблема: вы правы, что ищете «достаточно хорошее» решение, поскольку получить идеальное решение невозможно (я могу показать это в теории информации, но я выберу геометрию, так как она более читабельна. ). У вас есть N-мерное пространство, поэтому метрики расстояния не могут быть спроецированы без потери информации:

distance projected onto X: (x,y,z).(1,0,0) = x

однако вы можете использовать векторы, которые учитывают несколько измерений, но в результате вы получите элементы, расположенные далеко друг от друга на одинаковом расстоянии:

(30,0,0).(1/3,1/3,1/3) = (0,30,0).(1/3,1/3,1/3) = (0,0,30).(1/3,1/3,1/3) = 10

Итак, теперь решение: лучшее, на что вы можете надеяться, - это кластеризовать с помощью Анализ основных компонентов, чтобы найти три измерения, по которым ваши строки различаются больше всего. Это опять же зависит от компонентов используемых вами метрик расстояния и нетривиально (т. Е. Я не хочу делать этот пост еще длиннее).

Для быстрого решения я предлагаю использовать расстояние Левенштейна из трех описанных ниже строк быстро пытается выполнить PCA в голове:

"acegikmoqsuwy" //use half your permitted symbols then repeat until you have a string of size equal to your longest string.
"bdfhjlnprtv" //use the other half then repeat as above.
"" //The empty string, this will just give you the length of the string, so a cheap one.

Кроме того, если вы хотите углубиться, вам может помочь эта метрика / расстояния: http://www.springer.com/matMathematics/geometry/book/978-3-642-00233-5

и демонстрация расстояния Левенштейна: http://www.merriampark.com/ld.htm

person Forthright    schedule 29.09.2011

Я хотел бы расширить ответ FryGuy, почему он не будет работать в любом фиксированном количестве измерений. Возьмем aaaaaaaaaa и baaaaaaaaa, abaaaaaaaa, ..., aaaaaaaaab. В этом примере строки имеют длину 10, но они могут быть произвольной длины. Расстояние каждой из 10 b-строк от aaaaaaaaaa равно 1, а их расстояние друг от друга равно 2. В общем, если вы возьмете фиксированные строки длины N в двухбуквенном алфавите, их график расстояний будет N-мерным гиперкубом. .

Невозможно отобразить это в фиксированном количестве измерений, если только ваши строки не имеют ограниченной длины.

person Rafał Dowgird    schedule 31.01.2009
comment
Ну да. Но все, что мне нужно, это достаточно хорошее отображение. (Конечно, мои струны имеют ограниченную длину - есть даже ограниченное, но огромное количество самих струн.) - person Alexander Gladysh; 31.01.2009

Измерьте расстояние редактирования от пустой строки, но вместо того, чтобы рассматривать каждое редактирование как имеющее значение «1», дайте ему индекс добавляемой / удаляемой буквы в алфавите, отсортированный по частоте использования (etaoinshrdlu ...), и разница между буквенными индексами, если ваш алгоритм позволяет обнаруживать замены как замены, а не как пары вставка + удаление.

person moonshadow    schedule 30.01.2009

Вы также можете попытаться изучить скрытый семантический анализ и модели векторного пространства, с проблемой, заключающейся в том, что вам нужно ограничить максимальную длину строки.

Ваши размеры - это произведение элементов вашего алфавита и позиций в строке. Учитывая алфавит («a», «b», «c», «t») и максимальную длину 3, размеры будут (a: 1, b: 1, c: 1, t: 1, ... , а: 3, б: 3, в: 3, т: 3)

Например, "cat" становится (0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1).

Это, конечно, огромный набор данных, но вы можете использовать методы уменьшения размерности (например, SVD), чтобы сократить количество размеров. Это должно сработать, потому что в словах много повторяющихся шаблонов. Вы можете настроить количество выходных размеров в соответствии со своими потребностями.

Сходство между двумя словами можно вычислить по косинусному сходству между векторами слов. Вы также можете сохранить векторы преобразования SVD, чтобы получить сокращенный вектор для слов, даже ранее невидимых.

person Torsten Marek    schedule 31.01.2009

Чтобы преодолеть проблему «относительного расстояния», все, что вам нужно сделать, это взять фиксированную точку для измерения.

Вы все еще можете использовать расстояние Левенштейна, но взять его из фиксированной строки «Origin». Например, вы можете использовать строку произвольной длины, состоящую из всех пробелов, в качестве исходной строки.

В любом случае, я бы сначала протестировал это с небольшим подмножеством известных строк, чтобы увидеть, отражают ли значения то, что вы ожидаете увидеть.

person Andrew Rollings    schedule 30.01.2009
comment
Это необоснованно, вы почти наверняка получите две очень разные струны, находящиеся на одинаковом расстоянии от фиксированной точки. например если фиксированной точкой является пустая строка, любая пара строк одинаковой длины будет иметь одно и то же значение. - person Daniel Nadasi; 31.01.2009
comment
Хм. Это хороший момент, но это также проблема любого решения из-за уменьшения размерности. Хорошо ... Как насчет наличия второй (и / или третьей) (другой) исходной строки и ее использования ... Проблема в том, что размерность решения сильно уменьшается. - person Andrew Rollings; 31.01.2009
comment
(продолжение) Этого нет простого решения из-за отображения многомерного значения (диапазона строк) в одномерное пространство (числовая линия) - person Andrew Rollings; 31.01.2009

Это ответ на вопрос "с головы до ног".

По сути, это вычисляет расстояние, которое предложение 2 отличается от предложения 1, как декартово расстояние от предложения 1 (предполагается, что оно находится в начале координат), где расстояния являются суммой минимальной разницы Левенштейна между словом в 2 предложениях. Он обладает тем свойством, что 2 равных предложения дают нулевое расстояние.

Если этот подход был опубликован где-то еще, мне об этом не известно.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string str1 = "The cat sat on the mat";
            string str2 = "The quick brown fox jumped over the lazy cow";
            ReportDifference(str1, str1);
            ReportDifference(str2, str2);
            ReportDifference(str1, str2);
            ReportDifference(str2, str1);
        }
        /// <summary>
        /// Quick test andisplay routine
        /// </summary>
        /// <param name="str1">First sentence to test with</param>
        /// <param name="str2">Second sentence to test with</param>
        static void ReportDifference(string str1, string str2)
        {
            Debug.WriteLine(
                String.Format("difference between \"{0}\" and \"{1}\" is {2}", 
                str1, str2, Difference(str1, str2))); 
        }
        /// <summary>
        /// This does the hard work.
        /// Basically, what it does is:
        /// 1) Split the stings into tokens/words
        /// 2) Form a cartesian product of the 2 lists of words. 
        /// 3) Calculate the Levenshtein Distance between each word.
        /// 4) Group on the words from the first sentance
        /// 5) Get the min distance between the word in first sentence and all of the words from the second
        /// 6) Square the distances for each word. 
        ///     (based on the distance betwen 2 points is the sqrt of the sum of the x,y,... axises distances
        ///     what this assumes is the first word is the origin)
        /// 7) take the square root of sum
        /// </summary>
        /// <param name="str1">sentence 1 compare</param>
        /// <param name="str2">sentence 2 compare</param>
        /// <returns>distance calculated</returns>
        static double Difference(string str1, string str2)
        {
            string[] splitters = { " " };

            var a = Math.Sqrt(
                (from x in str1.Split(splitters, StringSplitOptions.RemoveEmptyEntries)
                     from y in str2.Split(splitters, StringSplitOptions.RemoveEmptyEntries)
                     select new {x, y, ld = Distance.LD(x,y)} )
                    .GroupBy(x => x.x)
                    .Select(q => new { q.Key, min_match = q.Min(p => p.ld) })
                    .Sum(s =>  (double)(s.min_match * s.min_match )));
            return a;
        }
    }

    /// <summary>
    /// Lifted from http://www.merriampark.com/ldcsharp.htm
    /// </summary>
    public class Distance
    {

        /// <summary>
        /// Compute Levenshtein distance
        /// </summary>
        /// <param name="s">String 1</param>
        /// <param name="t">String 2</param>
        /// <returns>Distance between the two strings.
        /// The larger the number, the bigger the difference.
        /// </returns>
        public static int LD(string s, string t)
        {
            int n = s.Length; //length of s
            int m = t.Length; //length of t
            int[,] d = new int[n + 1, m + 1]; // matrix
            int cost; // cost
            // Step 1
            if (n == 0) return m;
            if (m == 0) return n;
            // Step 2
            for (int i = 0; i <= n; d[i, 0] = i++) ;
            for (int j = 0; j <= m; d[0, j] = j++) ;
            // Step 3
            for (int i = 1; i <= n; i++)
            {
                //Step 4
                for (int j = 1; j <= m; j++)
                {
                    // Step 5
                    cost = (t.Substring(j - 1, 1) == s.Substring(i - 1, 1) ? 0 : 1);
                    // Step 6
                    d[i, j] = System.Math.Min(System.Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1),
                              d[i - 1, j - 1] + cost);
                }
            }
            // Step 7
            return d[n, m];
        }
    }
}
person Aussie Craig    schedule 31.01.2009