Разработка рекомендательной системы на Python

Думаю, никого сегодня не удивить рекомендательными системами. Их можно встретить повсюду: на сайте с книгами (ozon.ru), блогах (habr.ru), интернет-магазинах (практически любой), стриминговых музыкальных (spotify, Яндекс.Музыка) и видео сервисах (kinopoisk, ivi, amediateka). Самый продуктивный способ разобраться как работают подобные алгоритмы — написать самому с самого начала. Этому и будет посвящена текущая статья.

В рекомендательных системах есть два основных подхода:

  1. Content-based рекомендации основанные на описании объектов, которые требуется рекомендовать (рекомендуем фильмы, похожие на те, которые понравились пользователю);
  2. Коллаборативная фильтрация основанная на оценках пользователя и похожести его на других пользователей.

Первый подход более трудоёмкий и сильно привязан к конкретной предметной области. Второй — значительно проще, но имеет недостатки. Например, проблема “холодного старта”: что рекомендовать новым пользователям, которые не оценили ни одного фильма или кому рекомендовать фильм, у которого ещё нет оценок?

В этой статье определим рекомендации на основе коллаборативной фильтрации.

Продолжение на makesomecode.me

 

Advertisements

[Перевод] Прощай, Объектно-Ориентированное программирование

Перепост статьи с makesomecode.me

Оригинал

Уже десятилетия я программирую с на объектно-ориентированных языках программтрования. Первым объектно-ориентированным языком, который я использовал, был C++. После этого был Smalltalk и, в конце, .NET и Java.

Я фанатично использовал преимущество Наследования, Инкапсуляции и Полиморфизма. Три столпа Объектно-ориентированного программирования.

Я жаждал получить обещаное Переиспользование и использовать мудрость полученную теми, кто был до меня в этом новом удивительном мире.

Я не мог сдержать волнения при мысли, что можно сопоставить объекты реального мира с моими классами и ожидал, что всё встанет на свои места.

Ещё никогда я так не ошибался.

Наследование. Первый столп падёт.

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

goodbye-oop

И Переиспользование – это слово дня. Нет… пусть будет словом года и даже больше.

Я повёлся на это и ринулся в мир с новыми знаниями.

Проблема банана, обезьяны, джунглей

С верой в сердце и задачами, требующими решения, я начал выстраивать иерархии классов и писать код. И всё в мире было отлично.

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

Появился новый проект и я вспомнил про Класс, который я так любил на предыдущем проекте.

Без проблем. Переиспользование – маё спасение. Всё что мне требуется – всего лишь перенести класс из старого проекта в новый.

Ну… На самом деле… не только этот Класс. Нам потребуется ещё родительский класс. Но… И это ещё не всё.

Тьфу… Погодите… Похоже, нам ещё потребуется родительский класс родительского класса… И ещё… Нам потребуются ВСЕ родительские классы. Хорошо… Я справляюсь с этим. Не проблема.

Великолепно. Теперь это не компилируется. Почему?? Ох, вижу… Этот объект содержит другой объект. Таким образом мне требуется и другой класс. Не проблема.

Погодите… Мне нужен не только другой объект. Мне нужен ещё родитель другогокласса и родительский класс родительского класса и так далее и так далее для каждого объекта который используется в используемом классе и ВСЕ родительские классы родителей, родители родителей и так далее.

Тьфу.

Вот великолепная цитата создателя Erlang Джо Армстронга:

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

Решение проблемы банана, обезьяны, джунглей

Я решил эту проблему не создавая глубоких иерархий классов. Но если наследование – это ключ к Переиспользованию, то любые ограничения этой возможности ограничат возможности Переиспользования. Так?

Именно.

Так что же делать бедному ООП-программисту?

Включать и Делегировать. Больше об этом далее.

Ромбовидное наследование (Diamond problem)

Рано или поздно эта проблема покажет свою ужасную и, в зависимости от языка программирования, неразрешимую голову.

goodbye-oop2

Многие объекто-ориентированные языки программирования не поддерживают эту возможность, даже не смотря на то, что она выглядит логично. В чём проблема поддержки этой фичи в объектно-ориентированном языке?

Окей, представьте следующий псевдокод:

Class PoweredDevice {
}

Class Scanner inherits from PoweredDevice {
  function start() {
  }
}

Class Printer inherits from PoweredDevice {
  function start() {
  }
}

Class Copier inherits from Scanner, Printer {
}

Заметьте, что оба класса Scanner и Printer реализуют метод start.

Так какой метод start класс Copier наследует? Scanner? Или Printer? Не может быть, что оба одновременно.

Решение проблемы ромбовидного наследования

Решение простое – не делать подобного.

Да, это так. Многие ОО языки программирования не позволяют вам такого сделать.

Но, но… если я должен сделать подобное? Мне необходимо моё Переиспользование!

Тогда вам надо Включать и Делегировать:

Class PoweredDevice {
}

Class Scanner inherits from PoweredDevice {
  function start() {
  }
}

Class Printer inherits from PoweredDevice {
  function start() {
  }
}

Class Copier {
  Scanner scanner
  Printer printer
  function start() {
    printer.start()
  }
}

Заметьте, что теперь класс Copier содержит ссылки на объекты классов Printer и Scanner. Класс Copier делегирует функцию start реализации из класса Printer. Так же просто можно делегировать её имплементацию классу Scanner.

Эта проблема – ещё одна трещина в столпе Наследования.

Проблема хрупкого базового класса

Итак, моя иерархия классов небольшая и я не допускаю циклических наследований. Бриллианты* не для меня.

И всё было отлично в мире. До тех пор пока…

Однажды, мой работавший ранее код на следующий день перестал работать. Загвоздка в том, что я не менял код.

Возможно, это баг… Но подождите… Что-то изменилось…

И это был не мой код. Ломающее изменение оказалось в базовом классе.

Каким образом изменение базового класса могло поломать мой код?

А вот так…

Представьте, что базовый класс ниже (написан на Java, но дожен быть понятен и тем, кто не знаком с этим языком):

import java.util.ArrayList;
 
public class Array
{
  private ArrayList<Object> a = new ArrayList<Object>();
 
  public void add(Object element)
  {
    a.add(element);
  }
 
  public void addAll(Object elements[])
  {
    for (int i = 0; i < elements.length; ++i)
      a.add(elements[i]); // эта строчка изменится.
  }
}

Обратите внимание на строку с комментарием. Она изменится и приведёт к тому, что мой код перестанет работать.

Класс Array содержит два метода add() и addAll(). Метод add() добавляет в коллекцию один элемент, а метод addAll() добавляет несколько элементов вызывая метод add().

И класс-наследник:

public class ArrayCount extends Array
{
  private int count = 0;
 
  @Override
  public void add(Object element)
  {
    super.add(element);
    ++count;
  }
 
  @Override
  public void addAll(Object elements[])
  {
    super.addAll(elements);
    count += elements.length;
  }
}

Класс ArrayCount является специализацией общего класса Array. С однойразницей, что ArrayCount хранит количество элементов массива в поле count.

Давайте внимательно посмотрим на оба класса.

Метод add() класса Array добавляет элемент в локальный ArrayList. Метод addAll() класса Array вызывает метод add класса ArrayList для каждого элемента.

Метод add() класса ArrayCount вызывает родительский метод add и затем увеличивает счётчик count. Метод addAll() класса ArrayCount вызывает родительский addAll() и затем увеличивает счётчик count на количество элементов в переданном массиве.

Всё работает отлично.

Теперь ломающее изменение. Строка с комментарием в базовом классе изменилась:

public void addAll(Object elements[])
{
    for (int i = 0; i < elements.length; ++i)
        add(elements[i]); // строчка изменилась
}

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

Но автору безразличен наследуемый класс. И автор класса-наследника получает проблемы.

Теперь ArrayCount.addAll() вызывает базовый addAll() который внутри вызывает переопределённый метод add() из класса-наследника.

Это приводит к тому, что count увеличивается каждый раз когда вызывается метод add() класса-наследника и увеличивается ещё раз, на количество элеметов в массиве, когда вызывается метод addAll() класса-наследника.

ЭЛЕМЕНЫ ПОДСЧИТЫВАЮТСЯ ДВАЖДЫ.

Если это могло произойти, то это произойдёт, автор класса-наследника обязан знать внутреннюю реализацию базового класса. И владельцев классов-наследников необходимо информировать обо всех изменениях в базовом классе так как это может непредсказуемо повлиять на класс-наследник.

Тьфу! Эта огромная трещина будет всегда угрожать стабильности прекрасного столпа Наследования.

Решение проблемы хрупкого базового класса

И снова Включение и Делегирование – путь к спасению.

Используя Включение и Делегирование мы переходим от разработки Белового Ящика (White Box programming) к разработке Чёрного Ящика (Black Box programming).

При разработке Белого Ящика мы должны знать то, как реализован базовый класс.

В случае Чёрного Ящика мы полностью ингорируем реализацию базового класса поскольку не имеем возможности внедрить код в базовый класс переопределяя его функции. Мы должны придерживаемся лишь некоего интерфейса.

Звучит тревожно…

Наследование должно было быть большим вкладом в Переиспользование кода.

Объектно-ориентированные языки программирования не были спроектированы для того, чтобы можно было просто Внедрять и Делегировать. Они были созданы для простого Наследования.

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

Проблема Иерархий

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

Должна ли быть создана папка “Документы” в которой будет папка с названием “Компания”?

Может быть, я должен создать папку “Компания” и в ней создать папку “Документы”?

Оба варианта подходят. Но какой из них – правильный? Который лучше?

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

Но если родитель и потомок могут произвольно меняться местами, то, очевидно, с этой моделью что-то не так.

Решение проблемы Иерархий.

Проблема в том, что…

Категориальные Иерерхии не работают

Тогда для чего иерархии подходят?

Включение

Если вы присмотритесь к реальному миру вокруг, то повсюду заметите Включаемые Иерерхии (или Иерархии Эксклюзивного Владения).

Вы не увидете Категориальных Иерархий. Отличный пример Включаемой Иерархии – это ваши носки. Они находятся в ящике для носков, который является одним из ящиков вашего комода, который находится в вашей спальне, которая находится в вашем доме, и так далее.

Директории на вашем жестком диске – другой пример Включаемой Иерархии. Они содержат папки.

Как мы можем их категоризировать?

Итак, если вы подумали о Документах Компании, то вообще не имеет значения куда я их положу. Я могу положить их в папку Документы или в папку Разное.

Для категоризации я решил использовать теги. И присвоил файлу теги:

Document
Company
Handbook

У тегов нет порядка в иерархии. (Это так же решает проблему ромбовидного наследования.)

Теги похожи на интерфейсы, которых может быть несколько для каждого типа.

Но с таким количеством трещин Наследование, похоже, разрушается.

Прощай, Наследование.

Инкапсуляция. Второй столп падёт.

На первый взгляд, Инкапсуляция – вторая большая плюшка объектно-ориентированного программирования.

Состояние объекта скрыто от внешнего доступа, т.е. оно инкапсулировано в объекте.

Больше нет необходимости думать о глобальных переменных, к которым имеют доступ непонятно кто.

Инкапсуляция предоставляет безопасность вашим переменным.

Инкапсуляция ВЕЛИКОЛЕПНА!!!

Долгой жизни инкапсуляции.

До тех пор пока…

Проблема ссылок

Ради эффективности, объекты передаются по ссылке, а не по значению.

Это означает, что в функцию передаётся не значение объекта, а ссылка или указатель на объект.

Если Объект передаётся в конструктор Объекта, то конструктор может сохранить ссылку на Объект в приватную переменную, которая защищена Инкапсуляцией.

Но переданный Объект не в безопасности!

Почему? Потому что указатель на Объект есть и в других местах. Например, в коде, который вызвал конструктор. Этот код объязан иметь ссылку на Объект. Иначе как он мог бы передать его в конструктор?

Решение проблемы ссылок

Конструктор должен создавать копию переданного Объекта. При этом должно осуществляться глубокое копирование, т.е. создаваться копии всех объектов, являющиеся членами переданного Объекта и копии всех объектов этих объектов и так далее.

Столько всего ради эффективности.

И в этом проблема. Не все объекты можно клонировать. Зависимость некоторых из них от ресурсов операционной системы делает клонирование бесполезным в лучшем случае и невозможным – в худшем.

И ВСЕ популярные объектно-ориентированные языки программирования имеют эту проблему.

Прощай, Инкапсуляция.

Полиморфизм. Третий столп падёт

Полиморфизм был рыжим приёмным ребёнком Объектно Ориентированной Троицы.

Он как Ларри Файн в этой группе.

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

Это не значит, что Полиморфизм плох, это значит лишь то, что вам не нужен Объектно-Ориентированный язык программирования, чтобы добиться его.

Интерфейсы предоставляют вам это. И без всего багажа Объектно-Ориентированности.

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

Таким образом, без лишнего шума, попрощаемся с Объектно-Ориентированным Полиморфизмом и скажем “Привет” Полиморфизму, основаному на интерфейсах.

Нарушенные обещания

Ну, Объектно-ориентированное программирование пообещало многое. И эти обещания даются наивным программистам в учебных классах, читающих блоги и проходящих онлайн-курсы.

Мне потребовались годы, чтобы понять как ООП меня обманывало. Я тоже был доверчивым.

И меня сожгли.

И что дальше?

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

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

Однажды обжёгсись на молоке, будешь дуть и на воду.

Ты понимаешь.

Вы можете присоединиться к сообществу веб-разработчиков изучающих и помогающих друг другу разрабатывать веб-приложения с использование функционального программирования на языке Elm, взгляните на группу в Facebook “Learn Elm Programming”

*Отсылка к оригинальному названию ромбовидных наследований

Как я Telegram бота на Go писал. Часть пятая. Dockerизируй это!

На днях наконец-то решил добраться до такой хайповой темы как Docker:

Docker — программное обеспечение для автоматизации развёртывания и управления приложениями в среде виртуализации на уровне операционной системы. Позволяет «упаковать» приложение со всем его окружением и зависимостями в контейнер, который может быть перенесён на любую Linux-систему с поддержкой cgroups в ядре, а также предоставляет среду по управлению контейнерами. Изначально использовал возможности LXC, с 2015 года применял собственную библиотеку, абстрагирующую виртуализационные возможности ядра Linux — libcontainer. С появлением ​Open Container Initiative начался переход от монолитной к модульной архитектуре. (Википедия)

И, в качестве эксперимента, решил развернуть одного из своих ботов на VDS внутри контейнера для простоты переноса и развёртывания. Бот написан на Go и, соответсвенно, это делает развёртывание немного нетривиальным (на самом деле, если читать документацию и уметь пользоваться поисковыми системами, то всё довольно просто).

Continue reading

Сколько страниц книги необходимо прочитать, чтобы узнать 90% всех слов?

 

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

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

В этой статье я хочу проверить аналогичное утверждение для русского языка: сколько страниц необходимо прочитать, чтобы узнать 90% книги.

Спойлер: прочитав 20 страниц книги вы практически наверняка не узнаете 90% всех слов. Далее будут графики, таблицы и немного кода.

Для анализа требуется стандартный для подобных задач набор бибилиотек Python: matplotlib, seaborn, pandas, numpy, stop_words и pymorphy2.

Что анализировать?

В качестве “подопытных” взял различные книги:

  1. Объёмную классику (Л. Толстой “Война и мир. Книга 1.”)
  2. Нехудожественная литература (С. Галёнкин “Маркетинг игр”)
  3. Случайную бесплатную современную книгу на litres (С. Тармашев “Закат Тьмы”)
  4. Книгу, переведённую на русский (Ш. Стрейд “Дикая”)
  5. Небольшой рассказ Антона Чехова (А. Чехов “Человек в футляре”)

Что хотим узнать?

  1. Как быстро появляются новые слова в книге?
  2. Какой процент книги можно прочитать зная слова, которые были встречены к некоторой странице?

На графиках к книгам зелёный линия даёт ответ на первый вопрос: какой процент всех уникальных слов в книге были встречены к текущей странице. Синяя линия на графике отвечает на второй вопрос: какой объём от всей книги написан при помощи слов встреченных к текущей странице.

from matplotlib import pyplot

import seaborn as sns
from collections import Counter
from itertools import chain

import pandas as pd
import numpy as np 

import re

import pymorphy2
from stop_words import get_stop_words

page_size = 200

Подготовка данных

Для подготовки книг в виде текстовых файлов для анализа используем pymorhy2 для лемматизации и stop_words для удаления стоп-слов:

morph = pymorphy2.MorphAnalyzer()
sw = get_stop_words('ru')
pyplot.rcParams["figure.figsize"] = (10,5)

def lemmatize(text):
    """Функция удаляет из текста стоп-слова, 
    лемматизирует текст и берёт для каждого начальную форму.
    
    # Arguments
        text: исходный текст.
    
    # Returns
        fixed_text: список лемматизированых не-стоп-слов.
    """
    letters_only = re.sub('[^a-zA-Zа-яА-Я]', ' ', text)
    fixed_text = []
    letters_only = list(filter(None, letters_only.split(' ')))
    for word in letters_only:
        # Берём первое совпадения только для простоты.
        nf = morph.parse(word)[0].normal_form
        if nf in sw:
            continue
        
        fixed_text.append(nf)
        
    return fixed_text


def txt_to_pages(filename, encoding='utf8'):
    """Функция преобразует текст из текстового файла в "страницы"
    
    # Arguments
        filename: путь до текстового файла.
        encoding: кодировка исходного файла.
    
    # Returns
        book_pages: список списков лемматизированных слов ("страниц")    
    """
    book_text = open(filename, mode='r', encoding=encoding).read()
    book_text = lemmatize(book_text)
    
    book_pages = []
    for i in range(len(book_text) // page_size + 1):
        book_pages.append(book_text[i * page_size : i * page_size + page_size])
    
    return book_pages

Обработка книг

Следующие две функции содержат всю логику по анализу книг:
* вычисляют “сложность” книги – сколько страниц текста необходимо прочитать, чтобы узнать 90% всех уникальных слов,
* вычисляют основные метрики книги – какой процент книги возможно прочитать зная слова встреченные к текущей странице, какой процент уникальных слов уже встречен.

def calculate_hardness(coverage, percent = 0.9):
    """ Функция вычисляет "сложность" книги - 
            насколько большую часть книги необходимо прочтитать, 
            чтобы узнать percent всех используемых слов.
    
    # Arguments
        coverage: массив с информацией какой процент книги
            можем прочитать, зная слова полученные к i-ой странице.
        percent: сколько слов хотим знать.
    
    # Returns
        i: номер страницы.
        hardness: какой процент книги будет прочитан к этой странице.
    """
    for i in range(len(coverage)):
        if coverage[i] > percent:
            break
    
    hardness = (100 * i) / float(len(coverage))
    return i, hardness


def percentage(values, title):
    """ Функция вычисляет всё.
    
    # Arguments
        values: список "страниц" книги.
    """
    all_words = list(chain.from_iterable(values))
    result = {}
    current_text = []
    page_counter = 0
    file_name = title.lower().replace(' ', '_').replace('\'','') + '.png'
    
    counter = Counter()
    # "Счётчик" всех слов книги. 
    # Используется для подсчёта процента книги, 
    # который можно прочитать зная некоторый объём слов. 
    occurances = Counter(all_words)
    
    # i-ый элемент: какой процент книги можем прочитать зная слова полученные к i-ой странице.
    coverage = []
    
    # i-ый элемент: какой процент слов используемых в книге знаем к i-ой странице.
    uniqueness = []
    
    # i-ый элемент: сколько уникальных слов используемых в книге знаем к i-ой странице.
    total_unique_words_count = []
    
    total = float(len(all_words))
    total_unique = float(len(occurances.keys()))
    
    print('page\ttotal\tpercent of all\tpercent of book\tuniqueness')
    for page_text in values:
        # Обновим счётчик уникальных слов словами с новой страницы.
        counter.update(page_text)
        
        # Посчитаем, сколько вообще раз встречаются слова 
        # которые уже были встречены к текущей странице.
        occured_words_count = sum((occurances[w] for w in counter.keys()))

        # Какой процент от всей книги составляют слова, 
        # которые уже встретили к текущей странице.
        _coverage = occured_words_count / total
        coverage.append(_coverage)
        
        # Какой процент от всех уникальных слов встретили к текущей странице.
        _uniqueness = len(counter.keys()) / total_unique
        uniqueness.append(_uniqueness)
        
        # Какой процент от книги прошли к текущей странице.
        percent_book = page_counter / len(values)
        
        # Сколько уникальных слов будет известно к текущей странице.
        total_unique_words_count.append(len(counter.keys()))
        
        print('{0}\t{1}\t{2:2f}\t{3:2f}\t{4:2f}'.format(page_counter, 
        len(counter.keys()), 
        _coverage, 
        percent_book, 
        _uniqueness))
        
        result[page_counter] = { 
            'page': page_counter,
            'unique_words': _uniqueness, 
            'all_words': _coverage
        }
        page_counter += 1
    
    percent_df = pd.DataFrame.from_dict(result,orient='index')
    
    page, hardness = calculate_hardness(coverage)
    
    print('Всего страниц: {0}'.format(len(values)))
    print('Всего слов: {}'.format(len(all_words)))
    print('Всего уникальных слов: {}'.format(len(occurances.keys())))
    print('Прочитав {0} страниц книги ({1:.2f}% всей книги) вы узнаете {2} уникальных слов.\n
           К этой странице будут встречены 90% всех слов книги и {3:.2f}% уникальных.'.format(
          page,
          hardness,
          total_unique_words_count[page],
          uniqueness[page] * 100))
    
    pyplot.plot(percent_df['unique_words'], color='g', label='Уникальные слова')
    pyplot.plot(percent_df['all_words'], color='b', label='Все слова')
    pyplot.legend(loc=4)
    pyplot.title(title)
    pyplot.xlabel('Страница')
    pyplot.ylabel('Покрытие (%)')
    pyplot.show()

Результаты

Л. Толстой “Война и мир. Книга 1”

war_and_peace_pages = txt_to_pages('./books/Tolstoyi_L._Voyinaimir1._Voyina_I_Mir_Kniga_1.txt', 
    encoding='1251')
percentage(war_and_peace_pages, 'Война и мир')
page    total   percent of all  percent of book uniqueness
0   141 0.087556    0.000000    0.009748
1   286 0.158995    0.001541    0.019773
2   404 0.201158    0.003082    0.027931
3   521 0.238750    0.004622    0.036020
4   630 0.271941    0.006163    0.043556
5   732 0.306381    0.007704    0.050608
6   832 0.341545    0.009245    0.057522
7   923 0.360869    0.010786    0.063814
8   1007    0.387377    0.012327    0.069621
9   1096    0.404189    0.013867    0.075774
10  1179    0.417508    0.015408    0.081513
...
639 14463   0.999992    0.984592    0.999931
640 14463   0.999992    0.986133    0.999931
641 14463   0.999992    0.987673    0.999931
642 14463   0.999992    0.989214    0.999931
643 14463   0.999992    0.990755    0.999931
644 14463   0.999992    0.992296    0.999931
645 14463   0.999992    0.993837    0.999931
646 14463   0.999992    0.995378    0.999931
647 14463   0.999992    0.996918    0.999931
648 14464   1.000000    0.998459    1.000000
Всего страниц: 649
Всего слов: 129734
Всего уникальных слов: 14464
Прочитав 197 страниц книги (30.35% всей книги) вы узнаете 8171 уникальных слов.
К этой стрнице будут встречены 90% всех слов книги и 56.49% уникальных.

war_and_peace.png

Книга “Война и мир. Книга 1.” Льва Тостого содержит 649 страниц без учёта стоп-слов. На этих страницах можно встретить 14464 уникальных слов, каждое из которых, в среднем, встречается 9 раз. К 197-ой странице будет встречено 8171 уникальное слово (56.49% от всех уникальных слов). При помощи этих слов написано 90% всей книги.

С. Галёнкин “Маркетинг игр”

game_marketing_pages = txt_to_pages('./books/Games-Marketing-by-Galyonkin-v1.txt')
percentage(game_marketing_pages, 'Маркетинг игр')
page    total   percent of all  percent of book uniqueness
0   145 0.250361    0.000000    0.044602
1   274 0.334958    0.017857    0.084282
2   392 0.386150    0.035714    0.120578
3   514 0.434543    0.053571    0.158105
4   614 0.481040    0.071429    0.188865
5   715 0.511015    0.089286    0.219932
6   798 0.545323    0.107143    0.245463
7   879 0.581347    0.125000    0.270378
8   955 0.617822    0.142857    0.293756
9   1042    0.646443    0.160714    0.320517
10  1108    0.668021    0.178571    0.340818
...
46  2938    0.964608    0.821429    0.903722
47  2973    0.968039    0.839286    0.914488
48  3015    0.972553    0.857143    0.927407
49  3057    0.979415    0.875000    0.940326
50  3094    0.983026    0.892857    0.951707
51  3131    0.987450    0.910714    0.963088
52  3167    0.990791    0.928571    0.974162
53  3187    0.992597    0.946429    0.980314
54  3242    0.999097    0.964286    0.997232
55  3251    1.000000    0.982143    1.000000
Всего страниц: 56
Всего слов: 11076
Всего уникальных слов: 3251
Прочитав 34 страниц книги (60.71% всей книги) вы узнаете 2485 уникальных слов.
К этой стрнице будут встречены 90% всех слов книги и 76.44% уникальных.

game_marketing.png

“Маркетинг игр” Сергея Галёнкина менее объёмная и содержит лишь 56 страниц текста. Уникальных слов в книге 3251. Для того, чтобы иметь возможность прочитать 90% книги необходимо знать 2485 уникальных слов (76.44% всех уникальных слов). Этого можно достичь к 34 странице.

С. Тармашев “Закат Тьмы”

zakat_tmyi = txt_to_pages('./books/Tarmashev_S._Tma3._Zakat_Tmyi.txt',
    encoding='1251')
percentage(zakat_tmyi, 'Закат тьмы')
page    total   percent of all  percent of book uniqueness
0   132 0.060600    0.000000    0.012445
1   251 0.073714    0.002611    0.023664
2   363 0.076843    0.005222    0.034223
3   446 0.079892    0.007833    0.042048
4   535 0.082471    0.010444    0.050438
5   621 0.084290    0.013055    0.058546
6   704 0.085690    0.015666    0.066371
7   779 0.086842    0.018277    0.073442
8   851 0.089970    0.020888    0.080230
9   951 0.123516    0.023499    0.089658
10  1097    0.193396    0.026110    0.103422
...
373 10482   0.998063    0.973890    0.988215
374 10502   0.998338    0.976501    0.990101
375 10522   0.998626    0.979112    0.991986
376 10541   0.998940    0.981723    0.993778
377 10560   0.999346    0.984334    0.995569
378 10578   0.999594    0.986945    0.997266
379 10586   0.999699    0.989556    0.998020
380 10598   0.999856    0.992167    0.999152
381 10607   1.000000    0.994778    1.000000
382 10607   1.000000    0.997389    1.000000
Всего страниц: 383
Всего слов: 76403
Всего уникальных слов: 10607
Прочитав 140 страниц книги (36.55% всей книги) вы узнаете 6585 уникальных слов.
К этой стрнице будут встречены 90% всех слов книги и 62.08% уникальных.

sunset_of_the_darkness.png

Книгу Тармашева “Закат Тьмы” решил взять как образец современной литературы (и потому, что она была бесплатной на litres). Книга среднего объёма – 383 страницы без стоп-слов. Уникальных слов 10607 и зная 6585 (можно встретить прочитав 140 страниц или 36.55% всей книги) можно прочитать 90% всей книги.

Ш. Стрэйд “Дикая”

dikaya_df = txt_to_pages('./books/Stryeyid_Sh._Proekttruesto._Dikaya_Opasnoe_Puteshestv.txt', 
    encoding='1251')
percentage(dikaya_df, 'Дикая')
page    total   percent of all  percent of book uniqueness
0   156 0.083797    0.000000    0.014677
1   281 0.130858    0.003247    0.026437
2   421 0.157755    0.006494    0.039609
3   568 0.218298    0.009740    0.053439
4   711 0.269051    0.012987    0.066892
5   839 0.318194    0.016234    0.078935
6   948 0.352604    0.019481    0.089190
7   1052    0.372508    0.022727    0.098975
8   1158    0.407032    0.025974    0.108947
9   1258    0.430774    0.029221    0.118355
10  1342    0.448255    0.032468    0.126258
...
298 10423   0.996292    0.967532    0.980619
299 10430   0.996422    0.970779    0.981278
300 10456   0.996861    0.974026    0.983724
301 10469   0.997122    0.977273    0.984947
302 10484   0.997382    0.980519    0.986358
303 10515   0.997967    0.983766    0.989275
304 10603   0.999528    0.987013    0.997554
305 10629   1.000000    0.990260    1.000000
306 10629   1.000000    0.993506    1.000000
307 10629   1.000000    0.996753    1.000000
Всего страниц: 308
Всего слов: 61494
Всего уникальных слов: 10629
Прочитав 134 страниц книги (43.51% всей книги) вы узнаете 6997 уникальных слов.
К этой стрнице будут встречены 90% всех слов книги и 65.83% уникальных.

wild.png

Ещё одна книга среднего размера – перевод книги Шерил Стрэйд “Дикая”. В книге 308 страниц со 10629 уникальными словами. Для того, чтобы прочитать 90% книги необходимо знать не менее 6997 уникальных слов. Этого можно добиться прочитав 134 первые страницы (43.51% всей книги).

А. Чехов “Человек в футляре”

man_in_the_case = txt_to_pages('./books/Chehov_A._Man_in_the_case.txt')
percentage(man_in_the_case, 'Человек в футляре')
page    total   percent of all  percent of book uniqueness
0   169 0.266868    0.000000    0.153358
1   307 0.402820    0.100000    0.278584
2   426 0.507049    0.200000    0.386570
3   549 0.628902    0.300000    0.498185
4   652 0.717019    0.400000    0.591652
5   751 0.779960    0.500000    0.681488
6   858 0.854985    0.600000    0.778584
7   934 0.903323    0.700000    0.847550
8   1022    0.954683    0.800000    0.927405
9   1102    1.000000    0.900000    1.000000
Всего страниц: 10
Всего слов: 1986
Всего уникальных слов: 1102
Прочитав 7 страниц книги (70.00% всей книги) вы узнаете 934 уникальных слов.
К этой стрнице будут встречены 90% всех слов книги и 84.75% уникальных.

man_in_the_case.png

Последней книгой был короткий (всего 10 страниц) рассказ Антона Чехова “Человек в футляре”. Уникальных слов в книге немного – 1986. 90% книги написаны с использованием 934 уникальных слов (84.75% от всех уникальных слов), которые будете знать прочитав 7 страниц (но в это случае проще дочитать книгу, чем заниматься подобной ерундой, как подсчёт сложности книги).

Выводы

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

Возможно, это совпадение, но графики для художественной литературы (“Война и мир”, “Дикая”, “Закат Тьмы”) похожи. В это же время графики для книги “Маркетинг игр” идут значительно ближе друг к другу, т.е. книга “сложнее”.

Особняком стоит книга “Человек в футляре” Антона Чехова: она очень короткая и для того, чтобы встретить 90% уникальных слов потребуется прочитать всю книгу (см. таблицу).

На этом на сегодня всё.

 

Проблемы с запуском блокнота Jupyter

Наткнулся буквально только что, но, думаю, проблема должна быть известная: при запуске из консоли команды jupyter notebook браузер запускается, но при открытии блокнота намертво зависает.

Казалось бы, в чём может быть проблема, если только что на другом компе всё работало отлично?

Однако, внимательный поиск в Google моей любимой поисковой системе натолкнул на мысль, что дело может быть в размере этого блокнота (на момент обнаружения проблемы блокнот дорос до 21Мб). Для проверки этой догадки получил одну из первых версий этого блокнота из VSC (около 200Кб). Открытие блокнота происходит моментально и без единой проблемы.

Простейшее решение очевидно – необходимо уменьшать размер *.ipynb. Самый логичный способ – удалить все output-ячейки. К счастью, добрые люди уже написали скрипт на Python, который это делает. Использовать его элементарно:

python remove_output.py notebook.ipynb

После чего будет создан новый блокнот с именем notebook_removed.ipynb с удалёнными output-ами.

После обработки моего блокнота его размер стал значительно лучше: 24.4Кб против 21Мб и никаких проблем, как я и ожидал, далее с этим блокнотом не было.

[Перевод] Как я совершенствуюсь как программист

Некоторые люди на React Conf спрашивали у меня о том, как улучшить свои навыки программиста. По некоторым причинам люди видят во мне продвинутого программиста, которого следует прислушиваться. Я подумал, что стоит написать статью о моей “мыслительной модели” того, как я подхожу к программированию в течение лет.

Расскажу немного о себе: мне 32 года и у меня за спиной более 10 лет стажа. Но только в последние несколько лет я действительно начал чувствовать уверенность в том, что я делаю. Но даже сейчас иногда я сомневаюсь в своих способностях. Основная идея в том, что это чувство никогда не пропадёт, так что постарайтесь его игнорировать, продолжая разрабатывать и набираясь опыта.

Хочу предупредить, что это лишь несколько подсказок как улучшить свои навыки программиста. В конечном итоге вам самостоятельно потребуется выяснить, что будет работать именно для вас. Это лишь некоторые вещи, которые работают для меня.

Continue reading