Pour améliorer l’algorithme de Karnaj, tu peux aller plus loin en utilisant des espaces sémantiques. Ton idée de départ, il me semble. Qui consiste à vectoriser les mots puis à les comparer dans un espace multidimensionnel.
Cet après-midi, je me suis amusé à coder un petit prototype :
from unicodedata import normalize, category
import math
import re
class Semantic:
def __init__(self):
self._features = []
self._maxLen = 0
def loadDocs(self, docs):
for i in range(0, len(docs)):
docs[i] = self.cleanDoc(docs[i])
for j in range(0, len(docs[i])):
docs[i][j] = self.embedding(docs[i][j])
return docs
def cleanDoc(self, doc):
doc = doc.lower()
doc = ''.join((c for c in normalize('NFD', doc) if category(c) != 'Mn'))
doc = re.sub(r'((^|\n) +| +(\n|$))', r'\3', re.sub('([^a-z0-9\n -]| +)', ' ', doc))
return list(filter(None, doc.split('\n')))
def embedding(self, words):
vector = SmtcVector(self)
words = words.split(' ')
for i in range(0, len(words)):
if not len(words[i]): continue
if words[i] not in self._features:
self._features.append(words[i])
vector.add(words[i])
self._maxLen = max(self._maxLen, len(words[i]))
return vector
def dist(self, a, b):
a, b = a.getValue(), b.getValue()
fit = (len(list(filter(lambda x: x > 0, a)))
+ len(list(filter(lambda x: x > 0, b)))) / 2
sum = 0
for i in range(0, self._maxLen):
sum += (b[i]-a[i]) * (b[i]-a[i])
return math.sqrt(sum) / fit
def getFeatures(self):
self._features.sort()
self._features.sort(key=len)
return self._features
def getMaxLen(self):
return self._maxLen
class SmtcVector:
def __init__(self, semantic):
self._semantic = semantic
self._value = []
def add(self, word):
self._value.append(word)
def getValue(self):
ft = self._semantic.getFeatures()
maxFt = 1/len(ft)
vect = list((ft.index(x) + 1) * maxFt for x in self._value)
vect += [0.] * (self._semantic.getMaxLen() - len(vect))
return vect
def getWords(self):
return ' '.join(self._value)
def __str__(self):
return '<'+', '.join(str(x) for x in self.getValue())+'>'
def main ():
docs = []
with open('a.txt', 'r') as f: docs.append(f.read())
with open('b.txt', 'r') as f: docs.append(f.read())
app = Semantic()
embeds = app.loadDocs(docs)
print('Features:\n%s\n' % (', '.join(app.getFeatures())))
tolerance = 0.06
for i in range(0, len(embeds[0])):
for j in range(0, len(embeds[1])):
d1, d2 = embeds[0][i], embeds[1][j]
dist = app.dist(d1, d2)
if dist < tolerance:
print('L%s -> L%s' % ((i+1), (j+1)))
print('D1: %s' % d1.getWords())
print('D2: %s' % d2.getWords())
print('V1: %s' % d1)
print('V2: %s' % d2)
print('Dist: %s' % dist)
print(' ')
main()
Avec ces deux fichiers :
a.txt
Serpent d'argent
Dragon des landes
Ange des landes
Mélomancien du ciel d'azur
Arachnide des ombres
b.txt
Serpent d'albâte
Dragon des landes
Mélomancien des ombres
Ange des landes
Goule de la nuit
J’obtiens en sortie :
Features:
d, de, du, la, des, ange, azur, ciel, nuit, goule, albate, argent, dragon, landes, ombres, serpent, arachnide, melomancien
L1 -> L1
D1: serpent d argent
D2: serpent d albate
V1: <0.8888888888888888, 0.05555555555555555, 0.6666666666666666, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0>
V2: <0.8888888888888888, 0.05555555555555555, 0.611111111111111, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0>
Dist: 0.018518518518518528
L2 -> L2
D1: dragon des landes
D2: dragon des landes
V1: <0.7222222222222222, 0.2777777777777778, 0.7777777777777777, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0>
V2: <0.7222222222222222, 0.2777777777777778, 0.7777777777777777, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0>
Dist: 0.0
L3 -> L4
D1: ange des landes
D2: ange des landes
V1: <0.3333333333333333, 0.2777777777777778, 0.7777777777777777, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0>
V2: <0.3333333333333333, 0.2777777777777778, 0.7777777777777777, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0>
Dist: 0.0
L5 -> L3
D1: arachnide des ombres
D2: melomancien des ombres
V1: <0.9444444444444444, 0.2777777777777778, 0.8333333333333333, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0>
V2: <1.0, 0.2777777777777778, 0.8333333333333333, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0>
Dist: 0.018518518518518528
Dans le debug, tu as l’association de chaque ligne puis successivement les valeurs et les vecteurs obtenus. Puis la distance correspondante qui avec un paramètre de tolérance, indiquera avec une plus ou moins grande précision leur relation.
J’ai essayé avec ton exemple, il a du mal avec le cas n°4 ; ça mériterait certainement des améliorations. Je sais qu’il existe les modèles Word2vec en association avec Tensorflow (voir ici), ce sera certainement plus performant. Après il faut que t’apprenne Python.