Main Content

딥러닝을 사용하여 텍스트 생성하기

이 예제에서는 텍스트를 생성하도록 딥러닝 장단기 기억(LSTM) 신경망을 훈련시키는 방법을 보여줍니다.

텍스트 생성을 위해 딥러닝 신경망을 훈련시키려면 문자 시퀀스 내의 그다음 문자를 예측하도록 sequence-to-sequence LSTM 신경망을 훈련시키십시오. 그다음 문자를 예측하도록 신경망을 훈련시키려면 입력 시퀀스를 시간 스텝 하나만큼 이동한 값을 응답 변수로 지정하십시오.

LSTM 신경망에 문자 시퀀스를 입력하려면 각 훈련 관측값을 벡터 xRD으로 표현되는 문자 시퀀스로 변환하십시오. 여기서 D는 단어집에 포함된 고유 문자의 개수입니다. 각 벡터에 대해, 주어진 단어집에서 x가 인덱스 i를 갖는 문자와 일치하면 xi=1이고 ji이면 xj=0입니다.

훈련 데이터 불러오기

텍스트 파일 sonnets.txt에서 텍스트 데이터를 추출합니다.

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

소네트는 공백 문자 2개로 들여쓰기 되어 있고 새 줄 문자 2개로 구분되어 있습니다. replace를 사용하여 들여쓰기를 제거하고 split을 사용하여 텍스트를 구분된 소네트로 분할합니다. 처음 세 개의 요소에서 주 제목을 제거하고 각 소네트 앞에 나타나는 소네트 제목을 제거합니다.

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

처음 몇 개의 문서를 표시합니다.

textData(1:10)
ans = 10×1 cell
    {'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 mine↵Shall 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 viewest↵Now 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 womb↵Disdains 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 thee↵Calls 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 spend↵Upon 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 abuse↵The bounteous largess given thee to give?↵Profitless usurer, why dost thou use↵So 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 frame↵The lovely gaze where every eye doth dwell,↵Will play the tyrants to the very same↵And that unfair which fairly doth excel;↵For never-resting time leads summer on↵To 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 place↵With 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 fair↵To be death's conquest and make worms thine heir.'                                }
    {'Lo! in the orient when the gracious light↵Lifts up his burning head, each under eye↵Doth 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 are↵From 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 confounds↵In 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 weep↵That thou no form of thee hast left behind,↵When every private widow well may keep↵By children's eyes, her husband's shape in mind:↵Look! what an unthrift in the world doth spend↵Shifts 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 sits↵That 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 ruinate↵Which 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.'                     }

텍스트 데이터를 시퀀스로 변환하기

텍스트 데이터를 예측 변수의 경우 벡터로 구성된 시퀀스로, 응답 변수의 경우 categorical형 시퀀스로 변환합니다.

"텍스트 시작", "공백", "텍스트 끝" 및 "새 줄"을 나타내는 특수 문자를 만듭니다. 각각에 대해 특수 문자 "\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);

텍스트 데이터를 루프를 사용해 순회하여, 각 관측값의 문자를 나타내는 벡터로 구성된 시퀀스와 응답 변수에 대한 categorical형 문자 시퀀스를 만듭니다. 각 관측값의 끝을 나타내려면 텍스트 끝 문자를 삽입하십시오.

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
     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      r      e      ·      i      n      c      r      e      a      s      e      ,      ¶      T      h      a      t      ·      t      h      e      r      e      b      y      ·      b      e      a      u      t      y            s      ·      r      o      s      e      ·      m      i      g      h      t      ·      n      e      v      e      r      ·      d      i      e      ,      ¶      B      u      t      ·      a      s      ·      t      h      e      ·      r      i      p      e      r      ·      s      h      o      u      l      d      ·      b      y      ·      t      i      m      e      ·      d      e      c      e      a      s      e      ,      ¶      H      i      s      ·      t      e      n      d      e      r      ·      h      e      i      r      ·      m      i      g      h      t      ·      b      e      a      r      ·      h      i      s      ·      m      e      m      o      r      y      :      ¶      B      u      t      ·      t      h      o      u      ,      ·      c      o      n      t      r      a      c      t      e      d      ·      t      o      ·      t      h      i      n      e      ·      o      w      n      ·      b      r      i      g      h      t      ·      e      y      e      s      ,      ¶      F      e      e      d            s      t      ·      t      h      y      ·      l      i      g      h      t            s      ·      f      l      a      m      e      ·      w      i      t      h      ·      s      e      l      f      -      s      u      b      s      t      a      n      t      i      a      l      ·      f      u      e      l      ,      ¶      M      a      k      i      n      g      ·      a      ·      f      a      m      i      n      e      ·      w      h      e      r      e      ·      a      b      u      n      d      a      n      c      e      ·      l      i      e      s      ,      ¶      T      h      y      ·      s      e      l      f      ·      t      h      y      ·      f      o      e      ,      ·      t      o      ·      t      h      y      ·      s      w      e      e      t      ·      s      e      l      f      ·      t      o      o      ·      c      r      u      e      l      :      ¶      T      h      o      u      ·      t      h      a      t      ·      a      r      t      ·      n      o      w      ·      t      h      e      ·      w      o      r      l      d            s      ·      f      r      e      s      h      ·      o      r      n      a      m      e      n      t      ,      ¶      A      n      d      ·      o      n      l      y      ·      h      e      r      a      l      d      ·      t      o      ·      t      h      e      ·      g      a      u      d      y      ·      s      p      r      i      n      g      ,      ¶      W      i      t      h      i      n      ·      t      h      i      n      e      ·      o      w      n      ·      b      u      d      ·      b      u      r      i      e      s      t      ·      t      h      y      ·      c      o      n      t      e      n      t      ,      ¶      A      n      d      ·      t      e      n      d      e      r      ·      c      h      u      r      l      ·      m      a      k            s      t      ·      w      a      s      t      e      ·      i      n      ·      n      i      g      g      a      r      d      i      n      g      :      ¶      P      i      t      y      ·      t      h      e      ·      w      o      r      l      d      ,      ·      o      r      ·      e      l      s      e      ·      t      h      i      s      ·      g      l      u      t      t      o      n      ·      b      e      ,      ¶      T      o      ·      e      a      t      ·      t      h      e      ·      w      o      r      l      d            s      ·      d      u      e      ,      ·      b      y      ·      t      h      e      ·      g      r      a      v      e      ·      a      n      d      ·      t      h      e      e      .      ␃ 

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 최적화 함수를 사용하여 훈련시킵니다.

  • 훈련 Epoch의 횟수를 1000으로 지정하고 초기 학습률을 0.01로 지정합니다.

  • 학습률 감소 인자를 0.05로 설정하고 학습률 감소 주기를 50으로 설정합니다.

  • 기울기 임계값을 10으로 설정합니다.

  • 미니 배치 크기 옵션은 단일 반복에서 처리할 관측값의 개수를 지정합니다. 함수가 훈련에 모든 관측값을 사용하도록 하려면 데이터를 균등하게 나누는 미니 배치 크기를 지정하십시오. 그러지 않으면 함수가 미니 배치에 속하지 않는 관측값을 무시하게 됩니다. 미니 배치 크기를 77로 설정합니다.

  • 'Shuffle' 옵션을 'every-epoch'로 설정하여 매 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를 사용합니다. GPU에서 훈련시키려면 Parallel Computing Toolbox™ 라이선스와 지원되는 GPU 장치가 필요합니다. 지원되는 장치에 대한 자세한 내용은 GPU 연산 요구 사항을 참조하십시오. GPU를 사용할 수 없는 경우, trainnet 함수는 CPU를 사용합니다. 실행 환경을 지정하려면 ExecutionEnvironment 훈련 옵션을 사용하십시오.

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

새 텍스트 생성하기

이 예제의 마지막에 나오는 generateText 함수를 사용하여 훈련된 신경망을 사용해 텍스트를 생성합니다.

generateText 함수는 텍스트 시작 문자부터 시작해서 한 문자씩 텍스트를 생성하며 특수 문자를 사용하여 텍스트를 재생성합니다. 이 함수는 출력 예측 점수를 사용하여 각 문자를 샘플링합니다. 신경망이 텍스트 끝 문자를 예측하거나 생성된 텍스트의 길이가 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 함수는 텍스트 시작 문자부터 시작해서 한 문자씩 텍스트를 생성하며 특수 문자를 사용하여 텍스트를 재생성합니다. 이 함수는 출력 예측 점수를 사용하여 각 문자를 샘플링합니다. 신경망이 텍스트 끝 문자를 예측하거나 생성된 텍스트의 길이가 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;

predictdatasample을 사용하여, 훈련된 LSTM 신경망을 사용해서 한 문자씩 텍스트를 생성합니다. 신경망이 텍스트 끝 문자를 예측하거나 생성된 텍스트의 길이가 500자가 되면 예측을 중단합니다. datasample 함수를 사용하려면 Statistics and Machine Learning Toolbox™가 필요합니다.

대규모의 데이터 모음, 긴 시퀀스 또는 큰 신경망의 경우에는 일반적으로 GPU에서의 예측이 CPU에서의 예측보다 연산 속도가 빠릅니다. 그 밖의 경우에는 일반적으로 CPU에서의 예측이 연산 속도가 빠릅니다. 단일 시간 스텝 예측에는 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

참고 항목

| | | |

관련 항목