Main Content

このページの翻訳は最新ではありません。ここをクリックして、英語の最新版を参照してください。

自己符号化器を使用したテキストの生成

この例では、自己符号化器を使用してテキスト データを生成する方法を示します。

自己符号化器は、入力を複製するように学習させられた深層学習ネットワークの一種です。自己符号化器は、小規模な 2 つのネットワーク (符号化器および復号化器) で構成されます。符号化器は、入力データを何らかの潜在空間における特徴ベクトルにマッピングします。復号化器は、この潜在空間におけるベクトルを使用してデータを再構成します。

学習プロセスは教師なし学習となります。つまり、このモデルはラベル付けされたデータを必要としません。テキストを生成するために、復号化器を使用し、任意の入力からテキストを再構成することができます。

この例では、テキストを生成するように自己符号化器の学習を行います。符号化器は、単語埋め込みと LSTM 演算を使用し、入力テキストを潜在ベクトルにマッピングします。復号化器は、LSTM 演算および同じ埋め込みを使用し、潜在ベクトルからテキストを再構成します。

データの読み込み

ファイルsonnets.txtには、シェイクスピアのソネット全集が 1 つのテキスト ファイルとして格納されています。

ファイル"sonnets.txt"からシェイクスピアのソネットのデータを読み取ります。

文件名="sonnets.txt"; textData = fileread(filename);

ソネットは、2 つの空白文字でインデントされています。replaceを使用してインデントを削除し、関数splitを使用してテキストを個別の行に分割します。最初の 9 つの要素と短いソネット タイトルからヘッダーを削除します。

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

データの準備

テキスト データをトークン化および前処理する関数を作成します。例の最後にリストされている関数preprocessTextは以下のステップを実行します。

  1. 各入力ストリングの前後に、指定された開始トークンと停止トークンをそれぞれ付加する。

  2. tokenizedDocumentを使用してテキストをトークン化する。

テキストデータを前処理し,開始トークン""と停止トークン""をそれぞれ指定します。

startToken =""; stopToken =""; documents = preprocessText(textData,startToken,stopToken);

トークン化されたドキュメントから単語符号化オブジェクトを作成します。

enc = wordEncoding(documents);

深層学習モデルの学習を行う際には、入力データは固定長のシーケンスを含む数値配列でなければなりません。ドキュメントの長さは異なるため、短いシーケンスはパディング値でパディングしなければなりません。

パディング トークンを含み、そのトークンのインデックスも決定するように、単語符号化を再作成します。

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

モデル パラメーターの初期化

次のモデルのパラメーターを初期化します。

ここで、 T はシーケンス長、 x 1 , , x T は単語インデックスの入力シーケンス、 y 1 , , y T は再構成されたシーケンスです。

符号化器は、単語インデックスのシーケンスを潜在ベクトルにマッピングします。このマッピングは、埋め込みによって入力を単語ベクトルのシーケンスに変換し、単語ベクトル シーケンスを LSTM 演算に入力し、全結合演算を LSTM 出力の最後のタイム ステップに適用することによって行われます。復号化器は、符号化器の出力を初期化した LSTM を使用して入力を再構成します。タイム ステップごとに、復号化器は次のタイム ステップを予測し、次のタイム ステップ予測の出力を使用します。符号化器と復号化器の両方で同じ埋め込みを使用します。

パラメーターの次元を指定します。

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

パラメーターの構造体を作成します。

parameters = struct;

関数initializeGaussianを使用し、ガウスで埋め込みの重みを初期化します。この関数は、この例にサポート ファイルとして添付されています。平均値を 0、標準偏差を 0.01 に指定します。詳細については、ガウスによる初期化を参照してください。

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

符号化器の LSTM 演算に関する学習可能なパラメーターを初期化します。

  • 関数initializeGlorotを使用し、Glorot 初期化子で入力の重みを初期化します。この関数は、この例にサポート ファイルとして添付されています。詳細については、Glorot の初期化を参照してください。

  • 関数initializeOrthogonalを使用し、直交初期化子で再帰重みを初期化します。この関数は、この例にサポート ファイルとして添付されています。詳細については、直交初期化を参照してください。

  • 関数initializeUnitForgetGateを使用し、ユニット忘却ゲート初期化子でバイアスを初期化します。この関数は、この例にサポート ファイルとして添付されています。詳細については、ユニット忘却ゲートによる初期化を参照してください。

深圳= [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]);

重みの初期化の詳細については,モデル関数の学習可能パラメーターの初期化を参照してください。

モデル符号化器関数の定義

この例の符号化器モデル関数の節にリストされている関数modelEncoderを作成し、符号化器モデルの出力を計算します。関数modelEncoderは、モデル パラメーターおよびシーケンス長を単語インデックスの入力シーケンスとして受け取り、対応する潜在特徴ベクトルを返します。モデル符号化器関数の定義の詳細については、テキスト符号化器モデル関数の定義を参照してください。

モデル復号化器関数の定義

この例の復号化器モデル関数の節にリストされている関数modelDecoderを作成し、復号化器モデルの出力を計算します。関数modelDecoderは、モデル パラメーターおよびシーケンス長を単語インデックスの入力シーケンスとして受け取り、対応する潜在特徴ベクトルを返します。モデル復号化器関数の定義の詳細については、テキスト復号化器モデル関数の定義を参照してください。

モデル勾配関数の定義

この例のモデル勾配関数の節にリストされている関数modelGradientsを作成します。この関数は、モデルの学習可能なパラメーター、入力データdlX、マスク用のシーケンス長のベクトルを入力として受け取り、学習可能なパラメーターについての損失の勾配、および対応する損失を返します。モデル勾配関数の定義の詳細については、カスタム学習ループのモデル勾配関数の定義を参照してください。

学習オプションの指定

学習用のオプションを指定します。

ミニバッチ サイズを 128 として 100 エポック学習させます。

miniBatchSize = 128; numEpochs = 100;

学習率を 0.01 にして学習を行います。

learnRate = 0.01;

学習の進行状況をプロットに表示。

plots ="training-progress";

GPU が利用できる場合、GPU で学習を行います。GPU を使用するには、Parallel Computing Toolbox™ とサポートされている GPU デバイスが必要です。サポートされているデバイスについては、リリース別の GPU サポート(Parallel Computing Toolbox)を参照してください。

executionEnvironment ="auto";

ネットワークの学習

カスタム学習ループを使用してネットワークに学習させます。

Adam オプティマイザーのパラメーターを初期化します。

trailingAvg = []; trailingAvgSq = [];

学習の進行状況プロットを初期化します。対応する反復に対する損失をプロットする、アニメーションの線を作成します。

ifplots =="training-progress"figure lineLossTrain = animatedline('Color',[0.85 0.325 0.098]); xlabel("Iteration") ylabel("Loss") ylim([0 inf]) gridonend

モデルに学習させます。最初のエポックについて、データをシャッフルしてデータのミニバッチをループ処理します。

各ミニバッチで次を行います。

  • テキスト データを単語インデックスのシーケンスに変換します。

  • データをdlarrayに変換。

  • GPU での学習のために、データをgpuArrayオブジェクトに変換します。

  • 損失と勾配を計算。

  • 関数adamupdateを使用して学習可能なパラメーターを更新します。

  • 学習の進行状況プロットを更新します。

学習を行うのに時間がかかる場合があります。

numObservations = numel(documents); numIterationsPerEpoch = floor(numObservations / miniBatchSize); iteration = 0; start = tic;forepoch = 1:numEpochs% Shuffle.idx = randperm(numObservations); documents = documents(idx);fori = 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.ifplots =="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)) drawnowendendend

テキストの生成

ランダムな異なる状態で復号化器を初期化することにより、閉ループ生成を使用してテキストを生成します。閉ループ生成では、モデルが一度に 1 タイム ステップずつデータを生成し、前の予測を次の予測の入力として使用します。

長さ 16 のシーケンスを 3 つ生成するように指定します。

numGenerations = 3; sequenceLength = 16;

乱数値の配列を作成して復号化器の状態を初期化します。

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

GPU で予測する場合、データをgpuArrayに変換します。

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

例の最後にリストされている関数modelPredictionsを使用して予測を行います。関数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“爱你最好休息光生病的错误显示看到。正在ng farther cross enough by me" "as before his bending sickle's compass come look find." "summer's lays? truth once lead mayst take,"

符号化器モデル関数

関数modelEncoderは、モデル パラメーター、単語インデックスのシーケンス、シーケンス長を入力として受け取り、対応する潜在特徴ベクトルを返します。

入力データには長さの異なるパディング済みシーケンスが含まれるため、パディングによって損失計算に悪影響の及ぶ可能性があります。LSTM 演算について、シーケンスの最後のタイム ステップの出力 (多数のパディング値を処理した後の LSTM 状態に相当する可能性が高い) を返す代わりに、sequenceLengths入力によって与えられた実際の最後のタイム ステップを決定します。

functiondlZ = 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);forn = 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

復号化器モデル関数

関数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

モデル勾配関数

関数modelGradientsは、モデルの学習可能なパラメーター、入力データdlX、マスク用のシーケンス長のベクトルを入力として受け取り、学習可能なパラメーターについての損失の勾配、および対応する損失を返します。

マスクされた損失を計算するために、モデル勾配関数は、例の最後にリストされている損失関数maskedCrossEntropyを使用します。次のタイム ステップを予測する復号化器に学習させるには、1 タイム ステップ分シフトした入力シーケンスになるようにターゲットを指定します。

モデル勾配関数の定義の詳細については、カスタム学習ループのモデル勾配関数の定義を参照してください。

function[gradients, loss] = modelGradients(parameters,dlX,sequenceLengths)% Model encoder.dlZ = modelEncoder(parameters,dlX,sequenceLengths);% Initialize LSTM 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

モデル予測関数

関数modelPredictionsは、モデル パラメーター、復号化器の初期状態、最大シーケンス長、単語符号化、開始トークン、ミニバッチ サイズを所与として、復号化器の出力スコアを返します。

functiondlY = 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.fori = 1:numIterations idxMiniBatch = (i-1)*miniBatchSize+1:min(i*miniBatchSize,numObservations); miniBatchSize = numel(idxMiniBatch);% Initialize 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.fort = 1:maxLength% Predict next time step.[dlY(:,idxMiniBatch,t), state] = modelDecoder(parameters,decoderInput,state);% Closed loop generation.[~,idx] = max(dlY(:,idxMiniBatch,t)); decoderInput = idx;endendend

マスクされた交差エントロピー損失関数

関数maskedCrossEntropyはシーケンス長の指定されたベクトルを使用して、指定された入力シーケンスとターゲット シーケンスの間の損失を、パディングを含むタイム ステップを無視して計算します。

functionmaskedLoss = maskedCrossEntropy(dlY,T,sequenceLengths) numClasses = size(dlY,1); miniBatchSize = size(dlY,2); sequenceLength = size(dlY,3); maskedLoss = zeros(sequenceLength,miniBatchSize,'like'dlY);fort = 1:sequenceLength T1 = single(oneHot(T(:,:,t),numClasses)); mask = (t<=sequenceLengths)'; maskedLoss(t,:) = mask .* crossentropy(dlY(:,:,t),T1,'DataFormat','CBT');endmaskedLoss = sum(maskedLoss,1);end

テキスト前処理関数

関数preprocessTextは以下のステップを実行します。

  1. 各入力ストリングの前後に、指定された開始トークンと停止トークンをそれぞれ付加する。

  2. tokenizedDocumentを使用してテキストをトークン化する。

functiondocuments = preprocessText(textData,startToken,stopToken)% Add start and stop tokens.textData = startToken + textData + stopToken;% Tokenize the text.documents = tokenizedDocument(textData,'CustomTokens',[startToken stopToken]);end

埋め込み関数

関数embeddingは、与えられた重みを使用し、インデックスのシーケンスをベクトルにマッピングします。

functionZ = 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

one-hot 符号化関数

関数oneHotは、数値インデックスの配列を one-hot 符号化されたベクトルに変換します。

functionoh = oneHot(idx, outputSize) miniBatchSize = numel(idx); oh = zeros(outputSize,miniBatchSize);forn = 1:miniBatchSize c = idx(n); oh(c,n) = 1;endend

参考

||

関連するトピック