exponenta event banner

Создать лексикон для конкретных областей

В этом примере показано, как создать лексикон для анализа настроений с использованием 10-K и 10-Q финансовых отчетов.

Анализ настроений позволяет автоматически суммировать настроения в данном фрагменте текста. Например, присвойте части текста «Эта компания демонстрирует сильный рост». и «Эта другая компания обвиняется в введении потребителей в заблуждение». с позитивными и негативными настроениями, соответственно. Также, например, присвоить текст «Эта компания демонстрирует чрезвычайно сильный рост». более сильный рейтинг настроений, чем текст «Эта компания демонстрирует сильный рост».

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

Чтобы проанализировать настроения текста с помощью алгоритма VADER, используйте vaderSentimentScores функция. Если лексикон настроения, используемый vaderSentimentScores функция не подходит для анализируемых данных, например, если у вас есть набор данных для конкретной области, такой как медицинские или инженерные данные, то вы можете создать свой собственный лексикон настроений, используя небольшой набор начальных слов.

В этом примере показано, как генерировать лексикон настроения с набором начальных слов, используя основанный на графах подход, основанный на [1]:

  • Обучение встраиванию слов, которое моделирует сходство между словами с помощью обучающих данных.

  • Создайте упрощенный график, представляющий вложение с узлами, соответствующими словам и рёбрам, взвешенным по подобию.

  • Чтобы определить слова с сильной полярностью, определите слова, связанные с несколькими начальными словами по коротким, но сильно взвешенным путям.

Загрузить данные

Загрузите данные 10-K и 10-Q финансовых отчетов из Комиссии по ценным бумагам и биржам (SEC) через API электронного сбора, анализа и извлечения данных (EDGAR) [2] с помощью financeReports вспомогательная функция, присоединенная к этому примеру в качестве вспомогательного файла. Чтобы получить доступ к этому файлу, откройте этот пример как живой сценарий. financeReports загружает 10-K и 10-Q отчеты за указанный год, квартал и максимальную длину символа.

Скачать набор из 20 000 отчетов четвертого квартала 2019 года. В зависимости от размеров отчетов выполнение может занять некоторое время.

year = 2019;
qtr = 4;
textData = financeReports(year,qtr,'MaxNumReports',20000);
Downloading 10-K and 10-Q reports...
Done.
Elapsed time is 1799.718710 seconds.

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

seedsPositive = ["achieve" "advantage" "better" "creative" "efficiency" ...
    "efficiently" "enhance" "greater" "improved" "improving" ...
    "innovation" "innovations" "innovative" "opportunities" "profitable" ...
    "profitably" "strength" "strengthen" "strong" "success"]';

seedsNegative = ["adverse" "adversely" "against" "complaint" "concern" ...
    "damages" "default" "deficiencies" "disclosed" "failure" ...
    "fraud" "impairment" "litigation" "losses" "misleading" ...
    "omit" "restated" "restructuring" "termination" "weaknesses"]';

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

Создание имен функций preprocessText подготавливает текстовые данные для анализа. preprocessText функция, перечисленная в конце примера, выполняет следующие шаги:

  • Удалите все URL-адреса.

  • Выполните маркировку текста.

  • Удаление маркеров, содержащих цифры.

  • Преобразование текста в нижний регистр.

  • Удалите все слова, содержащие не более двух символов.

  • Удалите все стоп-слова.

Предварительная обработка текста с помощью preprocessText функция. В зависимости от размера текстовых данных выполнение может занять некоторое время.

documents = preprocessText(textData);

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

figure
wordcloud(documents);

Встраивание Train Word

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

Обучение встраиванию слов, которое моделирует сходство между словами с помощью обучающих данных. Укажите контекстное окно размера 25 и удалите слова, отображаемые менее 20 раз. В зависимости от размера текстовых данных выполнение может занять некоторое время.

emb = trainWordEmbedding(documents,'Window',25,'MinCount',20);
Training: 100% Loss: 1.44806  Remaining time: 0 hours 0 minutes.

Создать график Word

Создайте упрощенный график, представляющий вложение с узлами, соответствующими словам и рёбрам, взвешенным по подобию.

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

Для каждого слова в словаре найдите ближайшие 7 слов и их косинусные расстояния.

numNeighbors = 7;
vocabulary = emb.Vocabulary;
wordVectors = word2vec(emb,vocabulary);

[nearestWords,dist] = vec2word(emb,wordVectors,numNeighbors);

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

Определите исходный и целевой узлы.

sourceNodes = repelem(vocabulary,numNeighbors);
targetNodes = reshape(nearestWords,1,[]);

Вычислите веса кромок.

edgeWeights = reshape(dist,1,[]);

Создайте график, связывающий каждое слово с его соседями с рёберными весами, соответствующими баллам подобия.

wordGraph = graph(sourceNodes,targetNodes,edgeWeights,vocabulary);

Удалите повторяющиеся кромки с помощью simplify функция.

wordGraph = simplify(wordGraph);

Визуализируйте раздел графа слов, связанный со словом «потери».

word = "losses";
idx = findnode(wordGraph,word);
nbrs = neighbors(wordGraph,idx);
wordSubgraph = subgraph(wordGraph,[idx; nbrs]);
figure
plot(wordSubgraph)
title("Words connected to """ + word + """")

Генерировать оценки настроений

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

Инициализируйте массив оценок настроений, соответствующих каждому слову в словаре.

sentimentScores = zeros([1 numel(vocabulary)]);

Итеративно пройдите график и обновите оценки настроений.

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

Для каждой глубины:

  • Вычислите показатели положительной и отрицательной полярности.

  • Учтите разницу в общей массе положительного и отрицательного потока на графике.

  • Для каждого слова узла нормализуйте разницу двух его баллов.

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

Укажите максимальную длину пути, равную 4.

maxPathLength = 4;

Итеративно пройдите график и рассчитайте сумму оценок настроений.

for depth = 1:maxPathLength
    
    % Calculate polarity scores.
    polarityPositive = polarityScores(seedsPositive,vocabulary,wordGraph,depth);
    polarityNegative = polarityScores(seedsNegative,vocabulary,wordGraph,depth);
    
    % Account for difference in overall mass of positive and negative flow
    % in the graph.
    b = sum(polarityPositive) / sum(polarityNegative);
        
    % Calculate new sentiment scores.
    sentimentScoresNew = polarityPositive - b * polarityNegative;
    sentimentScoresNew = normalize(sentimentScoresNew,'range',[-1,1]);
    
    % Add scores to sum.
    sentimentScores = sentimentScores + sentimentScoresNew;
end

Нормализуйте оценки настроений по количеству итераций.

sentimentScores = sentimentScores / maxPathLength;

Создайте таблицу, содержащую словарь и соответствующие оценки настроения.

tbl = table;
tbl.Token = vocabulary';
tbl.SentimentScore = sentimentScores';

Чтобы удалить токены с нейтральными настроениями из лексикона, удалите токены с оценкой настроений, которые имеют абсолютное значение меньше порога 0,1.

thr = 0.1;
idx = abs(tbl.SentimentScore) < thr;
tbl(idx,:) = [];

Отсортируйте строки таблицы по убыванию оценки настроений и просмотрите первые несколько строк.

tbl = sortrows(tbl,'SentimentScore','descend');
head(tbl)
ans=8×2 table
         Token         SentimentScore
    _______________    ______________

    "opportunities"       0.95633    
    "innovative"          0.89635    
    "success"             0.84362    
    "focused"             0.83768    
    "strong"              0.81042    
    "capabilities"        0.79174    
    "innovation"          0.77698    
    "improved"            0.77176    

Вы можете использовать эту таблицу как пользовательский лексикон настроения для vaderSentimentScores функция.

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

figure
subplot(1,2,1);
idx = tbl.SentimentScore > 0;
tblPositive = tbl(idx,:);
wordcloud(tblPositive,'Token','SentimentScore')
title('Positive Words')

subplot(1,2,2);
idx = tbl.SentimentScore < 0;
tblNegative = tbl(idx,:);
tblNegative.SentimentScore = abs(tblNegative.SentimentScore);
wordcloud(tblNegative,'Token','SentimentScore')
title('Negative Words')

Экспорт таблицы в CSV-файл.

filename = "financeSentimentLexicon.csv";
writetable(tbl,filename)

Анализ настроений в тексте

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

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

textDataNew = [
    "This innovative company is continually showing strong growth."
    "This other company is accused of misleading consumers."];
documentsNew = preprocessText(textDataNew);

Оцените настроения с помощью vaderSentimentScores функция. Укажите лексикон настроения, созданный в этом примере с помощью 'SentimentLexicon' вариант.

compoundScores = vaderSentimentScores(documentsNew,'SentimentLexicon',tbl)
compoundScores = 2×1

    0.4360
   -0.1112

Положительные и отрицательные оценки указывают на положительные и отрицательные настроения соответственно. Величина значения соответствует силе настроений.

Вспомогательные функции

Функция предварительной обработки текста

preprocessText функция выполняет следующие шаги:

  • Удалите все URL-адреса.

  • Выполните маркировку текста.

  • Удаление маркеров, содержащих цифры.

  • Преобразование текста в нижний регистр.

  • Удалите все слова, содержащие не более двух символов.

  • Удалите все стоп-слова.

function documents = preprocessText(textData)

% Erase URLS.
textData = eraseURLs(textData);

% Tokenize.
documents = tokenizedDocument(textData);

% Remove tokens containing digits.
pat = textBoundary + wildcardPattern + digitsPattern + wildcardPattern + textBoundary;
documents = replace(documents,pat,"");

% Convert to lowercase.
documents = lower(documents);

% Remove short words.
documents = removeShortWords(documents,2);

% Remove stop words.
documents = removeStopWords(documents);

end

Функция оценки полярности

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

Функция выполняет следующие шаги:

  • Инициализируйте баллы начальных чисел единицами и другими нулями.

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

  • Для каждого уровня глубины закольцовывайте узлы в пространстве поиска и идентифицируйте их соседей на графике.

  • Наведите петлю на своих соседей и обновите соответствующие баллы. Обновленная оценка - это максимальное значение текущей оценки для начального числа и соседнего числа и оценки для начального числа и узла поиска, взвешенного соответствующим ребром графика.

  • В конце поиска уровня глубины добавьте соседей к пространству поиска. Это увеличивает глубину поиска следующей итерации.

Выходная полярность - это сумма баллов, связанных с входными начальными числами.

function polarity = polarityScores(seeds,vocabulary,wordGraph,depth)

% Remove seeds missing from vocabulary.
idx = ~ismember(seeds,vocabulary);
seeds(idx) = [];

% Initialize scores.
vocabularySize = numel(vocabulary);
scores = zeros(vocabularySize);
idx = ismember(vocabulary,seeds);
scores(idx,idx) = eye(numel(seeds));

% Loop over seeds.
for i = 1:numel(seeds)
    
    % Initialize search space.
    seed = seeds(i);
    idxSeed = vocabulary == seed;
    searchSpace = find(idxSeed);
    
    % Search at different depths.
    for d = 1:depth
    
        % Loop over nodes in search space.
        numNodes = numel(searchSpace);
        
        for k = 1:numNodes
            
            idxNew = searchSpace(k);
            
            % Find neighbors and weights.
            nbrs = neighbors(wordGraph,idxNew);
            idxWeights = findedge(wordGraph,idxNew,nbrs);
            weights = wordGraph.Edges.Weight(idxWeights);
            
            % Loop over neighbors.
            for j = 1:numel(nbrs)
                
                % Calculate scores.
                score = scores(idxSeed,nbrs(j));
                scoreNew = scores(idxSeed,idxNew);
                
                % Update score.
                scores(idxSeed,nbrs(j)) = max(score,scoreNew*weights(j));
            end
            
            % Appended nodes to search space for next depth iteration.
            searchSpace = [searchSpace nbrs'];
        end
    end
end

% Find seeds in vocabulary.
[~,idx] = ismember(seeds,vocabulary);

% Sum scores connected to seeds.
polarity = sum(scores(idx,:));

end

Библиография

  1. Великович, Ленид. «Жизнеспособность лексиконов полярности, полученных из Интернета». В трудах Ежегодной конференции Североамериканского отделения Ассоциации вычислительной лингвистики, 2010, стр. 777-785. 2010.

  2. Доступ к данным EDGAR. https://www.sec.gov/edgar/searchedgar/accessing-edgar-data.htm