Bắt chuỗi khớp gần nhất


397

Tôi cần một cách để so sánh nhiều chuỗi với một chuỗi kiểm tra và trả về chuỗi gần giống với chuỗi đó:

TEST STRING: THE BROWN FOX JUMPED OVER THE RED COW

CHOICE A   : THE RED COW JUMPED OVER THE GREEN CHICKEN
CHOICE B   : THE RED COW JUMPED OVER THE RED COW
CHOICE C   : THE RED FOX JUMPED OVER THE BROWN COW

(Nếu tôi đã làm điều này một cách chính xác) Chuỗi gần nhất với "TEST CHUINGI" phải là "CHOICE C". cách dễ nhất để làm điều này là gì?

Tôi dự định thực hiện điều này thành nhiều ngôn ngữ bao gồm VB.net, Lua và JavaScript. Tại thời điểm này, mã giả được chấp nhận. Nếu bạn có thể cung cấp một ví dụ cho một ngôn ngữ cụ thể, điều này cũng được đánh giá cao!


3
Các thuật toán thường làm loại công cụ này hoạt động để xác định có bao nhiêu thay đổi để biến một chuỗi được kiểm tra thành chuỗi đích. Những loại thuật toán đó hoàn toàn không hoạt động trong tình huống như thế này. Tôi nghĩ rằng có được một máy tính để thực hiện điều này sẽ rất khó khăn.
Matt Greer

3
Levenshtein mã nguồn khoảng cách trong nhiều ngôn ngữ: Java, Ruby, Python, PHP, vv en.wikibooks.org/wiki/Algorithm_Implementation/Strings/...
joelparkerhenderson

9
Nói chung, những gì được tính là "chuỗi gần nhất" sẽ phụ thuộc vào thước đo tương tự được sử dụng và các hình phạt được sử dụng để đưa ra các khoảng trống trong căn chỉnh. Ví dụ: bạn có coi "bò" và "gà" giống với "bò" và "đỏ" (vì chúng là các khái niệm liên quan) hoặc là cách khác (vì "gà" có nhiều chữ hơn "bò" )? Nhưng được đưa ra một thước đo tương tự và hình phạt khoảng cách, có thể thấy rằng thuật toán Levenshtein dưới đây được đảm bảo để tìm cho bạn chuỗi gần nhất. Điều tương tự cũng đúng với Needman-Wunsch và Smith-Waterman (bên dưới).
Sten L

Làm nhóm nhân vật, hoặc nhóm từ. Cho nó điểm số.
Casey

Câu trả lời:


952

Tôi đã gặp vấn đề này khoảng một năm trước khi tìm kiếm thông tin người dùng nhập thông tin về một giàn khoan dầu trong cơ sở dữ liệu thông tin linh tinh. Mục tiêu là để thực hiện một số loại tìm kiếm chuỗi mờ có thể xác định mục cơ sở dữ liệu với các yếu tố phổ biến nhất.

Một phần của nghiên cứu liên quan đến việc thực hiện khoảng cách Levenshtein thuật toán , xác định có bao nhiêu thay đổi phải được thực hiện đối với một chuỗi hoặc cụm từ để biến nó thành một chuỗi hoặc cụm từ khác.

Việc triển khai tôi đưa ra tương đối đơn giản và bao gồm một so sánh trọng số về độ dài của hai cụm từ, số lượng thay đổi giữa mỗi cụm từ và liệu mỗi từ có thể được tìm thấy trong mục tiêu hay không.

Bài viết trên một trang web riêng tư vì vậy tôi sẽ cố gắng hết sức để nối các nội dung liên quan ở đây:


Kết hợp chuỗi mờ là quá trình thực hiện ước lượng giống như con người về sự giống nhau của hai từ hoặc cụm từ. Trong nhiều trường hợp, nó liên quan đến việc xác định các từ hoặc cụm từ gần giống nhau nhất. Bài viết này mô tả một giải pháp nội bộ cho vấn đề khớp chuỗi mờ và tính hữu ích của nó trong việc giải quyết nhiều vấn đề khác nhau có thể cho phép chúng tôi tự động hóa các tác vụ đòi hỏi sự tham gia của người dùng trước đây.

Giới thiệu

Sự cần thiết phải thực hiện khớp chuỗi mờ ban đầu xuất hiện trong khi phát triển công cụ Trình xác thực Vịnh Mexico. Những gì tồn tại là một cơ sở dữ liệu của các giàn khoan và giàn khoan dầu mỏ Mexico đã biết, và những người mua bảo hiểm sẽ cung cấp cho chúng tôi một số thông tin sai về tài sản của họ và chúng tôi phải đối chiếu với cơ sở dữ liệu của các nền tảng đã biết. Khi có rất ít thông tin được đưa ra, điều tốt nhất chúng ta có thể làm là dựa vào một người bảo lãnh để "nhận ra" thông tin mà họ đang đề cập và gọi thông tin thích hợp. Đây là nơi giải pháp tự động này có ích.

Tôi đã dành một ngày để nghiên cứu các phương pháp khớp chuỗi mờ, và cuối cùng tình cờ tìm thấy thuật toán khoảng cách Levenshtein rất hữu ích trên Wikipedia.

Thực hiện

Sau khi đọc về lý thuyết đằng sau nó, tôi đã thực hiện và tìm cách tối ưu hóa nó. Đây là cách mã của tôi trông như thế nào trong VBA:

'Calculate the Levenshtein Distance between two strings (the number of insertions,
'deletions, and substitutions needed to transform the first string into the second)
Public Function LevenshteinDistance(ByRef S1 As String, ByVal S2 As String) As Long
    Dim L1 As Long, L2 As Long, D() As Long 'Length of input strings and distance matrix
    Dim i As Long, j As Long, cost As Long 'loop counters and cost of substitution for current letter
    Dim cI As Long, cD As Long, cS As Long 'cost of next Insertion, Deletion and Substitution
    L1 = Len(S1): L2 = Len(S2)
    ReDim D(0 To L1, 0 To L2)
    For i = 0 To L1: D(i, 0) = i: Next i
    For j = 0 To L2: D(0, j) = j: Next j

    For j = 1 To L2
        For i = 1 To L1
            cost = Abs(StrComp(Mid$(S1, i, 1), Mid$(S2, j, 1), vbTextCompare))
            cI = D(i - 1, j) + 1
            cD = D(i, j - 1) + 1
            cS = D(i - 1, j - 1) + cost
            If cI <= cD Then 'Insertion or Substitution
                If cI <= cS Then D(i, j) = cI Else D(i, j) = cS
            Else 'Deletion or Substitution
                If cD <= cS Then D(i, j) = cD Else D(i, j) = cS
            End If
        Next i
    Next j
    LevenshteinDistance = D(L1, L2)
End Function

Đơn giản, nhanh chóng, và một số liệu rất hữu ích. Sử dụng điều này, tôi đã tạo hai số liệu riêng biệt để đánh giá sự giống nhau của hai chuỗi. Một cái tôi gọi là "valuePhrase" và một cái tôi gọi là "valueTHER". valuePhrase chỉ là khoảng cách Levenshtein giữa hai cụm từ và valueTHER chia chuỗi thành các từ riêng lẻ, dựa trên các dấu phân cách như dấu cách, dấu gạch ngang và bất cứ thứ gì bạn thích và so sánh từng từ với nhau, tóm tắt từ ngắn nhất Khoảng cách Levenshtein kết nối bất kỳ hai từ. Về cơ bản, nó đo xem thông tin trong một "cụm từ" có thực sự được chứa trong một cụm từ khác hay không, giống như một hoán vị từ thông thái. Tôi đã dành vài ngày khi một dự án phụ xuất hiện với cách hiệu quả nhất có thể là tách một chuỗi dựa trên các dấu phân cách.

Hàm valueword, valuePhrase và 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

Các biện pháp tương tự

Sử dụng hai số liệu này và một số thứ ba chỉ đơn giản là tính khoảng cách giữa hai chuỗi, tôi có một loạt các biến mà tôi có thể chạy một thuật toán tối ưu hóa để đạt được số lượng khớp lớn nhất. Kết hợp chuỗi mờ là một khoa học mờ và do đó, bằng cách tạo ra các số liệu độc lập tuyến tính để đo độ tương tự của chuỗi và có một chuỗi các chuỗi đã biết mà chúng ta muốn khớp với nhau, chúng ta có thể tìm thấy các tham số cho các kiểu cụ thể của chúng ta chuỗi, cho kết quả phù hợp mờ tốt nhất.

Ban đầu, mục tiêu của số liệu là có giá trị tìm kiếm thấp cho một kết quả khớp chính xác và tăng giá trị tìm kiếm cho các biện pháp ngày càng được thẩm thấu. Trong trường hợp không thực tế, điều này khá dễ xác định bằng cách sử dụng một tập hợp các hoán vị được xác định rõ và thiết kế công thức cuối cùng sao cho chúng có kết quả giá trị tìm kiếm tăng như mong muốn.

Hoán đổi chuỗi mờ

Trong ảnh chụp màn hình ở trên, tôi đã điều chỉnh heuristic của mình để đưa ra một cái gì đó mà tôi cảm thấy được thu nhỏ độc đáo với sự khác biệt nhận thức của tôi giữa thuật ngữ tìm kiếm và kết quả. Các heuristic tôi sử dụng Value Phrasetrong bảng tính ở trên là =valuePhrase(A2,B2)-0.8*ABS(LEN(B2)-LEN(A2)). Tôi đã giảm một cách hiệu quả hình phạt của khoảng cách Levenstein bằng 80% sự khác biệt về độ dài của hai "cụm từ". Bằng cách này, "cụm từ" có cùng độ dài phải chịu hình phạt đầy đủ, nhưng "cụm từ" chứa 'thông tin bổ sung' (dài hơn) nhưng bên cạnh đó vẫn chủ yếu chia sẻ cùng một nhân vật bị giảm hình phạt. Tôi đã sử dụngValue Words hàm như vậy, và sau đó SearchValheuristic cuối cùng của tôi được định nghĩa là=MIN(D2,E2)*0.8+MAX(D2,E2)*0.2- một trung bình có trọng số. Bất cứ điểm nào trong hai điểm thấp hơn đều có trọng số 80% và 20% điểm cao hơn. Đây chỉ là một heuristic phù hợp với trường hợp sử dụng của tôi để có tỷ lệ khớp tốt. Các trọng số này là thứ mà người ta có thể điều chỉnh để có tỷ lệ phù hợp nhất với dữ liệu thử nghiệm của họ.

Chuỗi giá trị phù hợp với cụm từ mờ

Chuỗi giá trị phù hợp

Như bạn có thể thấy, hai số liệu cuối cùng, là số liệu phù hợp với chuỗi mờ, đã có xu hướng tự nhiên để cho điểm thấp cho các chuỗi có nghĩa là khớp (xuống theo đường chéo). Điều này là rất tốt.

Ứng dụng Để cho phép tối ưu hóa kết hợp mờ, tôi cân nhắc từng số liệu. Như vậy, mỗi ứng dụng khớp chuỗi mờ có thể cân các tham số khác nhau. Công thức xác định điểm số cuối cùng là sự kết hợp đơn giản giữa các số liệu và trọng số của chúng:

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

Sử dụng một thuật toán tối ưu hóa (mạng thần kinh là tốt nhất ở đây vì đây là một vấn đề rời rạc, đa chiều), mục tiêu bây giờ là tối đa hóa số lượng trận đấu. Tôi đã tạo một hàm phát hiện số lượng khớp chính xác của từng bộ với nhau, như có thể thấy trong ảnh chụp màn hình cuối cùng này. Một cột hoặc hàng nhận được một điểm nếu điểm thấp nhất được gán cho chuỗi có nghĩa phù hợp và một phần điểm được đưa ra nếu có một ràng buộc cho điểm thấp nhất và kết quả khớp chính xác nằm trong số các chuỗi được khớp. Sau đó tôi đã tối ưu hóa nó. Bạn có thể thấy rằng một ô màu xanh lá cây là cột phù hợp nhất với hàng hiện tại và một hình vuông màu xanh xung quanh ô là hàng phù hợp nhất với cột hiện tại. Điểm ở góc dưới gần bằng số trận đấu thành công và đây là những gì chúng tôi nói với vấn đề tối ưu hóa của chúng tôi để tối đa hóa.

Kết hợp chuỗi mờ được tối ưu hóa số liệu

Thuật toán là một thành công tuyệt vời và các tham số giải pháp nói rất nhiều về loại vấn đề này. Bạn sẽ nhận thấy điểm tối ưu hóa là 44 và điểm cao nhất có thể là 48. 5 cột ở cuối là các bộ giải mã và không có bất kỳ kết quả khớp nào với các giá trị hàng. Càng có nhiều mồi nhử, càng khó tìm ra trận đấu hay nhất.

Trong trường hợp khớp cụ thể này, độ dài của các chuỗi là không liên quan, bởi vì chúng tôi đang mong đợi các chữ viết tắt đại diện cho các từ dài hơn, vì vậy trọng số tối ưu cho độ dài là -0.3, có nghĩa là chúng tôi không xử phạt các chuỗi có độ dài khác nhau. Chúng tôi giảm số điểm trong dự đoán của các chữ viết tắt này, dành nhiều chỗ hơn cho các từ khớp một phần để thay thế các từ không phải từ mà chỉ cần thay thế ít hơn vì chuỗi ngắn hơn.

Trọng số từ là 1,0 trong khi trọng số cụm từ chỉ 0,5, có nghĩa là chúng tôi xử phạt toàn bộ các từ bị thiếu trong một chuỗi và giá trị cao hơn toàn bộ cụm từ còn nguyên vẹn. Điều này rất hữu ích vì rất nhiều chuỗi này có một từ chung (nguy hiểm) trong đó điều thực sự quan trọng là liệu sự kết hợp (vùng và nguy hiểm) có được duy trì hay không.

Cuối cùng, trọng số tối thiểu được tối ưu hóa ở mức 10 và trọng số tối đa là 1. Điều này có nghĩa là nếu điểm tốt nhất trong hai điểm số (cụm từ giá trị và từ giá trị) không tốt, trận đấu sẽ bị phạt rất nhiều, nhưng chúng tôi không 'Rất nhiều hình phạt tồi tệ nhất trong hai điểm số. Về cơ bản, điều này nhấn mạnh vào việc yêu cầu một trong hai các valueWord hoặc valuePhrase để có điểm tốt, nhưng không phải cả hai. Một loại tâm lý "lấy những gì chúng ta có thể nhận được".

Thật sự hấp dẫn những gì giá trị được tối ưu hóa của 5 trọng số này nói về loại khớp chuỗi mờ đang diễn ra. Đối với các trường hợp thực tế hoàn toàn khác nhau về khớp chuỗi mờ, các tham số này rất khác nhau. Tôi đã sử dụng nó cho 3 ứng dụng riêng biệt cho đến nay.

Mặc dù không được sử dụng trong tối ưu hóa cuối cùng, một bảng điểm chuẩn được thiết lập khớp với các cột cho tất cả các kết quả hoàn hảo theo đường chéo và cho phép người dùng thay đổi các tham số để kiểm soát tốc độ điểm phân kỳ từ 0 và lưu ý sự tương đồng bẩm sinh giữa các cụm từ tìm kiếm ( trong lý thuyết có thể được sử dụng để bù đắp kết quả dương tính giả trong kết quả)

Điểm chuẩn kết hợp chuỗi mờ

Ứng dụng khác

Giải pháp này có tiềm năng được sử dụng ở bất cứ nơi nào mà người dùng muốn có một hệ thống máy tính xác định một chuỗi trong một chuỗi các chuỗi không có kết hợp hoàn hảo. (Giống như một vlookup phù hợp gần đúng cho chuỗi).


Vì vậy, những gì bạn nên lấy từ điều này, là bạn có thể muốn sử dụng kết hợp các heuristic cấp cao (tìm từ từ một cụm từ trong cụm từ khác, độ dài của cả hai cụm từ, v.v.) cùng với việc thực hiện thuật toán khoảng cách Levenshtein. Bởi vì quyết định trận đấu "tốt nhất" là quyết định heuristic (mờ) - bạn sẽ phải đưa ra một tập hợp trọng số cho bất kỳ số liệu nào bạn đưa ra để xác định độ tương tự.

Với bộ heuristic và trọng lượng phù hợp, bạn sẽ có chương trình so sánh nhanh chóng đưa ra quyết định mà bạn sẽ đưa ra.


13
Bonus: Nếu ai muốn bao gồm số liệu bổ sung vào Heuristic trọng của họ, (kể từ khi tôi chỉ cung cấp 3 mà không được tất cả những gì tuyến tính độc lập) - đây là danh sách toàn bộ trên wikipedia: en.wikipedia.org/wiki/String_metric
Alain

1
Nếu S2 có nhiều từ (và tạo ra nhiều đối tượng nhỏ thì không quá chậm trong ngôn ngữ bạn chọn), bộ ba có thể tăng tốc mọi thứ. Khoảng cách Levenshtein nhanh và dễ dàng khi sử dụng Trie là một bài viết tuyệt vời về các thử.
JanX2

1
@ Alain Đây là một cách tiếp cận thú vị! Tôi chỉ chơi một chút với ý tưởng của bạn (trong C ++) nhưng không hiểu một điểm, giá trị của valuePhrase. Nếu tôi thấy đúng trong mã của bạn, thì giá trị trả về của hàm khoảng cách Levenshtein. Tại sao nó là một giá trị kép / float trong bảng tìm kiếm 'abcd efgh'? Khoảng cách Levenshtein là một giá trị nguyên và tôi không thể thấy các phép tính tiếp theo trong mã của bạn làm cho nó nổi. Tôi nhớ cái gì
Andreas W. Wylach

1
@ AndreasW.Wylach Quan sát tuyệt vời. VBA tôi chỉ ra là để tính khoảng cách Levenshtein, nhưng heuristic tôi sử dụng trong bảng tính của mình là =valuePhrase(A2,B2)-0.8*ABS(LEN(B2)-LEN(A2))Vì vậy, tôi đã giảm hình phạt của khoảng cách levenstein bằng 80% sự khác biệt về độ dài của hai "cụm từ". Bằng cách này, "cụm từ" có cùng độ dài phải chịu hình phạt đầy đủ, nhưng "cụm từ" chứa 'thông tin bổ sung' (dài hơn) nhưng bên cạnh đó vẫn chủ yếu chia sẻ cùng một nhân vật bị giảm hình phạt.
Alain

1
@Alain Cảm ơn bạn đã quay trở lại câu hỏi của tôi, tôi đánh giá cao điều đó. Giải thích của bạn làm cho mọi thứ rõ ràng hơn bây giờ. Trong khi đó, tôi đã triển khai một phương thức value_phrase để phân tích sâu hơn một chút về phân tích mã thông báo của cụm từ một chút, đó là thứ tự / vị trí của mã thông báo cụm từ, chuỗi mã thông báo không truy vấn và nó chấp nhận một chút mờ nhạt hơn khi nói đến một thứ gì đó như "acbd" so với "abcd". Xu hướng của cụm từ cụm từ_value bằng với bạn, nhưng nhận được một chút thấp hơn ở đây và ở đó. Một lần nữa, tập luyện tuyệt vời và nó đã cho tôi cảm hứng cho thuật toán tìm kiếm mờ!
Andreas W. Wylach

88

Vấn đề này xuất hiện tất cả các thời gian trong tin sinh học. Câu trả lời được chấp nhận ở trên (rất tuyệt vời) được biết đến trong tin sinh học là Needman-Wunsch (so sánh hai chuỗi) và Smith-Waterman (tìm một chuỗi con gần đúng trong một chuỗi dài hơn). Họ làm việc tuyệt vời và đã làm việc trong nhiều thập kỷ.

Nhưng nếu bạn có một triệu chuỗi để so sánh thì sao? Đó là một so sánh nghìn tỷ cặp, mỗi so sánh là O (n * m)! Các trình tự DNA hiện đại dễ dàng tạo ra một tỷ chuỗi DNA ngắn, mỗi chuỗi dài khoảng 200 chữ cái DNA. Thông thường, chúng tôi muốn tìm, cho mỗi chuỗi như vậy, phù hợp nhất với bộ gen của con người (3 tỷ chữ cái). Rõ ràng, thuật toán Needman-Wunsch và người thân của nó sẽ không làm được.

Cái gọi là "vấn đề căn chỉnh" này là một lĩnh vực nghiên cứu tích cực. Các thuật toán phổ biến nhất hiện tại có thể tìm thấy các kết quả không chính xác giữa 1 tỷ chuỗi ngắn và bộ gen của con người trong vài giờ trên phần cứng hợp lý (giả sử, tám lõi và RAM 32 GB).

Hầu hết các thuật toán này hoạt động bằng cách nhanh chóng tìm các kết quả khớp chính xác ngắn (hạt giống) và sau đó mở rộng chúng thành chuỗi đầy đủ bằng thuật toán chậm hơn (ví dụ: Smith-Waterman). Lý do điều này hoạt động là vì chúng tôi thực sự chỉ quan tâm đến một vài trận đấu gần, vì vậy nó được đền đáp để loại bỏ 99,9 ...% các cặp không có gì chung.

Làm thế nào để tìm trận đấu chính xác giúp tìm trận đấu không chính xác ? Vâng, giả sử chúng tôi chỉ cho phép một sự khác biệt duy nhất giữa truy vấn và mục tiêu. Dễ dàng thấy rằng sự khác biệt này phải xảy ra ở nửa bên phải hoặc bên trái của truy vấn và do đó, nửa còn lại phải khớp chính xác. Ý tưởng này có thể được mở rộng thành nhiều sự không phù hợp và là cơ sở cho ELAND thuật toán thường được sử dụng với các trình tự sắp xếp DNA Illumina.

Có nhiều thuật toán rất tốt để thực hiện khớp chuỗi chính xác. Đưa ra chuỗi truy vấn có độ dài 200 và chuỗi mục tiêu có độ dài 3 tỷ (bộ gen người), chúng tôi muốn tìm bất kỳ vị trí nào trong mục tiêu có chuỗi con có độ dài k khớp chính xác với chuỗi con của truy vấn. Một cách tiếp cận đơn giản là bắt đầu bằng cách lập chỉ mục cho mục tiêu: lấy tất cả các chuỗi con dài k, đặt chúng vào một mảng và sắp xếp chúng. Sau đó lấy từng chuỗi con dài k của truy vấn và tìm kiếm chỉ mục được sắp xếp. Sắp xếp và tìm kiếm có thể được thực hiện trong thời gian O (log n).

Nhưng lưu trữ có thể là một vấn đề. Một chỉ số của mục tiêu 3 tỷ chữ cái sẽ cần giữ 3 tỷ con trỏ và 3 tỷ chữ dài. Có vẻ khó có thể phù hợp với điều này trong ít hơn vài chục gigabyte RAM. Nhưng thật đáng kinh ngạc, chúng ta có thể nén chỉ số rất nhiều, sử dụng biến đổi Burrows-Wheeler và nó vẫn có thể truy vấn hiệu quả. Một chỉ số của bộ gen người có thể phù hợp với RAM dưới 4 GB. Ý tưởng này là cơ sở của các trình sắp xếp trình tự phổ biến như BowtieBWA .

Ngoài ra, chúng ta có thể sử dụng một mảng hậu tố , chỉ lưu trữ các con trỏ, nhưng đại diện cho một chỉ mục đồng thời của tất cả các hậu tố trong chuỗi đích (về cơ bản, một chỉ mục đồng thời cho tất cả các giá trị có thể của k; điều tương tự cũng đúng với biến đổi Burrows-Wheeler ). Một chỉ số mảng hậu tố của bộ gen người sẽ mất 12 GB RAM nếu chúng ta sử dụng con trỏ 32 bit.

Các liên kết ở trên chứa rất nhiều thông tin và liên kết đến các tài liệu nghiên cứu chính. Liên kết ELAND đi đến một tệp PDF với các số liệu hữu ích minh họa các khái niệm liên quan và cho thấy cách xử lý các thao tác chèn và xóa.

Cuối cùng, trong khi các thuật toán này về cơ bản đã giải quyết được vấn đề (sắp xếp lại) bộ gen người đơn lẻ (một tỷ chuỗi ngắn), công nghệ giải trình tự DNA cải thiện nhanh hơn cả định luật Moore và chúng tôi đang tiếp cận nhanh chóng các bộ dữ liệu nghìn tỷ chữ cái. Ví dụ, hiện đang có các dự án đang được tiến hành để sắp xếp bộ gen của 10.000 loài động vật có xương sống , mỗi loài dài một tỷ chữ cái. Đương nhiên, chúng tôi sẽ muốn thực hiện khớp chuỗi không chính xác theo cặp trên dữ liệu ...


3
Thực sự tốt chạy xuống. Một vài sửa chữa: Sắp xếp các phần tử mất ít nhất O (n), không phải O (log n). Và vì thực tế tìm kiếm O (log n) quá chậm, bạn thường xây dựng một bảng bổ sung để có được tra cứu O (1) (chỉ số q-gram). Hơn nữa, tôi không chắc tại sao bạn lại đối xử khác với mảng hậu tố - đó chỉ là tối ưu hóa cái sau, không (sắp xếp các phần tử có độ dài cố định thay vì hậu tố vì chúng ta thực sự không cần nhiều hơn độ dài cố định).
Konrad Rudolph

1
Hơn nữa, các thuật toán này vẫn không thực tế để giải trình tự de novo . Họ đã giải quyết trình tự bộ gen của con người chỉ trong chừng mực khi chúng ta có một chuỗi tham chiếu có thể được sử dụng để lập bản đồ. Nhưng đối với lắp ráp de novo, cần có các thuật toán khác (vâng, có một số căn chỉnh dựa trên ánh xạ nhưng ghép các đường viền lại với nhau là một vấn đề hoàn toàn). Cuối cùng, phích cắm không biết xấu hổ: luận án cử nhân của tôi chứa một mô tả chi tiết về thuật toán ELAND.
Konrad Rudolph

1
Cảm ơn. Tôi đã sửa lỗi. Lý do tôi bắt đầu bằng cách mô tả mảng có độ dài cố định là vì nó dễ hiểu. Mảng Suffix và BWT khó nắm bắt hơn một chút, nhưng thực tế đôi khi chúng ta muốn sử dụng một chỉ mục với các giá trị k khác nhau. Ví dụ, STAR sử dụng mảng hậu tố để tìm hiệu quả sắp xếp ghép . Điều này tất nhiên hữu ích cho việc sắp xếp RNA vào bộ gen.
Sten L

30

Tôi tranh luận rằng lựa chọn B gần với chuỗi thử nghiệm hơn, vì nó chỉ có 4 ký tự (và 2 lần xóa) khỏi chuỗi gốc. Trong khi đó bạn thấy C gần hơn vì nó bao gồm cả màu nâu và đỏ. Tuy nhiên, nó sẽ có một khoảng cách chỉnh sửa lớn hơn.

Có một thuật toán gọi là Khoảng cách Levenshtein để đo khoảng cách chỉnh sửa giữa hai đầu vào.

Đây là một công cụ cho thuật toán đó.

  1. Tỷ lệ lựa chọn A là khoảng cách 15.
  2. Tỷ lệ lựa chọn B là khoảng cách 6.
  3. Tỷ lệ lựa chọn C là khoảng cách 9.

EDIT: Xin lỗi, tôi tiếp tục trộn các chuỗi trong công cụ levenshtein. Cập nhật để trả lời đúng.


2
Ok, tôi đoán đó là sự thật. Tôi sẽ xem xét điều này. Cá nhân tôi không quan tâm cách đóng nó là mục tiêu miễn là nó là khá dang gần. Không cần sự hoàn hảo;) Điểm cho bạn cho đến khi tôi có thể xác minh kết quả câu trả lời của bạn :)
Freesnöw

18

Lua thực hiện, cho hậu thế:

function levenshtein_distance(str1, str2)
    local len1, len2 = #str1, #str2
    local char1, char2, distance = {}, {}, {}
    str1:gsub('.', function (c) table.insert(char1, c) end)
    str2:gsub('.', function (c) table.insert(char2, c) end)
    for i = 0, len1 do distance[i] = {} end
    for i = 0, len1 do distance[i][0] = i end
    for i = 0, len2 do distance[0][i] = i end
    for i = 1, len1 do
        for j = 1, len2 do
            distance[i][j] = math.min(
                distance[i-1][j  ] + 1,
                distance[i  ][j-1] + 1,
                distance[i-1][j-1] + (char1[i] == char2[j] and 0 or 1)
                )
        end
    end
    return distance[len1][len2]
end

14

Bạn có thể quan tâm đến bài viết trên blog này.

http://seatgeek.com/blog/dev/fuzzywuzzy-fuzzy-opes-matching-in-python

Fuzzywuzzy là một thư viện Python cung cấp các thước đo khoảng cách dễ dàng như khoảng cách Levenshtein để khớp chuỗi. Nó được xây dựng trên đỉnh difflib trong thư viện chuẩn và sẽ sử dụng triển khai C Python-levenshtein nếu có.

http://pypi.python.org/pypi/python-Levenshtein/


Đối với những người khác đọc điều này, Fuzzywuzzy thực sự thực hiện rất nhiều ý tưởng trong bài viết tuyệt vời của Alain. Nếu bạn thực sự muốn sử dụng một số ý tưởng đó thì đó là một nơi tuyệt vời để bắt đầu.
Gregory Arenius

12

Bạn có thể thấy thư viện này hữu ích! http://code.google.com.vn/p/google-diff-match-patch/

Nó hiện có sẵn trong Java, JavaScript, Dart, C ++, C #, Objective C, Lua và Python

Nó cũng hoạt động khá tốt. Tôi sử dụng nó trong một vài dự án Lua của tôi.

Và tôi không nghĩ sẽ quá khó để chuyển nó sang các ngôn ngữ khác!


2

Nếu bạn đang làm điều này trong ngữ cảnh của một công cụ tìm kiếm hoặc đối diện với cơ sở dữ liệu, bạn có thể cân nhắc sử dụng một công cụ như Apache Solr , với plugin ComplexPhraseQueryParser . Sự kết hợp này cho phép bạn tìm kiếm theo một chỉ số các chuỗi với kết quả được sắp xếp theo mức độ liên quan, như được xác định bởi khoảng cách Levenshtein.

Chúng tôi đã sử dụng nó để chống lại một bộ sưu tập lớn các nghệ sĩ và tên bài hát khi truy vấn đến có thể có một hoặc nhiều lỗi chính tả và nó hoạt động khá tốt (và nhanh chóng đáng kể khi xem xét các bộ sưu tập nằm trong hàng triệu chuỗi).

Ngoài ra, với Solr, bạn có thể tìm kiếm theo chỉ mục theo yêu cầu thông qua JSON, do đó bạn sẽ không phải phát minh lại giải pháp giữa các ngôn ngữ khác nhau mà bạn đang xem.


1

Một tài nguyên rất, rất tốt cho các loại thuật toán này là Simmetrics: http://sourceforge.net/projects/simmetrics/

Thật không may, trang web tuyệt vời chứa rất nhiều tài liệu đã biến mất :( Trong trường hợp nó quay trở lại, địa chỉ trước đó là: http://www.dcs.shef.ac.uk/~sam/simmetrics.html

Voila (lịch sự của "Wayback Machine"): http://web.archive.org/web/20081230184321/http://www.dcs.shef.ac.uk/~sam/simmetrics.html

Bạn có thể nghiên cứu nguồn mã, có hàng tá thuật toán cho các loại so sánh này, mỗi loại có một sự đánh đổi khác nhau. Việc triển khai là trong Java.


1

Để truy vấn một bộ văn bản lớn theo cách hiệu quả, bạn có thể sử dụng khái niệm Chỉnh sửa khoảng cách / Tiền tố Chỉnh sửa khoảng cách.

Chỉnh sửa Khoảng cách ED (x, y): số lượng transfrom tối thiểu để có được từ hạn x đến hạn y

Nhưng tính toán ED giữa mỗi thuật ngữ và văn bản truy vấn là tài nguyên và thời gian. Do đó, thay vì tính ED cho mỗi thuật ngữ trước tiên, chúng tôi có thể trích xuất các thuật ngữ phù hợp có thể bằng cách sử dụng một kỹ thuật có tên là Qgram Index. và sau đó áp dụng tính toán ED cho các thuật ngữ đã chọn.

Một lợi thế của kỹ thuật lập chỉ mục Qgram là nó hỗ trợ cho Tìm kiếm mờ.

Một cách tiếp cận khả thi để điều chỉnh chỉ số QGram là xây dựng Chỉ mục đảo ngược bằng cách sử dụng Qgram. Trong đó, chúng tôi lưu trữ tất cả các từ bao gồm Qgram cụ thể, bên dưới Qgram đó (Thay vì lưu trữ chuỗi đầy đủ, bạn có thể sử dụng ID duy nhất cho mỗi chuỗi). Bạn có thể sử dụng cấu trúc dữ liệu Tree Map trong Java cho việc này. Sau đây là một ví dụ nhỏ về việc lưu trữ các điều khoản

col: col mbia, col ombo, gan col a, ta col ama

Sau đó, khi truy vấn, chúng tôi tính toán số lượng Qgram phổ biến giữa văn bản truy vấn và các thuật ngữ có sẵn.

Example: x = HILLARY, y = HILARI(query term)
Qgrams
$$HILLARY$$ -> $$H, $HI, HIL, ILL, LLA, LAR, ARY, RY$, Y$$
$$HILARI$$ -> $$H, $HI, HIL, ILA, LAR, ARI, RI$, I$$
number of q-grams in common = 4

số lượng q-gram chung = 4.

Đối với các thuật ngữ có số lượng Qgram phổ biến cao, chúng tôi tính ED / PED theo thuật ngữ truy vấn và sau đó đề xuất thuật ngữ cho người dùng cuối.

bạn có thể tìm thấy việc triển khai lý thuyết này trong dự án sau (Xem "QGramIndex.java"). Hãy hỏi bất kỳ câu hỏi. https://github.com/Bhashitha-Gamage/City_Search

Để nghiên cứu thêm về Chỉnh sửa khoảng cách, tiền tố Chỉnh sửa chỉ số Qgram khoảng cách, vui lòng xem video sau đây của Giáo sư Tiến sĩ Hannah Bast https://www.youtube.com/embed/6pUg2wmGJRo (Bài học bắt đầu từ 20:06)


1

Vấn đề khó thực hiện nếu dữ liệu đầu vào quá lớn (giả sử hàng triệu chuỗi). Tôi đã sử dụng tìm kiếm đàn hồi để giải quyết điều này.

Bắt đầu nhanh: https://www.elastic.co/guide/en/elSTERearch/client/net-api/6.x/elaticsearch-net.html

Chỉ cần chèn tất cả dữ liệu đầu vào vào DB và bạn có thể tìm kiếm bất kỳ chuỗi nào dựa trên bất kỳ khoảng cách chỉnh sửa nào một cách nhanh chóng. Dưới đây là đoạn mã C # sẽ cung cấp cho bạn danh sách kết quả được sắp xếp theo khoảng cách chỉnh sửa (nhỏ hơn đến cao hơn)

var res = client.Search<ClassName>(s => s
    .Query(q => q
    .Match(m => m
        .Field(f => f.VariableName)
        .Query("SAMPLE QUERY")
        .Fuzziness(Fuzziness.EditDistance(5))
    )
));

Bạn đang sử dụng thư viện nào? Một số thông tin cần thiết cho việc này là hữu ích.
cược vào

0

Ở đây bạn có thể có một POC golang để tính khoảng cách giữa các từ đã cho. Bạn có thể điều chỉnh minDistancedifferencecho các phạm vi khác.

Sân chơi: https://play.golang.org/p/NtrBzLdC3rE

package main

import (
    "errors"
    "fmt"
    "log"
    "math"
    "strings"
)

var data string = `THE RED COW JUMPED OVER THE GREEN CHICKEN-THE RED COW JUMPED OVER THE RED COW-THE RED FOX JUMPED OVER THE BROWN COW`

const minDistance float64 = 2
const difference float64 = 1

type word struct {
    data    string
    letters map[rune]int
}

type words struct {
    words []word
}

// Print prettify the data present in word
func (w word) Print() {
    var (
        lenght int
        c      int
        i      int
        key    rune
    )
    fmt.Printf("Data: %s\n", w.data)
    lenght = len(w.letters) - 1
    c = 0
    for key, i = range w.letters {
        fmt.Printf("%s:%d", string(key), i)
        if c != lenght {
            fmt.Printf(" | ")
        }
        c++
    }
    fmt.Printf("\n")
}

func (ws words) fuzzySearch(data string) ([]word, error) {
    var (
        w      word
        err    error
        founds []word
    )
    w, err = initWord(data)
    if err != nil {
        log.Printf("Errors: %s\n", err.Error())
        return nil, err
    }
    // Iterating all the words
    for i := range ws.words {
        letters := ws.words[i].letters
        //
        var similar float64 = 0
        // Iterating the letters of the input data
        for key := range w.letters {
            if val, ok := letters[key]; ok {
                if math.Abs(float64(val-w.letters[key])) <= minDistance {
                    similar += float64(val)
                }
            }
        }

        lenSimilarity := math.Abs(similar - float64(len(data)-strings.Count(data, " ")))
        log.Printf("Comparing %s with %s i've found %f similar letter, with weight %f", data, ws.words[i].data, similar, lenSimilarity)
        if lenSimilarity <= difference {
            founds = append(founds, ws.words[i])
        }
    }

    if len(founds) == 0 {
        return nil, errors.New("no similar found for data: " + data)
    }

    return founds, nil
}

func initWords(data []string) []word {
    var (
        err   error
        words []word
        word  word
    )
    for i := range data {
        word, err = initWord(data[i])
        if err != nil {
            log.Printf("Error in index [%d] for data: %s", i, data[i])
        } else {
            words = append(words, word)
        }
    }
    return words

}

func initWord(data string) (word, error) {
    var word word

    word.data = data
    word.letters = make(map[rune]int)
    for _, r := range data {
        if r != 32 { // avoid to save the whitespace
            word.letters[r]++
        }

    }
    return word, nil
}
func main() {
    var ws words
    words := initWords(strings.Split(data, "-"))
    for i := range words {
        words[i].Print()
    }
    ws.words = words

    solution, _ := ws.fuzzySearch("THE BROWN FOX JUMPED OVER THE RED COW")
    fmt.Println("Possible solutions: ", solution)

}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.