Main Content

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

1 次元畳み込みを使用した sequence-to-sequence 分類

この例では、一般的な時間的畳み込みネットワーク (TCN) を使用してシーケンス データの各タイム ステップを分類する方法を説明します。

sequence-to-sequence のタスクは一般的に再帰型ニューラル ネットワーク アーキテクチャを使って解きますが、典型的なシーケンス モデリングのタスクにおいては畳み込みニューラル ネットワークでも再帰型ネットワークと同等あるいはそれ以上の性能を発揮できることが Bai et al. [1] によって示されています。畳み込みネットワークを使用する潜在的な利点は、並列処理に優れること、受容野のサイズの制御に優れること、学習中のネットワークのメモリ フットプリントの制御に優れること、および勾配が安定していることです。再帰型ネットワークと同様に、畳み込みネットワークは可変長の入力シーケンスに対する操作が可能で、sequence-to-sequence や sequence-to-one のタスクのモデル化に使用できます。

この例では、体に装着したスマートフォンから得られたセンサー データを使用して、時間的畳み込みネットワークに学習させます。学習は、カスタム学習ループと自動微分を使用して、3 つの異なる方向における加速度計の測定値を表す時系列データを受け取って、装着者の行動を認識する関数として行います。

学習データの読み込み

行動認識データを読み込みます。このデータには、体に装着したスマートフォンから得られたセンサー データの 7 つの時系列が含まれています。各シーケンスには 3 つの特徴があり、長さはさまざまです。3 つの特徴は、3 つの異なる方向での加速度計の測定値に対応します。6 つのシーケンスを学習に使用し、1 つのシーケンスを学習後のテストに使用します。

s = load("HumanActivityTrain.mat");

dsXTrain = arrayDatastore(s.XTrain,'OutputType','same');
dsYTrain = arrayDatastore(s.YTrain,'OutputType','same');

dsTrain = combine(dsXTrain,dsYTrain);

学習データ内の観測値の数とクラスの数を決定します。

numObservations = numel(s.XTrain);
classes = categories(s.YTrain{1});
numClasses = numel(classes);

1 つの学習シーケンスをプロットで可視化します。最初の学習シーケンスの最初の特徴をプロットし、対応する行動に応じてプロットに色を付けます。

X = s.XTrain{1}(1,:);

figure
for j = 1:numel(classes)
    label = classes(j);
    idx = find(s.YTrain{1} == label);
    hold on
    plot(idx,X(idx))
end
hold off

xlabel("Time Step")
ylabel("Acceleration")
title("Training Sequence 1, Feature 1")
legend(classes,'Location','northwest')

深層学習モデルの定義

時間的畳み込みネットワークの主な基本構成は、膨張因果的畳み込み層です。この層は各シーケンスのタイム ステップ全体にわたり操作を行います。このコンテキストにおける "因果的" とは、特定のタイム ステップに対して計算された活性化が、未来のタイム ステップからの活性化に依存できないことを意味します。

前のタイム ステップからコンテキストを構築するために、通常は複数の畳み込み層が重なり合っています。大きなサイズの受容野を得るには、下図に示すように、後続の畳み込み層の膨張係数を指数的に増加させます。k 番目の畳み込み層の膨張係数が 2(k-1)、ストライドが 1 と仮定すると、そのようなネットワークの受容野のサイズは R=(f-1)(2K-1)+1 で計算できます。ここで、f はフィルター サイズ、K は畳み込み層の数です。フィルター サイズと層の数を変更することで、使用するデータやタスクの必要に合わせて、受容野のサイズや学習可能なパラメーターの数を簡単に調整できます。

RNN と比較した場合の TCN の欠点の 1 つは、推論中のメモリ フットプリントが大きいことです。次のタイム ステップの計算には生のシーケンス全体が必要です。推論時間とメモリ消費量を削減するために、特にステップ先行の予測では、受容野の最小の妥当なサイズ R で学習させ、入力シーケンスの最後の R タイム ステップのみで予測を実行すると有益な場合があります。

一般的な TCN アーキテクチャ ([1] を参照) は、複数の残差ブロックで構成されています。それぞれのブロックには、同じ膨張係数をもつ膨張因果畳み込み層の 2 つのセットが含まれ、その後に正規化層、ReLU 活性化層、および空間ドロップアウト層が続きます。各ブロックの入力はブロックの出力に追加され (入力と出力のチャネル数が一致しない場合は入力の 1 x 1 の畳み込みも含む)、最終的な活性化関数が適用されます。

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

TCN アーキテクチャのパラメーターを指定します。ここで、膨張因果的畳み込み層を含む残差ブロックの数を 4、それぞれのフィルターの数を 175、フィルター サイズを 3 にします。

numBlocks = 4;
numFilters = 175;
filterSize = 3;
dropoutFactor = 0.05;

モデルのハイパーパラメーター (ブロック数とドロップアウト係数) をモデル関数に渡すために、これらの値を含む struct を作成します。

hyperparameters = struct;
hyperparameters.NumBlocks = numBlocks;
hyperparameters.DropoutFactor = dropoutFactor;

入力チャネル数とモデル アーキテクチャを定義するハイパーパラメーターに基づいて、モデルの学習可能な全パラメーターの dlarray オブジェクトを含む struct を作成します。各残差ブロックは、2 つの畳み込み演算のそれぞれに重みパラメーターとバイアス パラメーターを必要とします。最初の残差ブロックは通常、フィルター サイズが 1 の追加の畳み込み演算のための重みとバイアスも必要とします。最後の全結合演算でも、重みとバイアスのパラメーターが必要です。この例の最後にリストされている関数 initializeGaussian を使用して、学習可能な層の重みを初期化します。学習可能な層のバイアスを 0 で初期化します。

numInputChannels = 3;

parameters = struct;
numChannels = numInputChannels;

for k = 1:numBlocks
    parametersBlock = struct;
    blockName = "Block"+k;
    
    weights = initializeGaussian([filterSize, numChannels, numFilters]);
    bias = zeros(numFilters, 1, 'single');
    parametersBlock.Conv1.Weights = dlarray(weights);
    parametersBlock.Conv1.Bias = dlarray(bias);
    
    weights = initializeGaussian([filterSize, numFilters, numFilters]);
    bias = zeros(numFilters, 1, 'single');
    parametersBlock.Conv2.Weights = dlarray(weights);
    parametersBlock.Conv2.Bias = dlarray(bias);
    
    % If the input and output of the block have different numbers of
    % channels, then add a convolution with filter size 1.
    if numChannels ~= numFilters
        weights = initializeGaussian([1, numChannels, numFilters]);
        bias = zeros(numFilters, 1, 'single');
        parametersBlock.Conv3.Weights = dlarray(weights);
        parametersBlock.Conv3.Bias = dlarray(bias);
    end
    numChannels = numFilters;
    
    parameters.(blockName) = parametersBlock;
end

weights = initializeGaussian([numClasses,numChannels]);
bias = zeros(numClasses,1,'single');

parameters.FC.Weights = dlarray(weights);
parameters.FC.Bias = dlarray(bias);

ネットワーク パラメーターを表示します。

parameters
parameters = struct with fields:
    Block1: [1×1 struct]
    Block2: [1×1 struct]
    Block3: [1×1 struct]
    Block4: [1×1 struct]
        FC: [1×1 struct]

最初のブロックのパラメーターを表示します。

parameters.Block1
ans = struct with fields:
    Conv1: [1×1 struct]
    Conv2: [1×1 struct]
    Conv3: [1×1 struct]

最初のブロックの最初の畳み込み演算のパラメーターを表示します。

parameters.Block1.Conv1
ans = struct with fields:
    Weights: [3×3×175 dlarray]
       Bias: [175×1 dlarray]

モデルとモデル勾配関数の定義

この例の最後のモデル関数の節にリストされている関数 model を作成し、深層学習モデルの出力を計算します。関数 model は、入力データ、学習可能なモデル パラメーター、モデルのハイパーパラメーター、およびモデルで学習用と予測用のどちらの出力を返すかを指定するフラグを受け取ります。ネットワークは、入力シーケンスの各タイム ステップでラベルの予測を出力します。

この例の最後のモデル勾配関数の節にリストされている関数 modelGradients を作成します。関数は入力データのミニバッチ、対応するターゲット シーケンス、およびネットワークのパラメーターを受け取り、学習可能なパラメーターについての損失の勾配と、対応する損失を返します。

学習オプションの指定

カスタム学習ループで使用する一連の学習オプションを指定します。

  • ミニバッチ サイズ 1 で 30 エポック学習。

  • 初期学習率 0.001 で開始。

  • 12 エポックごとに学習率に 0.1 を乗算。

  • L2 ノルムをしきい値 1 で使用して勾配をクリップ。

maxEpochs = 30;
miniBatchSize = 1;
initialLearnRate = 0.001;
learnRateDropFactor = 0.1;
learnRateDropPeriod = 12;
gradientThreshold = 1;

学習の進行状況を監視するには、それぞれの反復の後で学習の損失をプロットできます。"training-progress" を含む変数 plots を作成します。学習の進行状況をプロットしない場合は、この値を "none" に設定します。

plots = "training-progress";

モデルの学習

学習データセットのシーケンス全体をループ処理し、パラメーターの勾配を計算し、ネットワーク パラメーターを Adam の更新ルールで更新することにより、確率的勾配降下法を使ってネットワークに学習させます。このプロセスは、学習が収束してエポックの最大数に到達するまで複数回繰り返し ("エポック" と呼ばれます) 行われます。

minibatchqueueを使用し、学習中にイメージのミニバッチを処理および管理します。各ミニバッチで次を行います。

  • カスタム ミニバッチ前処理関数 preprocessMiniBatch (この例の最後に定義) を使用して、シーケンス データを前処理し、タイム ステップ数を決定し、クラス ラベルを one-hot 符号化します。この関数には 3 つの出力があるため、minibatchqueue について 3 つの出力変数を指定します。

  • 基となる型が single の書式化されていない dlarray オブジェクトにデータを変換します。

  • GPU が利用できる場合、GPU で学習を行います。既定では、minibatchqueue オブジェクトは、GPU が利用可能な場合、各出力を gpuArray に変換します。GPU を使用するには、Parallel Computing Toolbox™、および Compute Capability 3.0 以上の CUDA® 対応 NVIDIA® GPU が必要です。

mbq = minibatchqueue(dsTrain,3,...
    'MiniBatchSize',miniBatchSize,...
    'MiniBatchFcn', @preprocessMiniBatch);

各エポックについて、学習データをシャッフルします。各ミニバッチで次を行います。

  • 関数 dlfeval および modelGradients を使用してモデルの勾配と損失を評価。

  • 勾配が大きすぎる場合は、この例の最後にリストされている関数 thresholdL2Norm および関数 dlupdate を使用してクリップ。

  • 関数 adamupdate を使用してネットワーク パラメーターを更新。

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

learnRateDropPeriod エポックを完了したら、現在の学習率に learnRateDropFactor を乗算して学習率を減らします。

LearnRateDropPeriod エポックごとに LearnRateDropFactor の値で乗算して学習率を初期化します。

learnRate = initialLearnRate;

Adam オプティマイザーで使用されるパラメーターの勾配と要素単位の勾配の 2 乗の移動平均を初期化します。

trailingAvg = [];
trailingAvgSq = [];

学習の進行状況を表示するプロットを初期化します。

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

モデルに学習させます。

iteration = 0;

start = tic;

% Loop over epochs.
for epoch = 1:maxEpochs
    
    % Shuffle the data.
    shuffle(mbq)
    
    % Loop over mini-batches.
    while hasdata(mbq)

        iteration = iteration + 1;
        
        [dlX,dlY,numTimeSteps] = next(mbq);
        
        % Evaluate the model gradients and loss using dlfeval.
        [gradients, loss] = dlfeval(@modelGradients,dlX,dlY,parameters,hyperparameters,numTimeSteps);
        
        % Clip the gradients.
        gradients = dlupdate(@(g) thresholdL2Norm(g,gradientThreshold),gradients);
        
        % Update the network parameters using the Adam optimizer.
        [parameters,trailingAvg,trailingAvgSq] = adamupdate(parameters,gradients, ...
            trailingAvg, trailingAvgSq, iteration, learnRate);
        
        if plots == "training-progress"
            % Plot training progress.
            D = duration(0,0,toc(start),'Format','hh:mm:ss');
            
            % Normalize the loss over the sequence lengths            
            loss = mean(loss ./ numTimeSteps);
            loss = double(gather(extractdata(loss)));
            loss = mean(loss);
            
            addpoints(lineLossTrain,iteration, mean(loss));

            title("Epoch: " + epoch + ", Elapsed: " + string(D))
            drawnow
        end
    end
    
    % Reduce the learning rate after learnRateDropPeriod epochs
    if mod(epoch,learnRateDropPeriod) == 0
        learnRate = learnRate*learnRateDropFactor;
    end
end

モデルのテスト

各タイム ステップの真のラベルをもつホールド アウトされたテスト セットの予測を比較して、モデルの分類精度をテストします。学習データと同じ設定の minibatchqueue オブジェクトを使用し、テスト データ セットを管理します。

s = load("HumanActivityTest.mat");

dsXTest = arrayDatastore(s.XTest,'OutputType','same');
dsYTest = arrayDatastore(s.YTest,'OutputType','same');

dsTest = combine(dsXTest,dsYTest);

mbqTest = minibatchqueue(dsTest,3,...
    'MiniBatchSize',miniBatchSize,...
    'MiniBatchFcn', @preprocessMiniBatch);

テスト データのラベルを予測するために、学習済みのパラメーター、ハイパーパラメーター、および false に設定した doTraining オプションを指定したモデル関数を使用します。関数 onehotdecode を使用し、スコアが最も高い予測されたクラスを見つけ、その予測を真のラベルと比較します。平均分類精度を評価します。

doTraining = false;

predictions = [];
predCorr = [];

while hasdata(mbqTest)
    [dlXTest,dlYTest] = next(mbqTest);
    
    dlYPred = model(dlXTest,parameters,hyperparameters,doTraining);
    dlYPred = softmax(dlYPred,'DataFormat','CBT');    
    
    YPred = onehotdecode(dlYPred,classes,1);
    YTest = onehotdecode(dlYTest,classes,1);
    
    predictions = [predictions YPred];
    predCorr = [predCorr YPred == YTest];   
    
end

mean(predCorr)
ans = 0.9996

プロットを使用して、単一シーケンスの予測を対応するテスト データと比較します。

figure
idx = 1;
plot(squeeze(predictions),'.-')
hold on
plot(squeeze(YTest))
hold off

xlabel("Time Step")
ylabel("Activity")
title("Predicted Activities")
legend(["Predicted" "Test Data"])

モデル関数

関数 model は入力データ dlX、学習可能なモデル パラメーター、モデルのハイパーパラメーター、およびモデルの出力として学習と予想のどちらを返すかを指定するフラグ doTraining を受け取ります。ネットワークは、入力シーケンスの各タイム ステップでラベルの予測を出力します。モデルは、膨張係数が指数的に増加する複数の残差ブロックで構成されます。最後の残差ブロックの後に、最終の fullyconnect 演算を行って、出力をターゲット データにあるクラスの数にマッピングします。

function dlY = model(dlX,parameters,hyperparameters,doTraining)

numBlocks = hyperparameters.NumBlocks;
dropoutFactor = hyperparameters.DropoutFactor;

dlY = dlX;

% Residual blocks.
for k = 1:numBlocks
    dilationFactor = 2^(k-1);
    parametersBlock = parameters.("Block"+k);
    
    dlY = residualBlock(dlY,dilationFactor,dropoutFactor,parametersBlock,doTraining);
end

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

end

残差ブロック関数

関数 residualBlock は、時間的畳み込みネットワークの主な基本構成を実装します。

1 次元の膨張因果的畳み込みを適用するには、関数 dlconv を使用します。

  • 空間次元全体を畳み込むために、'DataFormat' オプションを 'CBS' に設定 (次元ラベルは 'T' ではなく 'S' を使用)。

  • 残差ブロックの膨張係数に従って 'DilationFactor' オプションを設定。

  • 必ず過去のタイム ステップのみが使用されるよう、シーケンスの先頭のみにパディングを適用。

function dlY = residualBlock(dlX,dilationFactor,dropoutFactor,parametersBlock,doTraining)

% Convolution options.
filterSize = size(parametersBlock.Conv1.Weights,1);
paddingSize = (filterSize - 1) * dilationFactor;

% Convolution.
weights = parametersBlock.Conv1.Weights;
bias =  parametersBlock.Conv1.Bias;
dlY = dlconv(dlX,weights,bias, ...
    'DataFormat','CBS', ...
    'Stride', 1, ...
    'DilationFactor', dilationFactor, ...
    'Padding', [paddingSize; 0] );

% Instance normalization, ReLU, spatial dropout.
dlY = instanceNormalization(dlY,'CBS');
dlY = relu(dlY);
dlY = spatialDropout(dlY,dropoutFactor,'CBS',doTraining);

% Convolution.
weights = parametersBlock.Conv2.Weights;
bias = parametersBlock.Conv2.Bias;
dlY = dlconv(dlY,weights,bias, ...
    'DataFormat','CBS', ...
    'Stride', 1, ...
    'DilationFactor', dilationFactor, ...
    'Padding',[paddingSize; 0] );

% Instance normalization, ReLU, spatial dropout.
dlY = instanceNormalization(dlY,'CBS');
dlY = relu(dlY);
dlY = spatialDropout(dlY,dropoutFactor,'CBS',doTraining);

% Optional 1-by-1 convolution.
if ~isequal(size(dlX),size(dlY))
    weights = parametersBlock.Conv3.Weights;
    bias = parametersBlock.Conv3.Bias;
    dlX = dlconv(dlX,weights,bias,'DataFormat','CBS');
end

% Addition and ReLU
dlY = relu(dlX+dlY);

end

モデル勾配関数

関数 modelGradients は、入力データのミニバッチ dlX、対応するターゲット シーケンス T、学習可能なパラメーター、およびハイパーパラメーターを受け取り、学習可能なパラメーターについての損失の勾配と、対応する損失を返します。勾配を計算するため、学習ループの中で関数 dlfeval を使用して、関数 modelGradients を評価します。

function [gradients,loss] = modelGradients(dlX,T,parameters,hyperparameters,numTimeSteps)

dlY = model(dlX,parameters,hyperparameters,true);
dlY = softmax(dlY,'DataFormat','CBT');

dlT = dlarray(T,'CBT');
loss = maskedCrossEntropyLoss(dlY, dlT, numTimeSteps);

gradients = dlgradient(mean(loss),parameters);

end

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

関数 maskedCrossEntropyLoss は、長さが異なるシーケンスのミニバッチに対する交差エントロピー損失を計算します。

function loss = maskedCrossEntropyLoss(dlY,dlT,numTimeSteps)

numObservations = size(dlY,2);
loss = dlarray(zeros(1,numObservations,'like',dlY));

for i = 1:numObservations
    idx = 1:numTimeSteps(i);
    loss(i) = crossentropy(dlY(:,i,idx),dlT(:,i,idx),'DataFormat','CBT');
end

end

インスタンスの正規化関数

関数 instanceNormalization は、最初に各入力チャネル全体の各観測値に対する平均 μ と分散 σ2 を計算し、入力 dlX を正規化します。その後、正規化された活性化を次のように計算します。

Xˆ=X-μσ2+ϵ.

バッチ正規化と比較すると、ミニバッチの各観測値によって平均と分散は異なります。インスタンスの正規化など、畳み込み層の間の正規化と非線形性を使用して、畳み込みニューラル ネットワークの学習速度を上げ、収束を改善します。

function dlY = instanceNormalization(dlX,fmt)

reductionDims = find(fmt == 'S');
mu = mean(dlX,reductionDims);
sigmaSq = var(dlX,1,reductionDims);

epsilon = 1e-5;
dlY = (dlX-mu) ./ sqrt(sigmaSq+epsilon);

end

空間ドロップアウト関数

関数 spatialDropout は、doTraining フラグが true の場合、入力 dlX に対し、次元ラベル fmt を使用して空間ドロップアウト [3] を実行します。空間ドロップアウトは、入力データのチャネル全体を除外します。つまり、特定のチャネルのすべてのタイム ステップが、dropoutFactor で指定した確率で除外されます。チャネルはバッチ次元で個別にドロップアウトされます。

function dlY = spatialDropout(dlX,dropoutFactor,fmt,doTraining)

if doTraining
    maskSize = size(dlX);
    maskSize(fmt=='S') = 1;
    
    dropoutScaleFactor = single(1 - dropoutFactor);
    dropoutMask = (rand(maskSize,'like',dlX) > dropoutFactor) / dropoutScaleFactor;
    
    dlY = dlX .* dropoutMask;
else
    dlY = dlX;
end

end

重み初期化関数

関数 initializeGaussian は、平均 0、標準偏差 0.01 のガウス分布から重みをサンプリングします。

function parameter = initializeGaussian(sz)

parameter = randn(sz,'single') .* 0.01;

end

ミニバッチ前処理関数

関数 preprocessMiniBatch は、学習用データを前処理します。N 個の入力シーケンスは、左パディングされた 1 次元シーケンスの C x N x S の数値配列と、各シーケンスのタイム ステップ数に変換されます。ここで、C はシーケンスの特徴の数に、S は最長シーケンスのタイム ステップ数に対応します。

関数 preprocessMiniBatch は、次の手順でデータを前処理します。

  1. 最長シーケンスの長さを計算します。

  2. 各タイム ステップのカテゴリカル ラベルを数値配列に one-hot 符号化します。

  3. 関数 leftPad を使用し、ミニバッチ内で最長のシーケンスと同じ長さになるようにシーケンスをパディングします。

function [XTransformed,YTransformed,numTimeSteps] = preprocessMiniBatch(XCell,YCell) 

    numTimeSteps = cellfun(@(sequence) size(sequence,2),XCell);
    sequenceLength = max(cellfun(@(sequence) size(sequence,2),XCell));
    
    miniBatchSize = numel(XCell);
    numFeatures = size(XCell{1},1);    
    classes = categories(YCell{1});
    numClasses = numel(classes);
    
    szX = [numFeatures miniBatchSize sequenceLength];
    XTransformed = zeros(szX,'single');
    
    szY = [numClasses miniBatchSize sequenceLength];
    YTransformed = zeros(szY,'single');
    
    for i = 1:miniBatchSize
        predictors = XCell{i};
        
        responses = onehotencode(YCell{i},1);
        
        % Left pad.
        XTransformed(:,i,:) = leftPad(predictors,sequenceLength);
        YTransformed(:,i,:) = leftPad(responses,sequenceLength);
        
    end

end

左パディング関数

関数 leftPad は、シーケンスを受け取り、ゼロで左パディングして指定されたシーケンス長にします。

function sequencePadded = leftPad(sequence,sequenceLength)

[numFeatures,numTimeSteps] = size(sequence);

paddingSize = sequenceLength - numTimeSteps;
padding = zeros(numFeatures,paddingSize);

sequencePadded = [padding sequence];

end

勾配クリップ関数

関数 thresholdL2Norm は、勾配 g をスケーリングして、勾配の L2 ノルムが gradientThreshold よりも大きい場合に L2 ノルムが gradientThreshold と等しくなるようにします。

function g = thresholdL2Norm(g,gradientThreshold)

gradientNorm = sqrt(sum(g.^2,'all'));
if gradientNorm > gradientThreshold
    g = g * (gradientThreshold / gradientNorm);
end

end

参考文献

[1] Bai, Shaojie, J. Zico Kolter, and Vladlen Koltun. "An empirical evaluation of generic convolutional and recurrent networks for sequence modeling." arXiv preprint arXiv:1803.01271 (2018).

[2] Van Den Oord, Aäron, et al. "WaveNet: A generative model for raw audio." SSW 125 (2016).

[3] Tompson, Jonathan, et al. "Efficient object localization using convolutional networks." Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition.2015.

参考

| | | | | | | | | | |

関連するトピック