Что такое семантический поиск?

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

Несколько примеров семантического поиска слов

Как найти похожие/родственные слова:

Слово банкотносится кфонду, валюте, финансам.

TensorFlow Projector — хорошее место, чтобы понять, насколько похожие слова ближе друг к другу в многомерном пространстве. В приведенном ниже примере показано, как слово «банк» и связанные с ним слова расположены ближе друг к другу.

http://projector.tensorflow.org/

Как ответить на запрос типа:Дели — столица Индии. Лондон является столицей? мужская прогулка. птица?

Несколько примеров семантического поиска предложений

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

Запрос пользователя: «Найдите статьи о влиянии изменения климата на популяции диких животных».

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

Встраивание слов: строительный блок семантического поиска

Метод преобразования слова в осмысленный вектор, который кодирует значение слова таким образом, что слова, расположенные ближе в векторном пространстве, должны быть похожи по своей природе, называется встраиванием слов. Примеры расширенных синонимов, такие как принять, взять, получить, будут ближе друг к другу в векторном пространстве, поскольку они являются синонимами [Визуализировать в многомерном пространстве]. На приведенном ниже снимке экрана показан предыдущий пример банка слов и близлежащих слов в многомерном пространстве.

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

Что такое вектор?

В контексте математики: вектор — это объект, который имеет как величину, так и направление. Вектор можно представить как одномерный массив чисел. На изображении ниже изображен вектор в двух измерениях ( x, y ). В виде массива его можно представить как [12,5]. В трехмерной плоскости вектор может быть представлен как массив, содержащий три элемента для x, y и z соответственно.

В контексте информатики и анализа данных: вектор – это упорядоченный набор числовых значений или признаков, представляющих конкретный объект или точку данных.

Ниже приведены несколько алгоритмов для реализации встраивания Word.

1. Word2Vec

2. ПЕРЧАТКА

3. БЕРТ

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

  1. Среднее вложение слов с использованием word2vec
  2. Рекуррентные нейронные сети (RNN): RNN, такие как долговременная память (LSTM).
  3. Модели-трансформеры: модели на основе трансформаторов, такие как BERT.

Давайте углубимся в Word2Vec

Word2vec — это метод обработки естественного языка (NLP), опубликованный Google в 2013 году. Алгоритм word2vec использует модель нейронной сети для изучения словесных ассоциаций. Word2Vec поддерживается двумя вариантами: CBOW и Skip Gram. В этой статье я сосредоточусь на Skip Gram. Word2Vec состоит из входного слоя, скрытого слоя и выходного слоя. Нейронная сеть, используемая в случае word2Vec, использует несколько иные методы. Веса между входным слоем и скрытым слоем являются векторными представлениями слов. Конечный результат выходного слоя нейронной сети не является векторным вложением слов. Попробуем разобраться в word2Vec на примере. Все основные фреймворки по умолчанию предоставляют реализации word2Vec. Ниже я пытаюсь объяснить внутренности алгоритма на примере реализации.

Что такое модель Skip Gram и как ее реализовать?

Skip-gram — это архитектура нейронной сети, используемая для обработки естественного языка, особенно в моделях встраивания слов, таких как Word2Vec. Он предназначен для изучения представлений слов путем предсказания контекстных слов с учетом целевого слова.

Попробуем разобраться на примерах.

Мы создадим вложение для приведенных ниже предложений, описывающих персонажей «Книги джунглей».

Шерхан — король джунглей.

Маугли — храбрый мальчик.

Багира сильная.

Баллу очень помогает.

Лали очень добрая.

  1. Преобразуйте строку в нижний регистр, удалите лишние пробелы и удалите стоп-слова, такие как is, the, a, . Это также называется Sub Sampling: Высокочастотные и низкочастотные слова часто дают мало информации. Слова с частотой выше определенного порога или ниже определенного порога могут быть подвергнуты субдискретизации или удалены для ускорения обучения. Так как стоп-слова не несут особого смысла и, следовательно, их лучше удалить.
  2. Создайте карту всех уникальных слов. Это будет составлять общий словарный запас.
  3. Кодируйте все слова, используя одну горячую кодировку. В словаре 12 уникальных слов. Одной горячей кодировкой будет матрица размером 12x12. Каждая строка матрицы будет представлять одно слово. В строке только один элемент будет иметь значение 1, а все остальные будут равны 0. На изображении ниже показаны слова с их единственной горячей кодировкой.

Итак, входной слой будет иметь 12 входов. Для простоты пусть в скрытом слое будет 4 нейрона. Выходной слой снова будет иметь 12 выходов. Функция активации для выходного слоя будет SoftMax. Веса между входным слоем и скрытым слоем — это встраивание слов. Вывод первого скрытого слоя будет действовать как отдельное вложение, как описано ниже.

Веса между входным слоем и скрытым слоем будут представлять собой матрицу размером 12x4, как показано ниже.

Skip Gram использует скользящее окно для генерации входного слова и контекстного выходного слова. Примем значение скользящего окна равным 2. На снимке экрана ниже показано.

Набор обучающих данных подается в нейронную сеть. После обучения модели модель должна вернуться к королю, когда на вход подается Шерхан. Веса между входным слоем и скрытым слоем — это встраивание/векторы слов для всех слов. Вывод первого скрытого слоя будет действовать как отдельное вложение, как описано ниже.

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

public static void main(String[] args) {
  String str = "Sherkhan is king of the Jungle";
  String str1 = "Mowgli is a brave boy";
  String str2 = "Bagira is strong";
  String str3 = "Ballu is very helping";
  String str4 = "Lali is very kind";
  
  List<String> words = new ArrayList<>();
  words.add(str);
  words.add(str1);
  words.add(str2);
  words.add(str3);
  words.add(str4);

  HashSet<String> set = new HashSet<>();
  set.add("the");
  set.add("is");
  set.add("a");
  set.add("of");
  set.add("very");
  OneHotEncoder encoder = new OneHotEncoder(words, set,2);
  List<Integer[][]> trainingSet = encoder.generateSkipGramsTrainingDataSet();
 }

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

package org.ai.hope;

import java.util.*;

public class Tokenizer {

 private List<String> inputs;

 private HashSet<String> stopWords;
 
 private int skipGramWindow;

 public Tokenizer(List<String> ins, HashSet<String> words) {
  this.inputs = ins;
  stopWords = words;
 }
 
 public Tokenizer(List<String> ins, HashSet<String> words,int skipGramWindow ) {
  this.inputs = ins;
  stopWords = words;
  this.skipGramWindow=skipGramWindow;
 }

 private HashSet<String> uniqueWords = new HashSet<>();

 private List<String[]> removeOutStopWords() {
  List<String[]> list = new ArrayList<>();

  for (int i = 0; i < inputs.size(); i++) {
   String temp = inputs.get(i).toLowerCase();
   String[] words = temp.split(" ");
   int space =0;
   for (int j = 0; j < words.length; j++) {
    if (stopWords.contains(words[j])) {
     words[j] = "";
     space = space+1;
    } else {
     uniqueWords.add(words[j]);
    }
   }

   String[] finalWord = new String[words.length-space];
   int index =0;
   for(int k =0;k<words.length;k++)
   {
    if(words[k] == "")
    {
     continue;
    }
    finalWord[index]=words[k] ;
    index=index+1;
   }
   list.add(finalWord);
  }

  return list;
 }

 private List<String[]> createBigrams(List<String[]> words) {
  List<String[]> list = new ArrayList<>();

  for (int i = 0; i < words.size(); i++) {
   String[] array = words.get(i);

   for (int j = 0; j < array.length; j++) {
    if (array[j] == null) {
     continue;
    }

    for (int k = 0; k < array.length; k++) {
     if (k == j || array[k] == null) {
      continue;
     } else {
      String[] bigrams = new String[2];

      bigrams[0] = array[j];
      bigrams[1] = array[k];
      list.add(bigrams);
     }
    }
   }
  }

  return list;
 }

 private List<String[]> createSkipGramsTrainingDataSet(List<String[]> words, int window) {
  List<String[]> list = new ArrayList<>();

  for (int i = 0; i < words.size(); i++) {
   String[] array = words.get(i);

   for (int j = 0; j < array.length; j++) {
    
    if(array[j]==null)
    {
     continue;
    }
    int tWindow = window;
    int k = j - 1;
    int p = j + 1;
    while (tWindow > 0) {
     if (k >= 0) {
      String[] grams = new String[2];
      grams[0] = array[j];
      grams[1] = array[k];
      k = k - 1;
      list.add(grams);

     }
     if (p < array.length) {
      String[] grams = new String[2];
      grams[0] = array[j];
      grams[1] = array[p];
      p = p + 1;
      list.add(grams);
     }
     tWindow = tWindow - 1;
    }
   }

  }

  return list;
 }

 public List<Integer[][]> generateBigramsEncoding() {
  List<Integer[][]> doubles = new ArrayList<>();
  List<String[]> temp = removeOutStopWords();
  List<String[]> grams = createBigrams(temp);

  HashMap<String, Integer[]> hashMap = oneHotEncoder();

  System.out.println(hashMap);

  for (int i = 0; i < grams.size(); i++) {
   String[] str = grams.get(i);
   Integer[][] array = new Integer[2][uniqueWords.size()];

   array[0] = hashMap.get(str[0]);
  }
  return doubles;
 }
 
 public List<Integer[][]> generateSkipGramsTrainingDataSet() {
  List<Integer[][]> doubles = new ArrayList<>();
  List<String[]> temp = removeOutStopWords();
  List<String[]> grams = createSkipGramsTrainingDataSet(temp,skipGramWindow);

  HashMap<String, Integer[]> hashMap = oneHotEncoder();

  System.out.println(hashMap);

  for (int i = 0; i < grams.size(); i++) {
   String[] str = grams.get(i);
   Integer[][] array = new Integer[2][uniqueWords.size()];

   array[0] = hashMap.get(str[0]);
   array[1] = hashMap.get(str[1]);
   doubles.add(array);
  }
  return doubles;
 }
 
 public HashMap<Integer, List<Integer[]>>  generateSkipGramsTrainingData() {
  List<String[]> temp = removeOutStopWords();
  HashMap<Integer, List<Integer[]>> dataSet = new HashMap<>();
  List<String[]> grams = createSkipGramsTrainingDataSet(temp,skipGramWindow);
  List<Integer[]> inputs = new ArrayList<>();
  List<Integer[]> outputs = new ArrayList<>();
  HashMap<String, Integer[]> hashMap = oneHotEncoder();
  dataSet.put(1, inputs);
  dataSet.put(2, outputs);
  System.out.println(hashMap);

  for (int i = 0; i < grams.size(); i++) {
   String[] str = grams.get(i);
   Integer[] tempInput = hashMap.get(str[0]);
   Integer[] tempOutput = hashMap.get(str[0]);
   inputs.add(tempInput);
   outputs.add(tempOutput);
  }
  return dataSet;
 }

 public HashMap<String, Integer[]> oneHotEncoder() {
  int i = 0;
  HashMap<String, Integer[]> encoder = new HashMap<>();
  for (String key : uniqueWords) {
   Integer[] values = new Integer[uniqueWords.size()];
   values[i] = 1;
   encoder.put(key, values);
   i = i+1;

  }
  return encoder;
 }

 public static void main(String[] args) {
  String str = "Sherkhan is king of the Jungle";
  String str1 = "Mowgli is a brave boy";
  String str2 = "Bagira is strong";
  String str3 = "Ballu is very helping";
  String str4 = "Lali is very kind";


  List<String> words = new ArrayList<>();

  words.add(str);
  words.add(str1);
  words.add(str2);
  words.add(str3);
  words.add(str4);


  HashSet<String> set = new HashSet<>();

  set.add("the");
  set.add("is");
  set.add("a");
  set.add("of");
  set.add("very");

  Tokenizer encoder = new Tokenizer(words, set,2);
  List<Integer[][]> trainingSet = encoder.generateSkipGramsTrainingDataSet();

 }

}

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

package org.ai.hope;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Random;

import org.deeplearning4j.datasets.iterator.impl.ListDataSetIterator;
import org.deeplearning4j.nn.conf.NeuralNetConfiguration;
import org.deeplearning4j.nn.conf.layers.DenseLayer;
import org.deeplearning4j.nn.conf.layers.OutputLayer;
import org.deeplearning4j.nn.multilayer.MultiLayerNetwork;
import org.deeplearning4j.nn.weights.WeightInit;
import org.deeplearning4j.optimize.listeners.ScoreIterationListener;
import org.nd4j.linalg.activations.Activation;
import org.nd4j.linalg.api.ndarray.INDArray;
import org.nd4j.linalg.dataset.DataSet;
import org.nd4j.linalg.dataset.api.iterator.DataSetIterator;
import org.nd4j.linalg.factory.Nd4j;
import org.nd4j.linalg.learning.config.Nesterovs;

public class WordEmbeddingSample {

 public static void main(String[] args) {
  DataSetIterator iterator = getTrainingData(batchSize, rng);
  final int numInputs = 12;
  int nHidden = 4;

  MultiLayerNetwork net = new MultiLayerNetwork(new NeuralNetConfiguration.Builder().seed(seed)
    .weightInit(WeightInit.XAVIER).updater(new Nesterovs(learningRate, 0.9)).list()
    .layer(0, new DenseLayer.Builder().nIn(numInputs).nOut(nHidden).build())
    .layer(1, new OutputLayer.Builder().activation(Activation.SOFTMAX).nIn(nHidden).nOut(numInputs).build())
    .build());
  net.init();
  net.setListeners(new ScoreIterationListener(1));

  for (int i = 0; i < nEpochs; i++) {
   iterator.reset();
   net.fit(iterator);
  }

 }
 
 public static final int seed = 12345;
 // Number of iterations per minibatch
 public static final int iterations = 1;
 // Number of epochs (full passes of the data)
 public static final int nEpochs = 200;
 // Number of data points
 public static final int nSamples = 1000;
 // Batch size: i.e., each epoch has nSamples/batchSize parameter updates
 public static final int batchSize = 12;
 // Network learning rate
 public static final double learningRate = 0.01;
 public static final Random rng = new Random(seed);

 private static DataSetIterator getTrainingData(int batchSize, Random rand) {

  String str = "Sherkhan is king of the Jungle";
  String str1 = "Mowgli is a brave boy";
  String str2 = "Bagira is strong";
  String str3 = "Ballu is very helping";
  String str4 = "Lali is very kind";

  List<String> words = new ArrayList<>();

  words.add(str);
  words.add(str1);
  words.add(str2);
  words.add(str3);
  words.add(str4);

  HashSet<String> set = new HashSet<>();

  set.add("the");
  set.add("is");
  set.add("a");
  set.add("of");
  set.add("very");

  Tokenizer encoder = new Tokenizer(words, set, 2);
  HashMap<Integer, List<Integer[]>> trainingSet = encoder.generateSkipGramsTrainingData();

  List<Integer[]> inputs = trainingSet.get(1);
  List<Integer[]> oputs = trainingSet.get(2);

  double[][] ins = new double[inputs.size()][12];
  double[][] ous = new double[oputs.size()][12];
  for (int i = 0; i < inputs.size(); i++) {
   double[] arrinput = new double[inputs.get(i).length];
   double[] arrop = new double[inputs.get(i).length];
   for (int j = 0; j < arrinput.length; j++) {
    arrinput[j] = inputs.get(i)[j] == null ? 0 : inputs.get(i)[j].intValue();
    arrop[j] = oputs.get(i)[j] == null ? 0 : oputs.get(i)[j].intValue();
   }

   ins[i] = arrinput;
   ous[i] = arrop;
  }
  INDArray inputNDArray = Nd4j.create(ous);
  ;
  INDArray outPut = Nd4j.create(ins);
  DataSet dataSet = new DataSet(inputNDArray, outPut);
  List<DataSet> listDs = dataSet.asList();
  Collections.shuffle(listDs, rng);
  return new ListDataSetIterator(listDs, batchSize);

 }

}

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