Как получить одинаковые векторы word2vec/doc2vec/абзаца на каждой тренировке

искусственный интеллект GitHub алгоритм Medium

Эта статья переведена с твита, опубликованного автором на медиуме, вотОригинальная ссылка

Эта статьяWord EmbeddingПервый в серии. Эта статья подходит для читателей выше среднего уровня или подготовленныхword2vec/doc2vec/Paragraph Vectorsчитатели, но не волнуйтесь, я расскажу о теории, а также о предыстории в следующем твите, иСвяжитесь с газетой, чтобы объяснить, как реализован кодиз.

Я постараюсь изо всех сил не вести читателей к длинному списку руководств, которые на самом деле не заставляют людей понимать, и, наконец, сдаюсь (поверьте мне, я также являюсь жертвой многих руководств в Интернете). я думаю мы можем быть вместеЧтобы понять word2vec на уровне кода,Итак, мы можем знатьКак спроектировать и реализовать собственное встраивание слов и языковую модель.

Если вы когда-либо обучали вектора слов самостоятельно, вы обнаружите, что, несмотря на обучение на одних и тех же данных, каждый раз при обучении вы получаете другую модель и представление вектора слов. Это происходит из-за случайности, вносимой во время обучения. Давайте узнаем из кода, как вводится эта случайность и как ее убрать. я используюDL4jизParagraph Vectorsизвыполнитьпоказать код. Если вы хотите увидеть реализацию других пакетов, вы можете посмотретьdoc2vec для gensim, который имеет ту же реализацию.

откуда берется случайность

Инициализация весов моделей и векторов слов

Мы знаем, что в начале обучения параметры модели и представление вектора слов будут инициализированы случайным образом, случайность здесь определяетсяseedреализуется контроль. Поэтому, когда мы устанавливаем seed в 0, мы получаем точно такую ​​же инициализацию в каждом обучении.здесьЧтобы увидеть, как начальное значение влияет на инициализацию, syn0 — это вес модели.

// Nd4j 设置有关生成随机数的seed
Nd4j.getRandom().setSeed(configuration.getSeed());
// Nd4j 为 syn0 初始化一个随机矩阵
syn0 = Nd4j.rand(new int[] {vocab.numWords(), vectorLength}, rng).subi(0.5).divi(vectorLength);

Алгоритм PV-DBOW

Если мы обучаем векторы абзацев с помощью алгоритма PV-DBOW, на итерации обучения слова случайным образом берутся из окна, а модель вычисляется и обновляется. Но случайность здесьКодне является действительно случайным.

// nextRandom 是一个 AtomicLong,并被threadId初始化
this.nextRandom = new AtomicLong(this.threadId);

следующийСлучайно черезtrainSequence(sequence, nextRandom, alpha);используется вtrainSequenceсередина,nextRandom.set(nextRandom.get() * 25214903917L + 11);Если мы углубимся в каждый шаг обучения, мы обнаружим, что nextRandom возникает из тех же шагов и методов, т.е. выполнения фиксированных математических операций (дляздесьиздесьпонятно почему)nextRandomзависит отthreadIdчисла, при этомthreadIdравны 0, 1, 2, 3, ... так что здесь у нас больше нет случайности.

Параллельная токенизация

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

if (!allowParallelBuilder) {
    try {
        runnable.awaitDone();
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        throw new RuntimeException(e);
    }
}

Очередь, предоставляющая обучающие данные для каждого потока

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

// 初始化一个 sequencer 来提供数据给每个线程
val sequencer = new AsyncSequencer(this.iterator, this.stopWords);
// 每个线程使用同一个 sequencer
// worker是我们设置的进行训练的线程数
for (int x = 0; x < workers; x++) {
    threads.add(x, new VectorCalculationsThread(x, ..., sequencer);                
    threads.get(x).start();            
}
// 在sequencer中 初始化一个 LinkedBlockingQueue buffer
// 同时保持该buffer的size在[limitLower, limitUpper]
private final LinkedBlockingQueue<Sequence<T>> buffer;
limitLower = workers * batchSize;
limitUpper = workers * batchSize * 2;
// 线程从buffer中读取数据
buffer.poll(3L, TimeUnit.SECONDS);

Итак, если мы будемworkerСтавим 1, то есть используем один поток для обучения, тогда мы будем получать одинаковый порядок данных для каждого обучения. Здесь следует отметить, что если используется один поток, скорость обучения сильно снизится.

Суммировать

Чтобы исключить случайность, нам нужно сделать следующее:

  1. будетseedустановить на 0;
  2. будетallowParallelTokenizationустановить какfalse;
  3. будетworkerУстановите на 1.
Таким образом, при обучении на одних и тех же данных мы получим точно такие же параметры модели и векторное представление.

В конечном итоге наш тренировочный код будет выглядеть так:

ParagraphVectors vec = new ParagraphVectors.Builder()
                .minWordFrequency(1)
                .labels(labelsArray)
                .layerSize(100)
                .stopWords(new ArrayList<String>())
                .windowSize(5)
                .iterate(iter)
                .allowParallelTokenization(false)
                .workers(1)
                .seed(0)
                .tokenizerFactory(t)
                .build();

vec.fit();

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

Ссылаться на

  1. Deeplearning4j, ND4J, DataVec и другие - глубокое обучение и линейная алгебра для Java/Scala с графическими процессорами + Spark - От Skymind http://deeplearning4j.org https://github.com/deeplearning4j/deeplearning4j
  2. Платформа Java™, спецификация API Standard Edition 8 https://docs.oracle.com/javase/8/docs/api/