Wejście: "tableapplechairtablecupboard..."
wiele słów
Jaki byłby skuteczny algorytm do podzielenia takiego tekstu na listę słów i uzyskania:
Wynik: ["table", "apple", "chair", "table", ["cupboard", ["cup", "board"]], ...]
Pierwszą rzeczą, która przychodzi na myśl, jest przejście przez wszystkie możliwe słowa (zaczynając od pierwszej litery) i znalezienie najdłuższego możliwego słowa, kontynuuj od position=word_position+len(word)
PS
Mamy listę wszystkich możliwych słów.
Wyrazem „szafka” może być „filiżanka” i „deska”, wybierz najdłuższe.
Język: python, ale najważniejszy jest sam algorytm.
['able', 'air', 'apple', 'boa', 'boar', 'board', 'chair', 'cup', 'cupboard', 'ha', 'hair', 'lea', 'leap', 'oar', 'tab', 'table', 'up']
Odpowiedzi:
Naiwny algorytm nie da dobrych wyników, gdy zostanie zastosowany do rzeczywistych danych. Oto 20-liniowy algorytm, który wykorzystuje względną częstotliwość słów w celu uzyskania dokładnych wyników dla tekstu rzeczywistego.
(Jeśli chcesz odpowiedzieć na swoje pierwotne pytanie, które nie używa częstotliwości słów, musisz sprecyzować, co dokładnie oznacza „najdłuższe słowo”: czy lepiej mieć 20-literowe słowo i dziesięć 3-literowych, czy też lepiej mieć pięć 10-literowych słów? Gdy już zdecydujesz się na precyzyjną definicję, wystarczy zmienić definicję linii,
wordcost
aby odzwierciedlić zamierzone znaczenie.)Pomysł
Najlepszym sposobem postępowania jest modelowanie rozkładu wyników. Dobrym pierwszym przybliżeniem jest założenie, że wszystkie słowa są rozmieszczone niezależnie. Wtedy wystarczy znać względną częstotliwość wszystkich słów. Można założyć, że są zgodne z prawem Zipfa, to znaczy słowo o randze n na liście słów ma prawdopodobieństwo około 1 / ( n log N ), gdzie N to liczba słów w słowniku.
Po ustaleniu modelu można użyć programowania dynamicznego, aby wywnioskować położenie przestrzeni. Najbardziej prawdopodobne zdanie to takie, które maksymalizuje iloczyn prawdopodobieństwa każdego pojedynczego słowa i można je łatwo obliczyć za pomocą programowania dynamicznego. Zamiast bezpośrednio korzystać z prawdopodobieństwa, używamy kosztu zdefiniowanego jako logarytm odwrotności prawdopodobieństwa, aby uniknąć przepełnienia.
Kod
z którym możesz korzystać
Wyniki
Używam tego szybkiego i nieczytelnego słownika na 125 000 słów, który stworzyłem na podstawie niewielkiego podzbioru Wikipedii.
Jak widać, jest w zasadzie bezbłędny. Najważniejszą częścią jest upewnienie się, że twoja lista słów została przeszkolona w korpusie podobnym do tego, z czym faktycznie się spotkasz, w przeciwnym razie wyniki będą bardzo złe.
Optymalizacja
Implementacja zużywa liniowo ilość czasu i pamięci, więc jest dość wydajna. Jeśli potrzebujesz dalszego przyspieszenia, możesz zbudować drzewo sufiksów z listy słów, aby zmniejszyć rozmiar zestawu kandydatów.
Jeśli chcesz przetworzyć bardzo duży ciąg ciągów kolejnych, rozsądne byłoby podzielenie ciągu, aby uniknąć nadmiernego wykorzystania pamięci. Na przykład, możesz przetworzyć tekst w blokach po 10000 znaków plus margines 1000 znaków po obu stronach, aby uniknąć efektów brzegowych. Pozwoli to zminimalizować użycie pamięci i prawie na pewno nie będzie miało wpływu na jakość.
źródło
pip install wordninja
words.txt
zawiera "comp": `` $ grep "^ comp $" words.txt comp `` `` i jest posortowane alfabetycznie. ten kod zakłada, że jest posortowany według malejącej częstotliwości pojawiania się (co jest typowe dla takich list n-gramowych). jeśli używasz odpowiednio posortowanej listy, Twój ciąg będzie wyglądał dobrze: `` `` >>> wordninja.split ('namethecompanywherebonnie wasemployedwhenwesteddating') ['name', 'the', 'company', 'where', 'bonnie', ' był ',' zatrudniony ',' kiedy ',' my ',' zaczynał ',' randki '] ``Opierając się na doskonałej pracy w górnej odpowiedzi , stworzyłem
pip
pakiet do łatwego użycia.Aby zainstalować, uruchom
pip install wordninja
.Jedyne różnice są niewielkie. Zwraca a
list
zamiast astr
, działa w programiepython3
, zawiera listę słów i poprawnie dzieli, nawet jeśli istnieją znaki inne niż alfa (takie jak podkreślenia, myślniki itp.).Jeszcze raz dziękuję Generic Human!
https://github.com/keredson/wordninja
źródło
Oto rozwiązanie wykorzystujące wyszukiwanie rekurencyjne:
plony
źródło
Używając trie struktury danych , która zawiera listę możliwych słów, nie byłoby zbyt skomplikowane wykonanie następujących czynności:
źródło
"tableprechaun"
a następnie podzielić"tab"
."tableprechaun"
najdłuższym dopasowaniem od początku jest"table"
wyjście"prechaun"
, którego nie można podzielić na słowa słownikowe. Więc musisz wziąć krótszy mecz,"tab"
zostawiając cię z"leprechaun"
.Rozwiązanie Unutbu było dość bliskie, ale uważam, że kod jest trudny do odczytania i nie przyniósł oczekiwanych rezultatów. Rozwiązanie Generic Human ma tę wadę, że wymaga częstotliwości słów. Nie nadaje się do wszystkich przypadków użycia.
Oto proste rozwiązanie wykorzystujące algorytm Divide and Conquer .
find_words('cupboard')
powróci['cupboard']
zamiast['cup', 'board']
(zakładając, żecupboard
,cup
iboard
są w dictionnary)find_words('charactersin')
['characters', 'in']
['character', 'sin']
Kod:
Zajmie to około 5 sekund na moim komputerze 3GHz:
źródło
Odpowiedź https://stackoverflow.com/users/1515832/generic-human jest świetna. Ale najlepszą implementacją tego, jaką kiedykolwiek widziałem, był sam Peter Norvig w swojej książce „Beautiful Data”.
Zanim wkleię jego kod, pozwolę sobie wyjaśnić, dlaczego metoda Norviga jest dokładniejsza (choć trochę wolniejsza i dłuższa pod względem kodu).
1) Dane są nieco lepsze - zarówno pod względem wielkości, jak i precyzji (używa liczby słów zamiast prostego rankingu) 2) Co ważniejsze, to logika stojąca za n-gramami naprawdę sprawia, że podejście jest tak dokładne .
Przykładem, który podaje w swojej książce, jest problem rozszczepienia struny „sitdown”. Teraz nie-bigramowa metoda podziału ciągów uwzględniłaby p ('sit') * p ('down'), a jeśli to mniej niż p ('sitdown') - co będzie miało miejsce dość często - NIE zostanie podzielone to, ale chcielibyśmy, żeby tak było (przez większość czasu).
Jednak gdy masz model bigramu, możesz wycenić p ('siadaj') jako bigram vs p ('siad') i ten pierwszy wygrywa. Zasadniczo, jeśli nie używasz bigramów, traktuje prawdopodobieństwo podzielenia słów jako niezależne, co nie jest prawdą, niektóre słowa częściej pojawiają się jedno po drugim. Niestety są to również słowa, które często są sklejane ze sobą w wielu przypadkach i mylą rozdzielacz.
Oto link do danych (są to dane dla 3 oddzielnych problemów, a segmentacja to tylko jeden. Przeczytaj rozdział po szczegóły): http://norvig.com/ngrams/
a oto link do kodu: http://norvig.com/ngrams/ngrams.py
Te linki są już od jakiegoś czasu, ale i tak skopiuję tutaj część kodu z segmentacją
źródło
RuntimeError: maximum recursion depth exceeded in cmp
Oto akceptowana odpowiedź przetłumaczona na JavaScript (wymaga node.js i pliku „wordninja_words.txt” z https://github.com/keredson/wordninja ):
źródło
Jeśli prekompilujesz listę słów do DFA (co będzie bardzo powolne), to czas potrzebny na dopasowanie danych wejściowych będzie proporcjonalny do długości ciągu (w rzeczywistości tylko trochę wolniej niż tylko iterowanie po ciągu).
W rzeczywistości jest to bardziej ogólna wersja algorytmu trie, o którym wspomniano wcześniej. Wspominam o tym tylko jako kompletnie - na razie nie ma implementacji DFA, której możesz po prostu użyć. RE2 zadziałałoby, ale nie wiem, czy powiązania Pythona pozwolą ci dostroić, jak duży może być DFA, zanim po prostu wyrzuci skompilowane dane DFA i przeszuka NFA.
źródło
Wygląda na to, że wystarczy dość przyziemne cofanie się. Zacznij od początku sznurka. Skanuj w prawo, aż znajdziesz słowo. Następnie wywołaj funkcję na pozostałej części ciągu. Funkcja zwraca wartość „false”, jeśli skanuje całą drogę w prawo bez rozpoznawania słowa. W przeciwnym razie zwraca znalezione słowo i listę słów zwróconych przez wywołanie rekurencyjne.
Przykład: „tableapple”. Znajduje „tab”, a następnie „skok”, ale nie zawiera słowa „ple”. Żadnego innego słowa w „skoku”. Znajduje „tabelę”, a następnie „aplikację”. „le” nie jest słowem, więc próbuje apple, rozpoznaje, zwraca.
Aby uzyskać jak najdłuższy czas, kontynuuj, emitując (zamiast zwracać) tylko poprawne rozwiązania; następnie wybierz optymalny według dowolnego kryterium (maxmax, minmax, Average, itp.)
źródło
W oparciu o rozwiązanie unutbu zaimplementowałem wersję Java:
Wejście:
"tableapplechairtablecupboard"
Wynik:
[table, apple, chair, table, cupboard]
Wejście:
"tableprechaun"
Wynik:
[tab, leprechaun]
źródło
W przypadku języka niemieckiego istnieje CharSplit, który wykorzystuje uczenie maszynowe i działa całkiem nieźle w przypadku ciągów kilku słów.
https://github.com/dtuggener/CharSplit
źródło
Rozwijając sugestię @ miku dotyczącą użycia a
Trie
, użycie tylko dopisywaniaTrie
jest stosunkowo proste do zaimplementowania wpython
:Następnie możemy zbudować
Trie
słownik oparty na zestawie słów:Który utworzy drzewo, które wygląda tak (
*
wskazuje początek lub koniec słowa):Możemy uwzględnić to w rozwiązaniu, łącząc je z heurystyką dotyczącą doboru słów. Na przykład możemy preferować dłuższe słowa od krótszych:
Możemy użyć tej funkcji w następujący sposób:
Ponieważ utrzymujemy naszą pozycję w
Trie
jak szukamy dłuższe słowy, przejdziecietrie
co najwyżej raz na możliwe rozwiązanie (zamiast2
czasach dlapeanut
:pea
,peanut
). Końcowe zwarcie ratuje nas przed chodzeniem zwęźle przez strunę w najgorszym przypadku.Końcowy wynik to tylko kilka inspekcji:
Zaletą tego rozwiązania jest fakt, że bardzo szybko wiesz, czy istnieją dłuższe słowa z danym przedrostkiem, co pozwala uniknąć wyczerpującego testowania kombinacji sekwencji w słowniku. Sprawia również, że dotarcie do pliku
unsolvable
odpowiedzi jest stosunkowo tanie w porównaniu z innymi wdrożeniami.Wadami tego rozwiązania są duże zużycie pamięci
trie
i koszt budowy ztrie
góry.źródło
Jeśli masz wyczerpującą listę słów zawartych w ciągu:
word_list = ["table", "apple", "chair", "cupboard"]
Korzystanie z funkcji list złożonych do iteracji po liście w celu zlokalizowania słowa i tego, ile razy występuje.
Funkcja zwraca
string
wynik słów w kolejności na liścietable table apple chair cupboard
źródło
Wielkie dzięki za pomoc w https://github.com/keredson/wordninja/
Mały wkład tego samego w Javie z mojej strony.
Metoda publiczna
splitContiguousWords
może być osadzona z pozostałymi 2 metodami w klasie mającej ninja_words.txt w tym samym katalogu (lub zmodyfikowane zgodnie z wyborem kodera). I metodasplitContiguousWords
może być wykorzystana do tego celu.źródło
public
metoda przyjmuje zdanie typu,String
które jest podzielone na podstawie pierwszego poziomu z wyrażeniem regularnym. A listaninja_words
jest dostępna do pobrania z repozytorium git.To pomoże
źródło
Musisz zidentyfikować swoje słownictwo - być może wystarczy dowolna lista słów.
Po zakończeniu użyj tego słownictwa, aby zbudować drzewo sufiksów i dopasuj strumień danych wejściowych do tego: http://en.wikipedia.org/wiki/Suffix_tree
źródło