2016-01-29

Python NLTK + Stanford NLP

Как известно, в Python стандартом работы с натуральным языком де-факто является NLTK. Несмотря на это, я довольно долго использовал Pattern от CLiPS из-за его простоты и скорости (многие отмечают тормознутость NLTK).

Но наступил момент, когда почти вся кодовая база была успешно портирована на Python 3.5, а разработчики Pattern так и не сделали версию с поддержкой третьей версии. И, судя по всему, не собираются.

Что-ж, будем использовать NLTK. От него мне нужны: токенизация, выделение POS (part-of-speech), получение N-grams и классификация твитов на группы с использованием Naive Bayes. Все это дело на Python 3.5.


pip3 install nltk

Модули для NLTK устанавливаются так:


import nltk
nltk.download()

... или так:


python3 -m nltk.downloader punkt averaged_perceptron_tagger
# python3 -m nltk.downloader all

Теперь немного примеров.


tweet = '''
    Kinto by Mozilla - An open source Parse alternative >>
    https://github.com/Kinto/kinto/ #python #parse
'''

# Получаем токены. Стандартный универсальный метод:
from nltk import word_tokenize
print(word_tokenize(tweet))
'''
[
    'Kinto', 'by', 'Mozilla', '-', 'An', 'open', 'source',
    'Parse', 'alternative','>', '>', 'https', ':',
    '//github.com/Kinto/kinto/', '#', 'python', '#', 'parse'
]
'''

# С использованием специализированного класса
from nltk.tokenize import TweetTokenizer
# Убрать имена пользователей и многократно повторяемые символы
tt = TweetTokenizer(preserve_case=True, reduce_len=False, strip_handles=False)
print(tt.tokenize(tweet))
'''
[
    'Kinto', 'by', 'Mozilla', '-', 'An', 'open',
    'source', 'Parse', 'alternative', '>', '>',
    'https://github.com/Kinto/kinto/', '#python', '#parse'
]
'''

# Возьмем немного почищеные токены. Воспользуемся обычным токенизатором
tokens = [
    t for t in word_tokenize(tweet)
        if len(t) > 1 and not t.startswith('http')
        and not t.startswith('/')
]
'''
[
    'Kinto', 'by', 'Mozilla', 'An', 'open', 'source',
    'Parse', 'alternative', 'python', 'parse'
]
'''

Токены получили, теперь будем узнавать части речи. Можно использовать встроенный метод, а можно пойти интересным путем, и привлечь Stanford NLP библиотеки. Для этого скачаем и распакуем каталог с ними в нашу рабочую директорию, где пишем код. В Python укажем место расположения JAR и модулей через переменные окружения.


import os
from nltk.tag import StanfordPOSTagger
os.environ['CLASSPATH'] = os.path.join(
    os.path.curdir, 'stanford-postagger-2015-04-20'
)
os.environ['STANFORD_MODELS'] = os.path.join(
    os.path.curdir, 'stanford-postagger-2015-04-20', 'models'
)
stanford_tagger = StanfordPOSTagger('english-bidirectional-distsim.tagger')

Теперь можно сравнить результаты методов:


from nltk import pos_tag
print(pos_tag(tokens))
print(stanford_tagger.tag(tokens))

Результаты почти идентичны, за исключением "An". StanfordPOSTagger считает его существительным. Допустим, мы выберем последний вариант. Найдем все имена существительные (NN, NNS, NNP, NNP-PERS, NNP-ORG):


tagger = stanford_tagger.tag
nouns = [word.lower() for word, pos in tagger(tokens) if pos.startswith('NN')]
print(nouns)  # ['kinto', 'mozilla', 'an', 'source', 'parse', 'python', 'parse']

Теперь N-grams. Не проблема сделать самостоятельно, но в NLTK уже есть пара готовых функций.


from nltk.util import everygrams, ngrams
print(list(ngrams(nouns, 2)))
# [('kinto', 'mozilla'), ('mozilla', 'an'), ...  ('python', 'parse')]
print(list(everygrams(nouns, min_len=2, max_len=3)))
'''
[
    ('kinto', 'mozilla'), ('mozilla', 'an'), ... ('python', 'parse'),
    ('kinto', 'mozilla', 'an'), ('mozilla', 'an', 'source'),
    ... ('parse', 'python', 'parse')
]
'''

Как я уже говорил, NLTK порой очень медленный. С использованием Stanford библиотек он медленнее в разы. Существенно улучшает ситуацию обработка больших объемов твитов разом, а не один за другим.


from nltk import pos_tag, pos_tag_sents
tokens_group = [tokens for i in range(100)]
print(pos_tag_sents(tokens_group))  # 0.3 sec
print(stanford_tagger.tag_sents(tokens_group))  # 6.4 sec !!

На сём откланиваюсь.

2016-01-19

Postgres: generate the most large followers intersections

Задача: получить список пользователей твиттера, с которыми у одного из них есть общие фоловеры, и отсортировать по их количеству. Лучше всего продемонстрировать на примере. Создадим таблицу с идентификаторами пользователей и фоловеров:


CREATE TEMP TABLE f(uid INT, fid INT);
INSERT INTO f(uid, fid) VALUES
 (1, 10), (1, 11), (1, 12), (1, 13), (1, 14),
 (2, 10), (2, 11), (2, 12),
 (3, 13), (3, 14),
 (4, 13), (4, 14), (4, 10), (4, 11)
;

А вот собственно и запрос, интересуют пересечения с пользователем 2, самые большие сверху.


SELECT uid, array_agg(fid) followers_intersect
FROM f GROUP BY uid
ORDER BY cardinality(
 (
  SELECT array_agg(a1) FROM unnest(array_agg(fid)) a1
  INNER JOIN unnest(
   (SELECT array_agg(fid) FROM f WHERE uid=2)::INT[]
  ) a2
  ON a1=a2
 )::INT[]
) DESC NULLS LAST;

Результат, как и ожидалось:

  • 1,"{10,11,12,13,14}"
  • 2,"{10,11,12}"
  • 4,"{13,14,10,11}"
  • 3,"{13,14}"

2015-11-26

Python 3.5 + Docker

Один из самых удобных и современных способов деплоя рабочего окружения является Docker. В предыдущих постах мы устанавливали свежий Python из исходников, но если у вас несколько серверов, да еще и с разными версиями ОС, то этот процесс может отличаться. К тому же это безумие в чистом виде. Как вариант предлагают использование virtualenv, но он тоже не чистая песочница, и использует часть библиотек окружения, что может иметь последствия. Короче, Docker - идеальная изоляция не в ущерб производительности, не зависящая от версии ОС и установленных в ней библиотек. На DockerHub уже есть готовые образы, часть из них официальные, часть от частных лиц. Мы возьмем официальный Python 3.5 и добавим нужные нам пакеты. Минимум телодвижений - и набор готов.

Будем использовать скрипт автоматизации, который называется Dockerfile. Для создания нужного образа достаточно будет выполнить команду в каталоге с Dockerfile:


docker build -t python35 .

А вот примерное содержимое этого конфигурационного файла:


FROM python:3.5
MAINTAINER Main Tainer maintainer@email.com

# Update and install needed packaged:
# libblas-dev and liblapack-dev for matplotlib, gfortran for scipy
RUN apt-get -y update && apt-get install -y libblas-dev liblapack-dev gfortran

# Install packages for DataScience
RUN pip3 install numpy scipy sklearn pandas matplotlib seaborn nltk ipython[all] jupyter

# Web frameworks
RUN pip3 install tornado flask flask-admin flask-login flask-restful
RUN pip3 install django django-bootstrap3 django-admin-bootstrapped

# Databases: psycopg2 (Postgres), pika (RabbitMQ), sqlalchemy (ORM)
RUN pip3 install psycopg2 sqlalchemy pymongo pika redis elasticsearch

# HTTP-requests http://docs.python-requests.org/en/latest/
RUN pip3 install requests requests_oauthlib

# Image manipulations https://pillow.readthedocs.org/en/3.0.x/
RUN pip3 install pillow

# Make C-modules http://cython.org/
RUN pip3 install cython

# Scheduler https://apscheduler.readthedocs.org/en/latest/
RUN pip3 install apscheduler

Вот и все, можно запускать!


docker run -ti python35 python

Python 3.5.0 (default, Nov 20 2015, 06:18:32)
[GCC 4.9.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

Что-нибудь посодержательнее? Ок, прокинем в контейнер каталог с реальным кодом, например с реализацией алгоритма для вычисления количества шестизначных счастливых билетов, и выполним скрипт:


docker run -v /path/outside:/path/inside python35 python /path/inside/code.py

Ура, теперь то мы знаем, что их всего 55252.

2015-11-24

Python3.5 + PyQt5 - The Hard Way

Сегодня мы, в продолжение предыдущей статьи, установим библиотеку PyQt5 для собранного из исходников Python3.5. Для версии Python3, идущей в репозитории дистрибутива есть простой путь, и в общем случае лучше придерживаться его - он проще и дает аналогичный результат.


sudo apt-get install python3-pyqt5 pyqt5-dev-tools

Маководам тоже повезло, им также хватит одной строки (если стоит Homebrew, а куда ж без него?):


brew install pyqt5

Тех же, кто не ищет легких путей или кому нужна именно собственноручно собранная версия, милости прошу к прочтению.

Итак, поехали. Для начала, скачиваем подходящий установщик Qt5, мне нужен был для Linux x64, запускаем и устанавливаем, можно в каталог по умолчанию ~/Qt.

Качаем и распаковываем свежие версии PyQt5 и sip отсюда: http://sourceforge.net/projects/pyqt/files/. Помним, что мы установили python в локальный каталог ~/local. В каталоге с распакованным sip конфигурируем и устанавливаем:


python3 configure.py -d ~/local/lib/python3.5/site-packages/
make
make install

Для успешной сборки GUI модулей PyQt5 (типа PyQt5.QtWidgets) пришлось установить пакетик с исходниками mesa:


sudo apt-get install libgl1-mesa-dev

Теперь в каталоге с распакованным PyQt5 конфигурируем и устанавливаем:


python3 configure.py --destdir ~/local/lib/python3.5/site-packages/\
 --qmake ~/Qt/5.5/gcc_64/bin/qmake --disable QtPositioning
make
make install

Почему здесь фигурирует "--disable QtPositioning"? Потому что иначе не собирается, выбрасывая сообщение "qgeolocation.h: No such file or directory". Да и шут с ним. Вот собственно и всё, можно начинать писать код.


from PyQt5.QtWidgets import QApplication, QWidget
app = QApplication([])
w = QWidget()
w.resize(300, 200)
w.move(400, 400)
w.setWindowTitle('PyQt5 installed')
w.show()

Получите, распишитесь.