メインコンテンツ

深層学習を使用したテキストの生成

R2026b

この例では、深層学習長短期記憶 (LSTM) ネットワークに学習させてテキストを生成する方法を説明します。

テキスト生成用の深層学習ネットワークに学習させるには、文字のシーケンスにおける次の文字を予測する sequence-to-sequence LSTM ネットワークに学習させます。次の文字を予測するネットワークに学習させるには、1 タイム ステップ分シフトした入力シーケンスを応答として指定します。

文字のシーケンスを LSTM ネットワークに入力するには、各学習観測値をベクトル x∈RD で表される文字のシーケンスに変換します。ここで、D はボキャブラリに含まれる一意の文字の数です。各ベクトルについて、x が、与えられたボキャブラリ内のインデックス i の文字に対応する場合は xi=1 であり、j≠i の場合は xj=0 です。

学習データの読み込み

テキスト データをテキスト ファイル sonnets.txt から抽出します。

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

ソネットは 2 つの空白文字でインデントされ、2 つの改行文字で区切られています。replace を使用してインデントを削除し、split を使用してテキストを個別のソネットに分割します。最初の 3 つの要素のメイン タイトルと、各ソネットの前に現れるソネット タイトルを削除します。

textData = replace(textData,"  ","");
textData = split(textData,[newline newline]);
textData = textData(5:2:end);

最初のいくつかのドキュメントを表示します。

textData(1:10)
ans = 10×1 cell array
    "'From fairest creatures we desire increase,That thereby beauty's rose might never die,But as the riper should by time decease,His tender heir might bear his memory:But thou, contracted to thine own bright eyes,Feed'st thy light's flame with self-substantial fuel,Making a famine where abundance lies,Thy self thy foe, to thy sweet self too cruel:Thou that art now the world's fresh ornament,And only herald to the gaudy spring,Within thine own bud buriest thy content,And tender churl mak'st waste in niggarding:Pity the world, or else this glutton be,To eat the world's due, by the grave and thee.'"
    "'When forty winters shall besiege thy brow,And dig deep trenches in thy beauty's field,Thy youth's proud livery so gazed on now,Will be a tatter'd weed of small worth held:Then being asked, where all thy beauty lies,Where all the treasure of thy lusty days;To say, within thine own deep sunken eyes,Were an all-eating shame, and thriftless praise.How much more praise deserv'd thy beauty's use,If thou couldst answer 'This fair child of mineShall sum my count, and make my old excuse,'Proving his beauty by succession thine!This were to be new made when thou art old,And see thy blood warm when thou feel'st it cold.'"
    "'Look in thy glass and tell the face thou viewestNow is the time that face should form another;Whose fresh repair if now thou not renewest,Thou dost beguile the world, unbless some mother.For where is she so fair whose unear'd wombDisdains the tillage of thy husbandry?Or who is he so fond will be the tomb,Of his self-love to stop posterity?Thou art thy mother's glass and she in theeCalls back the lovely April of her prime;So thou through windows of thine age shalt see,Despite of wrinkles this thy golden time.But if thou live, remember'd not to be,Die single and thine image dies with thee.'"
    "'Unthrifty loveliness, why dost thou spendUpon thy self thy beauty's legacy?Nature's bequest gives nothing, but doth lend,And being frank she lends to those are free:Then, beauteous niggard, why dost thou abuseThe bounteous largess given thee to give?Profitless usurer, why dost thou useSo great a sum of sums, yet canst not live?For having traffic with thy self alone,Thou of thy self thy sweet self dost deceive:Then how when nature calls thee to be gone,What acceptable audit canst thou leave?Thy unused beauty must be tombed with thee,Which, used, lives th' executor to be.'"
    "'Those hours, that with gentle work did frameThe lovely gaze where every eye doth dwell,Will play the tyrants to the very sameAnd that unfair which fairly doth excel;For never-resting time leads summer onTo hideous winter, and confounds him there;Sap checked with frost, and lusty leaves quite gone,Beauty o'er-snowed and bareness every where:Then were not summer's distillation left,A liquid prisoner pent in walls of glass,Beauty's effect with beauty were bereft,Nor it, nor no remembrance what it was:But flowers distill'd, though they with winter meet,Leese but their show; their substance still lives sweet.'"
    "'Then let not winter's ragged hand deface,In thee thy summer, ere thou be distill'd:Make sweet some vial; treasure thou some placeWith beauty's treasure ere it be self-kill'd.That use is not forbidden usury,Which happies those that pay the willing loan;That's for thy self to breed another thee,Or ten times happier, be it ten for one;Ten times thy self were happier than thou art,If ten of thine ten times refigur'd thee:Then what could death do if thou shouldst depart,Leaving thee living in posterity?Be not self-will'd, for thou art much too fairTo be death's conquest and make worms thine heir.'"
    "'Lo! in the orient when the gracious lightLifts up his burning head, each under eyeDoth homage to his new-appearing sight,Serving with looks his sacred majesty;And having climb'd the steep-up heavenly hill,Resembling strong youth in his middle age,Yet mortal looks adore his beauty still,Attending on his golden pilgrimage:But when from highmost pitch, with weary car,Like feeble age, he reeleth from the day,The eyes, 'fore duteous, now converted areFrom his low tract, and look another way:So thou, thyself outgoing in thy noon:Unlook'd, on diest unless thou get a son.'"
    "'Music to hear, why hear'st thou music sadly?Sweets with sweets war not, joy delights in joy:Why lov'st thou that which thou receiv'st not gladly,Or else receiv'st with pleasure thine annoy?If the true concord of well-tuned sounds,By unions married, do offend thine ear,They do but sweetly chide thee, who confoundsIn singleness the parts that thou shouldst bear.Mark how one string, sweet husband to another,Strikes each in each by mutual ordering;Resembling sire and child and happy mother,Who, all in one, one pleasing note do sing:Whose speechless song being many, seeming one,Sings this to thee: 'Thou single wilt prove none.''"
    "'Is it for fear to wet a widow's eye,That thou consum'st thy self in single life?Ah! if thou issueless shalt hap to die,The world will wail thee like a makeless wife;The world will be thy widow and still weepThat thou no form of thee hast left behind,When every private widow well may keepBy children's eyes, her husband's shape in mind:Look! what an unthrift in the world doth spendShifts but his place, for still the world enjoys it;But beauty's waste hath in the world an end,And kept unused the user so destroys it.No love toward others in that bosom sitsThat on himself such murd'rous shame commits.'"
    "'For shame! deny that thou bear'st love to any,Who for thy self art so unprovident.Grant, if thou wilt, thou art belov'd of many,But that thou none lov'st is most evident:For thou art so possess'd with murderous hate,That 'gainst thy self thou stick'st not to conspire,Seeking that beauteous roof to ruinateWhich to repair should be thy chief desire.O! change thy thought, that I may change my mind:Shall hate be fairer lodg'd than gentle love?Be, as thy presence is, gracious and kind,Or to thyself at least kind-hearted prove:Make thee another self for love of me,That beauty still may live in thine or thee.'"

テキスト データのシーケンスへの変換

テキスト データを予測子用のベクトルのシーケンスと応答用のカテゴリカル シーケンスに変換します。

"テキスト開始"、"空白"、"テキスト終結"、および "改行" を表す特殊文字を作成します。特殊文字 "\x0002" (テキスト開始)、"\x00B7" ("·"、中黒)、"\x2403" ("␃"、テキスト終結)、および "\x00B6" (""、段落記号) をそれぞれ使用します。あいまいさを避けるために、テキストに現れない特殊文字を選択しなければなりません。これらの文字は学習データに現れないため、この目的に使用できます。

startOfTextCharacter = compose("\x0002");
whitespaceCharacter = compose("\x00B7");
endOfTextCharacter = compose("\x2403");
newlineCharacter = compose("\x00B6");

各観測値について、先頭にテキスト開始文字を挿入し、空白と改行を対応する文字に置き換えます。

textData = startOfTextCharacter + textData;
textData = replace(textData,[" " newline],[whitespaceCharacter newlineCharacter]);

テキスト内の一意の文字から成るボキャブラリを作成します。

uniqueCharacters = unique([textData{:}]);
numUniqueCharacters = numel(uniqueCharacters);

テキスト データについてループし、各観測値の文字を表すベクトルのシーケンスと応答用の文字のカテゴリカル シーケンスを作成します。各観測値の終わりを表すために、テキスト終結文字を含めます。

numDocuments = numel(textData);
XTrain = cell(1,numDocuments);
YTrain = cell(1,numDocuments);
for i = 1:numel(textData)
    characters = textData{i};
    sequenceLength = numel(characters);
    
    % Get indices of characters.
    [~,idx] = ismember(characters,uniqueCharacters);
    
    % Convert characters to vectors.
    X = zeros(numUniqueCharacters,sequenceLength);
    for j = 1:sequenceLength
        X(idx(j),j) = 1;
    end
    
    % Create vector of categorical responses with end of text character.
    charactersShifted = [cellstr(characters(2:end)')' endOfTextCharacter];
    Y = categorical(charactersShifted, [string(uniqueCharacters(2:end)'); endOfTextCharacter]);
    XTrain{i} = X;
    YTrain{i} = Y;
end

最初の観測値と、対応するシーケンスのサイズを表示します。シーケンスは D 行 S 列の行列です。D は特徴の数 (一意の文字の数)、S はシーケンス長 (テキストの文字数) です。

textData{1}
ans = 
'□From·fairest·creatures·we·desire·increase,¶That·thereby·beauty's·rose·might·never·die,¶But·as·the·riper·should·by·time·decease,¶His·tender·heir·might·bear·his·memory:¶But·thou,·contracted·to·thine·own·bright·eyes,¶Feed'st·thy·light's·flame·with·self-substantial·fuel,¶Making·a·famine·where·abundance·lies,¶Thy·self·thy·foe,·to·thy·sweet·self·too·cruel:¶Thou·that·art·now·the·world's·fresh·ornament,¶And·only·herald·to·the·gaudy·spring,¶Within·thine·own·bud·buriest·thy·content,¶And·tender·churl·mak'st·waste·in·niggarding:¶Pity·the·world,·or·else·this·glutton·be,¶To·eat·the·world's·due,·by·the·grave·and·thee.'
size(XTrain{1})
ans = 1×2

    62    611

対応する応答シーケンスを表示します。このシーケンスは、応答から成る 1 行 S 列の categorical ベクトルです。

YTrain{1}
ans = 1×611 categorical array
    "F"    "r"    "o"    "m"    "·"    "f"    "a"    "i"    "r"    "e"    "s"    "t"    "·"    "c"    "r"    "e"    "a"    "t"    "u"    "r"    "e"    "s"    "·"    "w"    "e"    "·"    "d"    "e"    "s"    "i"

LSTM ネットワークの作成と学習

LSTM アーキテクチャを定義します。隠れユニットが 200 個の sequence-to-sequence LSTM 分類ネットワークを指定します。学習データの特徴次元 (一意の文字の数) を入力サイズとして、応答に含まれるカテゴリの数を全結合層の出力サイズとして設定します。

inputSize = size(XTrain{1},1);
numHiddenUnits = 200;
numClasses = numel(categories([YTrain{:}]));

layers = [
    sequenceInputLayer(inputSize)
    lstmLayer(numHiddenUnits,'OutputMode','sequence')
    fullyConnectedLayer(numClasses)
    softmaxLayer];

学習オプションを指定します。オプションの中から選択するには、経験的解析が必要です。実験を実行してさまざまな学習オプションの構成を調べるには、実験マネージャー アプリを使用できます。

  • Adam オプティマイザーを使用して学習させます。

  • 学習エポックの回数を 1000 に、初期学習率を 0.01 に指定します。

  • 学習率低下係数を 0.05 に、学習率低下周期を 50 に設定します。

  • 勾配しきい値を 10 に設定します。

  • ミニバッチ サイズ オプションは、1 回の反復で処理する観測値の数を指定します。関数が学習のすべての観測値を確実に使用するように、データを均等に分割するミニバッチ サイズを指定します。そうでない場合、関数はミニバッチに収まっていない観測値を無視します。ミニバッチのサイズを 77 に設定します。

  • 'Shuffle' オプションを 'every-epoch' に設定して、エポックごとにデータをシャッフルするように指定します。

  • 学習の進行状況を監視するには、'Plots' オプションを 'training-progress' に設定します。

  • 詳細出力を表示しないようにするには、'Verbose' を false に設定します。

  • 学習データには、チャネルとタイム ステップにそれぞれ対応する行と列を含むシーケンスがあるため、入力データ形式 'CTB' (チャネル、時間、バッチ) を指定します。

options = trainingOptions('adam', ...
    'MaxEpochs',1000, ...
    'InitialLearnRate',0.01, ...
    'LearnRateDropFactor',0.05, ...
    'LearnRateDropPeriod',50, ...
    'GradientThreshold',10, ...
    'MiniBatchSize',77,...
    'Shuffle','every-epoch', ...
    'Plots','training-progress', ...
    'Verbose',false, ...
    'InputDataFormats','CTB', ...
    'Metrics','accuracy');

trainnet 関数を使用してニューラル ネットワークに学習させます。分類には、クロスエントロピー損失を使用します。このモデルは、sequence-to-sequence 分類ネットワークです。入力データの各タイム ステップにおいて、ターゲット ラベルはシーケンスの次の文字になります。つまり、モデルは各タイム ステップをそのタイム ステップの次の文字を使って分類します。

既定では、trainnet 関数は利用可能な GPU がある場合にそれを使用します。GPU での学習には、Parallel Computing Toolbox™ ライセンスとサポートされている GPU デバイスが必要です。サポートされているデバイスの詳細については、「GPU 計算の要件」を参照してください。そうでない場合、trainnet 関数は CPU を使用します。実行環境を指定するには、ExecutionEnvironment 学習オプションを使用します。

[net, info] = trainnet(XTrain,YTrain,layers,"crossentropy",options);

新しいテキストの生成

例の最後にリストされている関数 generateText を使用し、学習済みネットワークを使用してテキストを生成します。

関数 generateText は、テキスト開始文字から始めて 1 文字ずつテキストを生成し、特殊文字を使用してテキストを再構成します。この関数は、出力予測スコアを使用して各文字をサンプリングします。ネットワークがテキスト終結文字を予測するか、生成されたテキストの長さが 500 文字になると、関数が予測を停止します。

学習済みネットワークを使用してテキストを生成します。

vocabulary = string(categories(Y));
generatedText = generateText(net,vocabulary,uniqueCharacters,startOfTextCharacter,newlineCharacter,whitespaceCharacter,endOfTextCharacter)
generatedText = 
    "When and though det thee, kellit, thy frams awtor
     Tom that recoud is my lifer may pride.
     Which I betterpity nour so mons?
     Shol'd aving sowrast what thou doys did be;
     Tove,.F, thou for the roomemy graven's I sto then rake,
     Times it to I fell why of your love's greet;
     I con thee amoul loss thie powernatirs,
     Which fallers high, and I give and all,
     Unthorgast beath prowqerthites coptoni;
     Or, on theesp her beauty's rose hervers'd in,
     Now living black'd as yet do thit shor though:
     Olf whose daintity n"

テキスト生成関数

関数 generateText は、テキスト開始文字から始めて 1 文字ずつテキストを生成し、特殊文字を使用してテキストを再構成します。この関数は、出力予測スコアを使用して各文字をサンプリングします。ネットワークがテキスト終結文字を予測するか、生成されたテキストの長さが 500 文字になると、関数が予測を停止します。

function generatedText = generateText(net,vocabulary,uniqueCharacters,startOfTextCharacter,newlineCharacter,whitespaceCharacter,endOfTextCharacter)

テキスト開始文字のインデックスを見つけて、そのベクトルを作成します。

numUniqueCharacters = numel(uniqueCharacters);
X = zeros(numUniqueCharacters,1);
idx = strfind(uniqueCharacters,startOfTextCharacter);
X(idx) = 1;

predict および datasample を使用し、学習済み LSTM ネットワークを使用して 1 文字ずつテキストを生成します。ネットワークがテキスト終結文字を予測するか、生成されたテキストの長さが 500 文字になったら予測を停止します。関数 datasample には、Statistics and Machine Learning Toolbox™ が必要です。

データの大規模なコレクション、長いシーケンス、または大規模ネットワークの場合は、通常、GPU での予測の方が CPU での予測より計算時間が短縮されます。そうでない場合、通常、CPU での予測の計算の方が高速です。1 タイム ステップの予測には、CPU を使用します。予測に CPU を使用するには、predict の 'ExecutionEnvironment' オプションを 'cpu' に設定します。

generatedText = "";
maxLength = 500;
while strlength(generatedText) < maxLength
    % Predict the next character scores.
    [characterScores,state] = predict(net,X,InputDataFormats='CTB');
    net.State = state;
    % Sample the next character.
    newCharacter = datasample(vocabulary,1,'Weights',characterScores);
    
    % Stop predicting at the end of text.
    if newCharacter == endOfTextCharacter
        break
    end
    
    % Add the character to the generated text.
    generatedText = generatedText + newCharacter;
    
    % Create a new vector for the next input.
    X(:) = 0;
    idx = strfind(uniqueCharacters,newCharacter);
    X(idx) = 1;
end

特殊文字を対応する空白文字や改行文字に置き換えて、生成されたテキストを再構成します。

generatedText = replace(generatedText,[newlineCharacter whitespaceCharacter],[newline " "]);

end

参考

| | | |

トピック