Сгенерируйте текст с помощью автоэнкодеров

В этом примере показано, как сгенерировать текстовые данные с помощью автоэнкодеров.

Автоэнкодер является типом нейронной сети для глубокого обучения, которая обучена тиражировать свой вход. Автоэнкодер состоит из двух небольших сетей: и энкодера и декодера. Энкодер преобразует входные данные в вектор функции в некотором скрытом пространстве. Декодер восстанавливает данные с помощью векторов в этом скрытом пространстве.

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

Этот пример обучает автоэнкодер генерировать текст. Энкодер использует встраивание слова и операцию LSTM, чтобы сопоставить входной текст с латентными векторами. Декодер использует операцию LSTM и то же встраивание, чтобы восстановить текст из латентных векторов.

Загрузка данных

Файл sonnets.txt содержит все сонеты Шекспира в одном текстовом файле.

Считайте данные Sonnets Шекспира из файла "sonnets.txt".

filename = "sonnets.txt";
textData = fileread(filename);

Сонеты изрезаны двумя пробелами символов. Удалите углубления с помощью replace и разделите текст на отдельные линии, используя split функция. Удалите заголовок из первых девяти элементов и кратких заголовков сонетов.

textData = replace(textData,"  ","");
textData = split(textData,newline);
textData(1:9) = [];
textData(strlength(textData)<5) = [];

Подготовка данных

Создайте функцию, которая токенизирует и предварительно обрабатывает текстовые данные. Функция preprocessText, перечисленный в конце примера, выполняет следующие шаги:

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

  2. Токенизация текста с помощью tokenizedDocument.

Предварительно обработайте текстовые данные и задайте начальные и стоповые лексемы "<start>" и "<stop>", соответственно.

startToken = "<start>";
stopToken = "<stop>";
documents = preprocessText(textData,startToken,stopToken);

Создайте объект кодирования слов из токенизированных документов.

enc = wordEncoding(documents);

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

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

paddingToken = "<pad>";
newVocabulary = [enc.Vocabulary paddingToken];
enc = wordEncoding(newVocabulary);
paddingIdx = word2ind(enc,paddingToken)
paddingIdx = 3595

Инициализация параметров модели

Инициализируйте параметры для следующей модели.

Вот, T - длина последовательности, x1,,xT - вход последовательность словесных индексов, и y1,,yT - восстановленная последовательность.

Энкодер преобразует последовательности индексов слов в латентный вектор путем преобразования входов в последовательности векторов слов с помощью встраивания, ввода векторных последовательностей слов в операцию LSTM и применения полностью связанной операции к последнему временному шагу выхода LSTM. Декодер восстанавливает вход с помощью LSTM, инициализированного выходом энкодера. Для каждого временного шага декодер предсказывает следующий временной шаг и использует выход для следующих временных предсказаний. И энкодер, и декодер используют одно и то же встраивание.

Задайте размерности параметров.

embeddingDimension = 100;
numHiddenUnits = 150;
latentDimension = 75;
vocabularySize = enc.NumWords;

Создайте struct для параметров.

parameters = struct;

Инициализируйте веса встраивания, используя Гауссов метод initializeGaussian функция, которая присоединена к этому примеру как вспомогательный файл. Задайте среднее значение 0 и стандартное отклонение 0,01. Для получения дополнительной информации смотрите Гауссову инициализацию.

mu = 0;
sigma = 0.01;
parameters.emb.Weights = initializeGaussian([embeddingDimension vocabularySize],mu,sigma);

Инициализируйте настраиваемые параметры для операции LSTM энкодера:

  • Инициализируйте входные веса с помощью инициализатора Glorot с помощью initializeGlorot функция, которая присоединена к этому примеру как вспомогательный файл. Дополнительные сведения см. в разделе «Инициализация Glorot».

  • Инициализируйте повторяющиеся веса с помощью ортогонального инициализатора с помощью initializeOrthogonal функция, которая присоединена к этому примеру как вспомогательный файл. Дополнительные сведения см. в разделе Ортогональная инициализация.

  • Инициализируйте смещение с помощью модуля forget gate initializer с помощью initializeUnitForgetGate функция, которая присоединена к этому примеру как вспомогательный файл. Для получения дополнительной информации смотрите Unit Forget Gate Initialization.

sz = [4*numHiddenUnits embeddingDimension];
numOut = 4*numHiddenUnits;
numIn = embeddingDimension;

parameters.lstmEncoder.InputWeights = initializeGlorot(sz,numOut,numIn);
parameters.lstmEncoder.RecurrentWeights = initializeOrthogonal([4*numHiddenUnits numHiddenUnits]);
parameters.lstmEncoder.Bias = initializeUnitForgetGate(numHiddenUnits);

Инициализируйте настраиваемые параметры для полностью подключенной операции энкодера:

  • Инициализируйте веса с помощью инициализатора Glorot.

  • Инициализируйте смещение с нулями, используя initializeZeros функция, которая присоединена к этому примеру как вспомогательный файл. Дополнительные сведения см. в разделе Инициализация нулей.

sz = [latentDimension numHiddenUnits];
numOut = latentDimension;
numIn = numHiddenUnits;

parameters.fcEncoder.Weights = initializeGlorot(sz,numOut,numIn);
parameters.fcEncoder.Bias = initializeZeros([latentDimension 1]);

Инициализируйте настраиваемые параметры для операции LSTM декодера:

  • Инициализируйте входные веса с помощью инициализатора Glorot.

  • Инициализируйте повторяющиеся веса с помощью ортогонального инициализатора.

  • Инициализируйте смещение с помощью модуля забудьте инициализатор затвора.

sz = [4*latentDimension embeddingDimension];
numOut = 4*latentDimension;
numIn = embeddingDimension;

parameters.lstmDecoder.InputWeights = initializeGlorot(sz,numOut,numIn);
parameters.lstmDecoder.RecurrentWeights = initializeOrthogonal([4*latentDimension latentDimension]);
parameters.lstmDecoder.Bias = initializeZeros([4*latentDimension 1]);

Инициализируйте настраиваемые параметры для полностью подключенной операции декодера:

  • Инициализируйте веса с помощью инициализатора Glorot.

  • Инициализируйте смещение с нулями.

sz = [vocabularySize latentDimension];
numOut = vocabularySize;
numIn = latentDimension;

parameters.fcDecoder.Weights = initializeGlorot(sz,numOut,numIn);
parameters.fcDecoder.Bias = initializeZeros([vocabularySize 1]);

Чтобы узнать больше об инициализации веса, смотрите Initialize Learnable Parameters for Model Function.

Задайте функцию энкодера модели

Создайте функцию modelEncoder, перечисленный в разделе Encoder Model Function примера, который вычисляет выход модели энкодера. The modelEncoder function, принимает в качестве входных последовательностей индексы слов, параметры модели и длины последовательности и возвращает соответствующий латентный вектор функции. Чтобы узнать больше об определении функции энкодера модели, смотрите Define Text Encoder Model Function.

Задайте функцию декодера модели

Создайте функцию modelDecoder, перечисленный в разделе Decoder Model Function примера, который вычисляет выход модели декодера. The modelDecoder function, принимает в качестве входных последовательностей индексы слов, параметры модели и длины последовательности и возвращает соответствующий латентный вектор функции. Чтобы узнать больше об определении функции декодера модели, смотрите Define Text Decoder Model Function.

Задайте функцию градиентов модели

The modelGradients функция, перечисленная в разделе Model Gradients Function примера, принимает в качестве входов обучаемые модели параметры, входные данные dlX, и вектор длин последовательностей для маскировки, и возвращает градиенты потерь относительно настраиваемых параметров и соответствующих потерь. Чтобы узнать больше об определении функции градиентов модели, смотрите Задать функцию градиентов модели для Пользовательского цикла обучения.

Настройка опций обучения

Укажите опции обучения.

Train на 100 эпох с мини-партией размером 128.

miniBatchSize = 128;
numEpochs = 100;

Обучите со скоростью обучения 0,01.

learnRate = 0.01;

Отображение процесса обучения на графике.

plots = "training-progress";

Обучите на графическом процессоре, если он доступен. Для использования графический процессор требуется Parallel Computing Toolbox™ и поддерживаемый графический процессор. Для получения информации о поддерживаемых устройствах смотрите Поддержку GPU by Release (Parallel Computing Toolbox).

executionEnvironment = "auto";

Обучите сеть

Обучите сеть с помощью пользовательского цикла обучения.

Инициализируйте параметры для оптимизатора Адама.

trailingAvg = [];
trailingAvgSq = [];

Инициализируйте график процесса обучения. Создайте анимированную линию, которая строит график потерь относительно соответствующей итерации.

if plots == "training-progress"
    figure
    lineLossTrain = animatedline('Color',[0.85 0.325 0.098]);
    xlabel("Iteration")
    ylabel("Loss")
    ylim([0 inf])
    grid on
end

Обучите модель. В первую эпоху перетасуйте данные и закольцовывайте мини-пакеты данных.

Для каждого мини-пакета:

  • Преобразуйте текстовые данные в последовательности индексов слов.

  • Преобразуйте данные в dlarray.

  • Для обучения графический процессор преобразуйте данные в gpuArray объекты.

  • Вычислите потери и градиенты.

  • Обновляйте настраиваемые параметры с помощью adamupdate функция.

  • Обновите график процесса обучения.

Обучение может занять некоторое время.

numObservations = numel(documents);
numIterationsPerEpoch = floor(numObservations / miniBatchSize);

iteration = 0;
start = tic;

for epoch = 1:numEpochs
    
    % Shuffle.
    idx = randperm(numObservations);
    documents = documents(idx);
    
    for i = 1:numIterationsPerEpoch
        iteration = iteration + 1;
        
        % Read mini-batch.
        idx = (i-1)*miniBatchSize+1:i*miniBatchSize;
        documentsBatch = documents(idx);
        
        % Convert to sequences.
        X = doc2sequence(enc,documentsBatch, ...
            'PaddingDirection','right', ...
            'PaddingValue',paddingIdx);
        X = cat(1,X{:});
        
        % Convert to dlarray.
        dlX = dlarray(X,'BTC');
        
        % If training on a GPU, then convert data to gpuArray.
        if (executionEnvironment == "auto" && canUseGPU) || executionEnvironment == "gpu"
            dlX = gpuArray(dlX);
        end
        
        % Calculate sequence lengths.
        sequenceLengths = doclength(documentsBatch);
        
        % Evaluate model gradients.
        [gradients,loss] = dlfeval(@modelGradients, parameters, dlX, sequenceLengths);
        
        % Update learnable parameters.
        [parameters,trailingAvg,trailingAvgSq] = adamupdate(parameters,gradients, ...
            trailingAvg,trailingAvgSq,iteration,learnRate);
        
        % Display the training progress.
        if plots == "training-progress"
            D = duration(0,0,toc(start),'Format','hh:mm:ss');
            addpoints(lineLossTrain,iteration,double(gather(extractdata(loss))))
            title("Epoch: " + epoch + ", Elapsed: " + string(D))
            
            drawnow
        end
    end
end

Сгенерируйте текст

Сгенерируйте текст с помощью генерации замкнутого цикла путем инициализации декодера с различными случайными состояниями. Генерация замкнутого цикла - это когда модель генерирует данные один временной шаг за раз и использует предыдущее предсказание в качестве входных данных для следующего предсказания.

Задайте, чтобы сгенерировать 3 последовательности длины 16.

numGenerations = 3;
sequenceLength = 16;

Создайте массив случайных значений, чтобы инициализировать состояние декодера.

dlZ = dlarray(randn(latentDimension,numGenerations),'CB');

Если предсказание на графическом процессоре, преобразуйте данные в gpuArray.

if (executionEnvironment == "auto" && canUseGPU) || executionEnvironment == "gpu"
    dlZ = gpuArray(dlZ);
end

Делайте предсказания, используя modelPredictions функции, перечисленной в конце примера. The modelPredictions функция возвращает выход счетов декодера с учетом параметров модели, начального состояния декодера, максимальной длины последовательности, кодирования слов, начальных лексем и размера мини-пакета.

dlY = modelDecoderPredictions(parameters,dlZ,sequenceLength,enc,startToken,miniBatchSize);

Найдите индексы слов с самыми высокими счетами.

[~,idx] = max(dlY,[],1);
idx = squeeze(idx);

Преобразуйте числовые индексы в слова и присоединяйтесь к ним с помощью join функция.

strGenerated = join(enc.Vocabulary(idx));

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

strGenerated = extractBefore(strGenerated+stopToken,stopToken);

Удалите лексемы заполнения.

strGenerated = erase(strGenerated,paddingToken);

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

Удалите пространства, которые появляются перед указанными символами пунктуации.

punctuationCharacters = ["." "," "’" ")" ":" ";" "?" "!"];
strGenerated = replace(strGenerated," " + punctuationCharacters,punctuationCharacters);

Удалите пространства, которые появляются после заданных символов пунктуации.

punctuationCharacters = ["(" "‘"];
strGenerated = replace(strGenerated,punctuationCharacters + " ",punctuationCharacters);

Удалите начальное и конечное пустое пространство с помощью strip функция и просмотр сгенерированного текста.

strGenerated = strip(strGenerated)
strGenerated = 3×1 string
    "love's thou rest light best ill mistake show seeing farther cross enough by me"
    "as before his bending sickle's compass come look find."
    "summer's lays? truth once lead mayst take,"

Функция модели энкодера

The modelEncoder function, принимает за вход параметры модели, последовательности словесных индексов и длины последовательности и возвращает соответствующий латентный вектор функции.

Поскольку входные данные содержат заполненные последовательности различной длины, заполнение может иметь неблагоприятные эффекты для вычислений потерь. Для операции LSTM, вместо возврата выхода последнего временного шага последовательности (который, вероятно, соответствует состоянию LSTM после обработки партий значений заполнения), определите фактический последний временной шаг, заданный sequenceLengths вход.

function dlZ = modelEncoder(parameters,dlX,sequenceLengths)

% Embedding.
weights = parameters.emb.Weights;
dlZ = embedding(dlX,weights);

% LSTM.
inputWeights = parameters.lstmEncoder.InputWeights;
recurrentWeights = parameters.lstmEncoder.RecurrentWeights;
bias = parameters.lstmEncoder.Bias;

numHiddenUnits = size(recurrentWeights,2);
hiddenState = zeros(numHiddenUnits,1,'like',dlX);
cellState = zeros(numHiddenUnits,1,'like',dlX);

dlZ1 = lstm(dlZ,hiddenState,cellState,inputWeights,recurrentWeights,bias,'DataFormat','CBT');

% Output mode 'last' with masking.
miniBatchSize = size(dlZ1,2);
dlZ = zeros(numHiddenUnits,miniBatchSize,'like',dlZ1);

for n = 1:miniBatchSize
    t = sequenceLengths(n);
    dlZ(:,n) = dlZ1(:,n,t);
end

% Fully connect.
weights = parameters.fcEncoder.Weights;
bias = parameters.fcEncoder.Bias;
dlZ = fullyconnect(dlZ,weights,bias,'DataFormat','CB');

end

Функция модели декодера

The modelDecoder функция, принимает за вход параметры модели, последовательности индексов слов и состояние сети и возвращает декодированные последовательности.

Потому что lstm функция является статической (когда заданы временные ряды как вход, функция распространяет и обновляет состояние между каждым временным шагом) и что embedding и fullyconnect функции распределены по умолчанию по времени (когда заданы временные ряды как вход, функции работают с каждым временным шагом независимо), modelDecoder функция поддерживает как последовательность, так и один временной шаг входов.

function [dlY,state] = modelDecoder(parameters,dlX,state)

% Embedding.
weights = parameters.emb.Weights;
dlX = embedding(dlX,weights);

% LSTM.
inputWeights = parameters.lstmDecoder.InputWeights;
recurrentWeights = parameters.lstmDecoder.RecurrentWeights;
bias = parameters.lstmDecoder.Bias;

hiddenState = state.HiddenState;
cellState = state.CellState;

[dlY,hiddenState,cellState] = lstm(dlX,hiddenState,cellState, ...
    inputWeights,recurrentWeights,bias,'DataFormat','CBT');

state.HiddenState = hiddenState;
state.CellState = cellState;

% Fully connect.
weights = parameters.fcDecoder.Weights;
bias = parameters.fcDecoder.Bias;
dlY = fullyconnect(dlY,weights,bias,'DataFormat','CBT');

% Softmax.
dlY = softmax(dlY,'DataFormat','CBT');

end

Функция градиентов модели

The modelGradients функция, которая принимает в качестве входов настраиваемые параметры модели, входные данные dlX, и вектор длин последовательностей для маскировки, и возвращает градиенты потерь относительно настраиваемых параметров и соответствующих потерь.

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

Чтобы узнать больше об определении функции градиентов модели, смотрите Задать функцию градиентов модели для Пользовательского цикла обучения.

function [gradients, loss] = modelGradients(parameters,dlX,sequenceLengths)

% Model encoder.
dlZ = modelEncoder(parameters,dlX,sequenceLengths);

% Initialize LSTM state.
state = struct;
state.HiddenState = dlZ;
state.CellState = zeros(size(dlZ),'like',dlZ);

% Teacher forcing.
dlY = modelDecoder(parameters,dlX,state);

% Loss.
dlYPred = dlY(:,:,1:end-1);
dlT = dlX(:,:,2:end);
loss = mean(maskedCrossEntropy(dlYPred,dlT,sequenceLengths));

% Gradients.
gradients = dlgradient(loss,parameters);

% Normalize loss for plotting.
sequenceLength = size(dlX,3);
loss = loss / sequenceLength;

end

Функция предсказаний модели

The modelPredictions функция возвращает выход счетов декодера с учетом параметров модели, начального состояния декодера, максимальной длины последовательности, кодирования слов, начальных лексем и размера мини-пакета.

function dlY = modelDecoderPredictions(parameters,dlZ,maxLength,enc,startToken,miniBatchSize)

numObservations = size(dlZ,2);
numIterations = ceil(numObservations / miniBatchSize);

startTokenIdx = word2ind(enc,startToken);
vocabularySize = enc.NumWords;

dlY = zeros(vocabularySize,numObservations,maxLength,'like',dlZ);

% Loop over mini-batches.
for i = 1:numIterations
    idxMiniBatch = (i-1)*miniBatchSize+1:min(i*miniBatchSize,numObservations);
    miniBatchSize = numel(idxMiniBatch);
    
    % Initialize state.
    state = struct;
    state.HiddenState = dlZ(:,idxMiniBatch);
    state.CellState = zeros(size(dlZ(:,idxMiniBatch)),'like',dlZ);
    
    % Initialize decoder input.
    decoderInput = dlarray(repmat(startTokenIdx,[1 miniBatchSize]),'CBT');
    
    % Loop over time steps.
    for t = 1:maxLength
        % Predict next time step.
        [dlY(:,idxMiniBatch,t), state] = modelDecoder(parameters,decoderInput,state);
        
        % Closed loop generation.
        [~,idx] = max(dlY(:,idxMiniBatch,t));
        decoderInput = idx;
    end
end

end

Функция маскированной Потери перекрестной энтропии

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

function maskedLoss = maskedCrossEntropy(dlY,T,sequenceLengths)

numClasses = size(dlY,1);
miniBatchSize = size(dlY,2);
sequenceLength = size(dlY,3);

maskedLoss = zeros(sequenceLength,miniBatchSize,'like',dlY);

for t = 1:sequenceLength
    T1 = single(oneHot(T(:,:,t),numClasses));
    
    mask = (t<=sequenceLengths)';
 
    maskedLoss(t,:) = mask .* crossentropy(dlY(:,:,t),T1,'DataFormat','CBT');
end

maskedLoss = sum(maskedLoss,1);

end

Функция предварительной обработки текста

Функция preprocessText выполняет следующие шаги:

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

  2. Токенизация текста с помощью tokenizedDocument.

function documents = preprocessText(textData,startToken,stopToken)

% Add start and stop tokens.
textData = startToken + textData + stopToken;

% Tokenize the text.
documents = tokenizedDocument(textData,'CustomTokens',[startToken stopToken]);

end

Функция встраивания

The embedding функция преобразует последовательности индексов в векторы с помощью заданных весов.

function Z = embedding(X, weights)

% Reshape inputs into a vector.
[N, T] = size(X, 2:3);
X = reshape(X, N*T, 1);

% Index into embedding matrix.
Z = weights(:, X);

% Reshape outputs by separating out batch and sequence dimensions.
Z = reshape(Z, [], N, T);

end

Функция одноразового кодирования

The oneHot функция преобразует массив числовых индексов в векторы с одним горячим кодированием.

function oh = oneHot(idx, outputSize)

miniBatchSize = numel(idx);
oh = zeros(outputSize,miniBatchSize);

for n = 1:miniBatchSize
    c = idx(n);
    oh(c,n) = 1;
end

end

См. также

| |

Похожие темы

Для просмотра документации необходимо авторизоваться на сайте