ฉันถูกนำเสนอด้วยปัญหานี้เมื่อประมาณหนึ่งปีก่อนเมื่อผู้ใช้ค้นหาข้อมูลที่ป้อนเกี่ยวกับแท่นขุดเจาะน้ำมันในฐานข้อมูลข้อมูลเบ็ดเตล็ด เป้าหมายคือทำการค้นหาสตริงแบบคลุมเครือซึ่งสามารถระบุรายการฐานข้อมูลด้วยองค์ประกอบที่พบบ่อยที่สุด
ส่วนหนึ่งของงานวิจัยที่เกี่ยวข้องกับการใช้อัลกอริทึมLevenshtein distanceซึ่งกำหนดจำนวนการเปลี่ยนแปลงที่ต้องทำกับสตริงหรือวลีเพื่อเปลี่ยนเป็นสตริงหรือวลีอื่น
การนำมาใช้นั้นค่อนข้างง่ายและเกี่ยวข้องกับการเปรียบเทียบความยาวของสองวลีจำนวนการเปลี่ยนแปลงระหว่างแต่ละวลีและพบว่าแต่ละคำสามารถพบได้ในรายการเป้าหมายหรือไม่
บทความนี้อยู่ในเว็บไซต์ส่วนตัวดังนั้นฉันจะพยายามอย่างดีที่สุดเพื่อเพิ่มเนื้อหาที่เกี่ยวข้องที่นี่:
การจับคู่สตริงฟัซซีเป็นกระบวนการของการประมาณค่าความเหมือนของมนุษย์ของความคล้ายคลึงกันของสองคำหรือวลี ในหลายกรณีมันเกี่ยวข้องกับการระบุคำหรือวลีที่คล้ายกันมากที่สุด บทความนี้จะอธิบายวิธีการแก้ปัญหาภายในของปัญหาการจับคู่สตริงฟัซซีและประโยชน์ในการแก้ปัญหาที่หลากหลายซึ่งสามารถช่วยให้เราทำงานอัตโนมัติซึ่งต้องมีส่วนร่วมของผู้ใช้ที่น่าเบื่อ
บทนำ
ความจำเป็นในการจับคู่สตริงฟัซซี่นั้นเกิดขึ้นในขณะที่พัฒนาเครื่องมือตรวจสอบอ่าวเม็กซิโก สิ่งที่มีอยู่คือฐานข้อมูลอ่าวที่รู้จักของแท่นขุดเจาะน้ำมันและแท่นขุดน้ำมันของเม็กซิโกและผู้ซื้อประกันจะให้ข้อมูลที่ไม่ดีเกี่ยวกับทรัพย์สินของพวกเขาและเราต้องจับคู่กับฐานข้อมูลของแพลตฟอร์มที่รู้จัก เมื่อมีข้อมูลที่ได้รับน้อยมากสิ่งที่ดีที่สุดที่เราทำได้คือการใช้ผู้จัดจำหน่ายเพื่อ "จดจำ" สิ่งที่พวกเขาอ้างถึงและเรียกข้อมูลที่เหมาะสม นี่คือที่โซลูชันอัตโนมัตินี้มีประโยชน์
ฉันใช้เวลาหนึ่งวันในการค้นคว้าวิธีของการจับคู่สตริงฟัซซีและในที่สุดก็สะดุดกับอัลกอริทึมระยะทางที่มีประโยชน์มากของ Levenshtein ใน Wikipedia
การดำเนินงาน
หลังจากอ่านเกี่ยวกับทฤษฎีที่อยู่เบื้องหลังฉันได้นำไปใช้และพบวิธีเพิ่มประสิทธิภาพ นี่คือลักษณะรหัสของฉันใน 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
ง่ายรวดเร็วและมีประโยชน์มาก เมื่อใช้สิ่งนี้ฉันได้สร้างการวัดสองชุดแยกกันเพื่อประเมินความคล้ายคลึงกันของสองสตริง หนึ่งฉันเรียกว่า "valuePhrase" และอีกหนึ่งฉันเรียกว่า "valueWords" valuePhrase เป็นเพียงระยะทาง Levenshtein ระหว่างสองวลีและ valueWords แยกสตริงออกเป็นคำแต่ละคำตามตัวคั่นเช่นช่องว่างขีดกลางและสิ่งอื่น ๆ ที่คุณต้องการและเปรียบเทียบแต่ละคำกับคำอื่น ๆ Levenshtein ระยะทางเชื่อมต่อสองคำใด ๆ โดยพื้นฐานแล้วมันจะวัดว่าข้อมูลใน 'วลี' หนึ่งมีอยู่จริงในอีกประโยคหนึ่งหรือไม่เช่นเดียวกับการเรียงสับเปลี่ยนคำ ฉันใช้เวลาสองสามวันในการโปรเจ็กต์ด้านข้างด้วยวิธีที่มีประสิทธิภาพที่สุดในการแบ่งสตริงตามตัวคั่น
ฟังก์ชัน 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
มาตรการความคล้ายคลึงกัน
การใช้สองเมทริกเหล่านี้และอันที่สามซึ่งคำนวณระยะห่างระหว่างสองสตริงฉันมีชุดของตัวแปรที่ฉันสามารถรันอัลกอริธึมการปรับให้เหมาะสมเพื่อให้ได้จำนวนแมตช์ที่มากที่สุด การจับคู่สตริงฟัซซี่คือตัวเองวิทยาศาสตร์ฟัซซี่และโดยการสร้างตัวชี้วัดอิสระเชิงเส้นสำหรับการวัดความคล้ายคลึงกันของสตริงและมีชุดของสตริงที่รู้จักที่เราต้องการที่จะจับคู่กันเราสามารถหาพารามิเตอร์ที่สำหรับลักษณะเฉพาะของเรา สตริงให้ผลลัพธ์การแข่งขันฟัซซี่ที่ดีที่สุด
เริ่มแรกเป้าหมายของการวัดคือการมีค่าการค้นหาต่ำสำหรับการจับคู่ที่ตรงกันและการเพิ่มมูลค่าการค้นหาสำหรับการวัดที่อนุญาตมากขึ้น ในกรณีที่ไม่สามารถปฏิบัติได้สิ่งนี้ค่อนข้างง่ายต่อการกำหนดโดยใช้ชุดการเรียงสับเปลี่ยนที่กำหนดไว้อย่างดีและกำหนดสูตรขั้นสุดท้ายเพื่อให้ได้ผลลัพธ์การค้นหาที่เพิ่มขึ้นตามที่ต้องการ
ในภาพหน้าจอด้านบนฉันปรับแต่งฮิวริสติกของฉันเพื่อหาบางสิ่งที่ฉันรู้สึกว่าได้รับความแตกต่างอย่างมากจากความแตกต่างระหว่างคำค้นหาและผลลัพธ์ ผมแก้ปัญหาที่ใช้สำหรับในสเปรดชีตดังกล่าวข้างต้นเป็นValue Phrase
=valuePhrase(A2,B2)-0.8*ABS(LEN(B2)-LEN(A2))
ฉันลดระยะทางของเลเวนสไตน์ลง 80% ของความแตกต่างของความยาวของ "วลี" ทั้งสอง ด้วยวิธีนี้ "วลี" ที่มีความยาวเท่ากันจะได้รับโทษเต็มรูปแบบ แต่ "วลี" ซึ่งมี 'ข้อมูลเพิ่มเติม' (นานกว่า) แต่นอกเหนือจากนั้นยังคงมีตัวละครเดียวกันที่ได้รับโทษน้อยลง ฉันใช้Value Words
ฟังก์ชั่นตามที่เป็นอยู่จากนั้นSearchVal
ฮิวริสติกสุดท้ายของฉันก็ถูกกำหนดเป็น=MIN(D2,E2)*0.8+MAX(D2,E2)*0.2
- ค่าเฉลี่ยถ่วงน้ำหนัก คะแนนใดก็ตามของทั้งสองที่ต่ำกว่านั้นมีน้ำหนัก 80% และ 20% ของคะแนนที่สูงกว่า นี่เป็นเพียงฮิวริสติกที่เหมาะกับกรณีการใช้งานของฉันเพื่อให้ได้อัตราการจับคู่ที่ดี น้ำหนักเหล่านี้เป็นสิ่งที่เราสามารถปรับแต่งเพื่อให้ได้อัตราการจับคู่ที่ดีที่สุดกับข้อมูลการทดสอบ
อย่างที่คุณเห็นตัวชี้วัดสองตัวสุดท้ายซึ่งเป็นตัวชี้วัดการจับคู่สตริงแบบคลุมเครือนั้นมีแนวโน้มที่จะให้คะแนนต่ำกับสตริงที่มีความหมายในการจับคู่ (ตามแนวทแยงมุม) อยู่แล้ว นี่เป็นสิ่งที่ดีมาก
แอปพลิเคชัน
เพื่อให้การเพิ่มประสิทธิภาพของการจับคู่แบบฟัซซีฉันให้น้ำหนักแต่ละเมตริก ดังนั้นแอปพลิเคชันของการจับคู่สตริงฟัซซี่ทุกตัวสามารถให้น้ำหนักพารามิเตอร์ต่างกัน สูตรที่กำหนดคะแนนสุดท้ายคือการผสมผสานระหว่างตัวชี้วัดและน้ำหนักของพวกเขา:
value = Min(phraseWeight*phraseValue, wordsWeight*wordsValue)*minWeight
+ Max(phraseWeight*phraseValue, wordsWeight*wordsValue)*maxWeight
+ lengthWeight*lengthValue
การใช้อัลกอริธึมการปรับให้เหมาะสม (เครือข่ายประสาทดีที่สุดที่นี่เพราะมันเป็นปัญหาที่แยกกันและหลายมิติ) ตอนนี้เป้าหมายคือการเพิ่มจำนวนการแข่งขันให้ได้มากที่สุด ฉันสร้างฟังก์ชันที่ตรวจจับจำนวนการจับคู่ที่ถูกต้องของแต่ละชุดซึ่งกันและกันซึ่งสามารถเห็นได้ในสกรีนช็อตสุดท้ายนี้ คอลัมน์หรือแถวจะได้รับคะแนนถ้าคะแนนต่ำสุดถูกกำหนดให้กับสตริงที่หมายถึงการจับคู่และจะมีการให้คะแนนบางส่วนหากมีการเสมอกันสำหรับคะแนนต่ำสุดและการจับคู่ที่ถูกต้องเป็นหนึ่งในสตริงที่ตรงกัน ฉันปรับให้เหมาะสมแล้ว คุณจะเห็นว่าเซลล์สีเขียวเป็นคอลัมน์ที่ตรงกับแถวปัจจุบันมากที่สุดและสี่เหลี่ยมสีน้ำเงินรอบ ๆ เซลล์คือแถวที่ตรงกับคอลัมน์ปัจจุบันมากที่สุด คะแนนที่มุมล่างคือจำนวนการจับคู่ที่ประสบความสำเร็จโดยประมาณและนี่คือสิ่งที่เราบอกให้ปัญหาการเพิ่มประสิทธิภาพของเราเพิ่มขึ้น
อัลกอริทึมนั้นประสบความสำเร็จอย่างยอดเยี่ยมและพารามิเตอร์ของโซลูชันพูดได้มากมายเกี่ยวกับปัญหาประเภทนี้ คุณจะสังเกตเห็นว่าคะแนนที่ปรับให้เหมาะสมคือ 44 และคะแนนที่ดีที่สุดที่เป็นไปได้คือ 48 5 คอลัมน์สุดท้ายคือล่อและไม่มีการจับคู่ใด ๆ กับค่าแถว ยิ่งมีล่อมากเท่าไหร่ก็ยิ่งยากที่จะหาคู่ที่ดีที่สุด
ในกรณีที่จับคู่นี้ความยาวของสตริงไม่เกี่ยวข้องเนื่องจากเราคาดหวังว่าตัวย่อที่เป็นตัวแทนของคำที่ยาวกว่าดังนั้นน้ำหนักที่เหมาะสมที่สุดสำหรับความยาวคือ -0.3 ซึ่งหมายความว่าเราไม่ได้ลงโทษสตริงที่แปรผันตามความยาว เราลดความคาดหวังของตัวย่อเหล่านี้เพื่อให้มีพื้นที่มากขึ้นสำหรับการจับคู่คำบางคำเพื่อแทนที่การจับคู่ที่ไม่ใช่คำที่ต้องการการแทนที่น้อยกว่าเนื่องจากสตริงสั้นกว่า
น้ำหนักคำคือ 1.0 ในขณะที่น้ำหนักวลีเพียง 0.5 ซึ่งหมายความว่าเราลงโทษทั้งคำที่หายไปจากหนึ่งสายและค่ามากกว่าวลีทั้งหมดเป็นเหมือนเดิม สิ่งนี้มีประโยชน์เพราะสตริงเหล่านี้จำนวนมากมีหนึ่งคำที่เหมือนกัน (อันตราย) ซึ่งสิ่งที่สำคัญจริงๆก็คือการรวมกัน (ภูมิภาคและอันตราย) หรือไม่
ในที่สุดน้ำหนักขั้นต่ำจะได้รับการปรับปรุงที่ 10 และน้ำหนักสูงสุดที่ 1 สิ่งนี้หมายความว่าหากคะแนนที่ดีที่สุดของทั้งสองคะแนน (วลีมูลค่าและคำมูลค่า) ไม่ดีมากการแข่งขันจะถูกลงโทษอย่างมาก แต่เราไม่ ลงโทษอย่างรุนแรงที่สุดของคะแนนทั้งสอง หลักนี้เน้นวางอยู่บนที่กำหนดให้ทั้ง valueWord หรือ valuePhrase จะมีคะแนนดี แต่ไม่ใช่ทั้งสอง ประเภทของ "รับสิ่งที่เราจะได้รับ" ความคิด
เป็นเรื่องที่น่าสนใจจริง ๆ ที่สิ่งที่มีค่าสูงสุดของน้ำหนักทั้งห้านี้พูดเกี่ยวกับการจับคู่สายฟัซซี่ สำหรับกรณีที่แตกต่างกันโดยสิ้นเชิงของการจับคู่สตริงฟัซซีพารามิเตอร์เหล่านี้จะแตกต่างกันมาก ฉันใช้มันเพื่อแอพพลิเคชั่น 3 ตัว
ในขณะที่ไม่ได้ใช้งานในการปรับให้เหมาะสมสุดท้ายแผ่นชีทการเปรียบเทียบถูกสร้างขึ้นซึ่งจับคู่คอลัมน์กับตัวเองเพื่อให้ได้ผลลัพธ์ที่สมบูรณ์แบบในแนวทแยงมุมและให้ผู้ใช้เปลี่ยนพารามิเตอร์เพื่อควบคุมอัตราที่คะแนนแตกต่างจาก 0 และบันทึกความคล้ายคลึง ซึ่งในทางทฤษฎีสามารถใช้เพื่อชดเชยผลบวกเท็จในผลลัพธ์)
การใช้งานเพิ่มเติม
โซลูชันนี้มีศักยภาพที่จะใช้ได้ทุกที่ที่ผู้ใช้ต้องการให้ระบบคอมพิวเตอร์ระบุสตริงในชุดของสตริงที่ไม่มีคู่ที่สมบูรณ์แบบ (เช่น vlookup ของการจับคู่โดยประมาณสำหรับสตริง)
ดังนั้นสิ่งที่คุณควรนำมาจากสิ่งนี้คือคุณอาจต้องการใช้การวิเคราะห์ฮิวริสติกระดับสูง (การค้นหาคำจากวลีหนึ่งในวลีอื่นความยาวของทั้งสองวลี ฯลฯ ) พร้อมกับการใช้อัลกอริทึมระยะทางของ Levenshtein เนื่องจากการตัดสินใจว่าการจับคู่แบบใด "ดีที่สุด" คือการตัดสินใจแบบฮิวริสติก (ฟัซซี่) - คุณจะต้องมีชุดน้ำหนักสำหรับตัวชี้วัดใด ๆ ที่คุณคิดขึ้นเพื่อกำหนดความคล้ายคลึงกัน
ด้วยชุดของฮิวริสติกและตุ้มน้ำหนักที่เหมาะสมคุณจะมีโปรแกรมการเปรียบเทียบของคุณในการตัดสินใจอย่างรวดเร็ว