22

ねえ、私はLevenshteinsアルゴリズムを使用して、ソース文字列とターゲット文字列の間の距離を取得しています。

また、0から1までの値を返すメソッドがあります。

/// <summary>
/// Gets the similarity between two strings.
/// All relation scores are in the [0, 1] range, 
/// which means that if the score gets a maximum value (equal to 1) 
/// then the two string are absolutely similar
/// </summary>
/// <param name="string1">The string1.</param>
/// <param name="string2">The string2.</param>
/// <returns></returns>
public static float CalculateSimilarity(String s1, String s2)
{
    if ((s1 == null) || (s2 == null)) return 0.0f;

    float dis = LevenshteinDistance.Compute(s1, s2);
    float maxLen = s1.Length;
    if (maxLen < s2.Length)
        maxLen = s2.Length;
    if (maxLen == 0.0F)
        return 1.0F;
    else return 1.0F - dis / maxLen;
}

しかし、これは私にとって十分ではありません。2つの文を一致させるためのより複雑な方法が必要だからです。

たとえば、いくつかの音楽に自動的にタグを付けたい、オリジナルの曲名を持っている、スーパー、クオリティ、2007、2008などの年などのゴミのある曲があります。また、一部のファイルにはhttp://trashだけがあります。 .thash..song_name_mp3.mp3、その他は正常です。私は今よりも完璧に機能するアルゴリズムを作成したいと思っています。多分誰かが私を助けることができますか?

これが私の現在のアルゴリズムです:

/// <summary>
/// if we need to ignore this target.
/// </summary>
/// <param name="targetString">The target string.</param>
/// <returns></returns>
private bool doIgnore(String targetString)
{
    if ((targetString != null) && (targetString != String.Empty))
    {
        for (int i = 0; i < ignoreWordsList.Length; ++i)
        {
            //* if we found ignore word or target string matching some some special cases like years (Regex).
            if (targetString == ignoreWordsList[i] || (isMatchInSpecialCases(targetString))) return true;
        }
    }

   return false;
}

/// <summary>
/// Removes the duplicates.
/// </summary>
/// <param name="list">The list.</param>
private void removeDuplicates(List<String> list)
{
    if ((list != null) && (list.Count > 0))
    {
        for (int i = 0; i < list.Count - 1; ++i)
        {
            if (list[i] == list[i + 1])
            {
                list.RemoveAt(i);
                --i;
            }
        }
    }
}

/// <summary>
/// Does the fuzzy match.
/// </summary>
/// <param name="targetTitle">The target title.</param>
/// <returns></returns>
private TitleMatchResult doFuzzyMatch(String targetTitle)
{
    TitleMatchResult matchResult = null;

   if (targetTitle != null && targetTitle != String.Empty)
   {
       try
       {
           //* change target title (string) to lower case.
           targetTitle = targetTitle.ToLower();

           //* scores, we will select higher score at the end.
           Dictionary<Title, float> scores = new Dictionary<Title, float>();

           //* do split special chars: '-', ' ', '.', ',', '?', '/', ':', ';', '%', '(', ')', '#', '\"', '\'', '!', '|', '^', '*', '[', ']', '{', '}', '=', '!', '+', '_'
           List<String> targetKeywords = new List<string>(targetTitle.Split(ignoreCharsList, StringSplitOptions.RemoveEmptyEntries));

          //* remove all trash from keywords, like super, quality, etc..
           targetKeywords.RemoveAll(delegate(String x) { return doIgnore(x); });
          //* sort keywords.
          targetKeywords.Sort();
        //* remove some duplicates.
        removeDuplicates(targetKeywords);

        //* go through all original titles.
        foreach (Title sourceTitle in titles)
        {
            float tempScore = 0f;
            //* split orig. title to keywords list.
            List<String> sourceKeywords = new List<string>(sourceTitle.Name.Split(ignoreCharsList, StringSplitOptions.RemoveEmptyEntries));
            sourceKeywords.Sort();
            removeDuplicates(sourceKeywords);

            //* go through all source ttl keywords.
            foreach (String keyw1 in sourceKeywords)
            {
                float max = float.MinValue;
                foreach (String keyw2 in targetKeywords)
                {
                    float currentScore = StringMatching.StringMatching.CalculateSimilarity(keyw1.ToLower(), keyw2);
                    if (currentScore > max)
                    {
                        max = currentScore;
                    }
                }
                tempScore += max;
            }

            //* calculate average score.
            float averageScore = (tempScore / Math.Max(targetKeywords.Count, sourceKeywords.Count)); 

            //* if average score is bigger than minimal score and target title is not in this source title ignore list.
            if (averageScore >= minimalScore && !sourceTitle.doIgnore(targetTitle))
            {
                //* add score.
                scores.Add(sourceTitle, averageScore);
            }
        }

        //* choose biggest score.
        float maxi = float.MinValue;
        foreach (KeyValuePair<Title, float> kvp in scores)
        {
            if (kvp.Value > maxi)
            {
                maxi = kvp.Value;
                matchResult = new TitleMatchResult(maxi, kvp.Key, MatchTechnique.FuzzyLogic);
            }
        }
    }
    catch { }
}
//* return result.
return matchResult;
}

これは正常に機能しますが、場合によっては、一致するはずの多くのタイトルが一致しないことがあります...ウェイトなどを操作するには、何らかの式が必要だと思いますが、1つは考えられません。

アイデア?提案?アルゴス?

ちなみに、私はすでにこのトピックを知っています(私の同僚はすでにそれを投稿していますが、この問題の適切な解決策を見つけることはできません。): 近似文字列マッチングアルゴリズム

4

5 に答える 5

14

ちょっと古いですが、今後の訪問者に役立つかもしれません。レーベンシュタイン アルゴリズムを既に使用していて、もう少し改善する必要がある場合は、このソリューションで非常に効果的なヒューリスティックをいくつか説明します。

最も近い文字列の一致を取得する

重要なのは、フレーズ間の類似性を測る 3 つまたは 4 つ (またはそれ以上) の方法を考え出すことです(レーベンシュタイン距離は 1 つの方法にすぎません)。次に、類似していると一致させたい文字列の実際の例を使用して、重み付けを調整します。肯定的な一致の数を最大化する何かが得られるまで、これらのヒューリスティックの組み合わせ。その後、今後のすべての試合でその式を使用すると、素晴らしい結果が得られるはずです。

ユーザーがプロセスに関与している場合は、ユーザーが最初の選択に同意しない場合に備えて、類似度が高くランク付けされている追加の一致をユーザーが確認できるインターフェイスを提供することも最適です。

リンクされた回答からの抜粋です。このコードをそのまま使用する場合は、VBA を C# に変換する必要があることをあらかじめお詫びします。


シンプルでスピーディーで、非常に便利な指標です。これを使用して、2 つの文字列の類似性を評価するための 2 つの個別のメトリックを作成しました。1 つは「valuePhrase」と呼び、もう 1 つは「valueWords」と呼びます。valuePhrase は 2 つのフレーズ間のレーベンシュタイン距離にすぎません。valueWords は、スペース、ダッシュ、その他任意の区切り文字に基づいて文字列を個々の単語に分割し、各単語を他の単語と比較して、最短の単語を合計します。任意の 2 つの単語を結ぶレーベンシュタイン距離。基本的に、単語単位の順列と同様に、ある「フレーズ」の情報が実際に別のフレーズに含まれているかどうかを測定します。区切り文字に基づいて文字列を分割する最も効率的な方法を考え出すサイド プロジェクトとして数日を費やしました。

valueWords、valuePhrase、および Split 関数:

Public Function valuePhrase#(ByRef S1$, ByRef S2$)
    valuePhrase = LevenshteinDistance(S1, S2)
End Function

Public Function valueWords#(ByRef S1$, ByRef S2$)
    Dim wordsS1$(), wordsS2$()
    wordsS1 = SplitMultiDelims(S1, " _-")
    wordsS2 = SplitMultiDelims(S2, " _-")
    Dim word1%, word2%, thisD#, wordbest#
    Dim wordsTotal#
    For word1 = LBound(wordsS1) To UBound(wordsS1)
        wordbest = Len(S2)
        For word2 = LBound(wordsS2) To UBound(wordsS2)
            thisD = LevenshteinDistance(wordsS1(word1), wordsS2(word2))
            If thisD < wordbest Then wordbest = thisD
            If thisD = 0 Then GoTo foundbest
        Next word2
foundbest:
        wordsTotal = wordsTotal + wordbest
    Next word1
    valueWords = wordsTotal
End Function

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' SplitMultiDelims
' This function splits Text into an array of substrings, each substring
' delimited by any character in DelimChars. Only a single character
' may be a delimiter between two substrings, but DelimChars may
' contain any number of delimiter characters. It returns a single element
' array containing all of text if DelimChars is empty, or a 1 or greater
' element array if the Text is successfully split into substrings.
' If IgnoreConsecutiveDelimiters is true, empty array elements will not occur.
' If Limit greater than 0, the function will only split Text into 'Limit'
' array elements or less. The last element will contain the rest of Text.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function SplitMultiDelims(ByRef Text As String, ByRef DelimChars As String, _
        Optional ByVal IgnoreConsecutiveDelimiters As Boolean = False, _
        Optional ByVal Limit As Long = -1) As String()
    Dim ElemStart As Long, N As Long, M As Long, Elements As Long
    Dim lDelims As Long, lText As Long
    Dim Arr() As String

    lText = Len(Text)
    lDelims = Len(DelimChars)
    If lDelims = 0 Or lText = 0 Or Limit = 1 Then
        ReDim Arr(0 To 0)
        Arr(0) = Text
        SplitMultiDelims = Arr
        Exit Function
    End If
    ReDim Arr(0 To IIf(Limit = -1, lText - 1, Limit))

    Elements = 0: ElemStart = 1
    For N = 1 To lText
        If InStr(DelimChars, Mid(Text, N, 1)) Then
            Arr(Elements) = Mid(Text, ElemStart, N - ElemStart)
            If IgnoreConsecutiveDelimiters Then
                If Len(Arr(Elements)) > 0 Then Elements = Elements + 1
            Else
                Elements = Elements + 1
            End If
            ElemStart = N + 1
            If Elements + 1 = Limit Then Exit For
        End If
    Next N
    'Get the last token terminated by the end of the string into the array
    If ElemStart <= lText Then Arr(Elements) = Mid(Text, ElemStart)
    'Since the end of string counts as the terminating delimiter, if the last character
    'was also a delimiter, we treat the two as consecutive, and so ignore the last elemnent
    If IgnoreConsecutiveDelimiters Then If Len(Arr(Elements)) = 0 Then Elements = Elements - 1

    ReDim Preserve Arr(0 To Elements) 'Chop off unused array elements
    SplitMultiDelims = Arr
End Function

類似度の測定

これら 2 つのメトリクスと、2 つの文字列間の距離を単純に計算する 3 つ目のメトリクスを使用して、最適化アルゴリズムを実行して最大数の一致を達成できる一連の変数を取得します。ファジー文字列マッチング自体はファジー科学であるため、文字列の類似性を測定するための線形に独立したメトリックを作成し、互いに一致させたい既知の文字列セットを用意することで、特定のスタイルのパラメーターを見つけることができます。文字列の場合、最良のあいまい一致結果が得られます。

当初、メトリクスの目標は、完全一致の検索値を低くし、順列が進むメジャーの検索値を増やすことでした。非現実的なケースでは、これは明確に定義された順列のセットを使用して定義するのはかなり簡単で、検索値の結果が必要に応じて増加するように最終的な式を設計しました。

ここに画像の説明を入力

ご覧のとおり、ファジー文字列一致メトリックである最後の 2 つのメトリックには、(斜めに) 一致するはずの文字列に低いスコアを与える自然な傾向が既にあります。これはとても良いです。

アプリケーション あいまい一致の最適化を可能にするために、各メトリックに重みを付けます。そのため、あいまい文字列一致を適用するたびに、パラメーターの重み付けが異なる場合があります。最終的なスコアを定義する式は、メトリックとその重みを単純に組み合わせたものです。

value = Min(phraseWeight*phraseValue, wordsWeight*wordsValue)*minWeight + 
        Max(phraseWeight*phraseValue, wordsWeight*wordsValue)*maxWeight + lengthWeight*lengthValue

最適化アルゴリズム (離散的で多次元の問題であるため、ここではニューラル ネットワークが最適です) を使用して、目標は一致数を最大化することです。この最後のスクリーンショットに見られるように、各セットが互いに正しく一致する数を検出する関数を作成しました。列または行は、一致するはずだった文字列に最低スコアが割り当てられた場合にポイントを獲得し、最低スコアが同点で、正しい一致が同点の一致文字列の中にある場合は、部分的なポイントが与えられます。次に、最適化しました。緑色のセルが現在の行に最も一致する列であり、セルの周りの青い四角形が現在の列に最も一致する行であることがわかります。下隅のスコアは、おおよそ成功した一致の数であり、これが最適化問題に最大化するよう指示するものです。

ここに画像の説明を入力

于 2012-04-09T20:17:26.430 に答える
7

あなたが望むのは、最長の部分文字列の一致かもしれません。つまり、あなたの例では、次のような2つのファイルです

ゴミ..thash..曲名_mp3.mp3とゴミ..スポット..曲名_mp3.mp3

同じように見えてしまいます。

もちろん、そこにはいくつかのヒューリスティックが必要です。あなたが試みるかもしれないことの1つは、弦をsoundexコンバーターに通すことです。Soundex は、物事が同じように「聞こえる」かどうかを確認するために使用される「コーデック」です (電話交換手と同じように)。それは多かれ少なかれ大まかな音声と誤発音の半証明音訳です. 編集距離よりは明らかに劣りますが、はるかに安価です。(正式な用途は名前であり、3 文字のみを使用します。ただし、そこで停止する理由はありません。文字列内のすべての文字のマッピングを使用するだけです。詳細については、wikipediaを参照してください)

したがって、私の提案は、弦をsoundexし、それぞれをいくつかの長さのトランシェ(5、10、20など)に切り刻み、クラスターを確認することです。クラスター内では、編集距離や最大部分文字列など、より高価なものを使用できます。

于 2008-09-10T06:37:49.960 に答える
6

ここでの問題は、ノイズ ワードと有用なデータを区別することかもしれません。

  • Rolling_Stones.Best_of_2003.Wild_Horses.mp3
  • Super.Quality.Wild_Horses.mp3
  • Tori_Amos.Wild_Horses.mp3

無視するノイズ ワードの辞書を作成する必要がある場合があります。それは不格好に思えますが、バンド/アルバム名とノイズを区別できるアルゴリズムがあるかどうかはわかりません。

于 2008-09-10T06:59:10.390 に答える
4

DNA 配列アラインメント (「ローカル シーケンス アラインメント」を検索) に多少関連する問題については、多くの作業が行われています。古典的なアルゴリズムは「Needleman-Wunsch」であり、より複雑な最新のアルゴリズムも簡単に見つけることができます。アイデアは-グレッグの答えに似ています-キーワードを特定して比較する代わりに、長い文字列内で最も長く緩く一致する部分文字列を見つけようとします。

悲しいことに、唯一の目標が音楽の並べ替えである場合、考えられる命名スキームをカバーする多数の正規表現は、おそらくどの汎用アルゴリズムよりもうまく機能するでしょう。

于 2008-09-11T06:44:57.703 に答える