Привет, давно не виделись. В этом посте мне хотелось бы рассказать о таком относительно новом понятии в машинном обучении, как transfer learning. Так как я не нашел какого-либо устоявшегося перевода этого термина, то и в названии поста фигурирует хоть и другой, но близкий по смыслу термин, который как бы является биологической предпосылкой к формализации теории передачи знаний от одной модели к другой. Итак, план такой: для начала рассмотрим биологические предпосылки; после коснемся отличия transfer learning от очень похожей идеи предобучения глубокой нейронной сети; а в конце обсудим реальную задачу семантического хеширования изображений. Для этого мы не будем скромничать и возьмем глубокую (19 слоев) сверточную нейросеть победителей конкурса imagenet 2014 года в разделе «локализация и классификация» (Visual Geometry Group, University of Oxford), сделаем ей небольшую трепанацию, извлечем часть слоев и используем их в своих целях. Поехали.
Нейропластичность
Для начала рассмотрим определение. Нейропластичность — это свойство
Чтобы действительно ощутить всю мощь нашего
С помощью осциллографа можно было визуализировать изображение, создаваемое вибрацией стимуляторов.
Годом позже Пол разработал мобильную версию своей системы:
В наше время вместо тактильной передачи сигнала используют «короткий» путь через более чувствительный орган — язык. Как пишут в статьях, достаточно нескольких часов, чтобы начать воспринимать изображение рецепторами языка.
И неудивительно, что такую находку пытаются монетизировать:
Так как же это работает? Говоря на простом языке (не биологов и нейрофизиологов, а скорее на языке анализа данных), нейроны обучаются эффективно извлекать признаки и делать выводы на них. Подменяя привычный сигнал на некоторый другой, наша нейронная сеть все равно извлекает хорошие низкоуровневые признаки (на примере компьютерного зрения, приводимого ниже, это будут различные градиентные переходы или паттерны) на слоях, которые расположены близко к сенсору (глазу, языку, уху и т.д.). Более глубокие слои пытаются извлечь высокоуровневые признаки (колесо, окно). И вероятно, если искать в звуковом сигнале колесо от машины, скорее всего мы его не найдем. К сожалению, посмотреть, какие признаки извлекают нейроны, у нас не очень получится, но благодаря статье "Visualizing and Understanding Convolutional Networks" есть возможность взглянуть на низкоуровневые и высокоуровневые признаки, извлекаемые глубокой сверточной нейронной сетью. Это, конечно, не биологическая сеть, и возможно, все в реальности не так. Как минимум, это дает некое интуитивное понимание причин того, что видеть можно даже рецепторами языка.
На большой картинке в спойлере изображено по несколько признаков каждого сверточного слоя и части оригинальных изображений, которые их активируют. Как вы можете заметить, чем глубже, тем менее абстрактными становятся признаки.
Можно предположить, что, подменяя сенсоры, нашему
А теперь давайте вспомним, с чего начиналась история искусственных нейронных сетей. В 1949 году Дональд Хебб публикует книгу The Organization of Behavior, в которой описывает первые принципы обучений ИНС. Стоит заметить, что современные алгоритмы обучения недалеко ушли от этих принципов.
- если рядом стоящие нейроны активируются синхронно, то связь между ними усиливается;
- если рядом стоящие нейроны активируются асинхронно, то связь между ними ослабевает (на самом деле, этого правила не было у Хебба, его дописали как дополнение уже позже).
Через девять лет Френк Розенблатт создает первую модель для обучения с учителем — персептрон. Автор первой искусственной нейронной сети не преследовал цели создать универсальный аппроксиматор. Он был нейрофизиологом, и его задачей было создать устройство, которое сможет обучаться наподобие человека. Только взгляните на то, что журналисты пишут про персептрон. Имхо, это прелестно:
…
В персептроне были реализованы правила обучения Хебба. Как мы видим, синаптическая пластичность в правилах уже учтена в какой-то степени. И в принципе, online learning дает нам некоторую пластичность — нейросеть может постоянно дообучаться на непрерывном потоке данных, а ее прогнозы в связи с этим будут со временем меняться, постоянно учитывая изменения в данных. Но нет никаких рекомендаций по другим аспектам нейропластичности, таким как сенсорная заменяемость или нейрогенез. Но это и неудивительно, до принятия нейропластичности еще остается чуть более 20 лет. Учитывая последующую историю ИНС, ученым было не до имитации нейропластичности, вопрос был о выживаемости теории в принципе. Только после очередного ренессанса нейросетей в двухтысячных годах, благодаря таким людям, как Хинтон, ЛеКун, Бенджио и Шмидтхубер, у других ученых появилась возможность всесторонне подойти к машинному обучению и прийти к понятию transfer learning.
Transfer learning
Определим цели transfer learning. Авторы публикации 2009 года с одноименным названием выделяют три основные цели:
- higher start — улучшение качества обучения уже на начальных итерациях за счет более тщательной подборки начальных параметров модели или какой-либо другой априорной информации;
- higher slope — ускорение сходимости алгоритма обучения;
- higher asymptote — улучшение верхней достижимой границы качества.
Если вы знакомы с предобучением глубоких сетей с помощью автоенкодеров или ограниченных машин Больцмана, то вы сразу подумаете: «Так вот оно, я уже, оказывается, практиковал transfer learning или, как минимум, знаю, как это сделать». А вот, оказывается, нет. Авторы проводят четкую границу между стандартным машинным обучением и transfer learning. В стандартном подходе у вас есть только цель и набор данных, а задача заключается в том, чтобы какими-либо методами достичь этой цели. В рамках решения задачи вы можете построить глубокую сеть, предобучить ее жадным алгоритмом, построить еще с десяток таких же и каким-либо образом сделать ансамбль из них. Но все это будет в рамках решения какой-то одной проблемы, и время, потраченное на решение такой проблемы, будет сравнимо с суммарным временем, потраченным на обучение каждой модели, и ее предобучение.
А теперь представьте, что есть две задачи, и решали их, возможно, даже разные люди. Один из них использует часть модели другого (source task) для уменьшения временных затрат на создание модели с нуля и улучшения производительности своей модели (target task). Процесс передачи знаний от одной проблемы к другой и есть transfer learning. А наш
Другой особенностью transfer learning является то, что информация может передаваться только из старой модели в сторону новой, ведь старая задача уже давно решена. В то время как при стандартном подходе разные модели, участвующие в решении задачи, могут обмениваться информацией друг с другом.
Этот пост касается только той части transfer learning, которая относится к задаче обучения с учителем, но интересующимся рекомендую почитать оригинал. Оттуда вы узнаете, что, скажем, в обучении с подкреплением или при обучении байесовых сетей transfer learning стал использоваться раньше, чем в нейронных сетях.
Итак, обучение с учителем является обучением на размеченных примерах, в то время как сам процесс обучения на примерах иногда называется индуктивным обучением (переход от частного к общему), а обобщающая способность называется inductive bias. Отсюда и второе название transfer learning — inductive transfer. Тогда можно сказать, что задача трансфера знаний в индуктивном обучении — позволить знаниям, накопленным в процессе обучения старой модели, повлиять на обобщающую способность новой модели (даже при решении другой задачи).
Трансфер знаний можно также рассматривать как некоторую регуляризацию, которая ограничивает пространство поиска до определенного набора допустимых и хороших гипотез.
Практика
Надеюсь, к этому моменту вы прониклись таким, на первый взгляд, ничем не примечательным подходом, как трансфер знаний. Ведь можно просто сказать, что никакой это не трансфер знаний, нейропластичность притянута за уши, а лучшее название для этого метода — копипаст. Тогда вы просто прагматик. Это, конечно, тоже неплохо, и тогда хотя бы третий раздел вам понравится. Давайте попробуем повторить нечто подобное описанному в первом разделе, но на искусственной нейронной сети.
Для начала сформулируем проблему. Допустим, у вас есть очень большой набор изображений. Необходимо организовать поиск похожих изображений. Тут возникает две проблемы. Во-первых, мера схожести между изображениями неочевидна, и если просто брать евклидово расстояние от n*m-мерного вектора, результат получится не очень удовлетворительным. Во-вторых, даже если есть качественная мера, то она не позволит нам избежать полного сканирования базы, а база может содержать миллиарды изображений.
Для решения этой задачи можно использовать семантическое хеширование, один из таких способов описан у Салакхудинова и Хинтона в статье Semantic Hashing. Их идея заключается в том, что исходный вектор данных кодируется бинарным вектором малой размерности (в нашем случае это изображение, а в оригинальной статье это бинарный вектор bag of words). При таком кодировании можно осуществлять поиск похожих изображений за линейное от длины кода время, используя расстояние Хэмминга. Такое кодирование называется семантическим, так как близкие по смыслу и содержанию изображения (тексты, музыка и т.д.) в новом пространстве признаков располагаются близко друг к другу. Для реализации этой идеи они использовали глубокую сеть доверия, алгоритм обучения которой был разработан Хинтоном и компанией еще в 2006 году:
- сеть на вход принимает в общем случае действительные значения (gaussian-bernoulli rbm) и кодирует их в длинный бинарный вектор, каждый следующий слой старается уменьшить длину бинарного кода (berboulli-bernoulli rbm);
- такая сеть предобучается последовательно снизу вверх своей собственной ограниченной машиной Больцмана, где каждая следующая использует выход предыдущей в качестве своих входных данных;
- затем сеть разворачивается в обратном порядке и обучается как глубокий автоенкодер, которому на вход подается немного зашумленный вектор, и он пытается восстановить оригинальный не зашумленный образ; этот шаг называется fine turning;
- в конце концов, бинарный образ в середине автоенкодера будет искомым бинарным хэшем от входного образа.
Мне кажется, что это гениальная модель, и я решил, что в принципе задача решена, спасибо Хинтону. Я запустил предобучение на NVIDIA Tesla K20, подождал пару дней, и оказалось, что все не так радужно, как описывает Хинтон. То ли из-за того, что картинки большого размера, то ли потому, что я использовал gaussian-bernoulli rbm, а в статье используется poisson-bernoulli rbm, то ли из-за специфичности данных, а может быть, вообще, я мало обучал. Но дальше ждать мне как-то не хотелось. И тут я вспомнил совет Максима Милакова — юзай сверточные сети, а также термин transfer learning из одной его презентации. Были, конечно, и другие варианты, начиная от сжатия размерности картинок и квантификации цветов, до классических признаков компьютерного зрения и объединения их в bag of visual words. Но раз вступив на тропу глубокого обучения, не так-то просто с нее свернуть, да и бонусы, которые обещает transfer learning (особенно экономия времени), меня прельстили.
Оказалось, что упомянутая в начале группа VGG, которая победила в 2014 году в конкурсе ImageNet, выложила свою натренированную нейросеть в свободный доступ для некоммерческого использования, так что чисто в исследовательских целях я ее и скачал.
Вообще, ImageNet — это не только конкурс, но и база изображений, которая содержит в себе немногим более одного миллиона реальных изображений. Каждое изображение отнесено к одному из 1000 классов. Набор сбалансирован по классам, то есть чуть более 1000 изображений на один класс. Парни из Оксфорда выиграли в номинации локализация и классификация. Так как объектов на изображениях может быть более одного, то и оценивание происходит по тому, находится ли правильный ответ в топ-5 наиболее вероятных вариантов по версии модели. На изображении ниже вы можете увидеть пример работы одной из моделей на картинках из imagenet. Обратите внимание на забавную ошибку с далматинцем, модель, к сожалению, не нашла там вишню.
При такой вариативности датасета логично предположить, что где-то в сети присутствует эффективный извлекатель признаков, а также классификатор, который и решает, к какому классу относится изображение. Хотелось бы достать этот самый извлекатель, отделить его от классификатора и поиспользовать его для обучения глубокого автоенкодера из статьи о семантическом хешировании.
Нейросеть VGG обучена и сохранена в формате Caffe. Очень крутая библиотека для глубокого обучения, а главное — легкая в освоении, рекомендую ознакомиться. Прежде чем трепанировать сеть VGG, используя caffe, предлагаю мельком ознакомиться с самой сетью. Если интересны подробности, то рекомендую оригинальную статью — Very Deep Convolutional Networks for Large-Scale Image Recognition (название уже как бы намекает на глубину сети). А тем, кто вообще не знаком со сверточными сетями, необходимо как минимум почитать русскую википедию, прежде чем двигаться дальше, это не займет более 5-10 минут (или же есть небольшое описание на Хабре).
Итак, для статьи и конкурса авторы натренировали несколько моделей:
На своей странице они выложили варианты D и E. Мы для эксперимента возьмем 19-слойный вариант E, в котором первые 16 слоев сверточные, а последние три — полносвязные. Последние три слоя чувствительны к размеру изображений, так что для эксперимента, недолго думая, я выкинул их и оставил первые 16 слоев, посчитав, что высокоуровневые признаки я удалил.
В библиотеке caffe для описание моделей используется гугловский Protocol Buffers, и полное описание сети выглядит следующим образом.
name: "VGG_ILSVRC_19_layers"
input: "data"
input_dim: 10
input_dim: 3
input_dim: 224
input_dim: 224
layers {
bottom: "data"
top: "conv1_1"
name: "conv1_1"
type: CONVOLUTION
convolution_param {
num_output: 64
pad: 1
kernel_size: 3
}
}
layers {
bottom: "conv1_1"
top: "conv1_1"
name: "relu1_1"
type: RELU
}
layers {
bottom: "conv1_1"
top: "conv1_2"
name: "conv1_2"
type: CONVOLUTION
convolution_param {
num_output: 64
pad: 1
kernel_size: 3
}
}
layers {
bottom: "conv1_2"
top: "conv1_2"
name: "relu1_2"
type: RELU
}
layers {
bottom: "conv1_2"
top: "pool1"
name: "pool1"
type: POOLING
pooling_param {
pool: MAX
kernel_size: 2
stride: 2
}
}
layers {
bottom: "pool1"
top: "conv2_1"
name: "conv2_1"
type: CONVOLUTION
convolution_param {
num_output: 128
pad: 1
kernel_size: 3
}
}
layers {
bottom: "conv2_1"
top: "conv2_1"
name: "relu2_1"
type: RELU
}
layers {
bottom: "conv2_1"
top: "conv2_2"
name: "conv2_2"
type: CONVOLUTION
convolution_param {
num_output: 128
pad: 1
kernel_size: 3
}
}
layers {
bottom: "conv2_2"
top: "conv2_2"
name: "relu2_2"
type: RELU
}
layers {
bottom: "conv2_2"
top: "pool2"
name: "pool2"
type: POOLING
pooling_param {
pool: MAX
kernel_size: 2
stride: 2
}
}
layers {
bottom: "pool2"
top: "conv3_1"
name: "conv3_1"
type: CONVOLUTION
convolution_param {
num_output: 256
pad: 1
kernel_size: 3
}
}
layers {
bottom: "conv3_1"
top: "conv3_1"
name: "relu3_1"
type: RELU
}
layers {
bottom: "conv3_1"
top: "conv3_2"
name: "conv3_2"
type: CONVOLUTION
convolution_param {
num_output: 256
pad: 1
kernel_size: 3
}
}
layers {
bottom: "conv3_2"
top: "conv3_2"
name: "relu3_2"
type: RELU
}
layers {
bottom: "conv3_2"
top: "conv3_3"
name: "conv3_3"
type: CONVOLUTION
convolution_param {
num_output: 256
pad: 1
kernel_size: 3
}
}
layers {
bottom: "conv3_3"
top: "conv3_3"
name: "relu3_3"
type: RELU
}
layers {
bottom: "conv3_3"
top: "conv3_4"
name: "conv3_4"
type: CONVOLUTION
convolution_param {
num_output: 256
pad: 1
kernel_size: 3
}
}
layers {
bottom: "conv3_4"
top: "conv3_4"
name: "relu3_4"
type: RELU
}
layers {
bottom: "conv3_4"
top: "pool3"
name: "pool3"
type: POOLING
pooling_param {
pool: MAX
kernel_size: 2
stride: 2
}
}
layers {
bottom: "pool3"
top: "conv4_1"
name: "conv4_1"
type: CONVOLUTION
convolution_param {
num_output: 512
pad: 1
kernel_size: 3
}
}
layers {
bottom: "conv4_1"
top: "conv4_1"
name: "relu4_1"
type: RELU
}
layers {
bottom: "conv4_1"
top: "conv4_2"
name: "conv4_2"
type: CONVOLUTION
convolution_param {
num_output: 512
pad: 1
kernel_size: 3
}
}
layers {
bottom: "conv4_2"
top: "conv4_2"
name: "relu4_2"
type: RELU
}
layers {
bottom: "conv4_2"
top: "conv4_3"
name: "conv4_3"
type: CONVOLUTION
convolution_param {
num_output: 512
pad: 1
kernel_size: 3
}
}
layers {
bottom: "conv4_3"
top: "conv4_3"
name: "relu4_3"
type: RELU
}
layers {
bottom: "conv4_3"
top: "conv4_4"
name: "conv4_4"
type: CONVOLUTION
convolution_param {
num_output: 512
pad: 1
kernel_size: 3
}
}
layers {
bottom: "conv4_4"
top: "conv4_4"
name: "relu4_4"
type: RELU
}
layers {
bottom: "conv4_4"
top: "pool4"
name: "pool4"
type: POOLING
pooling_param {
pool: MAX
kernel_size: 2
stride: 2
}
}
layers {
bottom: "pool4"
top: "conv5_1"
name: "conv5_1"
type: CONVOLUTION
convolution_param {
num_output: 512
pad: 1
kernel_size: 3
}
}
layers {
bottom: "conv5_1"
top: "conv5_1"
name: "relu5_1"
type: RELU
}
layers {
bottom: "conv5_1"
top: "conv5_2"
name: "conv5_2"
type: CONVOLUTION
convolution_param {
num_output: 512
pad: 1
kernel_size: 3
}
}
layers {
bottom: "conv5_2"
top: "conv5_2"
name: "relu5_2"
type: RELU
}
layers {
bottom: "conv5_2"
top: "conv5_3"
name: "conv5_3"
type: CONVOLUTION
convolution_param {
num_output: 512
pad: 1
kernel_size: 3
}
}
layers {
bottom: "conv5_3"
top: "conv5_3"
name: "relu5_3"
type: RELU
}
layers {
bottom: "conv5_3"
top: "conv5_4"
name: "conv5_4"
type: CONVOLUTION
convolution_param {
num_output: 512
pad: 1
kernel_size: 3
}
}
layers {
bottom: "conv5_4"
top: "conv5_4"
name: "relu5_4"
type: RELU
}
layers {
bottom: "conv5_4"
top: "pool5"
name: "pool5"
type: POOLING
pooling_param {
pool: MAX
kernel_size: 2
stride: 2
}
}
layers {
bottom: "pool5"
top: "fc6"
name: "fc6"
type: INNER_PRODUCT
inner_product_param {
num_output: 4096
}
}
layers {
bottom: "fc6"
top: "fc6"
name: "relu6"
type: RELU
}
layers {
bottom: "fc6"
top: "fc6"
name: "drop6"
type: DROPOUT
dropout_param {
dropout_ratio: 0.5
}
}
layers {
bottom: "fc6"
top: "fc7"
name: "fc7"
type: INNER_PRODUCT
inner_product_param {
num_output: 4096
}
}
layers {
bottom: "fc7"
top: "fc7"
name: "relu7"
type: RELU
}
layers {
bottom: "fc7"
top: "fc7"
name: "drop7"
type: DROPOUT
dropout_param {
dropout_ratio: 0.5
}
}
layers {
bottom: "fc7"
top: "fc8"
name: "fc8"
type: INNER_PRODUCT
inner_product_param {
num_output: 1000
}
}
layers {
bottom: "fc8"
top: "prob"
name: "prob"
type: SOFTMAX
}
Для того чтобы сделать описанную трепанацию, достаточно в описании модели удалить все слои, начиная от fc6 (full connected слой). Но стоит заметить, что тогда выход сети будет неограничен сверху, т.к. функцией активации является rectified linear unit:
Такой вопрос удобно решается взятием сигмойда от выхода сети. Мы можем надеяться, что многие нейроны будут либо 0, либо какое-либо большое число. Тогда после взятия сигмоида у нас получится много единиц и 0.5 (сигмоид от 0 равен 0.5). Если нормировать полученные значения в промежуток от 0 до 1, то их можно интерпретировать как вероятности активации нейронов, и почти все из них будут в районе нуля либо единицы. Вероятность активации нейрона трактуется как вероятность наличия признака на изображении (например, есть ли на нем человеческий глаз).
Вот типичный ответ такой сети в моем случае:
0.0
0.0
0.0
0.0
0.0
1.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.994934
0.0
0.0
0.999047
0.829219
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.997255
0.0
0.0
0.0
0.0
0.0
0.0
0.0
1.0
1.0
0.0
0.999382
0.0
0.0
0.0
0.0
0.988762
0.0
0.0
1.0
1.0
0.0
1.0
1.0
0.0
1.0
1.0
1.0
1.0
1.0
0.0
1.0
1.0
0.0
0.0
1.0
1.0
0.0
1.0
1.0
0.0
0.0
0.0
0.0
0.0
0.847886
0.0
0.0
0.0
0.0
0.957379
0.0
0.0
0.0
0.0
0.0
1.0
0.999998
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.0
1.0
0.999999
0.0
0.54814
0.739735
0.0
0.0
0.0
0.912179
0.0
0.0
0.78984
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.681776
0.0
0.0
0.991501
0.0
0.999999
0.999152
0.0
0.0
1.0
0.0
0.0
0.0
0.0
0.999996
1.0
0.0
1.0
1.0
0.0
0.880588
0.0
0.0
0.0
0.0
0.0
0.0
0.836756
0.995515
0.0
0.999354
0.0
1.0
1.0
0.0
1.0
1.0
0.0
0.999897
0.0
0.953126
0.0
0.0
0.999857
0.0
0.0
0.937695
0.999983
1.0
0.0
0.0
0.0
0.0
0.0
1.0
0.0
0.0
0.0
В caffe такой вопрос решается следующим образом:
layers {
bottom: "pool5"
top: "sigmoid1"
name: "sigmoid1"
type: SIGMOID
}
В caffe реализована обертка на питоне, и следующий код инициализирует сеть и делает нормировку:
caffe.set_mode_gpu()
net = caffe.Classifier('deploy.prototxt', 'VGG_ILSVRC_19_layers.caffemodel',
channel_swap=(2,1,0),
raw_scale=255,
image_dims=(options.width, options.height))
#..................
out = net.forward_all(**{net.inputs[0]: caffe_in})
out = out['sigmoid1'].reshape(out['sigmoid1'].shape[0], np.prod(out['sigmoid1'].shape[1:]))
out = (out - 0.5)/0.5
Итак, на данный момент у нас на руках есть бинарное (ну или почти) представление изображений векторами высокой размерности, в моем случае 4608, осталось обучить Deep Befief Network для сжатия этих представлений. Итоговая модель стала еще более глубокой сетью. Не дожидаясь, пока несколько дней будет обучаться DBN, давайте проведем эксперимент на поиск: будем выбирать случайное изображение и для него искать несколько ближайших, в смысле расстояния Хэмминга. Заметьте, это все на сырых фичах, на векторе высокой размерности, без всякого взвешивания признаков.
Пояснения: первая картинка — это картинка запрос, случайное изображение из базы; остальные — это ближайшие к ней.
Заключение и ссылки
Я думаю, вы легко можете придумать пример, в котором не просто перенесете статические слои, но и натренируете их по-своему. Скажем, вы можете инициализировать часть своей сети, используя другую сеть, а затем дообучить на своих классах.
А если попробовать что-то более близкое к эксперименту, описанному в первой части? Пожалуйста: вот пример статьи, где используется convolutional deep belief networks для извлечения признаков из аудиосигналов. Почему бы не использовать обученные свертки для инициализации весов cDBN? Чем такая вот спектрограма не изображение, и почему бы низкоуровневым признакам не работать на ней:
Если хотите поэкспериментировать с обработкой естественного языка и попробовать transfer learning, то вот подходящая статья ЛеКуна для старта. И да, там тоже текст представляется в виде изображения.
В общем, transfer learning — отличная штука, а caffe — крутая библиотека для глубокого обучения.
Ссылок много в тексте, приведу некоторые из них тут:
- Tongue Vision
- Self-taught Learning: Transfer Learning from Unlabeled Data
- Transfer Learning
- Large Scale Visual Recognition Challenge 2014 (ILSVRC2014)
- electricity for eyes
- Visualizing and Understanding Convolutional Networks
- Stacked Denoising Autoencoders: Learning Useful Representations in a Deep Network with a Local Denoising Criterion
- Semantic Hashing
- Visual Geometry Group
- Very Deep Convolutional Networks for Large-Scale Visual Recognition (model)
- Caffe
- Very Deep Convolutional Networks for Large-Scale Visual Recognition (article)
- Image representations for large-scale visual recognition (A. Vedaldi from VGG)
Автор: mephistopheies