Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
KL-LAB5(9).doc
Скачиваний:
14
Добавлен:
12.02.2016
Размер:
1.4 Mб
Скачать

Міністерство освіти і науки україни

НАЦІОНАЛЬНИЙ УНІВЕРСИТЕТ “ЛЬВІВСЬКА ПОЛІТЕХНІКА”

іНСТИТУТ КОМП’ютерних НАУК та ІНФОРМАЦІЙНИХ ТЕХНОЛОГІЙ

Кафедра “Системи автоматизованого проектування ”

ВИВЧЕННЯ БІБЛІОТЕКИ ПРИКЛАДНИХ ПРОГРАМ NLTK, ДЛЯ ОПРАЦЮВАННЯ ТЕКСТІВ ПРИРОДНОЮ МОВОЮ.

ПОЧАТКОВА ОБРОБКА ТЕКСТІВ ПРИРОДНОЮ МОВОЮ.

Методичні вказівки до лабораторної роботи № 5

з дисципліни “Комп’ютерна лінгвістика ”

для студентів спеціальності 7.030.505 “Прикладна лінгвістика ”

та магістрів за фахом 8.030.505 “Прикладна лінгвістика ”.

Затверджено

на засіданні кафедри

“Системи автоматизованого проектування ”

Протокол № 8 від 21.XI.2005 р.

на засіданні методичної ради ІКНІ

Протокол № 4-05/06 від 1.XII.2005 р.

ВАК № 1769 від 12.XII.2005 р.

Львів-2009

ВИВЧЕННЯ БІБЛІОТЕКИ ПРИКЛАДНИХ ПРОГРАМ NLTK, ДЛЯ ОПРАЦЮ­ВАННЯ ТЕКСТІВ ПРИРОДНОЮ МОВОЮ. ПОЧАТКОВА ОБРОБКА ТЕКСТІВ ПРИРОДНОЮ МОВОЮ..Методичні вказівки до лабораторної роботи № 3 з дисципліни “Комп’ютерна лінгвістика ” для студентів спеціальності 7.030.505 “Прикладна лінгвістика” та магістрів за фахом 8.030.505 “Прикладна лінгвістика” для стаціонарної та заочної форм навчання/Укл. А.Б.Романюк. - Львів: Національний університет ”Львівська політехніка”, 2009. - 18с.

Укладачі: Романюк а. Б., канд. Техн. Наук, ст. Викладач

Відповідальний за випуск: Лобур М. В., доктор технічних наук, професор

Рецензенти: Каркульовський В. І., канд. техн. наук, доцент

Шуневич Б.І., канд. філол. наук, доцент.

МЕТА РОБОТА

  • Вивчення основ програмування на мові Python.

  • Вивчення методів роботи з файлами на локальних дисках та з Інтернету.

  • Використання Юнікоду при обробці текстів.

  • Нормалізація текстів, стемінг, лематизація та сегментація.

КОРОТКІ ТЕОРЕТИЧНІ ВІДОМОСТІ

Виконанні цієї лабораторної роботи необхідно розпочати з:

 

>>> from __future__ import division

>>> import nltk, re, pprint

Корпуси текстів та тексти з Інтернету є важливими джерелами даних для здійснення лінгвістичних досліджень. Звичайно, якщо дослідник має власноруч зібрані тексти, то потрібні засоби для доступу до них.

В результаті виконання лабораторної роботи будуть вирішені наступні питання:

  1. Як написати програму для доступу до текстів з локальних файлів та з Інтернету, які є необмеженими джерелами лінгвістичних даних?

  2. Як поділити текст на окремі слова та розділові знаки, для одержання можливості проводити подальший аналіз тексту?

  3. Як написати програму для представлення результатів роботи в певному форматі та зберегти їх у файлі?

  1. Доступ до текстів з Інтернету та локальних дисків.

    1. Електронні книжки

Частина електронних книжок з Project Gutenberg розповсюджується разом з NLTK у вигляді корпуса текстів. Для використання інших текстів з цього проекту можна переглянути каталог 25000 електронних книжок за адресою http://www.gutenberg.org/catalog/ та встановити адресу (URL) потрібного текстового файлу в ASCII кодуванні. 90% текстів в Project Gutenberg є англійською мовою, але він включає також тексти більше ніж 50-ма іншими мовами (каталонська, китайська, датська, фінська, французька, німецька, італійська, португальська, іспанська…).

Текст за номером 2554 це переклад англійською Crime and Punishment(Злочин і кара), і отримати доступ до тексту можна наступним чином:

 

>>> from urllib import urlopen

>>> url = "http://www.gutenberg.org/files/2554/2554.txt"

>>> raw = urlopen(url).read()

>>> type(raw)

<type 'str'>

>>> len(raw)

1176831

>>> raw[:75]

'The Project Gutenberg EBook of Crime and Punishment, by Fyodor Dostoevsky\r\n'

Виконання read()займає певний час протягом якого відбувається завантаження цієї великої книжки. При використанні проксі сервера для доступу до Інтернету, при необхідності, його параметри потрібно вказати:

 

>>> proxies = {'http': 'http://www.someproxy.com:3128'}

>>> raw = urlopen(url, proxies=proxies).read()


Текст книжки збережений як значення змінної raw. Зміннаrawмістить стрічку довжиною 1,176,831 символів. (Перевірити тип змінної можна скориставшисьtype(raw).) Стрічка яка відповідає вмісту книжки містить багато не цікавої для аналізу інформації: пробіли, пусті стрічки, межі стрічки. Символи\rand\n, які є в тексті, це символи переводу каретки та початку нового рядка. Для подальшої роботи з текстом потрібно розділити текст на окремі слова та виділити розділові знаки. Такий процес називають токенізацією. При використанні програми токенізації з NLTK отримуємо список слів та розділових знаків.

 

>>> tokens = nltk.word_tokenize(raw)

>>> type(tokens)

<type 'list'>

>>> len(tokens)

255809

>>> tokens[:10]

['The', 'Project', 'Gutenberg', 'EBook', 'of', 'Crime', 'and', 'Punishment', ',', 'by']

Бібліотека NLTK використовувалась тільки на етапі токенізації і не використовувалась при доступі за адресою в Інтернеті та при читанні стрічки. Для подальшої роботи список перетворюється в NLTK текст і над ним можна здійснювати різноманітні операції:

 

>>> text = nltk.Text(tokens)

>>> type(text)

<type 'nltk.text.Text'>

>>> text[1020:1060]

['CHAPTER', 'I', 'On', 'an', 'exceptionally', 'hot', 'evening', 'early', 'in',

'July', 'a', 'young', 'man', 'came', 'out', 'of', 'the', 'garret', 'in',

'which', 'he', 'lodged', 'in', 'S', '.', 'Place', 'and', 'walked', 'slowly',

',', 'as', 'though', 'in', 'hesitation', ',', 'towards', 'K', '.', 'bridge', '.']

>>> text.collocations()

Katerina Ivanovna; Pulcheria Alexandrovna; Avdotya Romanovna; Pyotr

Petrovitch; Project Gutenberg; Marfa Petrovna; Rodion Romanovitch;

Sofya Semyonovna; Nikodim Fomitch; did not; Hay Market; Andrey

Semyonovitch; old woman; Literary Archive; Dmitri Prokofitch; great

deal; United States; Praskovya Pavlovna; Porfiry Petrovitch; ear rings

В побудованих колокаціях зустрічається Project Gutenberg, словосполучення, яке не міститься в тексті книжки. Завантажений текст з сайту містить метатекстову розмітку (інформацію про автора, про текст, про людей які готували електронний варіант та ін.). Ця інформація може бути, як на початку тексту так і в його кінці . Для роботи власне з текстом книжки потрібно в ручному режимі знайти межі цих додаткових даних і за допомогою зрізів доступитися до тексту.

 

>>> raw.find("PART I")

5303

>>> raw.rfind("End of Project Gutenberg's Crime")

1157681

>>> raw = raw[5303:1157681]

>>> raw.find("PART I")

0

Методи find()таrfind()("пошук з кінця") допомагають знайти потрібні індекси для їх подальшого використання в зрізах. Значення зрізу переприсвоюється зміннійraw.

    1. Робота з HTML файлами.

Більшість текстів в Інтернеті є у вигляді HTML документів (файлів). Інтернет сторінки можна зберігати на диску у вигляді файлів і доступатися до них. Python також дозволяє працювати Інтернет сторінками безпосередньо використовуючи функцію urlopen. Для прикладу переглянемо текст з BBC News story з назвоюBlondes to die out in 200 years:

 

>>> url = "http://news.bbc.co.uk/2/hi/health/2284783.stm"

>>> html = urlopen(url).read()

>>> html[:60]

'<!doctype html public "-//W3C//DTD HTML 4.0 Transitional//EN'

Текст, який вивели на екран містить HTML розмітку (мета теги, JavaScript, форми , таблиці). Вилучення тексту з HTML файлу доволі розповсюджена задача, яка в NLTK вирішується за допомогою функції nltk.clean_html(). Ця функція обробляє HTML стрічку і повертає текст у вигляді зручному для подальшої обробки (токенізації).

 

>>> raw = nltk.clean_html(html)

>>> tokens = nltk.word_tokenize(raw)

>>> tokens

['BBC', 'NEWS', '|', 'Health', '|', 'Blondes', "'", 'to', 'die', 'out', ...]

Видалення іншої небажаної інформації проводиться в ручному режимі, аналогічно до попередніх прикладів з електронними книжками.

 

>>> tokens = tokens[96:399]

>>> text = nltk.Text(tokens)

>>> text.concordance('gene')

they say too few people now carry the gene for blondes to last beyond the next tw

t blonde hair is caused by a recessive gene . In order for a child to have blonde

to have blonde hair , it must have the gene on both sides of the family in the gra

there is a disadvantage of having that gene or by chance . They don ' t disappear

ondes would disappear is if having the gene was a disadvantage and I do not think

    1. Обробка результатів пошукових запитів.

Виконати самостійно:Здійсніть аналіз результатів пошуку в Інтернеті наступного словосполучення"the of". Чи можна аналогічним чином знайти найчастотніші колокації англійської мови?

    1. Обробка RSS стрічок

Блогосфера важливе джерело текстів, як формальних так і не формальних. З допомогою бібліотеки Python Universal Feed Parser,http://feedparser.org/, можна отримати доступ до вмісту блогів, як показано у наступному прикладі:

 

>>> import feedparser

>>> llog = feedparser.parse("http://languagelog.ldc.upenn.edu/nll/?feed=atom")

>>> llog['feed']['title']

u'Language Log'

>>> len(llog.entries)

15

>>> post = llog.entries[2]

>>> post.title

u"He's My BF"

>>> content = post.content[0].value

>>> content[:70]

u'<p>Today I was chatting with three of our visiting graduate students f'

>>> nltk.word_tokenize(nltk.html_clean(content))

>>> nltk.word_tokenize(nltk.clean_html(llog.entries[2].content[0].value))

[u'Today', u'I', u'was', u'chatting', u'with', u'three', u'of', u'our', u'visiting',

u'graduate', u'students', u'from', u'the', u'PRC', u'.', u'Thinking', u'that', u'I',

u'was', u'being', u'au', u'courant', u',', u'I', u'mentioned', u'the', u'expression',

u'DUI4XIANG4', u'\u5c0d\u8c61', u'("', u'boy', u'/', u'girl', u'friend', u'"', ...]

    1. Читання локальних файлів.

Для читання локальних файлів необхідно використовувати вбудовану функцію Python open()таread()метод. Якщо існує файлdocument.txt, то зміннійrawможна присвоїти його вміст:

 

>>> f = open('document.txt')

>>> raw = f.read()

Якщо інтерпретатор не знайде файл, він видасть помилку, подібну до наступної:

 

>>> f = open('document.txt')

Traceback (most recent call last):

File "<pyshell#7>", line 1, in -toplevel-

f = open('document.txt')

IOError: [Errno 2] No such file or directory: 'document.txt'

Для перевірки чи дійсно файл є в потрібній директорії у графічному інтерфейсі IDLE використовується команда Open з пункту менюFile. Можна також перевірити вміст директорії наступним чином:

 

>>> import os

>>> os.listdir('.')

Інша можлива проблема при читанні текстових файлів – це різні способи маркування нового рядка у файлах різних операційних систем. При виклику функція open()може містити другий параметр для контролю відкривання файлуopen('document.txt', 'rU')(„r” файл для читання, ”U” universal дозволяє ігнорувати різні способи, які використовуються для маркування нового рядка).

Для читання вмісту файлу можна використати багато різних методів. Метод read(), використаний до об’єкту файл (f), читає вміст файлу і представляє його стрічкою:

 

>>> f.read()

'Time flies like an arrow.\nFruit flies like a banana.\n'

Символ'\n'– це символ нового рядка.

Файл можна читати стрічка за стрічкою, використовуючи for-цикл і використовувати зріз[:-1]або методstrip()для видалення символів нового рядка:

 

>>> f = open('document.txt', 'rU')

>>> for line in f:

... print line.strip()

Time flies like an arrow.

Fruit flies like a banana.

За допомогою цих методів також можна доступитися і до файлів з корпусів, які розповсюджуються з NLTK. Потрібно використати nltk.data.find()для одержання шляху до будь-якого файлу корпуса, а далі відкривати та читати файл, як показано:

 

>>> path = nltk.data.find('corpora/gutenberg/melville-moby_dick.txt')

>>> raw = open(path, 'rU').read()

    1. Ввід тексту з клавіатури.

Для вводу тексту з клавіатури (при взаємодії користувача з програмою) потрібно використати функцію raw_input(). Після збереження введеного тексту у змінній з ним можна працювати як зі звичайною стрічкою.

 

>>> s = raw_input("Enter some text: ")

Enter some text: On an exceptionally hot evening early in July

>>> print "You typed", len(nltk.word_tokenize(s)), "words."

You typed 8 words.

    1. Схема роботи з текстами при їх початковій обробці.

На Рис 1. представлена узагальнена, на основі вищевикладеного, схема початкової обробки текстів природною мовою. Початкова обробка текстів природною мовою може містити наступні етапи: відкривання та читання тексту з Інтернету, видалення розмітки, токенізація, перетворення тексту в NLTK текст.

Рис.1: Узагальнена схема початкової обробки текстів природною мовою.

Коли відбувається доступ до вмісту файлу чи вмісту вебсторінки і коли видаляється HTML розмітка то відбувається обробка стрічки:

 

>>> raw = open('document.txt').read()

>>> type(raw)

<type 'str'>

Результат токенізації це список в який входять всі слова з тексту. Нормалізація та сортування цього списку приводить до отримання інших списків:

 

>>> tokens = nltk.word_tokenize(raw)

>>> type(tokens)

<type 'list'>

>>> words = [w.lower() for w in tokens]

>>> type(words)

<type 'list'>

>>> vocab = sorted(set(words))

>>> type(vocab)

<type 'list'>

Тип об’єкту визначає, які операції можуть бути здійснені з цим об’єктом. Наприклад, можна додавати елементи до списку але не можна до стрічки:

 

>>> vocab.append('blog')

>>> raw.append('blog')

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

AttributeError: 'str' object has no attribute 'append'

Стрічки та списки можна поєднувати з іншими стрічками та списками, але не можна поєднувати стрічки зі списками:

 

>>> query = 'Who knows?'

>>> beatles = ['john', 'paul', 'george', 'ringo']

>>> query + beatles

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

TypeError: cannot concatenate 'str' and 'list' objects

.

  1. Використання Unicode при обробці текстів.

Програми обробки природної мови повинні працювати з різними мовами та з різними наборами символів. Твердження «1 байт = 1 символ» є застарілим і в переважній більшості практичних випадків є хибним. В англомовному світі переважно використовується ASCII кодування символів. В Європі використовується розширений Latin набір символів, який містить такі символи датської та норвежської, як "ø", угорської - "ő", іспанської та бретонської -"ñ" та "ň" чеської та словацької мов. Розглянемо, як використовується Unicode при обробці текстів, що містять відмінні від ASCII символи.

    1. Поняття Юнікод (Unicode).

Юніко́д, (англ. Unicode) — це промисловий стандарт розроблений, щоб зробити можливим для текстів і символів (графічних знаків) всіх писемних систем світу узгоджене представлення (репрезентацію) і обробку комп’ютерами. Юнікод підтримує більш ніж мільйон символів. Кожному символу ставиться у відповідність число, яке називають кодовою точкою. В Python кодові точки записуються у вигляді \uXXXX , де XXXX - чотири символи шістнадцяткового числа.

В межах програми обробка стрічок Unicode відбувається аналогічно до звичайних стрічок. Однак, коли Unicode символи зберігаються у файл або виводяться на екран, вони повинні бути закодовані, як потік байт. Деякі кодування (такі як ASCII та Latin-2) використовують один байт для представлення одної кодової точки і відповідно підтримують невеликий набір символів Unicode, достатній для одної мови. Інші кодування (такі як UTF-8) використовують послідовності байтів і можуть представити весь набір символів Unicode.

Текст у файлах є в певному кодування і потрібен певний механізм для перетворення його до Unicode. Такий механізм називають — декодування. Навпаки записати Unicode символи у файл або вивести на екран можна тільки попередньо перетворивши їх у потрібне кодування. Таке перетворення називають кодуванням. Рис.2.

Рис.2. Кодування і декодування Unicode.

From a Unicode perspective, characters are abstract entities which can be realized as one or moreglyphs. Only glyphs can appear on a screen or be printed on paper. A font is a mapping from characters to glyphs.

    1. Одержання закодованого тексту з файлів.

Нехай існують невеликі текстові файли відомого кодування. Файл Ukrainian1-Cyrillic(перше речення декларації прав людини) з текстом українською мовою в кодуванні Сyrillic та файл polish-lat2.txtз текстом польською мовою у кодуванні Latin-2 (ISO-8859-2). За допомогою функціїnltk.data.find() знайдемо місцезнаходження цих файлів:

 

>>> path = nltk.data.find('corpora/udhr/Ukrainian1-Cyrillic')

>>> path1 = nltk.data.find('corpora/unicode_samples/polish-lat2.txt')

Модуль Python codecsзабезпечує функції читання кодованих даних вUnicode стрічку і записуUnicode стрічки в кодовану форму. Функціяcodecs.open()потребує параметра кодування файлу для читання чи запису. Перед використанням модуля потрібно його імпортувати і при читанні вказувати тип кодування:

 

>>> import codecs

>>> f = codecs.open(path, encoding='cyrillic')

>>> f1 = codecs.open(path1, encoding='latin2')

Список параметрів кодування модуля codecs, можна переглянути за адресоюhttp://docs.python.org/lib/standard-encodings.html. Для запису даних у файл потрібно скористатись наступною конструкцією: f = codecs.open(path, 'w', encoding='utf-8').

Текст прочитаний з fбуде в Unicode. Для представлення цього тексту на екрані потрібно його закодувати. В Python кодуванняunicode_escapeперетворює всі не ASCII символи в їх представлення\uXXXX. Кодові точки вище ASCII 0=127 але до 256 представляються у двоцифровій формі \хХХ.

 

>>> for line in f1:

... line = line.strip()

... print line.encode('unicode_escape')

Pruska Biblioteka Pa\u0144stwowa. Jej dawne zbiory znane pod nazw\u0105

"Berlinka" to skarb kultury i sztuki niemieckiej. Przewiezione przez

Niemc\xf3w pod koniec II wojny \u015bwiatowej na Dolny \u015al\u0105sk, zosta\u0142y

odnalezione po 1945 r. na terytorium Polski. Trafi\u0142y do Biblioteki

Jagiello\u0144skiej w Krakowie, obejmuj\u0105 ponad 500 tys. zabytkowych

archiwali\xf3w, m.in. manuskrypty Goethego, Mozarta, Beethovena, Bacha.

В першій стрічці послідовність \u0144, починається з символу\u. Відповідний Unicode символ буде відображатися на екрані, як ń. В третій стрічці є символ\xf3, який відповідає ó, оскільки він є з проміжку 128-255.

В Python, Unicode стрічку записують вказавши на початку символ u, (u'hello' –Юнікод стрічка). Довільний Unicode символ може бути визначений всерединіUnicode стрічкивикористовуючи представлення\uXXXX. Можна знайти числове значення кодової точки використовуючи функціюord():

 

>>> ord('a')

97

>>> ord('à')

224

В шістнадцятковій формі 97 це 0061 а 224 це 00Е0 , що дозволяє представити ці символи відповідними кодовими точками:

 

>>> a = u'\u0061'

>>> b = u'\u00E0'

>>> a

u'a'

>>> print a

a

>>> b

u'\xe0'

>>> print b

à

    1. Використання локального кодування в Python.

При роботі з символами певного локального кодування, для можливості використання стандартних методів вводу і редагування стрічок у файлі Python потрібно записати стрічку'# -*- coding: <coding> -*-'першим або другим рядком у файлі.<coding>це стрічка така як'latin-1','big5'or'utf-8'(Рис.4.).

Рис.4. Використання локального кодування.

  1. Нормалізація тексту.

В попередніх лабораторних роботах перед обробкою тексту всі літери слів перетворювались у малі літери (set(w.lower() for w in text).). Використовуючиlower()текст нормалізується для усунення розбіжностей між «The» та «the». Часто потрібно відділити від слів афікси. Така задача називається стемінгом. Для перевірки чи словоформа є словом у словнику потрібно здійснити лематизацію (отримати канонічну форму лексеми):

 

>>> raw = """DENNIS: Listen, strange women lying in ponds distributing swords

... is no basis for a system of government. Supreme executive power derives from

... a mandate from the masses, not from some farcical aquatic ceremony."""

>>> tokens = nltk.word_tokenize(raw)

    1. Стемінг.

NLTK містить декілька стандартних програм для здійснення стемінгу. Porter та Lancaster програми здійснюють стемінг (відкидання афіксів) на основі наборів правил. Porter стемер коректно обробляє слово lying (lie), а Lancaster стемер це слово обробляє з помилкою.

 

>>> porter = nltk.PorterStemmer()

>>> lancaster = nltk.LancasterStemmer()

>>> [porter.stem(t) for t in tokens]

['DENNI', ':', 'Listen', ',', 'strang', 'women', 'lie', 'in', 'pond',

'distribut', 'sword', 'is', 'no', 'basi', 'for', 'a', 'system', 'of', 'govern',

'.', 'Suprem', 'execut', 'power', 'deriv', 'from', 'a', 'mandat', 'from',

'the', 'mass', ',', 'not', 'from', 'some', 'farcic', 'aquat', 'ceremoni', '.']

>>> [lancaster.stem(t) for t in tokens]

['den', ':', 'list', ',', 'strange', 'wom', 'lying', 'in', 'pond', 'distribut',

'sword', 'is', 'no', 'bas', 'for', 'a', 'system', 'of', 'govern', '.', 'suprem',

'execut', 'pow', 'der', 'from', 'a', 'mand', 'from', 'the', 'mass', ',', 'not',

'from', 'som', 'farc', 'aqu', 'ceremony', '.']

Результати стемінгу не є однозначними і тому стемер вибирається той, який дає кращі результати для визначеної задачі. Porter стемер найкраще використовувати для індексування деякого тексту для підтримки пошукових операцій (забезпечення пошуку всіх форм слова). Порівняйте результати побудови конкордансу для слова “lie”, без індексування та з попереднім індексуванням (class IndexedText(object)).

 

class IndexedText(object):

def __init__(self, stemmer, text):

self._text = text

self._stemmer = stemmer

self._index = nltk.Index((self._stem(word), i)

for (i, word) in enumerate(text))

def concordance(self, word, width=40):

key = self._stem(word)

wc = width/4 # words of context

for i in self._index[key]:

lcontext = ' '.join(self._text[i-wc:i])

rcontext = ' '.join(self._text[i:i+wc])

ldisplay = '%*s' % (width, lcontext[-width:])

rdisplay = '%-*s' % (width, rcontext[:width])

print ldisplay, rdisplay

def _stem(self, word):

return self._stemmer.stem(word).lower()

 

>>> porter = nltk.PorterStemmer()

>>> grail = nltk.corpus.webtext.words('grail.txt')

>>> text = IndexedText(porter, grail)

>>> text.concordance('lie')

r king ! DENNIS : Listen , strange women lying in ponds distributing swords is no

beat a very brave retreat . ROBIN : All lies ! MINSTREL : [ singing ] Bravest of

Nay . Nay . Come . Come . You may lie here . Oh , but you are wounded !

doctors immediately ! No , no , please ! Lie down . [ clap clap ] PIGLET : Well

ere is much danger , for beyond the cave lies the Gorge of Eternal Peril , which

you . Oh ... TIM : To the north there lies a cave -- the cave of Caerbannog --

h it and lived ! Bones of full fifty men lie strewn about its lair . So , brave k

not stop our fight ' til each one of you lies dead , and the Holy Grail returns t

 

>>> grail = nltk.Text(nltk.corpus.webtext.words('grail.txt'))

>>> grail.concordance('lie')

Nay . Nay . Come . Come . You may lie here . Oh , but you are wounded !

doctors immediately ! No , no , please ! Lie down . [ clap clap ] PIGLET : Well

h it and lived ! Bones of full fifty men lie strewn about its lair . So , brave k

    1. Лематизація.

WordNet лематизатор видаляє афікси тільки якщо слово, яке отримується в процесі лематизації є в його словнику. Ця процедура робить лематизацію повільнішою за стемінг. Слово lying оброблено з помилкою, але слово women перетворено у woman.

 

>>> wnl = nltk.WordNetLemmatizer()

>>> [wnl.lemmatize(t) for t in tokens]

['DENNIS', ':', 'Listen', ',', 'strange', 'woman', 'lying', 'in', 'pond',

'distributing', 'sword', 'is', 'no', 'basis', 'for', 'a', 'system', 'of',

'government', '.', 'Supreme', 'executive', 'power', 'derives', 'from', 'a',

'mandate', 'from', 'the', 'mass', ',', 'not', 'from', 'some', 'farcical',

'aquatic', 'ceremony', '.']

WordNet лематизатор можна використовувати для побудови словника деякого тексту. В результаті можна отримати список лем.

  1. Сегментація.

Токенізація це найбільш загальний випадок сегментації.

    1. Сегментація тексту на окремі речення.

Робота з текстами на рівні окремих слів часто передбачає можливість поділу тексту на окремі речення. Деякі корпуси забезпечують можливість доступу на рівні окремих речень. В наступному прикладі визначається середня довжина речення в корпусі Brown:

 

>>> len(nltk.corpus.brown.words()) / len(nltk.corpus.brown.sents())

20.250994070456922

У випадку якщо текст представлений, як послідовність символів, то перед токенізацією необхідно поділити текст на окремі речення. NLTK забезпечує таку можливість за допомогою програми Punkt сегментації тексту на речення. У наступному прикладі показано використання цієї програми:

 

>>> sent_tokenizer=nltk.data.load('tokenizers/punkt/english.pickle')

>>> text = nltk.corpus.gutenberg.raw('chesterton-thursday.txt')

>>> sents = sent_tokenizer.tokenize(text)

>>> pprint.pprint(sents[171:181])

['"Nonsense!',

'" said Gregory, who was very rational when anyone else\nattempted paradox.',

'"Why do all the clerks and navvies in the\nrailway trains look so sad and tired,...',

'I will\ntell you.',

'It is because they know that the train is going right.',

'It\nis because they know that whatever place they have taken a ticket\nfor that ...',

'It is because after they have\npassed Sloane Square they know that the next stat...',

'Oh, their wild rapture!',

'oh,\ntheir eyes like stars and their souls again in Eden, if the next\nstation w...'

'"\n\n"It is you who are unpoetical," replied the poet Syme.']

Зауважимо, що цей приклад це одне речення, повідомлення про промову Mr Lucian Gregory. Промова містить декілька речень і доцільно їх виділити, як окремі стрічки.

Сегментація тексту на речення є складною процедурою, оскільки навіть такий явний розділовий знак межі речення, як крапка, може використовуватись у скороченнях, абревіатурах та їх комбінаціях.

    1. Сегментація тексту на окремі слова.

Для деяких писемностей токенізація тексту є складною процедурою оскільки складно встановити межі слова. Наприклад три символи стрічки китайською 爱国人 (ai4 "love" (verb), guo3 "country", ren2 "person") можуть бути поділені і як爱国 /人, "country-loving person" і як爱 /国人, "love country-person."

Подібна проблема виникає при обробці усного мовлення, коли слухач змушений сегментувати безперервний потік звуків на окремі слова. Особливо складно це зробити, якщо слова є попередньо невідомі (при вивченні мови або якщо немовля сприймає мову батьків). Розглянемо наступний приклад в якому видалені межі слів:

(1)

a.

doyouseethekitty

b.

seethedoggy

c.

doyoulikethekitty

d.

likethedoggy

Спочатку потрібно описати задачу: потрібно знайти спосіб відділити текст від власне сегментації. Цього можна досягнути промарнувавши кожен символ булевим значенням, яке буде вказувати, що після символу йде межа слова. Подібний результат може отримати учень на слух сприймаючи паузи при вимові. Результат представимо наступним чином. seg1– початкова сегментація,seg2- результуюча сегментація:

 

>>> text = "doyouseethekittyseethedoggydoyoulikethekittylikethedoggy"

>>> seg1 = "0000000000000001000000000010000000000000000100000000000"

>>> seg2 = "0100100100100001001001000010100100010010000100010010000"

Стрічки сегментації містять нулі та одиниці та їх довжина менша на один символ від початкового тексту (текст довжиною N елементів може мати тільки N-1 місць розділу). Функція segment()в наступному прикладі дозволяє отримати оригінальний сегментований текст на основі поданого вище представлення.

 

def segment(text, segs):

words = []

last = 0

for i in range(len(segs)):

if segs[i] == '1':

words.append(text[last:i+1])

last = i+1

words.append(text[last:])

return words

 

>>> text = "doyouseethekittyseethedoggydoyoulikethekittylikethedoggy"

>>> seg1 = "0000000000000001000000000010000000000000000100000000000"

>>> seg2 = "0100100100100001001001000010100100010010000100010010000"

>>> segment(text, seg1)

['doyouseethekitty', 'seethedoggy', 'doyoulikethekitty', 'likethedoggy']

>>> segment(text, seg2)

['do', 'you', 'see', 'the', 'kitty', 'see', 'the', 'doggy', 'do', 'you',

'like', 'the', kitty', 'like', 'the', 'doggy']

Тепер задачу сегментації можна розглядати як пошукову задачу: знайти стрічку бітів, яка приводить до коректної сегментації стрічки тексту на слова. Учень запам’ятовує слова і зберігає їх у словнику. Маючи відповідний словник, можливо здійснити реконструкцію початкового тексту, як послідовність лексичних одиниць. Побудуємо цільову функцію, значення якої буде оптимізоване на основі розміру словника та розміру інформації необхідної для реконструкції початкового тексту зі словника Рис.3..

Рис.3. Визначення цільової функції:

На основі гіпотетично сегментованого початкового тексту(ліва колонка ) отримуємо словник та таблицю (середня колонка), які забезпечують реконструкцію початкового тексту. Загальна кількість символів у лексиконі (з врахуванням символу границі слова) та сума елементів в таблиці служать числовим значенням для оцінки якості сегментації (права колонка). Менше, за знайдене, значення вказує на кращий варіант сегментації.

Для розрахунку цієї цільової функції можна використати наступну функцію:

 

def evaluate(text, segs):

words = segment(text, segs)

text_size = len(words)

lexicon_size = len(' '.join(list(set(words))))

return text_size + lexicon_size

 

>>> text = "doyouseethekittyseethedoggydoyoulikethekittylikethedoggy"

>>> seg1 = "0000000000000001000000000010000000000000000100000000000"

>>> seg2 = "0100100100100001001001000010100100010010000100010010000"

>>> seg3 = "0000100100000011001000000110000100010000001100010000001"

>>> segment(text, seg3)

['doyou', 'see', 'thekitt', 'y', 'see', 'thedogg', 'y', 'doyou', 'like',

'thekitt', 'y', 'like', 'thedogg', 'y']

>>> evaluate(text, seg3)

46

>>> evaluate(text, seg2)

47

>>> evaluate(text, seg1)

63

Останній крок це власне пошук послідовності нулів та одиниць, яка максимізує цю цільову функцію. Потрібно зазначити, що найкращий варіант сегментації містить «слово» thekitty, оскільки недостатньо даних для подальшої сегментації.

 

from random import randint

def flip(segs, pos):

return segs[:pos] + str(1-int(segs[pos])) + segs[pos+1:]

def flip_n(segs, n):

for i in range(n):

segs = flip(segs, randint(0,len(segs)-1))

return segs

def anneal(text, segs, iterations, cooling_rate):

temperature = float(len(segs))

while temperature > 0.5:

best_segs, best = segs, evaluate(text, segs)

for i in range(iterations):

guess = flip_n(segs, int(round(temperature)))

score = evaluate(text, guess)

if score < best:

best, best_segs = score, guess

score, segs = best, best_segs

temperature = temperature / cooling_rate

print evaluate(text, segs), segment(text, segs)

print

return segs

 

>>> text = "doyouseethekittyseethedoggydoyoulikethekittylikethedoggy"

>>> seg1 = "0000000000000001000000000010000000000000000100000000000"

>>> anneal(text, seg1, 5000, 1.2)

60 ['doyouseetheki', 'tty', 'see', 'thedoggy', 'doyouliketh', 'ekittylike', 'thedoggy']

58 ['doy', 'ouseetheki', 'ttysee', 'thedoggy', 'doy', 'o', 'ulikethekittylike', 'thedoggy']

56 ['doyou', 'seetheki', 'ttysee', 'thedoggy', 'doyou', 'liketh', 'ekittylike', 'thedoggy']

54 ['doyou', 'seethekit', 'tysee', 'thedoggy', 'doyou', 'likethekittylike', 'thedoggy']

53 ['doyou', 'seethekit', 'tysee', 'thedoggy', 'doyou', 'like', 'thekitty', 'like', 'thedoggy']

51 ['doyou', 'seethekittysee', 'thedoggy', 'doyou', 'like', 'thekitty', 'like', 'thedoggy']

42 ['doyou', 'see', 'thekitty', 'see', 'thedoggy', 'doyou', 'like', 'thekitty', 'like', 'thedoggy']

'0000100100000001001000000010000100010000000100010000000'

Останній крок це власне пошук послідовності нулів та одиниць, яка максимізує цю цільову функцію. Потрібно зазначити, що найкращий варіант сегментації містить «слово» thekitty, оскільки недостатньо даних для подальшої сегментації.

  1. Форматування.

    1. Стрічки і форматування.

Відомі два шляхи вивести на екран вміст об’єктів:

 

>>> word = 'cat'

>>> sentence = """hello

... world"""

>>> print word

cat

>>> print sentence

hello

world

>>> word

'cat'

>>> sentence

'hello\nworld'

Команда print дозволяє отримати найбільш придатне, для читання, представлення об’єкту. Другий спосіб — ввести назву змінної в командному рядку — показує стрічку, яка може бути використана для оновлення цього об’єкту. Важливо зрозуміти, ці два результати це стрічки, виведені на екран для користувача і не дають ніякої інформації про фактичне внутрішнє представлення об’єкта.

Існують інші шляхи для виведення на екран об’єкту, як стрічки символів. Вони можуть також використовуватися і для експортування даних у файл певного формату, який буде використаний зовнішньою програмою.

Форматовані вихідні дані звичайно містять комбінацію змінних та наперед визначених стрічок. Наприклад результати частотного розподілу можна представити як:

 

>>> fdist = nltk.FreqDist(['dog', 'cat', 'dog', 'cat', 'dog', 'snake', 'dog', 'cat'])

>>> for word in fdist:

... print word, '->', fdist[word], ';',

dog -> 4 ; cat -> 3 ; snake -> 1 ;

Уникнути небажаних пробілів та покращити читабельність програми дозволяє використання виразів форматування.

 

>>> for word in fdist:

... print '%s->%d;' % (word, fdist[word]),

dog->4; cat->3; snake->1;

Тестування стрічки, яка містить вираз форматування дозволяє глибше зрозуміти механізм форматованого виводу.

 

>>> '%s->%d;' % ('cat', 3)

'cat->3;'

>>> '%s->%d;' % 'cat'

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

TypeError: not enough arguments for format string

Спеціальні символи %sта%dвказують на тимчасові місця (позиції) для стрічок та цілих (десяткових чисел). Ці символи записуються всередині стрічки і використовується оператор%для їх комбінування, подібно до наступного прикладу:

 

>>> '%s->' % 'cat'

'cat->'

>>> '%d' % 3

'3'

>>> 'I want a %s right now' % 'coffee'

'I want a coffee right now'

У виразі можуть бути багато тимчасових позицій але після оператора %потрібно визначити кортеж з такою самою кількістю значень.

 

>>> "%s wants a %s %s" % ("Lee", "sandwich", "for lunch")

'Lee wants a sandwich for lunch'

Значення для тимчасових позицій можна вказувати не безпосередньо, а наприклад так як в зроблено у наступному циклі:

 

>>> template = 'Lee wants a %s right now'

>>> menu = ['sandwich', 'spam fritter', 'pancake']

>>> for snack in menu:

... print template % snack

...

Lee wants a sandwich right now

Lee wants a spam fritter right now

Lee wants a pancake right now

Символи%sта%dназивають специфікаторами формату. Вони починаються з символу%і завершуються символом таким якs(для стрічки) чиd(для цілих чисел). Стрічка, яка містить специфікатори формату називають стрічкою форматування. Поєднання стрічки форматування з оператором%та кортежем значень утворює стрічку виразу форматування.

Стрічка форматування виводить вихідні дані на екран або на сторінку довільної ширини такої як %sта%d. Можна визначити ширину як%6s, що дозволить отримати стрічку доповнену пробілами до ширини 6 символів. Значення змінної буде вирівняне по правому краю, а у випадку використання символу «мінус » - по лівому краю. У випадку, коли наперед невідомо, якої ширини може бути змінна в стрічці форматування використовується зірочка, яка потім визначається за допомогою окремої змінної.

 

>>> '%6s' % 'dog'

' dog'

>>> '%-6s' % 'dog'

'dog '

>>> width = 6

>>> '%-*s' % (width, 'dog')

'dog '

>>> '%*s' % (15, "Monty Python")

' Monty Python'

Інший спосіб контролю символів використовується для цілих чисел та чисел з плаваючою крапкою. Оскільки символ %має спеціальне значення в стрічці форматування то при необхідності використати цей символ у вихідних даних потрібно додати перед ним ще один символ%.

 

>>> count, total = 3205, 9375

>>> "accuracy for %d words: %2.4f%%" % (total, 100 * count / total)

'accuracy for 9375 words: 34.1867%'

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

 

def tabulate(cfdist, words, categories):

print '%-16s' % 'Category',

for word in words: # column headings

print '%6s' % word,

print

for category in categories:

print '%-16s' % category, # row heading

for word in words: # for each word

print '%6d' % cfdist[category][word], # print table cell

print # end the row

 

>>> from nltk.corpus import brown

>>> cfd = nltk.ConditionalFreqDist(

... (genre, word)

... for genre in brown.categories()

... for word in brown.words(categories=genre))

>>> genres = ['news', 'religion', 'hobbies', 'science_fiction', 'romance', 'humor']

>>> modals = ['can', 'could', 'may', 'might', 'must', 'will']

>>> tabulate(cfd, modals, genres)

Category can could may might must will

news 93 86 66 38 50 389

religion 82 59 78 12 54 71

hobbies 268 58 131 22 83 264

science_fiction 16 49 4 12 8 16

romance 74 193 11 51 45 43

humor 16 30 8 8 9 13

Використовуючи вираз width = max(len(w) for w in words)можна автоматично налаштувати ширину колонки, а кома в кінці операторів print не дозволяє колонкам перекриватися між собою.

    1. Запис результатів у файл.

Наступний приклад показує, яким чином відкрити файл output.txtдля запису, та зберегти в ньому результати роботи програми.

 

>>> output_file = open('output.txt', 'w')

>>> words = set(nltk.corpus.genesis.words('english-kjv.txt'))

>>> for word in sorted(words):

... output_file.write(word + "\n")

Виконати самостійно:Перевірити навіщо перед записом у файл до кожного рядка додається символ\n. Спробуйте також використатиword + "\r\n"вираз. Що станеться, якщо опустити ці символиoutput_file.write(word)?

При записі у файл не текстових даних їх попередньо потрібно перетворити у стрічку. Перетворення можна зробити використавши стрічку форматування, як було показано вище або зробити перетворення наступним чином:

 

>>> len(words)

2789

>>> str(len(words))

'2789'

>>> output_file.write(str(len(words)) + "\n")

>>> output_file.close()

Текстові вихідні дані часто корисно обробити для покращення їх відображення. Розглянемо наступний приклад зі складним виглядом оператора print:

 

>>> saying = ['After', 'all', 'is', 'said', 'and', 'done', ',',

... 'more', 'is', 'said', 'than', 'done', '.']

>>> for word in saying:

... print word, '(' + str(len(word)) + '),',

After (5), all (3), is (2), said (4), and (3), done (4), , (1), more (4), is (2), said (4), than (4), done (4), . (1),

Аналогічний результат, але з фіксованою довжиною рядка можна отримати з використанням модуля textwrap:

 

>>> from textwrap import fill

>>> format = '%s (%d),'

>>> pieces = [format % (word, len(word)) for word in saying]

>>> output = ' '.join(pieces)

>>> wrapped = fill(output)

>>> print wrapped

After (5), all (3), is (2), said (4), and (3), done (4), , (1), more

(4), is (2), said (4), than (4), done (4), . (1),

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]