Использование преобразования Фурье векторных представлений, полученных из вложений BERT, для оценки семантической близости
Изучение взаимного влияния слов в предложении путем оценки различных представлений вложений BERT.
Встраивание BERT — это то, что дает большие возможности, когда речь идет о программных способах извлечения смысла из текста. Кажется, все, что нам (а также машинам) нужно для понимания текста, скрыто в этих числах. Это просто вопрос правильного манипулирования этими числами. Я обсуждал эту концепцию в своем недавнем посте Обнаружение тенденций во встраиваниях BERT разных уровней для задачи определения семантического контекста. Эта статья продолжает это обсуждение, рассматривая, могут ли векторные представления, полученные путем применения преобразования Фурье к вложениям BERT, также быть полезными в задачах НЛП по извлечению смысла.
Гипотеза
Как вы, наверное, знаете из физики, преобразование Фурье позволяет нам понять частоты внутри сигнала. Является ли вектор, представляющий встраивание слова, похожим на сигнал? То есть может ли знание частотной области, полученное из преобразования Фурье, быть полезным при обработке вложений BERT для обнаружения семантической близости? Проще говоря, имеет ли смысл преобразование Фурье при анализе вложений BERT? Давайте проверим это.
Эксперимент
Пример, обсуждаемый в оставшейся части этой статьи, предполагает, что у вас есть модель, определенная так, как описано в примере в предыдущем посте. Нам также понадобятся представления, полученные из примеров предложений, как обсуждалось в этом предыдущем посте. Обратите внимание, однако, что в этой статье используется набор примеров, которые немного отличаются от тех, что использовались в посте выше. Итак, перед созданием представлений вам необходимо определить следующие образцы:
sents = [] sents.append(‘I had a very good apple.’) sents.append(‘I had a very good orange.’) sents.append(‘I had a very good adventure.’)
Как видите, все предложения имеют одинаковый набор модификаторов прямого дополнения, в то время как прямые дополнения в каждом случае разные. Цель нашего эксперимента — проверить, как семантическая близость прямых дополнений влияет на близость модификаторов.
Процесс токенизации и генерации скрытых состояний для примеров предложений был описан в посте выше. Поэтому мы не будем снова рассматривать этот процесс здесь.
Давайте сосредоточимся на словосочетаниях («а/очень/хорошо») прямого объекта («яблоко/апельсин/приключение») в каждом предложении. Чтобы использовать правильную индексацию при получении их вложений, давайте сначала проверим токены в наших предложениях:
for i in range(len(sents)): print(tokenizer.convert_ids_to_tokens(tokenized_text[i])) [‘[CLS]’, ‘i’, ‘had’, ‘a’, ‘very’, ‘good’, ‘apple’, ‘.’, ‘[SEP]’] [‘[CLS]’, ‘i’, ‘had’, ‘a’, ‘very’, ‘good’, ‘orange’, ‘.’, ‘[SEP]’] [‘[CLS]’, ‘i’, ‘had’, ‘a’, ‘very’, ‘good’, ‘adventure’, ‘.’, ‘[SEP]’]
Поскольку нас интересуют модификаторы прямого объекта (в данном конкретном примере у нас три модификатора: а, очень, хорошо.), нам нужны токены с индексами: 3, 4,5. Поэтому нам нужно сдвинуть индексацию на 3. Ниже мы получаем контекстуальное встраивание в каждое предложение для каждого модификатора. Мы будем хранить вложения модификаторов в списке, определенном для каждого предложения:
l12_1 = [] l12_2 = [] l12_3 = [] for i in range(3): l12_1.append(hidden_states[0][12][0][i+3][:10].numpy()) l12_2.append(hidden_states[1][12][0][i+3][:10].numpy()) l12_3.append(hidden_states[2][12][0][i+3][:10].numpy())
Давайте теперь посмотрим, как семантическая близость прямых дополнений в разных предложениях влияет на близость соответствующих модификаторов.
from scipy import spatial for i in range(3): print(1 — spatial.distance.cosine(l12_1[i], l12_2[i])) 0.9003266096115112 0.9178041219711304 0.8865049481391907
В приведенном выше выводе мы видим, что контекстуальные вложения модификаторов Apple и Orange демонстрируют высокий уровень близости. Это вполне объяснимо, ведь сами непосредственные объекты: Apple и Orange находятся очень близко.
В то время как представления, производные от модификаторов Apple и Adventure, не так близки, как видно из следующего:
for i in range(3): print(1 — spatial.distance.cosine(l12_1[i], l12_3[i])) 0.49141737818717957 0.7987119555473328 0.6404531598091125
Модификаторы Orange и Adventure также не должны быть такими близкими:
for i in range(3): print(1 — spatial.distance.cosine(l12_2[i], l12_3[i])) 0.7402883768081665 0.8417230844497681 0.7215733528137207
Давайте теперь получим более сложные представления из вложений, предоставленных BERT. Для начала давайте получим начальные вложения для модификаторов в каждом предложении:
l0_1 = [] l0_2 = [] l0_3 = [] for i in range(3): l0_1.append(hidden_states[0][0][0][i+3][:10].numpy()) l0_2.append(hidden_states[1][0][0][i+3][:10].numpy()) l0_3.append(hidden_states[2][0][0][i+3][:10].numpy())
Теперь мы можем, например, разделить контекстные вложения (сгенерированные на 12-м уровне кодировщика) на соответствующие начальные вложения (как это обсуждалось в предыдущем посте), чтобы получить некоторые новые представления вложений, которые будут использоваться в дальнейшем анализе.
import numpy as np l0_12_1 = [] l0_12_2 = [] l0_12_3 = [] for i in range(3): l0_12_1.append(np.log(l12_1[i]/l0_1[i])) l0_12_2.append(np.log(l12_2[i]/l0_2[i])) l0_12_3.append(np.log(l12_3[i]/l0_3[i])) for i in range(3): l0_12_1[i] = np.where(np.isnan(l0_12_1[i]), 0, l0_12_1[i]) l0_12_2[i] = np.where(np.isnan(l0_12_2[i]), 0, l0_12_2[i]) l0_12_3[i] = np.where(np.isnan(l0_12_3[i]), 0, l0_12_3[i])
В целях анализа вы также можете создать еще один набор представлений, вычисляя только поэлементную разницу между контекстным и неконтекстным (исходным) встраиванием.
_l0_12_1 = [] _l0_12_2 = [] _l0_12_3 = [] for i in range(3): _l0_12_1.append(l12_1[i]-l0_1[i]) _l0_12_2.append(l12_2[i]-l0_2[i]) _l0_12_3.append(l12_3[i]-l0_3[i])
Прежде чем приступить к оценке только что созданных представлений, давайте создадим еще один набор представлений с помощью преобразования Фурье, чтобы затем сравнить все наши представления, полученные разными методами.
fourierTransform_1=[] fourierTransform_2=[] fourierTransform_3=[] for i in range(3): fourierTransform_1.append(np.fft.fft(l0_12_1[i])/len(l0_12_1[i])) fourierTransform_2.append(np.fft.fft(l0_12_2[i])/len(l0_12_2[i])) fourierTransform_3.append(np.fft.fft(l0_12_3[i])/len(l0_12_3[i]))
Теперь мы можем сравнить полученные представления для каждой пары предложений по модификатору.
print (sents[0]) print (sents[1]) print() for i in range(3): print(tokenizer.convert_ids_to_tokens(tokenized_text[0][i+3])) print(‘diff’, 1 — spatial.distance.cosine(_l0_12_1[i], _l0_12_2[i])) print(‘log_quotient’, 1 — spatial.distance.cosine(l0_12_1[i], l0_12_2[i])) print(‘fourier’, 1 — spatial.distance.cosine(abs(fourierTransform_1[i]), abs(fourierTransform_2[i]))) print()
Полученный результат должен выглядеть следующим образом:
I had a very good apple. I had a very good orange. a diff 0.8866338729858398 log_quotient 0.43184104561805725 fourier 0.9438706822278501 very diff 0.9572229385375977 log_quotient 0.9539480209350586 fourier 0.9754009221726183 good diff 0.8211167454719543 log_quotient 0.5680340528488159 fourier 0.7838190546462953
В приведенном выше эксперименте мы ожидаем увидеть высокий уровень сходства между одними и теми же модификаторами в первых двух предложениях. На самом деле, мы видим, что методы разности и преобразования Фурье хорошо справились с задачей.
Цель следующего эксперимента — определить близость модификаторов, у которых модифицируемые существительные не так близки.
print (sents[0]) print (sents[2]) print() for i in range(3): print(tokenizer.convert_ids_to_tokens(tokenized_text[0][i+3])) print(‘diff’, 1 — spatial.distance.cosine(_l0_12_1[i], _l0_12_3[i])) print(‘log_quotient’, 1 — spatial.distance.cosine(l0_12_1[i], l0_12_3[i])) print(‘fourier’, 1 — spatial.distance.cosine(abs(fourierTransform_1[i]), abs(fourierTransform_3[i]))) print()
Вот результат:
I had a very good apple. I had a very good adventure. a diff 0.5641788840293884 log_quotient 0.5351020097732544 fourier 0.8501702469740261 very diff 0.8958494067192078 log_quotient 0.5876994729042053 fourier 0.8582797441535993 good diff 0.6836684346199036 log_quotient 0.18607155978679657 fourier 0.8857107252606878
Приведенный выше вывод показывает, что разница и логарифмическое частное были лучшими при оценке близости модификаторов, чьи родственные существительные не так близки.
print (sents[1]) print (sents[2]) print() for i in range(3): print(tokenizer.convert_ids_to_tokens(tokenized_text[0][i+3])) print(‘diff’, 1 — spatial.distance.cosine(_l0_12_2[i], _l0_12_3[i])) print(‘log_quotient’, 1 — spatial.distance.cosine(l0_12_2[i], l0_12_3[i])) print(‘fourier’, 1 — spatial.distance.cosine(abs(fourierTransform_2[i]), abs(fourierTransform_3[i]))) print()
Выходы следующие:
I had a very good orange. I had a very good adventure. a diff 0.8232558369636536 log_quotient 0.7186723351478577 fourier 0.8378725099204362 very diff 0.9369465708732605 log_quotient 0.6996179223060608 fourier 0.9164374584436726 good diff 0.8077239990234375 log_quotient 0.5284199714660645 fourier 0.9069805698881434
Мы снова видим, что разница и логарифмическое частное оказались лучшими при оценке близости модификаторов, родственные существительные которых не так близки.
Заключение
Имеет ли смысл преобразование Фурье при анализе вложений BERT? На основании эксперимента, проведенного в этой статье, можно сделать вывод, что этот подход можно эффективно использовать наряду с другими методами.