<jupyter_start><jupyter_text>Давайте решим следующую задачу.Необходимо написать робота, который будет скачивать новости с сайта Лента.Ру и фильтровать их в зависимости от интересов пользователя. От пользователя требуется отмечать интересующие его новости, по которым система будет выделять области его интересов.Для начала давайте разберемся с обработкой собственно текстов. Самостоятельно это можно сделать прочитав одну из двух книг: поновее и постарше (в старой хорошо разобраны классификация и кластеризация, в новой - тематическое моделирование и рядом лежит видео лекций).Для обработки текста проводится два этапа анализа: графематический (выделение предложений и слов) и морфологический (определение начальной формы слова, его части речи и грамматических параметров). Этап синтаксического анализа мы разбирать не будем, так как его информация требуется не всегда.Задачей графематического анализа является разделение текста на составные части - врезки, абзацы, предложения, слова. В таких задачах как машинный перевод, точность данного этапа может существенно влиять на точность получаемых результатов. Например, точка, используемая для сокращений, может быть воспринята как конец предложения, что полность разорвет его семантику.Но в некоторых задачах (например нашей) используется подход "мешок слов" - текст воспринимается как неупорядоченное множество слов, для которых можно просто посчитать их частотность в тексте. Данный подход проще реализовать, для него не нужно делать выделение составных частей текста, а необходимо только выделить слова.  Именно этот подход мы и будем использовать.В путь!<jupyter_code>import re # Регулярные выражения.
import requests # Загрузка новостей с сайта.
from bs4 import BeautifulSoup # Превращалка html в текст.
import pymorphy2 # Морфологический анализатор.
import datetime # Новости будем перебирать по дате.
from collections import Counter # Не считать же частоты самим.
import math # Корень квадратный.<jupyter_output><empty_output><jupyter_text>Задачей морфологического анализа является определение начальной формы слова, его части речи и грамматических параметров. В некоторых случаях от слова требуется только начальная форма, в других - только начальная форма и часть речи.Существует два больших подхода к морфологическому анализу: стемминг и поиск по словарю. Для проведения стемминга оставляется справочник всех окончаний для данного языка. Для пришедшего слова проверяется его окончание и по нему делается прогноз начальной формы и части речи.Например, мы создаем справочник, в котором записываем все окончания прилагательных: -ому, -ему, -ой, -ая, -ий, -ый, ... Теперь все слова, которые имеют такое окончание будут считаться прилагаельными: синий, циклический, красного, больному. Заодно прилагательными будут считаться причастия (делающий, строившему) и местоимения (мой, твой, твоему). Также не понятно что делать со словами, имеющими пустое окончание. Отдельную проблему составляют такие слова, как стекло, больной, вина, которые могут разбираться несколькими вариантами (это явление называется омонимией). Помимо этого, стеммер может просто откусывать окончания, оставляя лишь псевдооснову.Большинство проблем здесь решается, но точность работы бессловарных стеммеров находится на уровне 80%. Чтобы повысить точность испольуют морфологический анализ со словарем. Разработчики составляют словарь слов, встретившихся в текстах (здесь можно найти пример такого словаря). Теперь каждое слово будет искаться в словаре и не предсказываться, а выдаваться точно. Для слов, отсутствующих в словаре, может применяться предсказание, пообное работе стеммера.Посмотрим как работает словарная морфология на примере системы pymorphy2.<jupyter_code>morph=pymorphy2.MorphAnalyzer() # Создает объект морфоанализатора и загружет словарь.
wordform=morph.parse('стекло')  # Проведем анализ слова "стекло"...
print(wordform)                 # ... и посмотрим на результат.<jupyter_output>[Parse(word='стекло', tag=OpencorporaTag('NOUN,inan,neut sing,nomn'), normal_form='стекло', score=0.75, methods_stack=((<DictionaryAnalyzer>, 'стекло', 545, 0),)), Parse(word='стекло', tag=OpencorporaTag('NOUN,inan,neut sing,accs'), normal_form='стекло', score=0.1875, methods_stack=((<DictionaryAnalyzer>, 'стекло', 545, 3),)), Parse(word='стекло', tag=OpencorporaTag('VERB,perf,intr neut,sing,past,indc'), normal_form='стечь', score=0.0625, methods_stack=((<DictionaryAnalyzer>, 'стекло', 968, 3),))]<jupyter_text>Как видно из вывода, слово "стекло" может быть неодушевленным существительным среднего рода, единственного числа, именительного падежа tag=OpencorporaTag('NOUN,inan,neut sing,nomn'), аналогично, но в винительном падеже ('NOUN,inan,neut sing,accs'), и глаголом 'VERB,perf,intr neut,sing,past,indc'. При этом в первой форме оно встречается в 75% случаев (score=0.75), во второй в 18,75% случаев (score=0.1875), а как глагол - лишь в 6,25% (score=0.0625). Самым простым видом борьбы с омонимией является выбор нулевого элемента из списка, возвращенного морфологическим анализом. Такой подход дает около 90% точности при выборе начальной формы и до 80% если мы обращаем внимание на грамматические параметры.Теперь перейдем к загрузке новостей. Для этого нам потребуется метод requests.get(url). Библиотека requests предоставляет серьезные возможности для загрузки информации из Интернет. Метод get получает URL стараницы и возвращает ее содержимое. В нашем случае результат будет получаться в формате html.<jupyter_code>requests.get("http://lenta.ru/")<jupyter_output><empty_output><jupyter_text>Однако количество служебной информации в странице явно превышает объем текста новости. Мы проделаем два шага. На первом мы вырежем только саму новость с ее оформлением используя для этого регулярные выражения (библиотека re). На втором шаге мы используем библиотеку BeautifulSoup для "выкусыввания" тегов html.<jupyter_code># Компилируем регулярные выражения - так работает быстрее при большом количестве повторов.
findheaders = re.compile("<h1.+?>(.+)</h1>", re.S)
boa = re.compile('<div class="b-text clearfix js-topic__text" itemprop="articleBody">', re.S)
eoa = re.compile('<div class="b-box">\s*?<i>', re.S)
delscript = re.compile("<script.*?>.+?</script>", re.S)

def getLentaArticle(url):
    # Получает текст страницы.
    art=requests.get(url)
    # Находим заголовок.
    title = findheaders.findall(art.text)[0]
    # Выделяем текст новости.
    text = eoa.split(boa.split(art.text)[1])
    # Иногда новость оканчивается другим набором тегов.
    if len(text)==1:
        text = re.split('<div itemprop="author" itemscope=""', text[0])
    # Выкусываем скрипты - BeautifulSoup не справляетсяя с ними.
    text = "".join(delscript.split(text[0]))
    # Выкусываем остальные теги.
    return BeautifulSoup(title+"\n-----\n"+text, "lxml").get_text()

art_text = getLentaArticle("https://lenta.ru/news/2018/02/15/greben/")
print(art_text)<jupyter_output>Гребенщиков обматерил «лживый» фильм Серебренникова о Цое
-----
Лидер группы «Аквариум» Борис Гребенщиков нецензурно раскритиковал еще не вышедший фильм Кирилла Серебренникова «Лето». Его слова приводит портал MR7.ru в четверг, 15 февраля.«Сценарий — ложь от начала до конца. Мы жили по-другому. В его сценарии московские хипстеры, которые кроме как [совокупляться] за чужой счет, больше ничего не умеют. Сценарий писал человек с другой планеты. Мне кажется, в те времена сценарист бы работал в КГБ», — подчеркн<NAME>.Музыкант также выразил надежду, что Серебренникова, находящегося под домашним арестом по делу о хищении бюджетных средств, освободят.Материалы по теме18:26 — 23 августа 2017«Клетка — всегда плохо»Дело Кирилла Серебренникова: реакция общественности и соцсетейРанее в феврале продюсеры «Лета» огласили актерский состав фильма. Одну из главных ролей — фронтмена рок-коллектива «Зоопарк» <NAME> — исполнил <NAME> из группы «Звери». Картина рассказывает малоизвестную[...]<jupyter_text>Для новостной заметки можно составить ее словарь, а также посчитать частоты всех слов. В итоге мы получим представление текста в виде вектора. В этом векторе координаты будут называться по соответствующим словам, а смещение по данной координате будет показывать частота. При составлении словаря будем учитывать только значимые слова - существительные, прилагательные и глаголы. Помимо этого предусмотрим возможность учитывать часть речи слова, прибавляя ее у начальной форме.Для разделения текста на слова используем простейший алгоритм: слово - это последовательность букв русского алфавита среди которых может попадаться дефис.<jupyter_code>posConv={'ADJF':'_ADJ','NOUN':'_NOUN','VERB':'_VERB'}

def getArticleDictionary(text, needPos=None):
    words=[a[0] for a in re.findall("([А-ЯЁа-яё]+(-[А-ЯЁа-яё]+)*)", text)]
    reswords=[]

    for w in words:
        wordform=morph.parse(w)[0]
        if wordform.tag.POS in ['ADJF', 'NOUN', 'VERB']:
            if needPos!=None:
                reswords.append(wordform.normal_form+posConv[wordform.tag.POS])
            else:
                reswords.append(wordform.normal_form)
            
    return Counter(reswords)

stat1=getArticleDictionary(art_text, True)
print(stat1)<jupyter_output>Counter({'серебренников_NOUN': 5, 'фильм_NOUN': 4, 'сценарий_NOUN': 3, 'гребенщик_NOUN': 3, 'зверь_NOUN': 2, 'группа_NOUN': 2, 'цой_NOUN': 2, 'август_NOUN': 2, 'дело_NOUN': 2, 'февраль_NOUN': 2, 'лето_NOUN': 2, 'домашний_ADJ': 2, 'год_NOUN': 2, 'науменко_NOUN': 2, 'арест_NOUN': 2, 'кирилл_NOUN': 2, 'счёт_NOUN': 1, 'борис_NOUN': 1, 'роль_NOUN': 1, 'освободить_VERB': 1, 'время_NOUN': 1, 'хищение_NOUN': 1, 'отношение_NOUN': 1, 'портал_NOUN': 1, 'надежда_NOUN': 1, 'картина_NOUN': 1, 'рок-коллектив_NOUN': 1, 'малоизвестный_ADJ': 1, 'наталья_NOUN': 1, 'писать_VERB': 1, 'который_ADJ': 1, 'человек_NOUN': 1, 'работать_VERB': 1, 'другой_ADJ': 1, 'общественность_NOUN': 1, 'рассказывать_VERB': 1, 'клетка_NOUN': 1, 'тема_NOUN': 1, 'бюджетный_ADJ': 1, 'средство_NOUN': 1, 'один_ADJ': 1, 'дата_NOUN': 1, 'главный_ADJ': 1, 'чужой_ADJ': 1, 'тот_ADJ': 1, 'конец_NOUN': 1, 'виктор_NOUN': 1, 'рома_NOUN': 1, 'актёрский_ADJ': 1, 'раскрываться_VERB': 1, 'лживый_ADJ': 1, 'огласить_VERB': 1, 'московский_ADJ': 1, [...]<jupyter_text>Для определения меры сходства двух статей теперь может использоваться косинусная мера сходства, рассчитываемая по следующей формуле: $cos(a,b)=\frac{\sum{a_i * b_i}}{\sqrt {\sum{a_i^2}*\sum{b_i^2}}}$.Вообще-то, использовать стандартную функцию рассчета косинусной меры сходства из sklearn было бы быстрее. Но в данной задаче нам бы пришлось сводить все словари в один, чтобы на одних и тех же местах в векторе были частоты одних и тех же слов. Чтобы избежать подобной работы, напишем собственную функцию рассчета косинусного расстояния, работающую с разреженными векторами в виде питоновских словарей.<jupyter_code>def cosineSimilarity(a, b):
    if len(a.keys())==0 or len(b.keys())==0:
        return 0
    sumab=sum([a[na]*b[na] for na in a.keys() if na in b.keys()])
    suma2=sum([a[na]*a[na] for na in a.keys()])
    sumb2=sum([b[nb]*b[nb] for nb in b.keys()])
    return sumab/math.sqrt(suma2*sumb2)<jupyter_output><empty_output><jupyter_text>Посчитаем значение косинусной меры для разных статей.<jupyter_code>stat2=getArticleDictionary(getLentaArticle("https://lenta.ru/news/2018/02/15/pengilly_domoi/"), True)
stat3=getArticleDictionary(getLentaArticle("https://lenta.ru/news/2018/02/15/tar_mor/"), True)
stat4=getArticleDictionary(getLentaArticle("https://lenta.ru/news/2018/02/15/olympmovies/"), True)

print(cosineSimilarity(stat1, stat2))
print(cosineSimilarity(stat1, stat3))
print(cosineSimilarity(stat2, stat3))
print(cosineSimilarity(stat2, stat4))
print(cosineSimilarity(stat3, stat4))<jupyter_output>0.06429108115745111
0.0
0.12321187388436787
0.10845193904480363
0.08038418992031009<jupyter_text>Получилось, на самом деле, так себе - статьи очень слабо походят друг на друга. Но может быть потом выйдет лучше.Пока оформим наш код в виде класса, который помимо загрузки новостей будет уметь сохранять их на диск и читать оттуда.<jupyter_code>class getNewsPaper:
    articles=[]     # Загруженные статьи.
    dictionaries=[] # Посчитанные словари (векторное представление статей).
        
    # Конструктор - компилирует регулярные выражения и загружает морфологию.
    def __init__(self):
        self.delscript = re.compile("<script.*?>.+?</script>", re.S)
        self.findheaders = re.compile("<h1.+?>(.+)</h1>", re.S)
        self.boa = re.compile('<div class="b-text clearfix js-topic__text" itemprop="articleBody">', re.S)
        self.eoa = re.compile('<div class="b-box">\s*?<i>', re.S)
        self.findURLs = re.compile('<h3>(.+?)</h3>', re.S)
        self.rboa = re.compile('<p class="MegaArticleBody_first-p_2htdt">', re.S)
        self.reoa = re.compile('<div class="Attribution_container_28wm1">', re.S)
        self.rfindURLs = re.compile('''<div class="headlineMed"><a href='(.+?)'>''', re.S)
        # Создаем и загружаем морфологический словарь.
        self.morph=pymorphy2.MorphAnalyzer()

    # Загрузка статьи по URL.
    def getLentaArticle(self, url):
        """ getLentaArticle gets the body of an article from Lenta.ru"""
        art = requests.get(url)
        title = self.findheaders.findall(art.text)[0]
        text = self.eoa.split(self.boa.split(art.text)[1])
        if len(text)==1:
            text = re.split('<div itemprop="author" itemscope=""', text[0])
        text = "".join(self.delscript.split(text[0]))
        self.articles.append(BeautifulSoup(title+"\n-----\n"+text, "lxml").get_text())

    # Загрузка всех статей за один день.
    def getLentaDay(self, url):
        """ Gets all URLs for a given day and gets all texts. """
        try:
            day = requests.get(url) # Грузим страницу со списком всех статей.
            cand = self.findURLs.findall(day.text) # Выделяем адреса статей.
            links = ['https://lenta.ru'+re.findall('"(.+?)"', x)[0] for x in cand]
            for l in links: # Загружаем статьи.
                self.getLentaArticle(l)
        except:
            pass

    # Загрузка всех статей за несколько дней.
    def getLentaPeriod(self, start, finish):
        curdate=start
        while curdate<=finish:
            print(curdate.strftime('%Y/%m/%d')) # Just in case.
            # Список статей грузится с вот такого адреса.
            res=self.getLentaDay('https://lenta.ru/news/'+curdate.strftime('%Y/%m/%d'))
            curdate+=datetime.timedelta(days=1)
      
    # Just in case.
    def getReutersArticle(self, url):
        """ Gets the body of an article from reuters.com's archive. """
        try:
            art = requests.get(url)
            title = self.findheaders.findall(art.text)[0]
            text = self.reoa.split(self.rboa.split(art.text)[1])[0]
            text = "".join(self.delscript.split(text))
            self.articles.append(BeautifulSoup(title+"\n-----\n"+text, "lxml").get_text())
        except:
            pass
            
    def getReutersDay(self, url):
        """ Gets all URLs for a given day and gets all texts. """
        day = requests.get(url)
        links = self.rfindURLs.findall(day.text)
        for l in links:
            self.getReutersArticle(l)
            
    # Потроение вектора для статьи.
    posConv={'ADJF':'_ADJ','NOUN':'_NOUN','VERB':'_VERB'}
    def getArticleDictionary(self, text, needPos=None):
        words=[a[0] for a in re.findall("([А-ЯЁа-яё]+(-[А-ЯЁа-яё]+)*)", text)]
        reswords=[]
    
        for w in words:
            wordform=self.morph.parse(w)[0]
            try:
                if wordform.tag.POS in ['ADJF', 'NOUN', 'VERB']:
                    if needPos!=None:
                        reswords.append(wordform.normal_form+self.posConv[wordform.tag.POS])
                    else:
                        reswords.append(wordform.normal_form)
            except:
                pass
            
        stat=Counter(reswords)
#        stat={a: stat[a] for a in stat.keys() if stat[a]>1}
        return stat

    # Посчитаем вектора для всех статей.
    def calcArticleDictionaries(self, needPos=None):
        self.dictionaries=[]
        for a in self.articles:
            self.dictionaries.append(self.getArticleDictionary(a, needPos))

    # Сохраняем стстьи в файл.
    def saveArticles(self, filename):
        """ Saves all articles to a file with a filename. """
        newsfile=open(filename, "w")
        for art in self.articles:
            newsfile.write('\n=====\n'+art)
        newsfile.close()

    # Читаем статьи из файла.
    def loadArticles(self, filename):
        """ Loads and replaces all articles from a file with a filename. """
        newsfile=open(filename)
        text=newsfile.read()
        self.articles=text.split('\n=====\n')[1:]
#        self.articles=[a.replace('\xa0', ' ') for a in text.split('\n=====\n')[1:]]
        newsfile.close()

    # Для удобства - поиск статьи по ее заголовку.
    def findNewsByTitle(self, title):
        for i, a in enumerate(self.articles):
            if title==a.split('\n-----\n')[0]:
                return i
        return -1

def cosineSimilarity(a, b):
    if len(a.keys())==0 or len(b.keys())==0:
        return 0
    sumab=sum([a[na]*b[na] for na in a.keys() if na in b.keys()])
    suma2=sum([a[na]*a[na] for na in a.keys()])
    sumb2=sum([b[nb]*b[nb] for nb in b.keys()])
    return sumab/math.sqrt(suma2*sumb2)<jupyter_output><empty_output><jupyter_text>Загрузим статьи.!!! Настоятельно рекомендую использовать ячейку с загрузкой статей из файла !!!<jupyter_code># Загрузка статей за заданный период.
# !!! Это рабоатет довольно долго, пользуйтесь сохраненными данными!!!
lenta=getNewsPaper()
lenta.getLentaPeriod(datetime.date(2018, 2, 1), datetime.date(2018, 2, 14))
lenta.saveArticles("lenta2018.txt")
#lenta.loadArticles("lenta2018.txt")
lenta.calcArticleDictionaries()
lenta=getNewsPaper()
lenta.loadArticles("lenta2018.txt")
lenta.calcArticleDictionaries()<jupyter_output><empty_output><jupyter_text>Из чистого любопытства попробуем найти статью, наиболее похожую на данную.<jupyter_code># Конечно же, правильнее делать это через np.argmax().
i1 = 0
maxCos, maxpos = -1, -1
for i in range(len(lenta.articles)):
    if i != i1:
        c = cosineSimilarity(lenta.dictionaries[i1], lenta.dictionaries[i])
        if c>maxCos:
            maxCos, maxpos = c, i
print(lenta.articles[i1].split('\n-----\n')[0])
print(lenta.articles[maxpos].split('\n-----\n')[0])
print(maxCos, maxpos)<jupyter_output>Раскрыто происхождение новейшей украинской крылатой ракеты
Россия поставила Украине оружие «сдерживания агрессора»
0.6522716186492549 516<jupyter_text>Сходство между статьями достаточно велико. Есть большие шансы за то, что они об одном и том же.Теперь попробуем решить основную задачу.Пользователь выбирает несколько статей на интересующую его тематику. Пусть это будут олимпиада и выборы.<jupyter_code>likesport=['Власти США обвинили МОК и ФИФА в коррупции', 'Пробирки WADA для допинг-проб оказались бракованными', 'Пожизненно отстраненных российских спортсменов оправдали', 'В Кремле порадовались за оправданных российских спортсменов', 'Россия вернется на первое место Олимпиады-2014', 'МОК разочаровало оправдание российских олимпийцев', 'Мутко загрустил после оправдания российских спортсменов', 'Оправданный призер Сочи-2014 призвал «добить ситуацию» с МОК', 'Путин предостерег от эйфории после оправдания российских олимпийцев', 'Родченков не смог вразумительно ответить на вопросы суда', 'Оправданный россиянин позлорадствовал над делившими медали Игр-2014 иностранцами', 'В CAS отказались считать оправданных россиян невиновными', 'Адвокат Родченкова заговорил о смерти чистого спорта после оправдания россиян', 'Американская скелетонистка сочла россиян ушедшими от законного наказания']
likeelect=['Социологи подсчитали планирующих проголосовать на выборах-2018', 'Собчак пообещала дать Трампу пару советов', 'На выборы президента России пойдут почти 80 процентов избирателей', 'Песков вспомнил предупреждение и отказался комментировать поездку Собчак в США', 'Собчак съездила на завтрак с Трампом и разочаровалась', 'Грудинин уступил в популярности КПРФ', 'Собчак потребовала признать незаконной регистрацию Путина на выборах', 'У Грудинина обнаружили два не до конца закрытых счета в Швейцарии и Австрии', 'Грудинин раскрыл историю происхождения дома в Испании', 'Путина зарегистрировали кандидатом в президенты', 'В Кремле отреагировали на слухи о голосовании Путина в Севастополе', 'Коммунистов вновь обвинили в незаконной агитации за Грудинина', 'ЦИК выявила обман со стороны Грудинина', 'Грудинин ответил на претензии ЦИК', 'Жириновский захотел сбросить ядерную бомбу на резиденцию Порошенко']<jupyter_output><empty_output><jupyter_text>Теперь объединим все выбранные тексты в один и посчитаем ветор для него. Сделаем это два раза для выбранных тематик.<jupyter_code>sporttext=' '.join([lenta.articles[lenta.findNewsByTitle(likesport[i])] for i in range(len(likesport))])
sportdict=lenta.getArticleDictionary(sporttext)
electtext=' '.join([lenta.articles[lenta.findNewsByTitle(likeelect[i])] for i in range(len(likeelect))])
electdict=lenta.getArticleDictionary(electtext)
#print(sportdict)
#print(electdict)<jupyter_output><empty_output><jupyter_text>А теперь отберем все статьи, косинусная мера которых превышает некоторый порог.<jupyter_code>thrs=0.4
thre=0.5
cosess=[lenta.articles[i].split('\n-----\n')[0] for i in range(len(lenta.dictionaries)) if cosineSimilarity(sportdict, lenta.dictionaries[i])>thrs]
print(cosess)
cosese=[lenta.articles[i].split('\n-----\n')[0] for i in range(len(lenta.dictionaries)) if cosineSimilarity(electdict, lenta.dictionaries[i])>thre]
print(cosese)<jupyter_output>['Пожизненно отстраненных российских спортсменов оправдали', 'В Кремле порадовались за оправданных российских спортсменов', 'Россия вернется на первое место Олимпиады-2014', 'МОК разочаровало оправдание российских олимпийцев', 'Мутко загрустил после оправдания российских спортсменов', 'Олимпиада в Пхенчхане побила рекорд по презервативам', 'Оправданный призер Сочи-2014 призвал «добить ситуацию» с МОК', 'Путин предостерег от эйфории после оправдания российских олимпийцев', 'Родченков не смог вразумительно ответить на вопросы суда', 'Оправданный россиянин позлорадствовал над делившими медали Игр-2014 иностранцами', 'В CAS отказались считать оправданных россиян невиновными', 'Адвокат Родченкова заговорил о смерти чистого спорта после оправдания россиян', 'Американская скелетонистка сочла россиян ушедшими от законного наказания', 'Глава USADA почуял вонь российской атаки на чистый спорт', 'После оправдания российских спортсменов Макларена назвали идиотом', 'МОК посчитал оправдание российск[...]<jupyter_text>Для проверки загрузим новости за какой-то другой день.<jupyter_code>lenta_new=getNewsPaper()
#lenta_new.getLentaPeriod(datetime.date(2018, 2, 15), datetime.date(2018, 2, 15))
#lenta_new.saveArticles("lenta20180215.txt")
lenta_new.loadArticles("lenta20180215.txt")
lenta_new.calcArticleDictionaries()<jupyter_output><empty_output><jupyter_text>А теперь проверим какие новости будут находиться.<jupyter_code>thrs_new = 0.3
thre_new = 0.3
cosess_new = [lenta_new.articles[i].split('\n-----\n')[0] for i in range(len(lenta_new.dictionaries)) if cosineSimilarity(sportdict, lenta_new.dictionaries[i])>thrs_new]
print(cosess_new)
cosese_new = [lenta_new.articles[i].split('\n-----\n')[0] for i in range(len(lenta_new.dictionaries)) if cosineSimilarity(electdict, lenta_new.dictionaries[i])>thre_new]
print(cosese_new)<jupyter_output>['Российский сноубордист сломал ногу на Олимпиаде', 'Российскую лыжницу затравили на Олимпиаде']
['Кремль опроверг информацию о засекречивании данных по россиянам в Сирии', 'Порошенко раскрыл детали разговора с Путиным', 'Грудинин оговорился о финансовых махинациях', 'Прогнозируемую явку на выборах президента сочли высокой']<jupyter_text>Как видно, метод нуждается в более точном подборе и корректировке параметров. Теперь попробуем применить для решения той же задачи модель Word2Vec, основная идея которой состоит в следующем. До сих пор мы работали в пространстве, размерность которого составляет несколько десятков, а может быть и сотен, тысяч измерений - по количеству используемых слов. Однако рядом будут находиться измерения для слов "бегемот" и "гиппопотам", являющихся синонимами. Следовательно, удалив одинаковые слова, мы можем снизить размерность пространства и уменьшить количество вычислений.Более того, каждое слово может быть выражено при помощи некоторых базовых понятий. Давайте попробуем отобразить теперь каждое слово в новое пространство, измерениями которого будут эти базовые понятия. Например, "король" будет раскладываться по измерениям "люди" (со значениями "мужчина" и "женщина"), "возраст" ("молодой", "зрелый", "старый"), "власть" ("верховная", "среднее звено", "местная", "локальная") и другим. При этом координаты не обязаны принимать заданные дискретные значения.Координаты слова в новом семантическом пространстве будут задаваться соседними словами. "Кушать" будет попадаться чаще с живыми существами, едой или посудой; "бегать" можно по некоторым местам и т.д. Правда, глаза могут и бегать, и есть. Это не будет добавлять модели детерминизма.Чтобы не мучиться в выбором новой системы координат натренируем некоторую модель, которая сама будет проводить уменьшение размерности пространства, а нам будет оставаться только выбрать число измерений. Эта же модель будет заниматься преобразованием точек старого пространства в новое. В этом новом семантическом пространстве становятся возможны векторные операции - сложение и вычитание. Разработчики модели Word2Vec утверждают, что они смогли получить "King"+"Man"-"Woman"="Queen". Посмотрим, получится ли у нас.<jupyter_code># Импортируем библиотеки Word2Vec
from gensim.models.word2vec import Word2Vec # Собственно модель.
from gensim.models.word2vec import LineSentence # Выравнивание текста по предложениям.
from gensim.models import KeyedVectors # Семантические вектора.
# На самом деле, нам потребуется только последняя.
import numpy as np # Вектора.<jupyter_output><empty_output><jupyter_text>Теперь загрузим модель, обученную разработчиками проекта RusVectores для русского языка на новостях. В зависимости от того, откуда вы берете модели, они могут загружаться по-разному. Более того, модель можно обучить самому - для этого нужно просто взять много размеченных текстов.<jupyter_code>model = KeyedVectors.load_word2vec_format('/home/edward/papers/kourses/Advanced Python/skillfactory/news_upos_cbow_600_2_2018.vec')<jupyter_output><empty_output><jupyter_text>Теперь можно получить представление слов в новом пространстве. Имейте в виду, что в данной модели они идут с частями речи!<jupyter_code>model['огонь_NOUN']<jupyter_output><empty_output><jupyter_text>Среди прочего, библиотека позволяет найти наиболее близкие слова к данному. Или даже к сочетанию слов.<jupyter_code>#model.most_similar(positive=[u'пожар_NOUN'])
#model.most_similar(positive=[u'пожар_NOUN', u'пламя_NOUN' ])
#model.most_similar(positive=[u'пожар_NOUN', u'пламя_NOUN' ], negative=[u'топливо_NOUN'])
#model.most_similar(positive=[u'женщина_NOUN', u'король_NOUN' ], negative=[u'мужчина_NOUN'])
model.most_similar(positive=[u'женщина_NOUN', u'король_NOUN' ])<jupyter_output><empty_output><jupyter_text>У нас есть смысл отдельных слов. Построим на его основе смысл текста как среднее арифметическое всех векторов для слов, составляющих данный текст.<jupyter_code>def text_to_vec(dct, model, size):
    text_vec = np.zeros((size,), dtype="float32")
    n_words = 0

    index2word_set = set(model.index2word)
    for word in dct.keys():
        if word in index2word_set:
            n_words = n_words + 1
            text_vec = np.add(text_vec, model[word]) 
    
    if n_words != 0:
        text_vec /= n_words
    return text_vec<jupyter_output><empty_output><jupyter_text>Переразметим наши тексты так, чтобы они содержали в себе и часть речи.<jupyter_code>lentaPos=getNewsPaper()
lentaPos.loadArticles("lenta2018.txt")
lentaPos.calcArticleDictionaries(True)<jupyter_output><empty_output><jupyter_text>Теперь посмотрим какова размерность векторов, хранимых в модели, и сколько в ней слов.<jupyter_code>print(len(model['огонь_NOUN']))
print(len(model.index2word))<jupyter_output>600
289191<jupyter_text>Размерность векторов 600 - с запасом. Почти 300 000 слов - тоже очень хорошо.Теперь попробем найти косинусное расстояние между полученными векторами.<jupyter_code>t2v1=text_to_vec(lentaPos.dictionaries[0], model, 600)
t2v2=text_to_vec(lentaPos.dictionaries[1], model, 600)
t2v516=text_to_vec(lentaPos.dictionaries[516], model, 600)
print(lentaPos.articles[0].split('\n-----\n')[0], lentaPos.articles[1].split('\n-----\n')[0])
print(cosineSimilarity(lentaPos.dictionaries[0], lentaPos.dictionaries[1]))
print(np.dot(t2v1, t2v2)/ np.linalg.norm(t2v1) / np.linalg.norm(t2v2))
print(lentaPos.articles[0].split('\n-----\n')[0], lentaPos.articles[516].split('\n-----\n')[0])
print(cosineSimilarity(lentaPos.dictionaries[0], lentaPos.dictionaries[516]))
print(np.dot(t2v1, t2v516)/ np.linalg.norm(t2v1) / np.linalg.norm(t2v516))<jupyter_output>Раскрыто происхождение новейшей украинской крылатой ракеты Русских гопников назвали настоящими древними славянами
0.056637725889743556
0.328594
Раскрыто происхождение новейшей украинской крылатой ракеты Россия поставила Украине оружие «сдерживания агрессора»
0.6522716186492549
0.872223<jupyter_text>Как видно, значения косинусной меры несколько выросли. Но может быть вектора можно просто вычитать и складывать?<jupyter_code>print(lentaPos.articles[0].split('\n-----\n')[0], lentaPos.articles[1].split('\n-----\n')[0])
print(np.linalg.norm(t2v1-t2v2))
print(lentaPos.articles[0].split('\n-----\n')[0], lentaPos.articles[516].split('\n-----\n')[0])
print(np.linalg.norm(t2v1-t2v516))<jupyter_output>Раскрыто происхождение новейшей украинской крылатой ракеты Русских гопников назвали настоящими древними славянами
0.260394
Раскрыто происхождение новейшей украинской крылатой ракеты Россия поставила Украине оружие «сдерживания агрессора»
0.10519<jupyter_text>Всё логично - расстояние между последней парой статей должно быть меньше. Попробуем теперь решить нашу задачу - отбор новостей - новым методом. Для начала попробуем при помощи косинусной меры.<jupyter_code>sportdictpos=lenta.getArticleDictionary(sporttext, True)
electdictpos=lenta.getArticleDictionary(electtext, True)

t2vs=text_to_vec(sportdictpos, model, 600)
t2ve=text_to_vec(electdictpos, model, 600)

thrs=0.85
thre=0.85

cosess=[lentaPos.articles[i].split('\n-----\n')[0] for i in range(len(lentaPos.dictionaries)) \
        if np.dot(t2vs, text_to_vec(lentaPos.dictionaries[i], model, 600))/ \
        np.linalg.norm(t2vs) / np.linalg.norm(text_to_vec(lentaPos.dictionaries[i], model, 600)) >thrs]
cosese=[lentaPos.articles[i].split('\n-----\n')[0] for i in range(len(lentaPos.dictionaries)) \
        if np.dot(t2ve, text_to_vec(lentaPos.dictionaries[i], model, 600))/ \
        np.linalg.norm(t2ve) / np.linalg.norm(text_to_vec(lentaPos.dictionaries[i], model, 600)) >thre]
print(thrs, thre)
print(cosess)
print(cosese)

thrs=0.8
thre=0.8

cosess=[lentaPos.articles[i].split('\n-----\n')[0] for i in range(len(lentaPos.dictionaries)) \
        if np.dot(t2vs, text_to_vec(lentaPos.dictionaries[i], model, 600))/ \
        np.linalg.norm(t2vs) / np.linalg.norm(text_to_vec(lentaPos.dictionaries[i], model, 600)) >thrs]
cosese=[lentaPos.articles[i].split('\n-----\n')[0] for i in range(len(lentaPos.dictionaries)) \
        if np.dot(t2ve, text_to_vec(lentaPos.dictionaries[i], model, 600))/ \
        np.linalg.norm(t2ve) / np.linalg.norm(text_to_vec(lentaPos.dictionaries[i], model, 600)) >thre]
print(thrs, thre)
print(cosess)
print(cosese)

thrs=0.9
thre=0.9

cosess=[lentaPos.articles[i].split('\n-----\n')[0] for i in range(len(lentaPos.dictionaries)) \
        if np.dot(t2vs, text_to_vec(lentaPos.dictionaries[i], model, 600))/ \
        np.linalg.norm(t2vs) / np.linalg.norm(text_to_vec(lentaPos.dictionaries[i], model, 600)) >thrs]
cosese=[lentaPos.articles[i].split('\n-----\n')[0] for i in range(len(lentaPos.dictionaries)) \
        if np.dot(t2ve, text_to_vec(lentaPos.dictionaries[i], model, 600))/ \
        np.linalg.norm(t2ve) / np.linalg.norm(text_to_vec(lentaPos.dictionaries[i], model, 600)) >thre]
print(thrs, thre)
print(cosess)
print(cosese)<jupyter_output>0.85 0.85
['Пожизненно отстраненных российских спортсменов оправдали', 'МОК разочаровало оправдание российских олимпийцев', 'Мутко загрустил после оправдания российских спортсменов', 'Оправданный призер Сочи-2014 призвал «добить ситуацию» с МОК', 'Адвокат Родченкова заговорил о смерти чистого спорта после оправдания россиян', 'После оправдания российских спортсменов Макларена назвали идиотом', 'Родченкова сочли борцом за правое дело и помогли деньгами', 'Федерацию бобслея России обвинили в нежелании пускать спортсменов на Олимпиаду', 'Четырехкратный олимпийский чемпион встал на сторону россиян и пристыдил МОК', 'МОК обозначил сроки по решению о допуске оправданных россиян на Олимпиаду', 'В секретной базе найдены сотни аномальных допинг-проб', 'Немецкий биатлонист сравнил оправдание российских олимпийцев с плевком в лицо', 'Хакеры раскрыли канадский заговор против российского спорта', 'Песков вспомнил предупреждение и отказался комментировать поездку Собчак в США', 'Потерявший шансы поп[...]<jupyter_text>Как видно, результат очень сильно зависит от порогового значения. А теперь решим ее просто вычитая вектора.<jupyter_code>thrs=0.15
thre=0.15

cosess=[lentaPos.articles[i].split('\n-----\n')[0] for i in range(len(lentaPos.dictionaries)) \
        if np.linalg.norm(t2vs-text_to_vec(lentaPos.dictionaries[i], model, 600))<thrs]
cosese=[lentaPos.articles[i].split('\n-----\n')[0] for i in range(len(lentaPos.dictionaries)) \
        if np.linalg.norm(t2ve-text_to_vec(lentaPos.dictionaries[i], model, 600))<thre]
print(thrs, thre)
print(cosess)
print(cosese)

thrs=0.2
thre=0.2

cosess=[lentaPos.articles[i].split('\n-----\n')[0] for i in range(len(lentaPos.dictionaries)) \
        if np.linalg.norm(t2vs-text_to_vec(lentaPos.dictionaries[i], model, 600))<thrs]
cosese=[lentaPos.articles[i].split('\n-----\n')[0] for i in range(len(lentaPos.dictionaries)) \
        if np.linalg.norm(t2ve-text_to_vec(lentaPos.dictionaries[i], model, 600))<thre]
print(thrs, thre)
print(cosess)
print(cosese)

thrs=0.1
thre=0.1

cosess=[lentaPos.articles[i].split('\n-----\n')[0] for i in range(len(lentaPos.dictionaries)) \
        if np.linalg.norm(t2vs-text_to_vec(lentaPos.dictionaries[i], model, 600))<thrs]
cosese=[lentaPos.articles[i].split('\n-----\n')[0] for i in range(len(lentaPos.dictionaries)) \
        if np.linalg.norm(t2ve-text_to_vec(lentaPos.dictionaries[i], model, 600))<thre]
print(thrs, thre)
print(cosess)
print(cosese)<jupyter_output>0.15 0.15
['Социологи подсчитали планирующих проголосовать на выборах-2018', 'Власти США обвинили МОК и ФИФА в коррупции', 'Китайская детская задачка поставила в тупик взрослых по всему миру', 'Пожизненно отстраненных российских спортсменов оправдали', 'В Кремле порадовались за оправданных российских спортсменов', 'МОК разочаровало оправдание российских олимпийцев', 'Мутко загрустил после оправдания российских спортсменов', 'Шефы российских спецслужб приехали в США и удивили американцев', 'Президента Чехии уличили в связях с украинскими сепаратистами', 'Оправданный призер Сочи-2014 призвал «добить ситуацию» с МОК', 'Австралиец предложил два ноутбука за собственное убийство', 'Рассказавшего о скупающих черную икру украинцах политолога назвали порохоботом', 'Стрелявший под кокаином у Кремля Джабраилов постреляет еще', 'Путин предостерег от эйфории после оправдания российских олимпийцев', 'Родченков не смог вразумительно ответить на вопросы суда', 'Опубликовано ранее неизвестное интервью [...]
{'fraction_non_alphanumeric': 0.11239152878149503, 'fraction_numerical': 0.021010630876977594, 'mean_word_length': 5.1045818429569065, 'pattern_counts': {'":': 0, '<': 128, '<?xml version=': 0, '>': 131, 'https://': 6, 'lorem ipsum': 0, 'www.': 0, 'xml': 3}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '26', 'dir': 'jupyter-structured-clean-dedup', 'id': '7894', 'license': 'Naumen', 'n_tokens_mistral': 14794, 'n_tokens_neox': 16138, 'n_words': 3267, 'path': 'hw05/lesson_05/.ipynb_checkpoints/SkillFactory_20180216-checkpoint.ipynb', 'repo_name': 'rojaster/sfml', 'size': '35859'}