LINUX.ORG.RU

Стемминг без регулярных выражений, как?

 


1

1

Стемминг - убрать из слов окончания и суффиксы, например из «винда» превратить в «винд», из «линуксу» в «линукс». Существуют примеры на других языках использующие регулярное выражения, но мне это хочется сделать на по-символьном анализаторе. Начинал делать, преобразуя логику регвыр в по-символьное движение по строке, но там есть несколько действия вперёд-назад, пока не осилил. Claude выдал вариант на строковых функциях, в общем оптимизация = 0.


Есть алгоритмы на это дело. Использовать для стемминга регулярки-школотронство. Есть куча либ с реализацией тамошних алгоритмов на всех ЯП. Не нравится как там сделано-сделай лучше (только е проси нейронку, время потеряешь)

slew
()

Регулярное выражение и работает посимвольно. Но если хочется самостоятельно, то пишите свой конечный автомат. Потом можно сделать генератор конечных автоматов, который по управляющей строке будет создавать КА для разбора ваших строчек.

ugoday ★★★★★
()
Ответ на: комментарий от ugoday

Регулярное выражение и работает посимвольно

Не хочется модуль встраивать, который в 2 раза больше моей проги. Всегда удавалось подменить собственным анализатором почти без увеличения размера.

Вот ещё немного инфы

AZJIO
() автор топика
Последнее исправление: AZJIO (всего исправлений: 1)
Ответ на: комментарий от AZJIO

Колхоз — дело добровольное. И если вам хочется извращений, то вот код функции, которая создаёт сканнер, который откусывает суффикс у слова:

(defun endless-scanner1 (suffix)
  (let ((suffix-length (length suffix)))
    (lambda (data-string)
      (let ((data-length (length data-string)))
	(if (> suffix-length data-length) data-string
	    (loop for i from (1- data-length) downto 0 ;; string index
		  for j = (1- suffix-length) then (1- j) ;; suffix index
		  when (and (>= j 0) (char-not-equal (char data-string i)
						    (char suffix j)))
		    return data-string
		  when (<= j 0)
		    return (subseq data-string 0 (- i j))))))))

Примеры использования

(funcall (endless-scanner1 "") "защищающихся") ;; пустой суффикс просто возвращает исходную строку
"защищающихся"
(funcall (endless-scanner1 "ся") "защищающихся") ;; в случае совпадения, возвращается часть строки до суффикса
"защищающих"
(funcall (endless-scanner1 "фся") "защищающихся") ;; если суффикс не подходит, возвращает исходную строку
"защищающихся"

Уверен, вы легко сможете перевести это на бэйсик и расширить до поддержки множества суффиксов одновременно.

ugoday ★★★★★
()
Ответ на: комментарий от ugoday

Проще правила русского языка вспомнить. Типа какие у глаголов с прилагательных окончания и тп… С помощью нейронок это возможно. И даже обычные слова типа скрипка-груша. Тут тоже закономерность когда в существительных буква а идет после глухих

anonymous
()
Ответ на: комментарий от ugoday

Тонко :-)

Уверен, вы легко сможете перевести это на бэйсик и расширить до поддержки множества суффиксов одновременно.

И спустя какое-то время, собрав сотни слов исключений и поняв, что естественный язык не работает так, как думается «доморощенному лингвисту», выкинуть весь этот ветвистый колхоз и застрелиться нахер во благо общества изучить то, как работают нормальные проверенные решения.

anonymous
()
Ответ на: комментарий от anonymous

Проще правила русского языка вспомнить.

Издеваетесь, да? Я кроме «жи-ши, ча-ща и чу-щу» ничего из школьного курса не помню. Хотя был отличником и хорошистом по всем предметам (кроме физры и рисования).

ugoday ★★★★★
()
Ответ на: комментарий от anonymous

Это как при поиске сокровищ — главное сокровище это дружба! А тут, попытавшись на практике обрабатывать естественный язык регулярными выражениями, раз-раз и поймёшь о языках важное (что они не разбираются регулярками). Одно дело об этом в книжке прочитать, а совсем другое — на практике убедиться.

ugoday ★★★★★
()
Ответ на: комментарий от anonymous

Какие нафиг сотни исключений, я дал дополнительную ссылку с текстовым описанием алгоритма, он выполнен на питоне, джаваскрипте и вроде си с помощью регулярных выражений. Нужно регулярку переписать на код. Есть строка в памяти, осуществляем сдвиг по строке с проверкой кода символа. Когда я делал замену упрощенных регулярок это в том числе работало быстрей. Если вы встраиваете регвыры для пользователя, тогда другое дело, а внутренний алгоритм вполне можно и кодом осуществить.

AZJIO
() автор топика

Наверное должно быть что-то для работы со строками. Методы и индексы.
Только вот как определить, где корень, а где суффикс?
«кремень» vs «пламень»

dmitry237 ★★★★★
()
Ответ на: комментарий от peregrine

Мне бы упростить задачу, а не усложнить. Восстановление слова избыточно. Я хочу код на 2-10кб, а не добавить словарь, в котором кстати сленга не будет.

AZJIO
() автор топика
Ответ на: комментарий от dmitry237

Только вот как определить, где корень, а где суффикс?

«кремень» vs «пламень»

Для моей задачи не нужно определять корень в точности, можно даже ещё упростить задачу, откинуть окончание, и даже ещё проще, откинуть гласную букву, пожалуй так и сделаю, по крайней мере функционал можно внедрять, а когда нибудь и стемминг воткнуть.

AZJIO
() автор топика
Ответ на: комментарий от AZJIO

плюсую за snowball и похожие (SNOBOL, Icon, Unicon: тыц (compiler.su) )

вот пример текста, который разбирается вот этим кодом на SNOBOL через ATN, атрибутивную сеть переходов (наподобие аттрибутивных грамматик): примеры отсюда AI-cdrom-r3 , здесь файл AISNOBOL.ZIP)(https://raw.githubusercontent.com/fortikeco/AI-cdrom-r3/refs/heads/master/PROG/AISNOBOL.ZIP)

ещё там рядом пример ассемблера на сноболе. кажется, эти же два примера были в книжке Robert Griesemeier «Programming in SNOBOL (for humanist)» или как-то так (или про гуманитариев это уже про Icon/Unicon).

SNOBOL – простой как палка декларативно-императивный язык для парсинга строк:

  • декларативный в смысле паттерн матчинга, есть свой язык для сопоставлений с образцом.

  • императивный в том смысле, что логика предложений на сноболе довольно проста:

 метка A=B FOO :S(uxxess) :F(ail) :(anyway)

здесь: код начинается с метки метка. далее, A=B FOO сопоставляется образец A=B и выполняется FOO с этими заполненными переменными. затем если успешно сопоставление, переходим на метку uxxess, если не успешно – на метку ail иначе (в любом случае, безусловный GOTO) – на метку anyway

то есть: по сути, программа на SNOBOL это конечный автомат, управляемый сопоставлениями с образцом. поэтому, она ОЧЕНЬ проста.

  • что делает язык по настоящему полезным, так это возможность определять свои структуры данных, например: списки, деревья и т.п. а не только базовые; и наличие функций или процедур. есть сборка мусора, вызов си кода из dll/.so динамических библиотек.

то есть, хоть он довольно таки прост – но тем не менее, расширяем и более-менее полезен.

если хочется чего-то посложнее чем конечные автоматы – развитием этой идеи от того же Griesemeier’а является Icon и Unicon.

Icon – процедурно/функциональный язык с генераторами (коалгебра ковыражений, когенераторов типа every, tab и т.п.)

Unicon – объектно-ориентированный Icon.

есть не только интерпретатор, но и конпелятор Icon/Unicon в Си.

anonymous
()
Ответ на: комментарий от anonymous

вообще, я вот удивляюсь чего все так упарываются по LLM-кам. да, одной стороны они более-менее справляются с переводом с разных языков, с другой стороны сама архитектура обработки строк векторами (токены и ембеддинги) ХЗ какой размерности стрёмная и нелепая, кучей неочевидных ограничений в духе 640 кб будет всем достаточно (ср.:размер контекстного окна, причём туда должно поместиться всё, и ввод и вывод; сравни, например: вот unix way состоял в проектировании compiler driver pipeline состоял например в troff -ms foo.roff |tbl|pic|grap > foo.ps|tee ps2pdf -|mupdf - или в том же gcc compiler driver: gcc/gcc -E/ld/collect/ar .... – здесь НЕТ ограничения на количество различных tools в одном pipeline, ни явного, ни неявного; хотя например, в ранних unix размер pipe был что-то около 4 кб.

то есть, на текущий момент LLM без MCP в чатике с локалхоста – это попытка запихнуть весь этот буфер размером контекстного окна в примерно те же 4к/8к.

это довольно таки глупо, пересказывать всю память снова текстом и то что внешней памяти в LLM фактически нет. это если бы в troff pipleline или в gcc pipeline весь выхлоп конпелятора должен был бы поместиться в эти самые 4кб/8кб контекстного окна токенов, а не храниться в других внешних файлах.

опять же, алгоритм внимания это костыль и горбуха. ковыряционный анализ K,V,Q. что-то там с чем-то крутится-вертится, ковыряется туда-сюда, коррелирует туда сюда типа. но ковылирует чего с чем? каких-то стрёмных токенов и ембеддингов с какими-то не менее стрёмными весами. каким образом? да ХЗ каким именно, чёткой дедуктивной силлогистической структуры и символьных исчислений тут нет и не было.

корреляция не означает следствие, но из логической материальной, а не формальной импликации автоматически следует корреляция.

то есть, без каких-то нулевой и не нулевой, альфа-бета гипотез, механизмов проверки тут дедукцией, индукцией и абдукцией и не пахло.

при том, что любая программа на лиспе или прологе – парсер текста сама и является такой символьного калькулюса структурой базы знаний.

или вот, например на сноболе, конечными автоматами.

так-то парсер например того же упрощённого контролируемого командного языка текстовых адвентюр типа Зорка (например,см. изначальные исходники на лиспе ZIL/MDL или более осовременненные на Inform7) – довольно не очень сложно пишется явно.

то есть, для парсинга упрощённого контролируемого языка – вполне работали и такие методы, незачем туда приплетать LLM.

стемматизация тут при том, что если делать например парсер упрощённого русского, а не контролируемого английского – то можно обойтись подобным ATN или тому же парсеру зорка по стеммам и словоформам.

вот в «русском информ6» например, вообще тупо задают падежи и окончания, отбрасывают окончания (получается типа стемма) или проверяют по всем нужным падежам, как синонимы.

всё это работает без всяких кучи GPU и VRAM, чисто на процессоре. работало ещё на Z80 в начале 80х, в тех же текстовых адвентюрах типа Зорка от Infocom, или более продвинутых от Level 9.

ещё на прологе можно писать понятный парсер. гуглить про DCG грамматики (directive clause grammar).

или например, сразу смотреть LogTalk := Prolog+SmallTalk и из его репозитория примеры парсеров естественного языка и «объектные» грамматики типа OMeta смоллтоковой, Meta II / TreeMeta , PEG. только с поправкой на формализм DCG вместо PEG.

похожие примеры есть и в PopLog, tamgu, LispE, Shen.

во всех этих примерах, что на сноболе через ATN, что на прологе/функционально-объектном прологе логток , что на DCG, что на PEG парсер «упрощённого контролируемого псевдоестественного языка» пишется довольно просто и наглядно.

прелесть LLM-ок, конечно в том что понимает и не контролируемый и не псевдо, а почти естественный. но какой ценой, перемолачивая шейдерами вектора токенов и заделок ХЗ какой размерности ХЗ какого контекстного окна и ХЗ ещё каких скрытых, неявных костылей в такой более сложно расширяемой чем compiler driver pipeline – архитектуре?

anonymous
()
Ответ на: комментарий от AZJIO

а что за задача-то? облако тегов, или что-то типа семантического дифференциала Коржибского изобразить ? :)))

или фильтровать по корням без суффиксов и окончаний и падежей чтобы не делать полноценный морфологический анализ и словоформы?

я вот думаю что для чего-то типа текстовых адвентюр типа Зорка в чём-то типа парсера командного языка «русского Inform 6» (тоже язык типа ObjectiveC/SmallTalk) – хватило бы и чего-то попроще.

Inform7 естественноязычный тут интереснее, конечно же. что-то по онтологиям для немецкого там было в rule book (стандартной библиотеке), для русского Inform7 можно было бы изобразить нечто похожее.

в Inform6.ru тупо или таблицу падежей и склонений/спряжений сделали, или проверяют тупо все синонимы корень без суффиксов и окончаний, как раз что-то типа стеммы получается.

anonymous
()
Ответ на: комментарий от AZJIO

откинуть гласную букву

И чего здесь сложного? Можно взять подстроку нужной длины или убрать последний символ по индексу

$endings = "а","у"
$words = "линуксу", "винда"

$OFS = ''

foreach ($word in $words)
{
    $ind = $word.length - 2
    if ($word[-1] -in $endings)
    {
        [string] $word[0..$ind]    # без последнего индекса
        $word.substring(0,++$ind)  # подстрока нужной длины
    }
}

линукс
линукс
винд
винд
dmitry237 ★★★★★
()
Ответ на: комментарий от peregrine

вот кстати ещё, тоже вспомнил.

пример парсера на прологе для языков разметки типа Markdown:

ъ: djot

why djot?

djota

что мы видим вот здесь? опа, вот тесты в testing.pl

а вот сам парсер: djota.pl:L141

на строках 141 и ниже (да впрочем, и выше) --> такой стрелочкой и задаются эти самые directive clause grammars

довольно наглядно задаются сами грамматики в DCG, кстати. что в самом прологе, что в ОО прологе LogTalk c метапредикатами: функционально-логическими объектами в духе SmallTalk+Prolog=LogTalk : метапредикаты, метаклассы, объекты-запросы.

в целом, довольно таки наглядно и похоже на ту же PEG, OMeta.

anonymous
()
Ответ на: комментарий от anonymous

А не школотронство сожрет процессор и память

А регулярки не сожрет? Ты вообще их применял в нагрузке? Регулярки вообще самый дебильный способ обоаботки текста, об этом не знают только школотроны

slew
()
Ответ на: комментарий от AZJIO

В словаре не нужно держать вообще все слова, там нужны только исключения, где наивный стеммер ошибается. Полагаю таких исключений будет совсем немного. Но вот сформировать этот словарь скорее всего не так-то просто. Кстати, насчёт словарей, мне удавалось относительно просто сжать все возможные слова из 5 букв вместе с реализацией одноимённой игры в 15кб.

neumond ★★
()

Наш светоч и гуру кодинга опять не смог загуглить либы морфологического анализа и не нашел ничего лучше регулярок. Он почти пришел к мудрости дэвэ по дэтэ, поэтому не буду мешать.

Lordwind ★★★★★
()

убрать из слов окончания и суффиксы

А зачем? Русский язык немножко гибкий, и на самом деле неплохо сохранять категории слов как есть (род, падеж и т.д.). Ты ж всё равно релевантность не будешь тупо по совпадению определять, а по N-граммам хотя бы и т.п.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 1)
  • Markdown
Пустая строка (два раза Enter) начинает новый абзац. Знак '>' в начале абзаца выделяет абзац курсивом цитирования.
Внимание: прочитайте описание разметки Markdown.
Используйте Ctrl-Enter для размещения комментария