Bu sorunla ilgili olarak, bir yıl önce çeşitli bilgi veritabanında bir petrol teçhizatı hakkında bilgi girmeye geldiğinde bu sorunla karşılaştım. Amaç, veritabanı girdisini en yaygın öğelerle tanımlayabilen bir tür bulanık dize araması yapmaktı.
Araştırmanın bir kısmı, bir dize veya cümle üzerinde başka bir dize veya cümleye dönüştürmek için kaç değişiklik yapılması gerektiğini belirleyen Levenshtein mesafe algoritmasını uygulamayı içeriyordu .
Geldiğim uygulama nispeten basitti ve iki ifadenin uzunluğunun, her bir cümle arasındaki değişikliklerin sayısının ve her kelimenin hedef girişinde bulunup bulunamayacağının ağırlıklı bir karşılaştırmasını içeriyordu.
Makale özel bir sitede, bu yüzden ilgili içeriği buraya eklemek için elimden geleni yapacağım:
Bulanık Dize Eşleme, iki kelime veya kelime öbeğinin benzerliğine dair insan benzeri bir tahmin gerçekleştirme işlemidir. Birçok durumda, birbirine en çok benzeyen kelimeleri veya ifadeleri tanımlamayı içerir. Bu makalede, bulanık dizgi eşleştirme sorununa yönelik şirket içi bir çözüm ve daha önce sıkıcı kullanıcı katılımı gerektiren görevleri otomatikleştirmemize olanak tanıyan çeşitli sorunları çözmedeki yararlılığı açıklanmaktadır.
Giriş
Meksika Körfezi Doğrulayıcı aracını geliştirirken başlangıçta bulanık dize eşleştirme yapma ihtiyacı ortaya çıktı. Var olan, Meksika petrol kuleleri ve platformlarının bilinen körfezinin bir veri tabanıydı ve sigorta satın alan insanlar bize varlıkları hakkında kötü bir şekilde yazılmış bazı bilgiler verecekti ve bunu bilinen platformların veritabanıyla eşleştirmek zorunda kaldık. Çok az bilgi verildiğinde yapabileceğimiz en iyi şey, atıfta bulundukları bilgiyi "tanımak" ve uygun bilgileri çağırmak için bir sigortacıya güvenmektir. İşte bu otomatik çözüm işe yarıyor.
Bulanık dizgi eşleştirme yöntemlerini araştırmak için bir gün geçirdim ve sonunda Wikipedia'da çok yararlı Levenshtein mesafe algoritmasına tökezledim.
uygulama
Arkasındaki teoriyi okuduktan sonra, onu optimize etmenin yollarını buldum. Kodum VBA'da şöyle görünür:
'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
Basit, hızlı ve çok kullanışlı bir metrik. Bunu kullanarak, iki dizenin benzerliğini değerlendirmek için iki ayrı metrik oluşturdum. Biri "valuePhrase", diğeri "valueWords" diyorum. valuePhrase, yalnızca iki kelime öbeği arasındaki Levenshtein mesafesidir ve valueWords, dizeyi boşluklar, tire işaretleri ve istediğiniz başka herhangi bir şey gibi sınırlayıcılara dayalı olarak tek tek sözcüklere böler ve her kelimeyi birbirleriyle en kısa şekilde karşılaştırır İki kelimeyi birleştiren Levenshtein mesafesi. Esasen, bir kelime öbeğindeki bilginin, tıpkı bir kelime bilimi permütasyonu gibi, gerçekten diğerinde bulunup bulunmadığını ölçer. Bir projeyi sınırlayıcılara dayalı bir dizeyi bölmenin mümkün olan en verimli yolunu bulmak için birkaç gün geçirdim.
valueWords, valuePhrase ve Split işlevi:
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
Benzerlik Önlemleri
Bu iki metriği ve sadece iki dize arasındaki mesafeyi hesaplayan bir üçüncü kullanarak, en fazla sayıda eşleşmeyi elde etmek için bir optimizasyon algoritması çalıştırabileceğim bir dizi değişkenim var. Bulanık dize eşleme, kendisi, bulanık bir bilimdir ve bu nedenle dize benzerliğini ölçmek için doğrusal olarak bağımsız metrikler oluşturarak ve birbirimizle eşleştirmek istediğimiz bilinen bir dizi diziye sahip olarak, belirli stillerimiz için dizeleri, en iyi bulanık maç sonuçları verir.
Başlangıçta, metriğin amacı kesin bir eşleşme için düşük bir arama değerine sahip olmak ve gittikçe daha fazla izin verilen önlemler için arama değerlerini artırmaktı. Pratik olmayan bir durumda, bu, iyi tanımlanmış bir dizi permütasyon kullanarak tanımlamak ve son formülü istendiği gibi artan arama değerleri sonuçlarına sahip olacak şekilde tasarlamak oldukça kolaydır.
Yukarıdaki ekran görüntüsünde, arama terimim ve sonuç arasındaki algılanan farkımla güzel bir şekilde ölçeklendiğini hissettiğim bir şey bulmak için buluşsal yöntemimi değiştirdim. İçin kullanılan sezgisel ben Value Phrase
yukarıdaki e-tabloda oldu =valuePhrase(A2,B2)-0.8*ABS(LEN(B2)-LEN(A2))
. Levenstein mesafesinin cezasını iki "kelime öbeğinin" uzunluğundaki farkın% 80'i kadar etkili bir şekilde azaltıyordum. Bu şekilde, aynı uzunlukta "ifadeler" tam cezayı alır, ancak "ek bilgi" (daha uzun) içeren "ifadeler" dışında, yine de çoğunlukla aynı karakterleri paylaşan cezalar daha az ceza alır. Value Words
İşlevi olduğu gibi kullandım ve son SearchVal
buluşsal yöntemim şöyle tanımlandı:=MIN(D2,E2)*0.8+MAX(D2,E2)*0.2
- ağırlıklı ortalama. İki skordan hangisi daha düşükse,% 80, daha yüksek skorun% 20'si ağırlıklı olmuştur. Bu sadece iyi bir eşleşme oranı elde etmek için kullanım durumuma uygun bir buluşsal yöntemdi. Bu ağırlıklar, test verileriyle en iyi eşleşme oranını elde etmek için ayarlanabilecek bir şeydir.
Gördüğünüz gibi, bulanık dize eşleme metrikleri olan son iki metrik, eşleşmesi gereken dizelere (diyagonalden aşağı) düşük puan verme eğilimindedir. Bu çok iyi.
Uygulama
Bulanık eşleşmenin optimizasyonuna izin vermek için her metriği ağırlıklandırıyorum. Bu nedenle, bulanık dizgi eşleşmesinin her uygulaması parametreleri farklı şekilde ağırlıklandırabilir. Nihai puanı tanımlayan formül, metriklerin ve ağırlıklarının basit bir kombinasyonudur:
value = Min(phraseWeight*phraseValue, wordsWeight*wordsValue)*minWeight
+ Max(phraseWeight*phraseValue, wordsWeight*wordsValue)*maxWeight
+ lengthWeight*lengthValue
Bir optimizasyon algoritması kullanarak (sinir ağı en iyisi burada ayrık, çok boyutlu bir sorun olduğu için), hedef artık eşleşme sayısını en üst düzeye çıkarmaktır. Bu son ekran görüntüsünde görülebileceği gibi, her bir kümenin birbirleriyle doğru eşleşme sayısını tespit eden bir işlev oluşturdum. Bir sütun veya satır, en düşük puanın eşleştirilmesi istenen dizeye atanması durumunda bir puan alır ve en düşük puan için bir bağ varsa ve doğru eşleşen dizeler arasında doğru eşleşme varsa kısmi puan verilir. Daha sonra optimize ettim. Yeşil hücrenin geçerli satırla en iyi eşleşen sütun olduğunu ve hücrenin etrafındaki mavi kare geçerli sütuna en iyi uyan satır olduğunu görebilirsiniz. Alt köşedeki puan kabaca başarılı eşleşmelerin sayısıdır ve optimizasyon sorunumuzu en üst düzeye çıkarmak için söylediklerimiz budur.
Algoritma harika bir başarıydı ve çözüm parametreleri bu tür bir sorun hakkında çok şey söylüyor. Optimize edilmiş puanın 44 olduğunu ve mümkün olan en iyi puanın 48 olduğunu göreceksiniz. Sondaki 5 sütun tuzaklardır ve satır değerleriyle hiç eşleşmez. Ne kadar çok tuzak varsa, doğal olarak en iyi eşleşmeyi bulmak o kadar zor olacaktır.
Bu özel eşleştirme durumunda, dizelerin uzunluğu önemsizdir, çünkü daha uzun kelimeleri temsil eden kısaltmalar bekleriz, bu nedenle uzunluk için en uygun ağırlık -0.3'tür, yani uzunlukları değişen dizeleri cezalandırmıyoruz. Bu kısaltmaların beklentisindeki skoru azaltarak, dizenin daha kısa olması nedeniyle daha az ikame gerektiren kelime dışı eşleşmelerin yerine kısmi kelime eşleşmeleri için daha fazla alan veririz.
Weight kelimesi 1,0 iken, cümle ağırlığı yalnızca 0,5'tir, yani bir dizede eksik olan tüm kelimeleri cezalandırırız ve tüm ifadenin bozulmadan daha fazla değer vermesi anlamına gelir. Bu yararlıdır, çünkü bu dizelerin birçoğunun ortak bir kelimesi vardır (tehlike), kombinasyonun (bölge ve tehlike) korunup korunmadığı gerçekten önemli olan şeydir.
Son olarak, minimum ağırlık 10'da ve maksimum ağırlık 1'de optimize edilir. Bunun anlamı, iki puanın en iyisi (değer ifadesi ve değer kelimeleri) çok iyi değilse, maç büyük ölçüde cezalandırılır, ancak iki skorun en kötüsünü cezalandırmaz. Esasen, gerek bu koyar vurgu ya valueWord veya valuePhrase iyi bir puan, ancak ikisi birden sahip olmak. Bir nevi "alabileceğimizi al" zihniyeti.
Bu 5 ağırlığın optimize edilmiş değerinin, meydana gelen bulanık dizgi eşleşmesi hakkında söylediği şey gerçekten büyüleyici. Bulanık dize eşleşmesinin tamamen farklı pratik durumları için, bu parametreler çok farklıdır. Şimdiye kadar 3 ayrı uygulama için kullandım.
Son optimizasyonda kullanılmasa da, diyagonalden aşağıya kadar tüm mükemmel sonuçlar için sütunları kendileriyle eşleşen ve kullanıcının puanların 0'dan sapma hızını kontrol etmek ve arama ifadeleri ( teoride sonuçlarda yanlış pozitifleri dengelemek için kullanılabilir)
Diğer Uygulamalar
Bu çözüm, kullanıcının bir bilgisayar sisteminin mükemmel bir eşleşmenin olmadığı bir dizi dizede bir dizeyi tanımlamasını istediği her yerde kullanılabilme potansiyeline sahiptir. (Dizeler için yaklaşık bir maç vlookup gibi).
Bundan dolayı, Levenshtein mesafe algoritmasının uygulanmasıyla birlikte muhtemelen yüksek seviyeli sezgiselliğin bir kombinasyonunu (diğer tümcede bir cümlenin kelimelerini bulmak, her iki cümlenin uzunluğu, vb.) Bir arada kullanmak istediğinizdir. Çünkü hangisinin "en iyi" eşleşmeye karar vermesi sezgisel (bulanık) bir karardır - benzerliği belirlemek için bulduğunuz metrikler için bir dizi ağırlık bulmanız gerekir.
Uygun sezgisel tarama ve ağırlık kümeleriyle, karşılaştırma programınızı vereceğiniz kararları hızlı bir şekilde alırsınız.