Использование преобразования Фурье векторных представлений, полученных из вложений 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? На основании эксперимента, проведенного в этой статье, можно сделать вывод, что этот подход можно эффективно использовать наряду с другими методами.