Главная страница
Навигация по странице:

  • Зеленский Дмитрий Максимович

  • Московский государственный университет им. М. В. Ломоносова


    Скачать 71.24 Kb.
    НазваниеМосковский государственный университет им. М. В. Ломоносова
    Дата19.09.2022
    Размер71.24 Kb.
    Формат файлаdocx
    Имя файлаKursovaya_2_kursa.docx
    ТипПрограмма
    #685549



    Московский государственный университет им. М. В. Ломоносова

    Прототип стеммера для русского языка

    в сопоставлении с иными программами этого типа и с разрешением части их проблем




    Зеленский Дмитрий Максимович




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



    Оглавление


    Введение 1

    Глава 1. Устройство стеммера Z 2

    Глава 2. Теоретически предсказуемые ошибки стеммера Z 8

    Глава 3. Сравнение стеммера Z со стеммером Портера Snowball 10

    Заключение 11

    Список литературы 11

    Приложение: код стеммера Z в языке программирования C# 12

    Введение


    Стеммером (в узком смысле) называется простейшая модель морфологического анализатора, отличающегося от более сложных тем, что он не использует словарь, соотносящий словоформы с лексемой, контекст и машинное обучение1. Вместо этого стеммер на основании конечного числа строго упорядоченных2 правил замены на другую (в частности, нулевую) подстроку конечной (или, теоретически, начальной; для русского языка, однако, изменение начала не столь актуально) подстроки словоформ, выделяет так называемую формальную основу (stem). Написание и оценка такой программы (называемой далее стеммером Z) является основной целью данной работы.

    Дальнейшее изложение разделено на 3 главы и заключение. В первой главе обсуждается устройство стеммера Z и алгоритм его работы. Во второй главе обсуждаются основные виды ошибок, допускаемые стеммером Z и очевидные уже на этапе создания, до проверки по корпусу. В третьей главе обсуждается другой предлагавшийся стеммер, а именно Snowball [ CITATION Por16 \l 1049 ], и упоминаются его основные отличия от стеммера Z.

    Глава 1. Устройство стеммера Z


    Стеммер Z написан на языке программирования C# и в собственно выделении формальной основы использует две функции: «стемминг» и усечение. Стемминг определяет, кончается ли предложенный сегмент (в значении [ CITATION Зализняк \l 1049 ]) на строку a, и, если кончается, заменяет строку a на строку b (возможные обозначения: стемминг a в b, стемминг a→b3). Усечение – это, собственно, стемминг со строкой b нулевой длины, однако в усечение вставлено условие, не позволяющее ему довести предложенный сегмент до нулевой длины. Прочие, технические, внутренние функции рассмотрены не будут.

    Работа стеммера Z состоит из следующих шагов:

    1. Разбиение входного файла (в коде – test.txt), анализируемого построчно, на «токены» по всем знакам препинания (включая и дефис, кроме слов «ток-шоу» и «хот-дог», выписанных в коде как исключения; возможно, впрочем, отдавать стеммеру на обработку и результат более совершенного токенизирования, уже определившего, какие дефисы должны быть устранены, а какие нет, с одним токеном на строку; в таком случае шаг 1 должен быть пропущен (заменён непосредственным обращением к функции конвертирования токена, выполняющей шаги 2-9); предполагается, однако, что частицы «либо», «то» и «нибудь», пишущиеся через дефис, должны быть отделены);

    2. Начало работы функции конвертирования токена: сведение первого символа к нижнему регистру; сведение к нижнему регистру всей строки, если она содержит заглавный мягкий или твёрдый знак (поскольку это не бывает частью аббревиатуры); заметим, что в остальных случаях написание заглавными буквами непервых символов сохраняется;

    3. Несколько частных замен для унификации обработки разных форм одной лексемы, которые можно убрать (тот→т, этот→эт, мы→ны, семян→семен, стремян→стремен, один→одн, нас→нах, вас→вах, много→многое (иначе интерпретируется как родительный падеж на «ого» и усекается до «мн» и далее до «м»), ты→тоб, тебя→тобя, тебе→тобе, я→мнь, он→й, она→йа, оно→йэ, они→й (не «йы», потому что к этому виду дальнейшая обработка приведёт союз «и»), меня→мня, зол→зл, весь→всь, уши (ушей, etc) → ухи (ухей, etc.), двумя→двуми);

    4. Деёфикация (ё→е) и замена йотированных гласных и и на сочетание запятой (заменяющей используемый в [ CITATION Зализняк \l 1049 ] при приведении к условному виду апостроф, поскольку в токенах может встретиться настоящий апостроф, а запятые устраняются на шаге 1 и, предположительно, тем более устраняются более совершенным токенизатором) и соответствующей гласной (е→,э; ю→,у; я→,а; и→,ы); после этого остаются гласные «а», «о», «у», «ы» и «э», и дальнейшее употребление словосочетания «все гласные» внутри описания работы стеммера Z подразумевает именно их;

    5. Замена сочетаний «ьо» на сочетание заглавного мягкого знака с латинской o, замена строчных мягких знаков на запятые и замена заглавных мягких знаков на строчные; таким образом, как и в [ CITATION Зализняк \l 1049 ], уничтожаются все мягкие знаки, кроме тех, за которыми следует о; латинское o вместо русского используется, чтобы слова вроде «каудильо» не были подвержены дальнейшему усечению;

    6. Замена сочетания двух запятых на сочетание запятой и й (,,→,й);

    7. Удаление запятой после заднеязычных (г, к, х), ц и шипящих (ж, ш, ч, щ), кроме слов «кюри» и «жюри», которые иначе совпали бы с повелительным наклонением ед. числа глаголов «курить» и «журить» соответственно;

    8. Замена запятой на й после другой й, твёрдого знака и гласной буквы и в начале слова;

    9. Замена цепочки «дву» на «два»; конец работы функции конвертирования токена; сохранение строки из полученных токенов, разделённых пробелами, для последующей записи их в промежуточный файл tokens.txt;

    10. Начало работы функции собственно выделения формальной основы, перехват4 союзов «и», «иль» и «или»;

    11. Начало выделения формальной основы остальных слов, стемминг кто→к и что→ч;

    12. Усечение окончаний «аяся» (айас,а; движущаяся), «уюся» (уйус,а; движущуюся), «имися» (ым,ыс,а; движущимися); это предваряет усечение окончаний существительных и прилагательных, которым может предшествовать мягкий «с», чтобы слова «карась» и «карасём» получили одинаковый разбор; заметим, что формы глагола на «уюсь» и «аясь», в отличие от этих форм, принадлежащих причастиям или бывшим причастиям, на этом шаге не усекаются; заметим также, что возвратным суффиксом могут быть закрыты только действительные причастия, которые имеют основы на шипящий, после которых запятая, обозначающая мягкость, удаляется;

    13. Усечение окончания «ми» (м,ы); это не только усекает слова типа «людьми», «детьми», но и приводит формы с окончаниями «ами» и «ыми» к независимо усекаемым далее окончаниям «а» и «ы»;

    14. При длине обрабатываемого токена больше четырёх символов усечение «йу» и «йа»: на этом этапе сохраняются формы вроде «дую», «дуя», «мою», «моя»;

    15. Усечение «ах», «ых», «а», «у», «э», «ы», «ый», «эй» и «ой» (в указанном порядке, что автоматически усекает формы на «эе» (т. е. «ее»), «ые» и «ое»); «о» не усекается как потому, что из-за деёфикации невозможна последовательность «с,о», так и потому, что есть окончания «ого», «эго», нуждающиеся в предварительной обработке; также заметим, что «с,а» (т. е. «ся») автоматически превращается таким образом в «с,»;

    16. При длине токена больше трёх символов усечение «ом» и «ым»; очевидно, таким образом сохраняются слова вроде «сом» и «дым»;

    17. Усечение «с,»; таким образом усекаются слова с возвратным суффиксом, но также и слова типа «карась»;

    18. Усечение окончаний «ого», «эго», «о», «а» и «ы» в указанном порядке; таким образом, окончания причастий, ранее закрытых возвратным суффиксом (движущегося), также частично получают обработку; «ого» и «о» нужды удалять до усечения «с,» не было, ибо они не могли за ним следовать из-за деёфикации;

    19. Усечение окончаний «л» при длине токена больше двух символов и «л,» при длине токена больше трёх символов в указанном порядке (не в обратном, чтобы сегменты типа «кристалле» не усекались до «криста», в то время как сочетание букв «льл» в русском языке невозможно);

    20. Усечение поочерёдно всех гласных; заметим, что формы на «те» (т,э: съешьте, бегите), на «ти» (т,ы: нести, грести) и на «ть» таким образом сводятся к единому окончанию «т,»;

    21. Усечение сочетаний всех гласных с «т,»;

    22. Усечение окончания «ый»;

    23. При длине токена больше трёх символов усечение окончаний «ом», «ам», «эм», «ым», «эт», «ыт», «ут», «эш», «ош», «уй», «ай», «ой», «эй», «от», «ущ», «ащ», «ат»; заметим, что таким образом также усекаются причастия, кроме причастий на «нн» и «вш», в том числе на «ом» (несомый), «т» (колотый, раздутый), «эм» (узнаваемый);

    24. Стемминг «шедш» до «ш»; таким образом причастия вроде «пришедший» сводятся к той же основе, что и формы «пришёл»; причастие «шедший», правда, получает основу «ш», в то время как «шёл», «шла», «шло» и «шли» получает основу «шл», но это исправляется на шаге 43;

    25. Усечение «вш»; заметим, что это усекает и деепричастия на «вши(сь)», поскольку и «сь», и «ы» отсекались ранее;

    26. Усечение «эств» (человечество, пиршество, etc.), «ств», «в», «о» и «э» в указанном порядке; очевидно, почему первые три упорядочены именно так (каждое следующее входит в предыдущее); заметим, что «в» устраняется не только из деепричастий, но в том числе и из форм вроде «живу», «плыву», «слыву» (а также, естественно, из множества слов на «в» вроде «кров», но при этом не теряется связь форм одной лексемы); повторное усечение «о» и «э» после усечения «в» позволяет избавиться от суффикса «ова/ева»;

    27. Усечение «й»;

    28. Стемминг сочетаний согласных «д», «т», «б», «п», «г», «к» и «с» с «ш»;

    29. Стемминг «нн» и «н,эн» в «н»;

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

    31. Стемминг «лечь» в «лег», то же самое со «стричь», «мочь», «беречь» и «стеречь», а при длине токена больше пяти символов также и «стичь» (ст,ыч; ограничение на длину «спасает»5 имя собственное «Стич», которое вместо этого превратится далее в «стик»); согласно [CITATION Зал80 \l 1049 ], это полный список (без учёта разных приставок, которое для стемминга с конца нерелевантно) глаголов с инфинитивом на «чь», имеющих основу на «г»;

    32. Стемминг «м,эн» в «м,»; это обрабатывает косвенные формы лексем на «-мя» наподобие «знамя», но также формы прилагательных типа «умён»;

    33. Стемминг «жэск» в «г», усечение «эск» и «ск» – очевидно, в указанном порядке;

    34. Стемминг «л,эц» в «л,к», «ч», «,эц» и «ц» в «к»; это объединяет глаголы на «-чь», лексему «око», слова типа «человечество» (от которых уже отсекли «эств») и некоторые другие, собирает слова типа «купец», «купца» и «купеческий», но также превращает слова типа «меч» в слова типа «мек»;

    35. При длине токена больше трёх символов стемминг «эк» и «ок» в «к» (как наиболее частого случая беглого гласного – суффиксов «ок» и «эк»; искажение слов «меч», «срок», «уро́к», «порок», «пророк» и им подобных несущественно, ибо не разрывает словоформы одной лексемы и едва ли смешивает формы разных лексем, кроме пары «уро́к (им. п., р. п. – уро́ка)» – «у́рок (род. п. мн. ч. от слова у́рка6)», где, однако, стеммер, как программа, не имеющая словаря, и должна объединять их, иначе форма «урок» (естественно, с неизвестным местом ударения) оторвётся от одной из лексем;

    36. При длине токена больше четырёх символов стемминг «рш» в «р»; ограничение на длину «спасает» слово «ёрш»;

    37. При длине токена больше пяти символов стемминг «ст,эн» в «стн» и усечение «ост,» и «остн» в указанном порядке; ограничение на длину «спасает» слова «стена», «кость» и «костный»;

    38. При длине токена больше шести символов усечение «эст,», «эстн»; ограничение на длину «спасает» слова вроде «месть», «честный»;

    39. При длине токена больше семи символов усечение «ыт,эл,» и «т,эл,» – очевидно, в указанном порядке; ограничение на длину спасает слово «китель»;

    40. При длине токена больше восьми символов усечение «ыт,эл,н» и «т,эл,н» – очевидно, в указанном порядке; ограничение на длину спасает слово «котельный»;

    41. Усечение «т,», которое теперь можно производить свободно, поскольку слова с суффиксом «ость» (ост,) и его вариантом «есть» (эст,) уже усечены независимо;

    42. Стемминг «мн,» и «мн» в «м» (это не только объединяет формы местоимения «я» с формами лексемы «мой», но и объединяет формы лексем наподобие «умный» – ранее, на шаге 32 побочным его эффектом было усечение кратких форм таких лексем в мужском роде единственного числа до «м», и теперь во избежание разрыва лексемы их необходимо объединить таким образом);

    43. Стемминг «шл»→«ш» (см. шаг 24);

    44. Усечение «,» и «,н» в указанном порядке;

    45. Стемминг «дам», «даш», «даст» и «дад» в «д», а «йэм», «йэш» и «йэст» в «йэд» и конец работы функции собственно выделения формальной основы; сохранение строки из полученных основ, разделённых пробелами, для последующей записи их в промежуточный файл output.txt;

    46. Начало работы функции обратного конвертирования токена: сочетания «,» или «й» с ы, э, а и у превращаются в и, е, я и ю соответственно;

    47. Оставшиеся запятые заменяются на мягкие знаки;

    48. После заднеязычных, ц7 и шипящих ы→и, э→е;

    49. Замена латинской o после мягкого знака на русскую о (отмена, таким образом, замены русской о на латинскую в шаге 5);

    50. Замена в каждом токене первой буквы на заглавную (для аббревиатур вроде «МГУ» таким образом восстанавливается исходный вид из неудачного «мГУ») и конец работы функции обратного конвертирования токена; сохранение строки из полученных токенов, разделённых пробелами, для последующей записи их в файл clearoutput.txt, который, собственно, и является основным результатом работы стеммера;

    51. Заполнение файлов tokens.txt, output.txt и clearoutput.txt.

    Глава 2. Теоретически предсказуемые ошибки стеммера Z


    Ошибки стеммеров сводятся к двум типам ошибок: недостемминг (англ. understemming) – сведение к разным основам разных словоформ одной лексемы или деривационно связанных лексем, подобные которым стеммер обычно сводит к одной основе (например, если стеммер обычно отсекает суффикс «ость», то разделение форм «злой» и «злость» также является недостеммингом) – и перестемминг (англ. overstemming) – сведение к одной основе разных лексем.

    Стеммер Z предположительно допускает только три частных случая недостемминга: глаголы «идти» и «ехать» вместе со своими приставочными производными сводятся к разным основам в формах настоящего/будущего и прошедшего времени и инфинитива, то же самое касается глагола «есть» и его производных, к разным основам приводятся припредложные и неприпредложные формы местоимений – и три общих: переходное смягчение в первом лице единственного числа глаголов второго спряжения с основой не на губные (запретить – запрещу, разбудить – разбужу, etc.), беглые гласные, не входящие в суффиксы «ок/ек/ёк» и «ец», и слова с суффиксом «ёнок», во множественном числе имеющими удаляемый стеммером Z суффикс «ат» на его месте. С общими случаями, очевидно, бороться более-менее бесполезно, поскольку первые два – это сложные и неоднозначные сандхи (например, щ может восходить к ск, ст или т, ж – к г, з или д; сон имеет беглый гласный (род. п. сна), а стон – нет (род. п. стона)), а третий – просто типично словарный случай. Частные случаи рассмотрим подробнее.

    Первый случай (глаголы «идти» и «ехать» и их приставочные производные) ожидаем и несуществен – это супплетивные глаголы, и трудно представить себе стеммер, который собирает их: это типичный случай словарной информации о том, что основы относятся к одной лексеме, а стеммер, как было сказано в определении в начале данной работы, не использует словарную информацию.

    Второй случай (глагол «есть») получает основу «ед» для настоящего времени и инфинитива, но, увы, «е» для прошедшего. Ввиду многообразия глаголов, заканчивающихся на «ел» в прошедшем времени, попытки перехватить это могут привести к очень серьёзному недостеммингу других глаголов. Связанным с этим глаголом недостатком также является то, что сегмент «есть» однозначно трактуется как форма глагола «есть» (и получает основу «ед»). Однако стеммер, как следует из его определения, не устраняет омонимию, поэтому это неустранимая ошибка.

    От обработки третьего случая было решено отказаться, поскольку иначе произошёл бы недостемминг формы «нем» от слова «немой», что, как кажется, является более серьёзной ошибкой (к основе «н» сводится и само слово «немой», но, если бы формам местоимения третьего лица вручную усекалось начальное «н», форма «нем» также свелась бы к «й», в то время как остальные формы слова «немой» продолжали бы сводиться к «н»); если же, напротив, приписывать и тем, и другим формам (а также формам «он», «она», «оно», «они») формальную основу «н», они совпадут и с формами местоимения первого лица множественного числа, и с формами прилагательного «немой», так что выбор между предсказуемым недостеммингом и непредсказуемым перестеммингом был сделан в этом случае в пользу первого. Это можно было бы решить обработкой контекста, но стеммер по определению не должен обрабатывать контекст точно так же, как не должен использовать словарную информацию, о чём уже говорилось в определении в начале данной работы.

    Перестемминг же происходит часто, но во многих случаях он обоснован: так, поскольку у слов «стать» и «сталь» есть омоформа «стали», стеммер во избежание недостемминга должен свести форму «стали» к основе, удовлетворяющей и слову «сталь», и слову «стать», из чего следует совпадение основ этих слов.

    Наконец, стоит отметить, что причастия на -нн не сводятся к породившим их глаголам, но, поскольку там частотна омонимия с прилагательными, возможно, это и к лучшему, и это даже не является в строгом смысле недостеммингом, если считать причастия другой лексемой, чем производящие их глаголы, как, например, в [ CITATION Плу11 \l 1049 ], а не как в [ CITATION Зал80 \l 1049 ] (хотя прочие причастия сводятся к основам глаголов).

    Глава 3. Сравнение стеммера Z со стеммером Портера Snowball


    Для русского языка ранее неоднократно старались разработать систему стеммера (а также другие системы морфологического анализа, не отвечающие данному определению стеммера). Наиболее известной, пожалуй, является [CITATION Por16 \l 1049 ], пытающаяся перенести на почву русской морфологии закономерности стеммера Портера, разработанного для английского языка, с заменой аффиксов на русские, но у неё есть ряд очевидных недостатков, устранённых в предлагаемом стеммере Z: сведение слов в нижний регистр заставляет аббревиатуры обрабатываться так же, как и обычные слова; так называемые йотированные гласные (е, ю, я) и «и» не обрабатываются специально, считаясь полноценной частью алфавита, что может разводить схожие явления; деривационные суффиксы ограничены суффиксом «ость» (по вышеупомянутым орфографическим причинам записанным как «ость» и «ост»); глагольный суффикс «ова/ева» не удаляется, что вызывает разрыв с формами настоящего времени, в которых он переходит в «у» и удаляется обоими стеммерами. Спорным решением является применение всех правил только после первой гласной буквы (долженствующее уменьшить усечение слов наподобие «спам» и «кровать», но в то же время лишающее обработки формы «дам», «злой», «льда», etc.), и в стеммере Z это ограничение сменено более слабым ограничением невозможности основ нулевой длины.

    С перечисленными в прошлой главе недостатками стеммера Z стеммер Портера также не борется. Отчасти, возможно, это связано с желанием его авторов иметь «лингвистически правдоподобные» основы, отсутствующем в стеммере Z (неважно, имеет ли, например, словоформа «мечом» формальную основу «меч», «мек» или «мьк», если эта основа однозначно идентифицирует все формы лексемы «меч» и не идентифицирует формы других лексем).

    Заключение


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

    Список литературы


    CITATION Ков16 \l 1049 : , (Коваленко),

    CITATION Por16 \l 1049 : , (Porter),

    CITATION Зализняк \l 1049 : , (Зализняк, 1967),

    CITATION Зал80 \l 1049 : , (Зализняк, 1980),

    CITATION Плу11 \l 1049 : , (Плунгян, 2011),

    CITATION Зал80 \l 1049 : , (Зализняк, 1980),

    CITATION Por16 \l 1049 : , (Porter),


    Приложение: код стеммера Z в языке программирования C#


    using System;

    using System.IO;

    using System.Collections.Generic;
    namespace Stemmer

    {

    class Program

    {

    static string DeleteEnd(string s, int l)

    {

    if (s.Length > l)

    {

    return s.Substring(0, s.Length - l);

    }

    else { return ""; } //чисто техническая мера, чтобы, если по ошибке l больше длины, строка была пустой, а не ошибочной

    }

    static string Stemming(string s, string so, string sn)

    {

    if (s.EndsWith(so))

    {

    return (DeleteEnd(s, so.Length) + sn);

    }

    else { return s; }

    }

    static string Cut(string s, string ss)

    {

    if (s.Length > ss.Length)

    {

    return Stemming(s, ss, "");

    }

    else { return s; }

    }

    static string Tokeniser(string input)

    {

    char[] dividers = new char[] { '!', '?', '.', ' ', '-', ',', '«', '»', '(', ')', '/', ':', '_', '@', '…', '–', '‘', '’' };

    input = input.Replace("Ток-шоу", "токшоу"); //не в конверторе токенов, чтобы обогнать Split

    input = input.Replace("ток-шоу", "токшоу"); //поэтому нужно отдельно обрабатывать заглавные и строчные

    input = input.Replace("Ток шоу", "токшоу");

    input = input.Replace("ток шоу", "токшоу");

    input = input.Replace("Хот-дог", "хотдог");

    input = input.Replace("хот-дог", "хотдог");

    input = input.Replace("хот дог", "хотдог");

    input = input.Replace("Хот дог", "хотдог");

    string[] Tokens = input.Split(dividers, StringSplitOptions.RemoveEmptyEntries);

    for (byte i = 0; i < Tokens.Length; i++)

    {

    Tokens[i] = ConvertToken(Tokens[i]);

    }

    return String.Join(" ", Tokens);

    }

    static char[] space = new char[] { ' ' };

    static string ConvertToken(string s)

    {

    string s1 = s[0].ToString().ToLower() + s.Substring(1);

    if ((s1.IndexOf('Ь') >= 0) || (s1.IndexOf('Ъ') >= 0)) //Мягкий и твёрдый знаки гарантируют, что речь идёт не об аббревиатуре.

    {

    s1 = s1.ToLower();

    }

    if (s1 == "тот") { s1 = "т"; } //все остальные if-ы обрабатывают конкретные словоформы и могут быть убраны

    if (s1 == "этот") { s1 = "эт"; }

    if (s1 == "мы") { s1 = "ны"; }

    if (s1 == "семян") { s1 = "семен"; }

    if (s1 == "стремян") { s1 = "стремен"; }

    if (s1 == "один") { s1 = "одн"; }

    if (s1 == "нас") { s1 = "нах"; }

    if (s1 == "вас") { s1 = "вах"; }

    if (s1 == "ты") { s1 = "тоб"; }

    if (s1 == "много") { s1 = "многое"; } //иначе распознаётся как прилагательные вроде "большого"

    if (s1 == "тебя") { s1 = "тобя"; }

    if (s1 == "тебе") { s1 = "тобе"; }

    if (s1 == "я") { s1 = "мнь"; }

    if (s1 == "он") { s1 = "й"; }

    if (s1 == "она") { s1 = "йа"; }

    if (s1 == "оно") { s1 = "йэ"; }

    if (s1 == "они") { s1 = "й"; }

    if ((s1 == "меня") || (s1 == "зол") || (s1 == "весь"))

    /* || (s1 == "день") || (s1 == "пень") || (s1 == "вошь"), etc.

    "Меня" не содержит беглого гласного и попало случайно, лишь потому, что его надо свести к основе "мн".

    "Весь" добавлено, чтобы не произошло разрыва "ве"/"в" при отрывании "сь". Аналогично "зол", "зо"/"зл" и "л".*/

    {

    s1 = s1[0] + s1.Substring(2);

    }

    if ((s1 == "уши") || (s1 == "ушей") || (s1 == "ушам") || (s1 == "ушами") || (s1 == "ушах"))

    {

    s1 = s1[0] + "х" + s1.Substring(2);

    }

    if (s1 == "двумя") { s1 = "двуми"; }

    s1 = s1.Replace("ё", "е"); //деёфикация, чтобы не разводились формы вроде "увлёк" и "увлекла"

    s1 = s1.Replace("и", ",ы");

    s1 = s1.Replace("я", ",а");

    s1 = s1.Replace("ю", ",у");

    s1 = s1.Replace("е", ",э");

    s1 = s1.Replace("ьо", "Ьo"); //NB! Русская о заменяется на латинскую, чтобы не исказить слова вроде "каудильо".

    s1 = s1.Replace("ь", ",");

    s1 = s1.Replace("Ь", "ь");

    s1 = s1.Replace(",,", ",й");

    if (s1.Substring(Math.Min(s1.Length - 1, 2)) != "ур,ы")

    {

    s1 = s1.Replace("к,", "к");

    s1 = s1.Replace("ж,", "ж");

    }

    s1 = s1.Replace("г,", "г");

    s1 = s1.Replace("х,", "х");

    s1 = s1.Replace("ш,", "ш");

    s1 = s1.Replace("ц,", "ц");

    s1 = s1.Replace("ч,", "ч");

    s1 = s1.Replace("щ,", "щ");

    s1 = s1.Replace("ъ,", "ъй");

    s1 = s1.Replace("й,", "йй");

    s1 = s1.Replace("а,", "ай");

    s1 = s1.Replace("о,", "ой");

    s1 = s1.Replace("у,", "уй");

    s1 = s1.Replace("э,", "эй");

    s1 = s1.Replace("ы,", "ый");

    if (s1[0] == ',') { s1 = "й" + s1.Substring(1); } //Для слов, начинающихся на йотированный гласный. Не отдельные слова, не подлежит убиранию.

    s1 = s1.Replace("дву", "два");

    return s1;

    }

    static string Stem(string s)

    {

    string s1 = s;

    if (s1 == "йы") { return "и"; } //чтобы союз "и" не объединялся с местоимением третьего лица

    else

    {

    if ((s1 == "йыл,ы") || (s1 == "йыл,")) { return "ил,"; }

    else {

    s1 = Stemming(s1, "кто", "к");

    s1 = Stemming(s1, "что", "ч");

    s1 = Cut(s1, "айас,а");

    s1 = Cut(s1, "уйус,а");

    s1 = Cut(s1, "ым,ыс,а");

    s1 = Cut(s1, "м,ы");

    if (s1.Length > 4)

    {

    s1 = Cut(s1, "йу");

    s1 = Cut(s1, "йа");

    }

    s1 = Cut(s1, "ах");

    s1 = Cut(s1, "ых");

    s1 = Cut(s1, "а");

    s1 = Cut(s1, "у");

    s1 = Cut(s1, "ы");

    s1 = Cut(s1, "э");

    s1 = Cut(s1, "ый");

    s1 = Cut(s1, "эй");

    s1 = Cut(s1, "ой");

    if (s1.Length > 3)

    {

    s1 = Cut(s1, "ом");

    s1 = Cut(s1, "ым");

    }

    s1 = Cut(s1, "ого");

    s1 = Cut(s1, "эго");

    s1 = Cut(s1, "эв");

    s1 = Cut(s1, "ов");

    if (s1.Length > 3)

    {

    s1 = Cut(s1, "ам");

    s1 = Cut(s1, "эм");

    }

    s1 = Cut(s1, "с,");

    s1 = Stemming(s1, "шэл", "шл"); //после деёфикации "шол" -> "шл" не нужно.

    s1 = Cut(s1, "ого");

    s1 = Cut(s1, "эго");

    s1 = Cut(s1, "о");

    s1 = Cut(s1, "а");

    s1 = Cut(s1, "ы");

    if (s1.Length > 2) { s1 = Cut(s1, "л"); } //ограничение на длину, чтобы "злой" объединялось со "злость"

    if (s1.Length > 3) { s1 = Cut(s1, "л,"); } //не существует слов с последовательностью льл, а, например, с лле есть

    s1 = Cut(s1, "ы");

    s1 = Cut(s1, "э");

    s1 = Cut(s1, "о");

    s1 = Cut(s1, "у");

    s1 = Cut(s1, "а");

    s1 = Cut(s1, "от,");

    s1 = Cut(s1, "эт,");

    s1 = Cut(s1, "ыт,");

    s1 = Cut(s1, "ат,");

    s1 = Cut(s1, "ут,");

    s1 = Cut(s1, "ый");

    if (s1.Length > 3)

    {

    s1 = Cut(s1, "ом");

    s1 = Cut(s1, "ам"); //для случаев вроде "имамам"

    s1 = Cut(s1, "эм");

    s1 = Cut(s1, "ым");

    s1 = Cut(s1, "эт");

    s1 = Cut(s1, "ыт");

    s1 = Cut(s1, "ут");

    s1 = Cut(s1, "эш");

    s1 = Cut(s1, "ош");

    s1 = Cut(s1, "уй");

    s1 = Cut(s1, "ай");

    s1 = Cut(s1, "ой");

    s1 = Cut(s1, "эй");

    s1 = Cut(s1, "от"); //чтобы спасти причастия на "от" от различения кратких и части полных форм

    s1 = Cut(s1, "ущ");

    s1 = Cut(s1, "ащ");

    s1 = Cut(s1, "ат");

    }

    s1 = Stemming(s1, "шэдш", "ш"); //не "шл", потому что в приставочных у "шл" л отрывается;

    s1 = Cut(s1, "вш");

    s1 = Cut(s1, "эств");

    s1 = Cut(s1, "ств");

    s1 = Cut(s1, "в"); //собирает не только деепричастия, но и глаголы вроде "жить", "плыть", "слыть"

    s1 = Cut(s1, "о");

    s1 = Cut(s1, "э");

    s1 = Cut(s1, "й");

    s1 = Stemming(s1, "дш", "д");

    s1 = Stemming(s1, "тш", "т");

    s1 = Stemming(s1, "бш", "б");

    s1 = Stemming(s1, "пш", "п");

    s1 = Stemming(s1, "гш", "г");

    s1 = Stemming(s1, "кш", "к");

    s1 = Stemming(s1, "сш", "с");

    s1 = Stemming(s1, "нн", "н");

    s1 = Stemming(s1, "н,эн", "н");

    s1 = Stemming(s1, "вл,эч", "вл,эк");

    s1 = Stemming(s1, "л,эч", "л,эг");

    s1 = Stemming(s1, "стр,ыч", "стр,ыг");

    s1 = Stemming(s1, "моч", "мог");

    s1 = Stemming(s1, "б,эр,эч", "б,эр,эг");

    s1 = Stemming(s1, "ст,эр,эч", "ст,эр,эг");

    if (s1.Length > 5)

    {

    s1 = Stemming(s1, "ст,ыч", "ст,ыг"); //Помещая сюда, спасаем имя собственное "Стич". Бесприставочного глагола *стичь не существует.

    }

    s1 = Stemming(s1, "м,эн", "м,");

    s1 = Stemming(s1, "жэск", "г");

    s1 = Cut(s1, "эск");

    s1 = Cut(s1, "ск");

    s1 = Stemming(s1, "л,эц", "л,к");

    s1 = Stemming(s1, "ч", "к");

    s1 = Stemming(s1, ",эц", "к");

    s1 = Stemming(s1, "ц", "к");

    if (s1.Length > 3)

    {

    s1 = Stemming(s1, "эк", "к");

    s1 = Stemming(s1, "ок", "к");

    if (s1.Length > 4)

    {

    s1 = Stemming(s1, "рш", "р"); //Помещая сюда, спасаем слово "ёрш".

    if (s1.Length > 5)

    {

    s1 = Stemming(s1, "ст,эн", "стн"); //Помещая сюда, спасаем слово "стена".

    s1 = Cut(s1, "ост,");

    s1 = Cut(s1, "остн");

    if (s1.Length > 6)

    {

    s1 = Cut(s1, "эст,");

    s1 = Cut(s1, "эстн");

    if (s1.Length > 7)

    {

    s1 = Cut(s1, "ыт,эл,");

    s1 = Cut(s1, "т,эл,");

    if (s1.Length > 8) //"котельный"

    {

    s1 = Cut(s1, "ыт,эл,н");

    s1 = Cut(s1, "т,эл,н");

    }

    }

    }

    }

    }

    }

    s1 = Cut(s1, "т,"); // так поздно, чтобы собирались всякие "ость" и "есть"; большинство инфинитивов съедятся с гласными раньше

    s1 = Stemming(s1, "мн,", "м");

    s1 = Stemming(s1, "мн", "м");

    s1 = Stemming(s1, "шл", "ш"); //чтобы собрать "шедший" и "шёл";

    s1 = Cut(s1, ",");

    s1 = Cut(s1, ",н");

    s1 = Stemming(s1, "дам", "д");

    s1 = Stemming(s1, "даш", "д");

    s1 = Stemming(s1, "даст", "д");

    s1 = Stemming(s1, "дад", "д");

    s1 = Stemming(s1, "йэст", "йэд");

    s1 = Stemming(s1, "йэш", "йэд");

    s1 = Stemming(s1, "йэм", "йэд");

    return s1;

    }

    }

    }

    static string TheStemmer(string tokens)

    {

    string[] Tokens = tokens.Split(space); //Конвертор токенов не создаёт пустых строк;

    for (byte i = 0; i < Tokens.Length; i++)

    {

    Tokens[i] = Stem(Tokens[i]);

    }

    return String.Join(" ", Tokens);

    }

    static string BackConverting(string s)

    {

    string s1 = s;

    s1 = s1.Replace(",ы", "и");

    s1 = s1.Replace(",э", "е");

    s1 = s1.Replace(",а", "я");

    s1 = s1.Replace(",у", "ю");

    s1 = s1.Replace("йы", "и");

    s1 = s1.Replace("йэ", "е");

    s1 = s1.Replace("йа", "я");

    s1 = s1.Replace("йу", "ю");

    s1 = s1.Replace(",", "ь");

    s1 = s1.Replace("жы", "жи");

    s1 = s1.Replace("шы", "ши");

    s1 = s1.Replace("чы", "чи");

    s1 = s1.Replace("цы", "ци");

    s1 = s1.Replace("щы", "щи");

    s1 = s1.Replace("кы", "ки");

    s1 = s1.Replace("хы", "хи");

    s1 = s1.Replace("гы", "ги");

    s1 = s1.Replace("цэ", "це");

    s1 = s1.Replace("жэ", "же");

    s1 = s1.Replace("шэ", "ше");

    s1 = s1.Replace("чэ", "че");

    s1 = s1.Replace("щэ", "ще");

    s1 = s1.Replace("кэ", "ке");

    s1 = s1.Replace("хэ", "хе");

    s1 = s1.Replace("гэ", "ге");

    s1 = s1.Replace("ьo", "ьо"); //Восстанавливаем русскую о в словах типа "каудильо";

    string[] tk = s1.Split(space);

    for (byte i = 0; i < tk.Length; i++)

    {

    if (tk[i].Length > 1)

    {

    tk[i] = tk[i][0].ToString().ToUpper() + tk[i].Substring(1);

    }

    else

    {

    tk[i] = tk[i].ToUpper();

    }

    }

    return String.Join(" ", tk);

    }

    static void Main()

    {

    List tokens = new List();

    List outputs = new List();

    List clearoutputs = new List();

    using (StreamReader sr = File.OpenText(@"..\..\test.txt"))

    {

    string s;

    while ((s = sr.ReadLine()) != null)

    {

    s = Tokeniser(s);

    tokens.Add(s);

    s = TheStemmer(s);

    outputs.Add(s);

    s = BackConverting(s);

    clearoutputs.Add(s);

    }

    }

    using (StreamWriter stok = File.CreateText(@"..\..\tokens.txt"))

    {

    foreach (string s in tokens)

    {

    stok.WriteLine(s);

    }

    }

    using (StreamWriter sst = File.CreateText(@"..\..\output.txt"))

    {

    foreach (string s in outputs)

    {

    sst.WriteLine(s);

    }

    }

    using (StreamWriter scst = File.CreateText(@"..\..\clearoutput.txt"))

    {

    foreach (string s in clearoutputs)

    {

    scst.WriteLine(s);

    }

    }

    tokens.Clear();

    outputs.Clear();

    clearoutputs.Clear();

    }

    }

    }

    1 Тем самым система Stemka [ CITATION Ков16 \l 1049 ] не является стеммером, так как использует машинное обучение.

    2 Хотя в некоторых случаях фактически порядок некоторых правил несуществен.

    3 Сама запись со знаком → может обозначать любое превращение строки a в строку b, не только стемминг.

    4 То есть инструкция, сводящаяся к «если входная строка равна «йы», возвращай «и», если входная строка равна «йыл,ы» или «йыл,», возвращай «иль», иначе выполняй шаги 11-45».

    5 Под «спасением» здесь и далее подразумевается предотвращение изменения по правилу, описываемому на этом шаге.

    6 Это слово означает сидящего в тюрьме и относится к блатному жаргону.

    7 Словами «цыган», «цыплёнок» и подобными можно пренебречь: эта операция не смешает их с какими-то другими словами ввиду отсутствия слов вида «циган», «циплёнок», etc.


    написать администратору сайта