Одно из основных достоинств онлайновых приложений состоит в том, что они все время получают обратную связь в виде поведения пользователей. В случае поисковой машины каждый пользователь тут же сообщает о том, насколько ему понравились результаты поиска, щелкая по одному результату и игнорируя остальные. Мы рассмотрим способ регистрации действий пользователя после получения результатов и то, как собранную таком образом информацию можно применить для более качественного ранжирования результатов. 
Итак, для тех, кто справился с предыдущей статьей, создал и запустил своего поискового робота, сегодня мы построим и обучим искусственную нейронную сеть. Предлагая ей поисковой запрос, результаты обработки запроса и ту ссылку, по которой пользователь пе- решел, мы научим нейронную сеть, как использовать для упорядочения результатов поиска, реальный выбор пользователя.
Итак, для тех, кто справился с предыдущей статьей, создал и запустил своего поискового робота, сегодня мы построим и обучим искусственную нейронную сеть. Предлагая ей поисковой запрос, результаты обработки запроса и ту ссылку, по которой пользователь пе- решел, мы научим нейронную сеть, как использовать для упорядочения результатов поиска, реальный выбор пользователя.
Проектирование сети отслеживания переходов
Есть много разновидностей нейронных сетей, но все они состоят из множества узлов (нейронов) и связей между ними (синапсов). Мы собираемся построить MLP сеть (multilayer perceptron). Такая сеть состоит из нескольких уровней нейронов, первый из которых принимает входные данные – в данном случае поисковой запрос. Последний уровень возвращает результаты – список весов найденных URL.![]()  | 
| Уровни в БД: word_list word_hidden hidden_node hidden_urls url_list . | 
Как пример, 
  
 
 
  в сети, изображенной на рисунке выше, сколько-то человек переходили на сайт Всемирного банка (World Bank) после запроса world bank, и это усилило ассоциацию между данными словами и URL. Сплошными линиями показаны сильные связи, а полужирный текст говорит о том, что узел стал очень активным.
Мы будем обучать сеть с помощью алгоритма обратного распространения backpropagation.   
Прошу обратить внимание на подпись под картинкой. Каждый уровень (прямоугольничек или стрелочка) имеет свое отображение в БД. Я подписал названия таблиц им соответствующих, для большего понимания, что мы будем делать, но если сейчас Вам что-то не очень понятно, вернитесь к прочтению данного принципа, после описания механизма работы и все встанет на свои места!)
Подготовка базы данных
У тех, кто осилил прошлую статью, уже есть 2 таблицы - это word_list и url_list, где хранятся списки слов и урлов, соответственно. Теперь нужно еще 3: 1 - hidden_node (скрытый слой) и 2 таблицы связи с ним (одна hidden_word – для связей между слоем слов и скрытым слоем, другая hidden_url – для связей между скрытым и выходным слоем).
 def get_strength(self, from_id, to_id, layer):
  if layer == 0: 
   table = 'word_hidden'
  else: 
   table = 'hidden_url'
  self.cur.execute('select strength from %s where from_id = %d and to_id = %d' % (table, from_id, to_id))
                res = self.cur.fetchone()
  if res == None: 
   if layer == 0: 
    return -0.2
   if layer == 1: 
    return 0
    
  return res[0]
Еще необходим метод set_strength, который выясняет, существует ли уже связь, и создает либо обновляет связь, приписывая ей заданную силу. Он будет использоваться в коде для обучения сети:
  
 
 
  
 def set_strength(self, from_id, to_id, table, strength):
  self.cur.execute('select id from %s where from_id = %d and to_id = %d' % (table, from_id, to_id))
  res = self.cur.fetchone()
  if res == None: 
   self.cur.execute('insert into %s (from_id, to_id, strength) values (%d, %d, %f)' % (table, from_id, to_id, strength))
  else:
   row_id = res[0]
   self.cur.execute('update %s set strength = %f where id = %d' % (table, strength, row_id))
  self.db_commit()
Можно было бы предварительно создать гигантскую сеть с тысячами узлов в скрытом слое и уже готовыми связями, но мы с Вами будем создавать скрытые узлы, когда в них возникает надобность.  def generate_hidden_node(self, word_ids, urls):
                # Для простоты ограничимся 2-х словными фразами
  if len(word_ids) > 3: 
   return None
  # Проверить, создавали ли мы уже узел для данного набора слов
  sorted_words = map(str, word_ids)
  sorted_words.sort()
  create_key = '_'.join(sorted_words)
  self.cur.execute("select count(id) from hidden_node where create_key = '%s'" % create_key)
  count_hidden_id = self.cur.fetchone()
  # Если нет, создадим сейчас
  if not count_hidden_id:
   self.cur.execute("insert into hidden_node (create_key) values ('%s')  returning hidden_node.id" % create_key)
   hidden_id = self.cur.fetchone()[0]
   # Задать веса по умолчанию
   for word_id in word_ids:
    self.set_strength(word_id, hidden_id, 'word_hidden', 1.0/len(word_ids))
   for url_id in urls:
    self.set_strength(hidden_id, url_id, 'hidden_url', 0.1)
   self.db_commit()
Прямой проход
Теперь все готово для написания функций, которые принимают на входе слова, активируют связи в сети и формируют выходные сигналы для URL.![]()  | 
| Функция tanh | 
 def get_all_hidden_ids(self, word_ids, url_ids):
  l1 = {}
  for word_id in word_ids:
   self.cur.execute('select to_id from word_hidden where from_id = %d' % word_id)
   cur = self.cur.fetchall()
   for row in cur:
    l1[row[0]] = 1
  for url_id in url_ids:
   cur = self.cur.execute('select from_id from hidden_url where to_id = %d' % url_id)
   cur = self.cur.fetchall()
   
   for row in cur:
    l1[row[0]] = 1
  return l1.keys()
Нам также понадобится метод для конструирования релевантной сети с текущими весами из базы данных. Эта функция инициализирует различные переменные экземпляра класса: список слов, относящиеся к запросу узлы и URL, уровень выходного сигнала для каждого узла и веса всех связей между узлами. Веса считываются из базы данных с помощью ранее разработанных функций.  def setup_network(self, word_ids, url_ids):
  # списки значений
  self.word_ids = word_ids
  self.hidden_ids = self.get_all_hidden_ids(word_ids, url_ids)
  self.url_ids = url_ids
  # выходные сигналы узлов
  self.hidden_word_output = [1.0]*len(self.word_ids)
  self.hidden_layer_output = [1.0]*len(self.hidden_ids)
  self.hidden_url_output = [1.0]*len(self.url_ids)
  # создаем матрицу весов
  self.word_layer_weights = [
     [self.get_strength(word_id, hidden_id, 0) for hidden_id in self.hidden_ids] for word_id in self.word_ids
       ]
  self.layer_url_weights = [
     [self.get_strength(hidden_id, url_id, 1) for url_id in self.url_ids] for hidden_id in self.hidden_ids
     ]
Вот теперь все готово для реализации алгоритма feedforward. Он принимает список входных сигналов, пропускает их через сеть и возвращает выходные сигналы от всех узлов на выходном уровне. Поскольку в данном случае мы сконструировали сеть только для слов, входящих в запрос, то выходной сигнал от всех входных узлов равен 1:  def feed_forward(self):
  # единственные входные сигналы – слова из запроса
  for i in xrange(len(self.word_ids)):
   self.hidden_word_output[i] = 1.0
  # возбуждение скрытых узлов
  for j in xrange(len(self.hidden_ids)):
   sum = 0.0
   for i in xrange(len(self.word_ids)):
    sum = sum + self.hidden_word_output[i] * self.word_layer_weights[i][j]
   self.hidden_layer_output[j] = tanh(sum)
  # возбуждение выходных узлов
  for k in xrange(len(self.url_ids)):
   sum = 0.0
   for j in range(len(self.hidden_ids)):
    sum = sum + self.hidden_layer_output[j] * self.layer_url_weights[j][k]
   self.hidden_url_output[k] = tanh(sum)
  return self.hidden_url_output[:]
Алгоритм feedforward в цикле обходит все узлы скрытого слоя и для каждого из них вычисляет сумму величин выходных сигналов от узлов входного слоя, помноженных на вес соответствующей связи. Выходной сигнал каждого скрытого узла – это результат применения функции tanh к взвешенной сумме входных сигналов. Этот сигнал передается на выходной уровень. Выходной уровень делает то же самое – умножает полученные от предыдущего уровня сигналы на веса связей и применяет функцию tanh для получения окончательного результата. Сеть можно легко обобщить, введя дополнительные уровни, которые будут преобразовывать выходные сигналы от предыдущего уровня во входные сигналы следующему. def get_result(self, word_ids, url_ids): self.setup_network(word_ids, url_ids) return self.feed_forward()
Числа в полученном списке обозначают релевантность поданных на вход URL. Не удивляйтесь, что для всех URL нейронная сеть выдает один и тот же ответ, ведь она еще не прошла обучения!)
Обучение с обратным распространением
А вот теперь начинаются самое интересное. Сеть принимает вход- ные сигналы и генерирует выходные, но, поскольку ее не научили, какой результат считать хорошим, выдаваемые ею ответы практически бесполезны. Сейчас мы это исправим!)def dtanh(y): return 1.0 - y*y
- Вычислить разность между текущим и желательным уровнем выходного сигнала.
 - С помощью функции dtanh определить, насколько должен измениться суммарный входной сигнал для этого узла.
 - Изменить вес каждой входящей связи пропорционально ее текущему весу и скорости обучения.
 
- Изменить выходной сигнал узла на сумму весов каждой выходной связи, умноженных на величину требуемого изменения выходного сигнала конечного узла этой связи.
 - С помощью функции dtanh вычислить, насколько должен измениться суммарный входной сигнал для этого узла.
 - Изменить вес каждой входящей связи пропорционально ее текущему весу и скорости обучения.
 
 def back_propagate(self, targets, N=0.5):
  # вычислить поправки для выходного слоя
  output_deltas = [0.0] * len(self.urlids)
  for k in xrange(len(self.urlids)):
   error = targets[k] - self.hidden_url_output[k]
   output_deltas[k] = dtanh(self.hidden_url_output[k]) * error
  # вычислить поправки для скрытого слоя
  hidden_deltas = [0.0] * len(self.hiddenids)
  for j in xrange(len(self.hiddenids)):
   error = 0.0
   for k in xrange(len(self.urlids)):
    error = error + output_deltas[k]*self.layer_url_weights[j][k]
   hidden_deltas[j] = dtanh(self.hidden_layer_output[j]) * error
  # обновить веса связеи между узлами скрытого и выходного слоя
  for j in xrange(len(self.hiddenids)):
   for k in xrange(len(self.urlids)):
    change = output_deltas[k]*self.hidden_layer_output[j]
    self.layer_url_weights[j][k] = self.layer_url_weights[j][k] + N*change
  # обновить веса связей между узлами входного и скрытого слоя
  for i in xrange(len(self.wordids)):
   for j in xrange(len(self.hiddenids)):
    change = hidden_deltas[j]*self.hidden_word_output[i]
    self.word_layer_weights[i][j] = self.word_layer_weights[i][j] + N*change
Осталось только написать простой метод, который подготовит сеть, вызовет алгоритм feed_forward и запустит обратное распространение. Этот метод принимает в качестве параметров списки word_ids, url_ids и выбранный URL:
def train_query(self, word_ids, url_ids, selected_url_id): # сгенерировать скрытыи узел, если необходимо self.generate_hidden_node(word_ids, url_ids) self.setup_network(word_ids, url_ids) self.feed_forward() targets = [0.0]*len(urlids) targets[urlids.index(selected_url_id)] = 1.0 error = self.back_propagate(targets) self.update_database()
 def update_database(self):
  
  for i in xrange(len(self.word_ids)):
   for j in xrange(len(self.hidden_ids)):
    self.set_strength(self.word_ids[i], self. hidden_ids[j], 0, self.word_layer_weights[i][j])
  for j in xrange(len(self.hidden_ids)):
   for k in xrange(len(self.url_ids)):
    self.set_strength(self.hidden_ids[j], self.url_ids[k], 1, self.layer_url_weights[j][k])
Подключение к поисковой машине
Ну вот и все!) Нам осталось только подключить результаты работы сети к поисковой машине. Это для тех, кто читал прошлую статью).  Соответствующая функция похожа на все остальные весовые функции.
def neiron_net_score(self, rows, word_ids):
  # Get unique URL IDs as an ordered list
  url_ids = [row[0] for row in rows]
  nn_res = self.neiron_net.get_result(word_ids, url_ids)
  scores = {url_ids[i]: nn_res[i] for i in xrange(len(url_ids))}
  return self.normalize_scores(scores)
Включите его в список weights и поэкспериментируйте с заданием разных весов. На практике следует повременить с привлечением этого метода к реальному ранжированию до тех пор, пока сеть не обучится на большом числе примеров.



Комментариев нет:
Отправить комментарий