МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ Федеральное государственное бюджетное образовательное учреждение

advertisement
МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ
Федеральное государственное бюджетное образовательное учреждение
высшего профессионального образования
‹‹КУБАНСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ››
(ФГБОУ ВПО ‹‹КубГУ ››)
Кафедра вычислительных технологий
ОПРЕДЕЛЕНИЕ ЧАСТЕЙ РЕЧИ РУССКОГО ЯЗЫКА С
ИСПОЛЬЗОВАНИЕМ НЕЙРОННЫХ СЕТЕЙ
Ерёмин Н. С.
Краснодар 2014
СОДЕРЖАНИЕ
Введение ................................................................................................................. 3
1. Машинное распознавание частей речи .......................................................... 4
2. Нейронная сеть и анализ слов ......................................................................... 7
2.1 Общее устройство нейронных сетей ......................................................... 7
2.2. Линейная классификация и метод опорных векторов ............................ 9
2.3. Метод опорных векторов ......................................................................... 10
3. Использованный инструментарий ................................................................ 13
3.1. Библиотека LIBLINEAR ................................................................... 13
3.2. Национальный корпус русского языка ........................................... 13
4. Разработка анализатора частей речи ............................................................. 15
Заключение .......................................................................................................... 19
ПРИЛОЖЕНИИЕ А. Исходный код программы ............................................. 21
2
ВВЕДЕНИЕ
Машинная обработка естественного языка – задача, набирающая с
течением
времени
всё
большую
актуальность.
С
повсеместным
распространением сети Интернет выросло и количество информации
описанной естественными языками, а также уровень её доступности.
Возникает проблема быстрого поиска необходимой информации среди
большого объёма текста. Одно из решений подобной проблемы – создание
системы автореферирования.
Автореферирование – выделение из текста смысловой, эмотивной,
оценочной и прочей информации. Первым этапом автореферирования
является
морфологический
разбор.
Зачастую
алгоритмы
морфологического разбора опираются на предопределённый словарь
словообразовательных основ, что значительно ограничивает возможности
алгоритма. Более эффективной альтернативой является нейронная сеть,
обученная распознавать части речи с помощью наборов размеченных
текстов. Такой подход позволяет избежать зависимости от вручную
заполненного словаря и расширить диапазон применения системы.
Цель курсовой работы – изучение методов машинного анализа
естественного языка и разработка
упрощённого
морфологического
анализатора для русского языка на основе нейронной сети МаккаллокаПиттса.
В
ходе
выполнения
курсовой
работы
были
задействованы
следующие ресурсы и инструментарий:
Язык реализации – Python 3.4.
LIBLINEAR 1.94 – библиотека для реализации алгоритмов линейной
классификации.
Случайная выборка размеченных предложений из Национального
корпуса русского языка для обучения нейронной сети.
3
1. Машинное распознавание частей речи
Типичная задача извлечения информации – автореферирование
документов. Производится череда просмотров текста документа, в ходе
просмотров
последовательно
выполняются
морфологический,
синтаксический и семантический анализы. На основе полученных данных
выделяются наиболее значимые части документа и компонуются в
реферат.
Морфологический анализ – первый этап работы любой системы
автореферирования, в ходе которого определяются свойства слова, в
частности принадлежность его к какой-либо части речи. Методы,
применяемые
в
машинном
морфологическом
анализе,
зависят
от
требований к точности выполняемого анализа, предъявляемых системой
обработки. В самом простом случае морфологический анализ сводится к
определению вероятного окончания слова путем сопоставления с
предопределенным словарем окончаний, где каждому окончанию с
некоторой вероятностью приписана характерная часть речи. Часто
возникает ситуация, когда несколько вероятных окончаний из словаря
ассоциируются с анализируемым словом. Можно встретить и обратную
ситуацию – отсутствие окончания у слова (характерно для многих имен
существительных, например, «язык», «анализ», «речь» и др.). Кроме того
необходимо иметь словарь исключений для выделения неизменяемых
групп слов. В этом словаре, в отличие от словаря изменяемых слов, каждая
группа
представлена
не
окончанием,
а
полным
перечнем
слов.
Морфологический анализ по окончанию срабатывает с ошибками в
ситуациях, когда слово отсутствует в словаре исключений, либо ему
приписывается несколько окончаний, характерных для разных частей речи,
либо у слова вообще нет окончания, что существенно сказывается на
качестве работы всей системы в целом. Так если система ошибочно
выявила окончание «-ан» у слова «кран», то будет выполняться поиск по
4
началу слова «кр*», в результате чего будут найдены документы, как
содержащие слово «кран» или его возможные словоформы («краны»,
«краном» и т.д.), так и содержащие другие слова, начинающиеся на «кр*»
(«краб», «кров» и т.д.). Поэтому такой примитивный подход используют
преимущественно при реализации поисковых систем. Тем более такой
метод не годится для более сложных задач обработки текста, таких,
например, как выявление противоречий.
Более
совершенным
является
метод
использующий
словарь
словообразовательных основ, допускает ошибки значительно реже,
поскольку опирается на предопределенный словарь основ, а не окончаний.
В словаре каждой основе приписан набор постоянных морфологических
признаков (часть речи, вид, переходность и др.). При анализе слова,
соответствующая основа ищется в словаре, затем по ней определяются
постоянные морфологические признаки слова. Окончание, по которому
определяются переменные морфологические признаки (падеж, род, число
и др.), выделяется путём отсечения основы и суффиксов. Однако, подобная
реализация обладает серьёзным недостатком, значительно влияющим на
качество разбора и ограничивающим область применения анализатора –
полнота словаря основ. В ситуации, когда подходящая основа не найдена
в словаре, анализ слова либо выполняется приближенным методом,
основанным на окончаниях и рассмотренным ранее, либо прекращается
безрезультатно. Также значительные трудности связаны с составлением
словаря основ, так как оно выполняется вручную. При этом подходе
возникает дополнительная задача – постоянное обновление и пополнение
словаря. Это необходимо в связи с образованием новых терминов в
литературе (особенно быстро новые понятия образовываются в сфере
высоких технологий) [1].
Морфологический анализ также используется при выполнении такой
важной задачи как выделение канонической (нормализованной) формы
слова. Например, зачастую инфинитивная форма глагола является
5
канонической формой для причастий и деепричастий, образованных от
этого глагола. Решение этой задачи реализуется с использованием
анализаторов на базе словаря словообразовательных основ путем введения
ассоциативных таблиц суффиксов. В этих таблицах суффикс канонической
формы ставится в соответствие некоторому суффиксу. Каноническая
форма получается через выделение основы, суффикса и окончания слова и
замену суффикса на суффикс канонической формы при помощи
ассоциативной таблицы. Затем найденный суффикс объединяют со
словообразовательной основой обрабатываемого слова. Очевидно, для
выполнения такой процедуры кроме таблицы суффиксов необходимо
иметь мощный словарь словообразовательных основ (либо словарь
канонических форм с явно выделенными суффиксами) [2].
Самым совершенным на данный момент подходом является
использование нейронной сети обученной процедуре морфологического
анализа, позволяющий избежать использования вручную заполненных
словарей словообразующих основ, к тому же нейронные сети успешно
применяются при реализации задач распознавания и классификации. Для
того чтобы определить грамматические характеристики слов без словаря,
был предложен принцип аналогии. Он основан на том, что существует
сильная корреляционная связь между грамматическими характеристиками
слов и буквенным составом их концов. Например: организация,
приватизация, концентрация имеют ж. р., им. п. и ед. ч.; работают,
понимают, привлекают - это глаголы в 3-ем лице мн. ч. и т. д.
Принцип аналогии проверялся на ряде индоевропейских языков:
(русский, болгарский, латышский, испанский, английский) и оказался
эффективным. Сначала он применялся для определения грамматических
характеристик слов, не включенных в машинный словарь. Затем возникла
идея при проведении морфологического анализа полностью отказаться от
машинного словаря [3].
6
2. Нейронная сеть и анализ слов
2.1. Общее устройство нейронных сетей
Искусственная нейронная сеть – математическая модель, а также её
программная или аппаратная реализация, построенная по принципу
организации и функционирования биологических нейронных сетей – сетей
нервных клеток живого организма.
Широкий круг задач, решаемый НС, не позволяет в настоящее время
создавать
универсальные,
мощные
сети,
вынуждая
разрабатывать
специализированные НС, функционирующие по различным алгоритмам.
Несмотря на существенные различия, отдельные типы НС обладают
несколькими общими чертами.
Во-первых, основу каждой НС составляют относительно простые, в
большинстве случаев – однотипные, элементы (ячейки), имитирующие
работу нейронов мозга. Каждый искусственный нейрон характеризуется
своим текущим состоянием по аналогии с нервными клетками головного
мозга, которые могут быть возбуждены или заторможены. Он обладает
группой синапсов – однонаправленных входных связей, соединенных с
выходами других нейронов, а также имеет аксон – выходную связь
данного нейрона, с которой сигнал (возбуждения или торможения)
поступает на синапсы следующих нейронов. Общий вид нейрона приведен
на рисунке 1. Каждый синапс характеризуется величиной синаптической
связи или ее весом wi, который по физическому смыслу эквивалентен
электрической проводимости.
Текущее состояние нейрона определяется, как взвешенная
сумма его входов:
n
s   x i  wi
i 1
7
(1)
Выход нейрона есть функция его состояния (активационная
функция):
y = f(s)
(2)
Рисунок 1 – Искусственный нейрон
Во-вторых, в любой нейронной сети принцип параллельной
обработки сигналов достигается путем объединения большого числа
нейронов в так называемые слои и соединения определенным образом
нейронов различных слоев, а также, в некоторых конфигурациях, и
нейронов одного слоя между собой, причем обработка взаимодействия
всех нейронов ведется послойно [4].
В качестве примера простейшей НС рассмотрим трехнейронный
перцептрон (рисунок. 2), то есть такую сеть, нейроны которой имеют
активационную функцию в виде единичного скачка. На n входов
поступают некие сигналы, проходящие по синапсам на 3 нейрона,
образующие единственный слой этой НС и выдающие три выходных
сигнала:
n

y j  f  xi  wij  , j=1...3
 i 1

8
(3)
Рисунок 2 – Однослойный перцептрон
Нейронные сети не программируются в привычном смысле этого
слова, они обучаются. Возможность обучения — одно из главных
преимуществ нейронных сетей перед традиционными алгоритмами.
Технически обучение заключается в нахождении коэффициентов связей
между нейронами. В процессе обучения нейронная сеть способна выявлять
сложные зависимости между входными данными и выходными, а также
выполнять обобщение. Это значит, что в случае успешного обучения сеть
сможет вернуть верный результат на основании данных, которые
отсутствовали
в
обучающей
выборке,
а
также
неполных
и/или
«зашумленных», частично искаженных данных [5].
2.2. Линейная классификация и метод опорных векторов
В данной работе для реализации распознавания частей речи был
задействован метод опорных векторов, один из алгоритмов линейной
классификации.
Линейный классификатор — алгоритм классификации, основанный
на построении линейной разделяющей поверхности. В случае двух классов
разделяющей поверхностью является гиперплоскость, которая делит
9
пространство признаков на два полупространства. В случае большего
числа классов разделяющая поверхность кусочно-линейна.
Пусть объекты описываются n числовыми признаками
fj: X → R; j = 1, … ,n. Тогда пространство признаковых описаний объектов
есть X = Rn. Пусть Y — конечное множество номеров (имён, меток)
классов. В случае двух классов (Y={-1,+1}) линейным классификатором
называется алгоритм классификации 𝑎: 𝑋 → 𝑌 вида
𝑛
𝑎(𝑥) = 𝑠𝑖𝑔𝑛 (∑ 𝑤𝑗 𝑥 𝑗 − 𝑤0 ) = 𝑠𝑖𝑔𝑛(< 𝑤, 𝑥 > −𝑤0 )
(4)
𝑗=1
где wj – вес j-го признака, w0 – порог принятия решения, w = (w0, w1,
…, wn) – вектор весов, <x, w> - скалярное произведение признакового
описания объекта на вектор весов. Предполагается, что искусственно
введён «константный» нулевой признак: f0(x) = -1.
В
случае
произвольного
количества
классов
линейный
классификатор определяется выражением
𝑛
𝑎(𝑥, 𝑤) = argmax ∑ 𝑤𝑦𝑗 𝑓𝑗 (𝑥) = argmax ⟨𝑥, 𝑤𝑦 ⟩
𝑦∈𝑌
(5)
𝑦∈𝑌
𝑗=0
2.3. Метод опорных векторов
Метод опорных векторов (англ. SVM, support vector machine) – набор
схожих алгоритмов обучения с учителем, использующихся для задач
классификации и регрессионного анализа. Принадлежит к семейству
линейных классификаторов, является одной из наиболее популярных
методологий обучения по прецедентам. Особым свойством метода
опорных векторов является непрерывное уменьшение эмпирической
ошибки классификации и увеличение зазора, поэтому метод также
известен как метод классификатора с максимальным зазором.
10
Основная идея метода — перевод исходных векторов в пространство
более высокой размерности и поиск разделяющей гиперплоскости с
максимальным
зазором
гиперплоскости
разделяющей
строятся
наши
в
этом
по
классы.
пространстве.
обеим
Две
сторонам
Разделяющей
параллельных
гиперплоскости,
гиперплоскостью
будет
гиперплоскость, максимизирующая расстояние до двух параллельных
гиперплоскостей. Алгоритм работает в предположении, что чем больше
разница или расстояние между этими параллельными гиперплоскостями,
тем меньше будет средняя ошибка классификатора.
Рисунок 3 – Несколько классифицирующих разделяющих прямых
(гиперплоскостей). Но только одна достигает оптимального разделения
При использовании данного метода каждый классифицируемый
объект будет представлен как точка в p-мерном пространстве. Каждая из
этих точек принадлежит только одному из двух классов. Нас интересует,
можем ли мы разделить точки гиперплоскостью размерностью (p−1). Это
типичный случай линейной разделимости. Таких гиперплоскостей может
быть много. Поэтому вполне естественно полагать, что максимизация
зазора между классами способствует более уверенной классификации.
Если такая гиперплоскость существует, то она нас будет интересовать
больше
всего;
она
называется
оптимальной
разделяющей
гиперплоскостью, а соответствующий ей линейный классификатор
называется оптимально разделяющим классификатором.
11
Пусть имеется обучающая выборка:
(x1, y1), …, (xm, ym), xi ∈ Rn, yi ∈ {-1, 1}.
Метод опорных векторов строит классифицирующую функцию F в
виде
𝐹(𝑥) = 𝑠𝑖𝑔𝑛(⟨𝑤, 𝑥⟩ + 𝑏)
(6)
где ⟨𝑤, 𝑥⟩ – скалярное произведение, w – нормальный вектор к
разделяющей гиперплоскости,b – вспомогательный параметр. Те объекты,
для которых F(x) = 1 попадают в один класс, а объекты с F(x) = -1 – в
другой. Выбор именно такой функции неслучаен: любая гиперплоскость
может быть задана в виде для некоторых w и b.
Далее, мы хотим выбрать такие w и b, которые максимизируют
расстояние до каждого класса. Можно подсчитать, что данное расстояние
равно
1
‖𝑤‖
. Проблема нахождения максимума
1
‖𝑤‖
эквивалентна проблеме
нахождения минимума ‖𝑤‖2 . Запишем все это в виде задачи оптимизации:
𝑎𝑟𝑔 min‖𝑤‖2 ,
𝑤,𝑏
{
(⟨𝑤,
⟩
𝑦𝑖
𝑥𝑖 + 𝑏) ≥ 1, 𝑖 = 1, … , 𝑚.
(7)
которая является стандартной задачей квадратичного программирования и
решается с помощью множителей Лагранжа [6].
Преимущества и недостатки SVM:
 это наиболее быстрый метод нахождения решающих функций;
 метод сводится к решению задачи квадратичного программирования
в выпуклой области, которая всегда имеет единственное решение;
 находит разделяющую полосу максимальной ширины, что позволяет
в дальнейшем осуществлять более уверенную классификацию;
 метод чувствителен к шумам и стандартизации данных;
 не существует общего подхода к автоматическому выбору ядра в
случае линейной неразделимости классов.
12
3. Использованный инструментарий
3.1. Библиотека LIBLINEAR
Для обучения нейронной сети с помощью метода опорных векторов
была использована библиотека с открытым исходным кодом для линейных
классификаций
на
больших
объёмах
данных
LIBLINEAR.
Язык
реализации – С++. Она предоставляет возможность настроить линейный
классификатор при помощи логической регрессии или SVM. Пакет имеет
интерфейсы для множества различных языков программирования и
математических
систем,
таких
как
MATLAB,
Octave,
достаточно
популярен, его часто используют для решения реальных задач, он быстро
работает и прост в использовании.
3.2. Национальный корпус русского языка
Корпус — это информационно-справочная система, основанная на
собрании
текстов
на
некотором
языке
в
электронной
форме.
Национальный корпус представляет данный язык на определенном этапе
(или этапах) его существования и во всём многообразии жанров, стилей,
территориальных и социальных вариантов и т. п.
Национальный корпус имеет две важные особенности. Во-первых, он
характеризуется представительностью, или сбалансированным составом
текстов. Это означает, что корпус содержит по возможности все типы
письменных
и
устных
текстов, представленные в данном языке
(художественные разных жанров, публицистические, учебные, научные,
деловые, разговорные, диалектные и т.п.), и что все эти тексты входят в
корпус
по
возможности
пропорционально
соответствующего периода.
13
их
доле
в
языке
Во-вторых, корпус содержит особую дополнительную информацию
о свойствах входящих в него текстов (так называемую разметку, или
аннотацию). Разметка — главная характеристика корпуса; она отличает
корпус от простых коллекций (или «библиотек») текстов, в изобилии
представленных в современном интернете, в том числе и на русском языке.
Национальный корпус, в отличие от электронной библиотеки, — это не
собрание «интересных» или «полезных» текстов; это собрание текстов,
интересных или полезных для изучения языка. А такими могут оказаться и
роман второстепенного писателя, и запись обычного телефонного
разговора, и типовой договор аренды и т.п. — наряду, конечно, с
классическими произведениями художественной литературы.
Чем богаче и разнообразнее разметка, тем выше научная и учебная
ценность корпуса. В Национальном корпусе русского языка в настоящее
время используется пять типов разметки: метатекстовая, морфологическая
(словоизменительная), синтаксическая, акцентная и семантическая.
14
4. Разработка анализатора частей речи
Для начала разберемся, как обычный человек определяет, к какой
части речи относится слово.
 Обычно мы знаем, к какой части речи относится знакомое нам слово.
Например, мы знаем, что “съешьте” — это глагол.
 Если нам встречается слово, которое мы не знаем, то мы можем
угадать часть речи, сравнивая с уже знакомыми словами. Например,
мы можем догадаться, что слово “конгруэнтность”
— это
существительное, т.е. имеет окончание “-ость”, присущее обычно
существительным.
 Мы также можем догадаться какая это часть речи, проследив за
цепочкой слов в предложении: “съешьте французских x” — в этом
примере, х скорее всего будет существительным.
 Длина слова также может дать полезную информацию. Если слово
состоит всего лишь из одной или двух букв, то скорее всего это
предлог, местоимение или союз.
Поэтому при построении анализирующего алгоритма использованы
размеченные файлы, полученные из Национального корпуса русского
языка, в формате XML. Размеченное предложение выглядит следующим
образом:
<se>
<w><ana lex="вот" gr="PART"></ana>Вот</w>
<w><ana lex="так" gr="ADV-PRO"></ana>так</w>,
<w><ana lex="за" gr="PR"></ana>з`а</w>
<w><ana lex="пять" gr="NUM=acc"></ana>пять</w>
<w><ana lex="минута" gr="S,f,inan=pl,gen"></ana>мин`ут</w>
<w><ana lex="до" gr="PR"></ana>до</w>
<w><ana lex="съемка" gr="S,f,inan=pl,gen"></ana>съёмок</w> ,
15
<w><analex="родиться"gr="V,pf,intr,med=m,sg,praet,indic">
</ana>род`илс`я</w>
<w><analex="новый" gr="A=m,sg,nom,plen"></ana>н`овый</w>
<w><analex="персонаж" gr="S,m,anim=sg,nom">
</ana>персон`аж</w> .
</se>
Предложения заключены в теги <se>, внутри которых расположены
слова в теге <w>. Информация о каждом слове содержится в теге <ana>,
аттрибут lex соответствует лексеме, gr — грамматические категории.
Первая категория — это часть речи:
'S': 'сущ.',
'A': 'прил.',
'NUM': 'числ.',
'A-NUM': 'числ.-прил.',
'V': 'глаг.',
'ADV': 'нареч.',
'PRAEDIC': 'предикатив',
'PARENTH': 'вводное',
'S-PRO': 'местоим. сущ.',
'A-PRO': 'местоим. прил.',
'ADV-PRO': 'местоим. нареч.',
'PRAEDIC-PRO': 'местоим. предик.',
'PR': 'предлог',
'CONJ': 'союз',
'PART': 'частица',
'INTJ': 'межд.'
Из файлов программа формирует массив слов и массив разметки.
Затем, из каждого элемента массива разметки с помощью регулярного
выражения выделяется участок разметки, указывающий на часть речи.
16
Формируются массивы суффиксов (2-3 последние буквы слова), префиксов
(2-3 первые буквы слова) и массив непосредственно слов, затем этот набор
массивов добавляется в массив х. Разметка, соответствующая i-ому слову
добавляется в массив у на i-ую позицию. Затем с помощью библиотеки
LIBLINEAR формируется задача problem(y, x) и запускается процесс
обучения SVM-классификатора. Алгоритм обученияполучает на вход не
только сформированную задачу, но и результаты работы алгоритма с
предыдущим словом, если таковое имеется. Таким образом, алгоритм
будет устанавливать соответствия, опираясь не только на само слово, но и
на характерные для конкретных частей речи морфемы, а также на слова,
предшествующие
текущему.
Результаты
обучения
сохраняются
в
отдельный файл.
После этого, файл со сформированным алгоритмом классификации
загружается в нейронную сеть Макколлока-Питтса. Сеть, получив на вход
предложение, последовательно проанализирует каждое слово (с учётом
предшествующего
слова,
если
такое
имеется)
и
вернёт
массив
размеченных слов. В выходном массиве элемент представляет пару
(<слово>, <часть речи>).
Рисунок 4.1 – Пример работы анализатора
17
Рисунок 4.2 – Пример работы анализатора
Как видно из рисунка 4.2 имеются некоторые погрешности в работе
системы. Не распознаются корректно причастия и деепричастия, система
относит их к прилагательным и глаголам соответственно. Также возникают
погрешности в ситуациях, когда правильность определения зависит от
контекста. Например, в предложении «Эти типы стали есть в литейном
цехе» система определяет слово «стали» как глагол, хотя изначально
имеется в виду слово «сталь» в родительном падеже.
Однако, за исключением подобных случаев, а также случаев сложных
языковых конструкций, система даёт хорошие результаты. При
перекрёстной проверке становится видно, что система работает с точностью
более 90%.
18
ЗАКЛЮЧЕНИЕ
В ходе курсовой работы была успешно создано приложение на
основе нейронной сети, определяющее части речи слов русского языка,
были
изучены
алгоритмы
морфологического
анализа
текстов
на
естественном языке, общий принцип работы алгоритмов линейной
классификации и более подробно метод опорных векторов. Разработанное
приложение выполняет очень узкую задачу, однако эта задача лежит в
основе любого рода работы с естественным языком. Впоследствии
приложение
может
быть
расширенно
для
определения
большего
количества признаков и использовано как основа для разработки
лингвистических систем.
19
СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ
1. Андреев А. М., Берёзкин Д. В., Симаков К. В. Обучение
морфологического анализатора на большой электронной коллекции
текстовых документов. //Электронные библиотеки: перспективные
методы и технологии, электронные коллекции: Труды седьмой
всероссийской научной конференции – Ярославль: Ярославский
государственный университет, 2005. – С.173–181.
2. Вапник В. Н., Червоненкис А. Я. Теория распознавания образов. //М.:
Наука, 1974. — 416 с.
3. Charniak Eugene, Statistical Techniques for Natural Language Parsing
//1997. AI Magazine 18(4), с. 33–44.
4. Хайкин С. Нейронные сети. //2006
5. Портал искусственного интеллекта. Цикл статей о нейронных сетях
[Электронный ресурс] 2010 http://www.aiportal.ru/articles/neuralnetworks/ [Дата обращения: 28.04.2014]
6. Chih-Wei Hsu, Chih-Chung Chang, Chih-Jen Lin. A Practical Guide to
Support Vector Classification [Электронный ресурс] 2010
http://www.csie.ntu.edu.tw/~cjlin/papers/guide/guide.pdf [Дата
обращения: 15.05.2014]
20
ПРИЛОЖЕНИЕ А
Исходный код программы.
main.py
import sys
import pos
sentence = sys.argv[1].split(' ')
tagger = pos.Tagger()
tagger.load('/home/derp/pos-nn/tmp/svm.model', '/home/derp/pos-nn/tmp/ids.pickle')
rus = {
'S': 'сущ.',
'A': 'прил.',
'NUM': 'числ.',
'A-NUM': 'числ.-прил.',
'V': 'глаг.',
'ADV': 'нареч.',
'PRAEDIC': 'предикатив',
'PARENTH': 'вводное',
'S-PRO': 'местоим. сущ.',
'A-PRO': 'местоим. прил.',
'ADV-PRO': 'местоим. нареч.',
'PRAEDIC-PRO': 'местои. предик.',
'PR': 'предлог',
'CONJ': 'союз',
'PART': 'частица',
'INTJ': 'межд.',
'INIT': 'инит',
'NONLEX': 'нонлекс'
}
tagged = []
for word, label in tagger.label(sentence):
tagged.append((word, rus[tagger.get_label(label)]))
print(tagged)
21
pos.py
import sys
import nn
import liblinearutil
sys.path.append('/home/derp/liblinear-1.8/python')
tagset = ['S', 'A', 'NUM', 'A-NUM', 'V', 'ADV', 'PRAEDIC', 'PARENTH', 'S-PRO', 'A-PRO', 'ADV-PRO',
'PRAEDIC-PRO', 'PR', 'CONJ', 'PART', 'INTJ', 'INIT', 'NONLEX']
tag_id = {}
tag_inv = {}
for i in range(0, len(tagset)):
tag_id[tagset[i]] = i + 1
tag_inv[i + 1] = tagset[i]
class Tagger:
def __init__(self):
self.chain_len = 3
self._features = TaggerFeatures()
pass
def load(self, modelname, featuresname):
self._svm_model = nn.load_model(modelname)
self._features.load(open(featuresname, 'rb'))
self._features._train = False
def save(self, modelname, featuresname):
nn.save_model(modelname, self._svm_model)
self._features.save(open(featuresname, 'wb'))
def get_label_id(self, pos):
if pos in tag_id:
return tag_id[pos]
else:
return 0
def get_label(self, id):
if id in tag_inv:
return tag_inv[id]
else:
return '?'
def train(self, sentences, labels, cross_validation = False):
x = []
y = []
22
for i in range(0, len(sentences)):
sentence = sentences[i]
prev = []
j=0
for word in sentence:
body = word.lower()
featurespace = self._construct_featurespace(body, prev)
prev.append((body, labels[i][j]))
if len(prev) > self.chain_len:
del(prev[0])
x.append(featurespace.featureset)
j += 1
y.extend(labels[i])
prob = liblinearutil.problem(y, x)
if cross_validation:
param = liblinearutil.parameter('-c 1 -v 4 -s 4')
liblinearutil.train(prob, param)
else:
param = liblinearutil.parameter('-c 1 -s 4')
self._svm_model = liblinearutil.train(prob, param)
def label(self, sentence):
labeled = []
prev = []
for word in sentence:
body = word.lower()
featurespace = self._construct_featurespace(body, prev)
net = nn.perceptron.__init(1, 5, 1);
p_label, _, _ = net.predict([featurespace.featureset], self._svm_model)
label = p_label[0]
prev.append((body, label))
if len(prev) > self.chain_len:
del(prev[0])
labeled.append((word, label))
return labeled
23
def _construct_featurespace(self, word, prev):
featurespace = nn.FeatureSpace()
featurespace.add({1: len(word)}, 10)
featurespace.add(self._features.from_suffix(word))
featurespace.add(self._features.from_prefix(word))
featurespace.add(self._features.from_body(word))
for item in prev:
featurespace.add({1: item[1]}, 100)
return featurespace
import pickle
import nn
class TaggerFeatures:
def __init__(self):
self._body_id = {}
self._suffix_id = {}
self._prefix_id = {}
self._train = True
self._featurespace = nn.FeatureSpace()
def load(self, fp):
(self._body_id, self._suffix_id, self._prefix_id) = pickle.load(fp)
self._train = False
def save(self, fp):
pickle.dump((self._body_id, self._suffix_id, self._prefix_id), fp)
def from_body(self, body):
featureset = {}
if self._train:
if body not in self._body_id:
self._body_id[body] = len(self._body_id) + 1
featureset[self._body_id[body]] = 1
else:
if body in self._body_id:
featureset[self._body_id[body]] = 1
return featureset
24
def from_suffix(self, body):
featureset = {}
suffix2 = body[-2:]
if suffix2 not in self._suffix_id:
self._suffix_id[suffix2] = len(self._suffix_id) + 1
featureset[self._suffix_id[suffix2]] = 1
suffix3 = body[-3:]
if suffix3 not in self._suffix_id:
self._suffix_id[suffix3] = len(self._suffix_id) + 1
featureset[self._suffix_id[suffix3]] = 1
return featureset
def from_prefix(self, body):
featureset = {}
prefix2 = body[:2]
if prefix2 not in self._prefix_id:
self._prefix_id[prefix2] = len(self._prefix_id) + 1
featureset[self._prefix_id[prefix2]] = 1
prefix3 = body[:3]
if prefix3 not in self._prefix_id:
self._prefix_id[prefix3] = len(self._prefix_id) + 1
featureset[self._prefix_id[prefix3]] = 1
return featureset
25
train.py
#!/usr/bin/env python3
import sys
import re
import rnc
import pos
sentences = []
sentences.extend(rnc.Reader().read('/home/derp/pos-nn/tmp/media1.xml'))
sentences.extend(rnc.Reader().read('/home/derp/pos-nn/tmp/media2.xml'))
sentences.extend(rnc.Reader().read('/home/derp/pos-nn/tmp/media3.xml'))
re_pos = re.compile('([\w-]+)(?:[^\w-]|$)'.format('|'.join(pos.tagset)))
tagger = pos.Tagger()
sentence_labels = []
sentence_words = []
for sentence in sentences:
labels = []
words = []
for word in sentence:
gr = word[1]['gr']
m = re_pos.match(gr)
if not m:
print(gr, file = sys.stderr)
pos = m.group(1)
if pos == 'ANUM':
pos = 'A-NUM'
label = tagger.get_label_id(pos)
if not label:
print(gr, file = sys.stderr)
labels.append(label
body = word[0].replace('`', '')
words.append(body)
sentence_labels.append(labels)
sentence_words.append(words)
tagger.train(sentence_words, sentence_labels, True)
tagger.train(sentence_words, sentence_labels)
tagger.save('/home/derp/ pos-nn/tmp/svm.model', '/home/derp /pos-nn/tmp/ids.pickle')
26
rnc.py
import xml.parsers.expat
class Reader:
def __init__(self):
self._parser = xml.parsers.expat.ParserCreate()
self._parser.StartElementHandler = self.start_element
self._parser.EndElementHandler = self.end_element
self._parser.CharacterDataHandler = self.char_data
def start_element(self, name, attr):
if name == 'ana':
self._info = attr
def end_element(self, name):
if name == 'se':
self._sentences.append(self._sentence)
self._sentence = []
elif name == 'w':
self._sentence.append((self._cdata, self._info))
elif name == 'ana':
self._cdata = ''
def char_data(self, content):
self._cdata += content
def read(self, filename):
f = open(filename)
content = f.read()
f.close()
self._sentences = []
self._sentence = []
self._cdata = ''
self._info = ''
self._parser.Parse(content)
return self._sentences
27
nn.py
import math
import random
from collections import defaultdict
random.seed()
class Perceptron(nn.Classifier):
def __init__(self, Nh):
self.Nh = Nh
self._labels = nn.Autoincrement()
self._features = nn.Autoincrement()
def _init(self, Ni, Nh, No):
self.momentum = 0.9
self.learn_rate = 0.5
self._Wh = [[self._seed() for _ in range(0, Ni)] for __ in range(0, Nh)]
self._Wo = [[self._seed() for _ in range(0, Nh)] for __ in range(0, No)]
self._dWh = [[0] * Ni] * Nh
self._dWo = [[0] * Nh] * No
def get_class_id(self, C):
if C not in self._class_ids:
self._class_ids[C] = len(self._class_ids)
def _seed(self):
return (random.random() - 0.5)
def _sigmod(self, x):
return x
return 1 / (1 + math.exp(-x))
def _calc_layer(self, input, W):
output = []
for i in range(0, len(W)):
s=0
for j in range(0, len(W[i])):
s += W[i][j] * input[j]
output.append(self._sigmod(s))
return output
28
def _propagate(self, input):
self._pi = input
self._ph = self._calc_layer(self._pi, self._Wh)
self._po = self._calc_layer(self._ph, self._Wo)
return self._po
#
def _backpropagate(self, output):
# delta's for output layer
do = []
for i in range(0, len(self._Wo)):
print(output[i], self._po[i])
do.append(self._po[i] * (1 - self._po[i]) * (output[i] - self._po[i]))
print(do)
# correct output layer weights
for i in range(0, len(self._Wo)):
for j in range(0, len(self._Wo[i])):
self._dWo[i][j] = self.momentum * self._dWo[i][j] + (1 self.momentum) * self.learn_rate * do[i] * self._ph[j]
self._Wo[i][j] += self._dWo[i][j]
#
# delta's for hidden layer
dh = []
for i in range(0, len(self._Wh)):
d=0
for j in range(0, len(self._Wo)):
d += do[j] * self._Wo[j][i]
d *= self._ph[i] * (1 - self._ph[i])
dh.append(d)
print(dh)
# correct hidden layer weights
for i in range(0, len(self._Wh)):
for j in range(0, len(self._Wh[i])):
self._dWh[i][j] = self.momentum * self._dWh[i][j] + (1 self.momentum) * self.learn_rate * dh[i] * self._pi[j]
self._Wh[i][j] += self._dWh[i][j]
print(self._Wo)
print(self._Wh)
print()
29
def train(self, x, y):
labels = [self._labels.setId(C) for C in y]
data = []
for sample in x:
data.append(defaultdict(float, [(self._features.setId(d), sample[d]) for d in
sample]))
self._init(self._features.count(), self.Nh, self._labels.count())
epsilon = 1e-3
for epoch in range(1, 10):
i=0
error = 0
for sample in data:
output = self._propagate(sample)
target = defaultdict(float)
target[labels[i] - 1] = 1
self._backpropagate(target)
for j in range(0, len(output)):
error += (output[j] - target[j]) ** 2
i += 1
print(error)
print()
if error < epsilon:
break
def predict(self, x):
y = []
for sample in x:
output = self._propagate(defaultdict(float, [(self._features.getId(d), sample[d])
for d in sample]))
which_max = max(range(0, len(output)), key = lambda i: output[i])
y.append(self._labels.getVal(which_max + 1))
return y
30
Download