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.
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 Phrase
trong 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 đó SearchVal
heuristic 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ọ.
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.
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ả)
Ứ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.