Привет, Хабр!

Меня зовут Пётр Мананников я Data Scientist и являюсь участником профессионального сообщества NTA. Представьте ситуацию: вас назначили спикером на мероприятии, и вы даже знаете, о чем хотите рассказать аудитории. Но будет ли публикой воспринят ваш доклад так, как вы себе это представляли? Давайте посмотрим, что может пойти не так, и как это исправить.

Как часто нам приходится выступать с докладом, презентацией, проводить обучение, быть спикером на конференции? Если деятельность напрямую не связана с человеческим общением, навык грамотно доносить свою точку зрения теряется естественным образом. Друзья и близкие зачастую воспринимают нас “как есть”, исключая обратную связь для сохранения отношений. Несмотря на лояльность друзей и коллег, практика публичных выступлений важна и необходима для поддержания способности передавать свои мысли и чувства.

Данное исследование поможет разобраться с нашими вербальными привычками и подсветит зоны роста. К его созданию меня подтолкнул спикер одного из youtube каналов it-направленности. Его речь, наполненная идиомами и вводными словами, мешала восприятию основного полезного контента. Впоследствии родилась идея перевести аудиозаписи роликов в текст и выяснить, какие выражения чаще других перегружают речь. Первой задачей стала транскрибация целевой аудиодорожки, второй – анализ текста, третьей — выводы и работа над ошибками.

Детали исследования с полным кодом я выложил на GitHub.

Транскрибация

Поиск качественного инструмента для анализа аудио свелся к выбору между облачными сервисами. Решения, доступные ‘for free’ в открытых репозиториях, не соответствовали моим ожиданиям по качеству результата. Адекватное решение, способное обрабатывать длинные записи, нашлось только у тындекса. После нетривиальных настроек облака стал доступен API асинхронного распознавания. Настройки сервиса описаны здесь: cloud.yandex.ru/docs/speechkit/. Для теста была записана аудиодорожка с моими впечатлениями за текущий год. Далее в коде используется именно она.

Обработка mp3-файла

import pandas as pd
import requests
import time
import json
from collections import Counter

Подключаемся к cloud.yandex.ru , проходим все стартовые настройки сервера и рабочего пространства. Заливаем аудиозапись в выделенное хранилище облака, назначаем права на чтение сервису Speech_Kit, копируем ссылку на файл, вставляем её в POST-запрос:

# параметры запроса
key = 'ваш API - key yc’

# путь к файлу в бакете YC
filelink = 'https://storage.yandexcloud.net/bucket0011/test_audio.mp3' 
   
body ={
    "config": {
        "specification": {
            "languageCode": "ru-RU"
            , "audioEncoding": "MP3"
        }
    },
    "audio": {
        "uri": filelink
    }
}

header = {'Authorization': 'Api-Key {}'.format(key)}
POST = "https://transcribe.api.cloud.yandex.net/speech/stt/v2/longRunningRecognize"

# структура POST - запроса согласно инструкции API
req = requests.post(POST, headers=header, json=body)

#Получаем ответ от сервера, из которого забирам ID задачи на обработку аудиозаписи.
data = req.json()
id_ = data['id']  
print(id_)

Запрашиваем на сервере статус операции раз в 10 секунд и ожидаем окончания процесса распознавания:

for i in range(1000):
    # проверяем распознано ли аудио
    GET = "https://operation.api.cloud.yandex.net/operations/{id}"
    req = requests.get(GET.format(id=id_), headers=header)
    req = req.json()
    # если распознано — выходим из цикла
    if req['done']: break
    # если нет — выводим сообщение
    print("файл в обработке")
    # ждём 10 секунд
    time.sleep(10)

#создаем временные хранилища
all_sentenses = []
sentenses_dic = {}
all_text =''
for id,chunk in enumerate(req['response']['chunks']):
#     if id%2==1:  #актуально только для двухканального аудио

    chnk = chunk['alternatives'][0]['text'].lower()
    all_text = all_text+chnk+' '
        
print("Часть распознанного текста: ",all_text[:55])

Переводим весь текст в нижний регистр, чистим от знаков пунктуации:

all_text = all_text.lower()
all_text = all_text.replace('.','').replace(',','').replace(' - ','')

Анализ текста

Ищем уникальные идиомы из двух слов, не подпадающие под стандартные речевые обороты:

def get_word_pairs_dic(data):
   
    dic_words_order1 = {}
    counter=0
    for num, word in enumerate(data.split()):

        dic_words_order1[num] = word

    all_p =[]
    all_p2 =[]
    pair=[]
    pair2=[]
    p=''
    p2=''
    cnt2=0
    for word in dic_words_order1.items():
        pair.append(word[1])

    #     сочетания двух слов
        pair2.append(word[1])
        if int(word[0])%2==0:        
            try:
                p = pair[0]+' '+pair[1]
            except:
                pass
            all_p.append(p)
            pair=[]

    #     сочетания двух слов со смещением на 1 слово
        if int(word[0])%2==1:

            try:
                p2 = pair2[0]+' '+pair2[1]
            except:
                pass
            all_p2.append(p2)
            pair2=[]

    #  ------------ чистим списки от пробелов -----------------
    try:
        all_p.pop(all_p.index(''))
    except:
        pass
    try:
        all_p2.pop(all_p2.index(''))
    except:
        pass
    # -------------- удаляем дубликаты ------------------
    all_p = list(set(all_p))
    all_p2 = list(set(all_p2))

    return all_p ,all_p2
all_pairs1 ,all_pairs2 = get_word_pairs_dic(all_text)

Считаем количество повторений пар из 2 слов в тексте:

def pair_counter(pair_list,text):
    phrase_dic = dict(zip(pair_list,[text.count(k) for k in pair_list ]))
    df_phrase = pd.DataFrame.from_dict(phrase_dic,  orient='index')
    df_phrase = df_phrase.reset_index().rename(columns={'index': 'word_pair', 0: 'count'})
    df_phrase.where(df_phrase['word_pair'].str.len()>5).sort_values(by='count', ascending = False)
    
    return df_phrase
def combine_pairs_count_results(pair_list, pair_list2, text):
    df_pairs = pair_counter(pair_list, text)
    df_pairs2 = pair_counter(pair_list2, text)
    combine_df = pd.concat([df_pairs,df_pairs2])
    combine_df = combine_df.where(combine_df['word_pair'].str.len()>5).drop_duplicates()
    .sort_values(by='count', ascending = False)
    
    return combine_df

Объединяем результаты и оставляем уникальные:

combine_df = combine_pairs_count_results(all_pairs1, all_pairs2,all_text)
combine_df[:5]
    word_pair  count
105   это был    6.0
28    в общем    4.0
55    и потом    3.0
81     22 год    3.0
71    был еще    3.0

Промежуточные выводы:

most_popular_phrase = combine_df.iloc[0]['word_pair']
print(f'В тексте часто повторяется фраза "{most_popular_phrase}"')
В тексте часто повторяется фраза "это был"

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

bad_word_dic = {}
bad_words = ['то есть','по моему','по-моему','честно говоря','как бы','так сказать','по сути','собственно говоря','как бы'
             ,'таким образом','как говорится','так далее','как его','так вот','как сказать','на самом деле','в общем-то'
             ,'в общем','в некотором роде','в принципе','типа того','в самом деле','всё такое','в целом','то есть'
             ,'это самое','ну вот','ну это','так сказать','да ладно', 'можно сказать', 'как-то','не вопрос','без проблем'
             ,'как-то так','ничего себе','соответственно']

Создаем словарь повторений выбранных фраз в тексте:

def bad_phrase_counter(text):
    dic = dict(zip(bad_words,[text.count(k) for k in bad_words ]))
    df = pd.DataFrame.from_dict(dic,  orient='index')
    df = df.reset_index().rename(columns={'index': 'word', 0: 'count'})
    return df

Считаем повторения слов-паразитов в тексте:

df = bad_phrase_counter(all_text)
df.sort_values(by='count', ascending = False)[:5]
            word  count
16       в общем      4
1       по моему      1
18    в принципе      0
19     типа того      0
20  в самом деле      0

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

def word_counter(all_sentenses):
    all_words = []
    for sentance in all_sentenses:
        for word in sentance.split(sep=' '):
            word = word.lower()
            if len(word)>=2:
                all_words.append(word)
    unique = dict(zip(all_words,[all_words.count(i) for i in all_words]))
    df = pd.DataFrame.from_dict(unique,  orient='index')
    df2 =df.reset_index().rename(columns={'index': 'word', 0: 'count'})
    return df2
all_sentenses = all_text.split()
df_words = word_counter(all_sentenses)
df_words.sort_values(by='count', ascending = False)[:5]
   word  count
71  это     14
12   на     10
33  что     10
56   не     10
22  так     10

Находим самую «вредную» фразу из всего текста, даем рекомендации:

bad_word = str(combine_df.iloc[0]['word_pair'])
bad_word2 = str(combine_df.iloc[1]['word_pair'])
print(f'Самые популярные выражения в вашей речи - "{bad_word}" и "{bad_word2}".')
word_to_post = bad_word.replace(' ','%20')
word_to_post2 = bad_word2.replace(' ','%20')
print(f'Ознакомьтесь, пожалуйста, с их синонимами: \nhttps://sinonim.org/s/
      {word_to_post} \nhttps://sinonim.org/s/{word_to_post2}')
Самые популярные выражения в вашей речи - "это был" и "в общем".
Ознакомьтесь, пожалуйста, с их синонимами: 
https://sinonim.org/s/это%20был 
https://sinonim.org/s/в%20общем

Историческое отступление

Речь Стива Джобса

Давайте немного отвлечемся и для проверки работы скрипта возьмем текст презентации компании Apple 2007 года, где исполнительный директор анонсировал первый мобильный телефон компании с сенсорным дисплеем и функцией геолокации. Анализ текста позволит нам понять, какие фразы Стив Джобс использовал для представления своего нового творения общественности.

Для упрощения процесса загружаем речь спикеров с сайта https://singjupost.com/ по ссылке: https://singjupost.com/wp-content/uploads/2014/07/Steve-Jobs-iPhone-2007-Presentation-Full-Transcript.pdf

Оставляем только речь Стива:

with open ('apple2007_.txt','r') as file:
    presentation_text = file.read()
presentation_text = presentation_text.replace('.','').replace(',','').replace(' - ','')
presentation_text = presentation_text.lower()
print(presentation_text[:211])
this is the day i’ve been looking forward to for two and a half years
every once in a while a revolutionary product comes along that changes everything and
apple has been – well first of all one’s very fortunate
Разбиваем текст на пары рядом стоящих слов:
all_p ,all_p2 = get_word_pairs_dic(presentation_text)
all_p[:5]
['completely automatically',
 'hold on',
 'wide screen',
 'mail this',
 'are calling']

Считаем количество повторений пар слов:

combine_df2 = combine_pairs_count_results(all_p, all_p2,presentation_text)
combine_df2[:10].sort_values(by='count', ascending = False)
     word_pair  count
1456  going to   68.0
1375   want to   57.0
2436    i want   47.0
2139    this i   44.0
2444    here a   42.0
2942   this is   40.0
3069  here and   39.0
930     in the   38.0
2777    here i   35.0
2473   you can   33.0

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

Подводя итог

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

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

Комментарии (2)


  1. YAKOROLEVAZAMKA
    29.12.2022 08:36

    я для подобной задачи использовал библиотеку nltk:

    import nltk

    string_to_analyze = 'some long text string'
    nltk_tokens = nltk.word_tokenize(string_to_analyze)

    bigrams = list(nltk.bigrams(nltk_tokens))
    trigrams = list(nltk.trigrams(nltk_tokens))

    в ней так же есть набор стоп-слов (+слов-паразитов) для большинства языков:

    nltk.download('stopwords')
    from nltk.corpus import stopwords

    Сам файлик со словами лежит где-то в документах и его можно редактировать по необходимости)


    1. Peter120
      29.12.2022 11:52

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