Générateur délirant: créez des textes dans n'importe quelle langue à l'aide d'un réseau de neurones

Salut, Habr.

Cet article sera dans un format un peu "vendredi", aujourd'hui nous traiterons de la PNL. Pas le PNL sur lequel les livres sont vendus dans les passages souterrains, mais celui que Natural Language Processing traite les langues naturelles. Comme exemple d'un tel traitement, la génération de texte utilisant un réseau neuronal sera utilisée. Nous pouvons créer des textes dans n'importe quelle langue, du russe ou de l'anglais, au C ++. Les résultats sont très intéressants, vous pouvez probablement le deviner sur la photo.



Pour ceux qui sont intéressés par ce qui se passe, les résultats et le code source sont sous la coupe.

Préparation des données


Pour le traitement, nous utiliserons une classe spéciale de réseaux de neurones - les réseaux de neurones dits récurrents (RNN). Ce réseau diffère de l'habituel en ce qu'en plus des cellules habituelles, il possède des cellules mémoire. Cela nous permet d'analyser des données d'une structure plus complexe, et en fait, plus proche de la mémoire humaine, car nous ne partons pas non plus de toutes les pensées «à partir de zéro». Pour écrire du code, nous utiliserons les réseaux LSTM (Long Short-Term Memory), car ils sont déjà pris en charge par Keras.



Le problème suivant qui doit être résolu est, en fait, de travailler avec du texte. Et ici, il y a deux approches - pour soumettre des symboles ou les mots entiers à l'entrée. Le principe de la première approche est simple: le texte est divisé en blocs courts, où les «entrées» sont un morceau de texte et la «sortie» est le caractère suivant. Par exemple, pour la dernière phrase, «les entrées sont un morceau de texte»:

input: output: ""
input: : output: ""
input: : output:""
input: : output: ""
input: : output: "".

Et ainsi de suite. Ainsi, le réseau neuronal reçoit des fragments de texte en entrée et en sortie les caractères qu'il doit former.

La deuxième approche est fondamentalement la même, seuls des mots entiers sont utilisés à la place des mots. Tout d'abord, un dictionnaire de mots est compilé et des nombres sont saisis à la place des mots à l'entrée du réseau.

Ceci, bien sûr, est une description plutôt simplifiée. Keras a déjà des exemples de génération de texte, mais d'une part, ils ne sont pas décrits en détail, et d'autre part, tous les didacticiels en anglais utilisent des textes plutôt abstraits comme Shakespeare, qui sont difficiles à comprendre pour le natif. Eh bien, nous testons un réseau de neurones sur notre grand et puissant réseau, qui, bien sûr, sera plus clair et compréhensible.

Formation réseau


En tant que texte d'entrée, j'ai utilisé ... les commentaires de Habr, la taille du fichier source est de 1 Mo (il y a en fait plus de commentaires, bien sûr, mais je n'ai dû utiliser qu'une partie, sinon le réseau de neurones aurait été formé pendant une semaine et les lecteurs n'auraient pas vu ce texte vendredi). Permettez-moi de vous rappeler que seules les lettres sont alimentées à l'entrée d'un réseau neuronal, le réseau ne «sait» rien du langage ou de sa structure. Allons-y, commençons la formation du réseau.

5 minutes de formation:

Jusqu'à présent, rien n'est clair, mais vous pouvez déjà voir des combinaisons de lettres reconnaissables:

. . . «

15 minutes de formation:

Le résultat est déjà nettement meilleur:



1 heure de formation:

« » — « » » —

Pour une raison quelconque, tous les textes se sont révélés être sans points et sans majuscules, peut-être que le traitement utf-8 n'a pas été fait correctement. Mais dans l'ensemble, c'est impressionnant. En analysant et en se souvenant uniquement des codes de symboles, le programme a effectivement appris «indépendamment» des mots russes et peut générer un texte d'aspect assez crédible.

Non moins intéressant est le fait que le programme mémorise assez bien le style de texte. Dans l'exemple suivant, le texte d'une loi a été utilisé comme outil pédagogique. Temps de formation réseau 5 minutes.

"" , , , , , , , ,

Et ici, les annotations médicales pour les médicaments ont été utilisées comme un ensemble d'entrée. Temps de formation réseau 5 minutes.



, ,

Ici, nous voyons des phrases presque entières. Cela est dû au fait que le texte original est court et que le réseau neuronal a en fait "mémorisé" certaines phrases dans leur ensemble. Cet effet est appelé «recyclage» et doit être évité. Idéalement, vous devez tester un réseau de neurones sur de grands ensembles de données, mais la formation dans ce cas peut prendre de nombreuses heures, et malheureusement je n'ai pas de supercalculateur supplémentaire.

Un exemple amusant d'utilisation d'un tel réseau est la génération de noms. Après avoir téléchargé une liste de noms masculins et féminins dans le fichier, j'ai obtenu de nouvelles options assez intéressantes qui conviendraient tout à fait à un roman de science-fiction: Rlar, Laaa, Aria, Arera, Aelia, Ninran, Air. Quelque chose en eux sent le style d'Efremov et de la nébuleuse d'Andromède ...

C ++


Ce qui est intéressant, c'est que dans l'ensemble, un réseau de neurones, c'est comme se souvenir. L'étape suivante consistait à vérifier comment le programme gère le code source. Comme test, j'ai pris différentes sources C ++ et les ai combinées en un seul fichier texte.

Honnêtement, le résultat a surpris encore plus que dans le cas de la langue russe.

5 minutes de formation

Merde, c'est presque du vrai C ++.

 if ( snd_pcm_state_channels = 0 ) { errortext_ = "rtapialsa::probedeviceopen: esror stream_.buffer stream!"; errortext_ = errorstream_.str(); goto unlock; } if ( stream_.mode == input && stream_.mode == output || false; if ( stream_.state == stream_stopped ) { for ( unsigned int i=0; i<stream_.nuserbuffer[i] ) { for (j=0; j<info.channels; } } } 

30 minutes de formation

 void maxirecorder::stopstream() { for (int i = 0; i < ainchannels; i++ ) { int input=(stream, null; conternallock( pthread_cond_wate); // this should not ported to the device is a structure are is and the stream the buffer of the interleaved as and the probe the arauliored format for the the stream os and the software and the supported of the is so aa is set the directsound of allocates the reset and real the read the setup the buffer // this is the setup to the the devices. // asio will open the supported by the supported buffer are asioned structure. // this should not have to setup the counting and the proper in the position we mast it alw the aftware configuration. // this is the the stream and probe the supported be audio for sched/ond changed to this the sample rate it in the counting the fres // mecorend. windevoppart( null, 0, &phase ); possint=>buffersize( 1, bufferlist); } } } ///********************************************* void maxirecorder::abortdevice( device ) { if (cout << aindeff( thread_cont_start); for (int i = 0; i < sample_rates[0]) { if (count == int) { std::cout << "checksing of convernion allocation handler (" << device << ")."; errortext_ = errorstream_.str(); return failure; } } } if ( stream_.userbuffer[i] ) { errortext_ = "rtapicore::probedeviceopen: system error (" << geterrorcode( result ) << ") getting stream configuration info state."; error( rterror::warning ); return; } if ( stream_.mode == output || stream_.mode == duplex ) { callbackinfo *info = (const char *) callback( stream_.userbuffer[1], stream_.convertinfo[0] ); return info; } // setup buffer callback for conversion on do any the supported be audio bet that al alinums be of support of the supported buffer // the buffer size a stream and buffers. info.name = dsdevices[ device ]; return 0; } 

Comme vous pouvez le voir, le programme a "appris" à écrire des fonctions entières. En même temps, il séparait complètement «humainement» les fonctions par un commentaire avec des astérisques, mettait des commentaires dans le code, et tout ça. Je voudrais apprendre un nouveau langage de programmation avec une telle vitesse ... Bien sûr, il y a des erreurs dans le code, et bien sûr, il ne se compilera pas. Et au fait, je n'ai pas formaté le code, le programme a également appris à mettre des parenthèses et des retraits "moi-même".

Bien sûr, ces programmes n'ont pas l'essentiel - le sens , et ont donc l'air surréaliste, comme s'ils étaient écrits dans un rêve, ou qu'ils n'étaient pas écrits par une personne en parfaite santé. Néanmoins, les résultats sont impressionnants. Et peut-être qu'une étude plus approfondie de la génération de différents textes aidera à mieux comprendre certaines des maladies mentales de vrais patients. Par ailleurs, comme suggéré dans les commentaires, une telle maladie mentale dans laquelle une personne parle dans un texte grammaticalement lié mais complètement dénué de sens ( schizophasie ) existe.

Conclusion


Les réseaux de neurones récréatifs sont considérés comme très prometteurs, et c'est en effet un grand pas en avant par rapport aux réseaux «ordinaires» comme MLP, qui n'ont pas de mémoire. En effet, les capacités des réseaux de neurones à stocker et traiter des structures assez complexes sont impressionnantes. C'est après ces tests que j'ai pensé pour la première fois qu'Ilon Mask avait probablement raison quand j'ai écrit que l'IA à l'avenir pourrait être "le plus grand risque pour l'humanité" - même si un simple réseau de neurones peut facilement se souvenir et se reproduire modèles assez complexes, que peut faire un réseau de milliards de composants? Mais d'un autre côté, n'oubliez pas que notre réseau de neurones ne peut pas penser , il ne se souvient essentiellement que mécaniquement de séquences de caractères, ne comprenant pas leur signification. C'est un point important - même si vous entraînez un réseau neuronal sur un supercalculateur et un énorme ensemble de données, au mieux il apprendra à générer des phrases grammaticalement correctes à 100%, mais complètement dénuées de sens.

Mais il ne sera pas supprimé en philosophie, l'article s'adresse toujours plus aux praticiens. Pour ceux qui veulent expérimenter par eux-mêmes, le code source de Python 3.7 est sous le spoiler. Ce code est une compilation de divers projets github, et n'est pas un échantillon du meilleur code, mais il semble accomplir sa tâche.

L'utilisation du programme ne nécessite pas de compétences en programmation, il suffit de savoir installer Python. Exemples de démarrage à partir de la ligne de commande:
- Création et formation de modèles et génération de texte:
python. \ keras_textgen.py --text = text_habr.txt --epochs = 10 --out_len = 4000
- Génération de texte uniquement sans formation de modèle:
python. \ keras_textgen.py --text = text_habr.txt --epochs = 10 --out_len = 4000 --generate

keras_textgen.py
 import os # Force CPU os.environ["CUDA_VISIBLE_DEVICES"] = "-1" os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' # 0 = all messages are logged, 3 - INFO, WARNING, and ERROR messages are not printed from keras.callbacks import LambdaCallback from keras.models import Sequential from keras.layers import Dense, Dropout, Embedding, LSTM, TimeDistributed from keras.optimizers import RMSprop from keras.utils.data_utils import get_file import keras from collections import Counter import pickle import numpy as np import random import sys import time import io import re import argparse # Transforms text to vectors of integer numbers representing in text tokens and back. Handles word and character level tokenization. class Vectorizer: def __init__(self, text, word_tokens, pristine_input, pristine_output): self.word_tokens = word_tokens self._pristine_input = pristine_input self._pristine_output = pristine_output tokens = self._tokenize(text) # print('corpus length:', len(tokens)) token_counts = Counter(tokens) # Sort so most common tokens come first in our vocabulary tokens = [x[0] for x in token_counts.most_common()] self._token_indices = {x: i for i, x in enumerate(tokens)} self._indices_token = {i: x for i, x in enumerate(tokens)} self.vocab_size = len(tokens) print('Vocab size:', self.vocab_size) def _tokenize(self, text): if not self._pristine_input: text = text.lower() if self.word_tokens: if self._pristine_input: return text.split() return Vectorizer.word_tokenize(text) return text def _detokenize(self, tokens): if self.word_tokens: if self._pristine_output: return ' '.join(tokens) return Vectorizer.word_detokenize(tokens) return ''.join(tokens) def vectorize(self, text): """Transforms text to a vector of integers""" tokens = self._tokenize(text) indices = [] for token in tokens: if token in self._token_indices: indices.append(self._token_indices[token]) else: print('Ignoring unrecognized token:', token) return np.array(indices, dtype=np.int32) def unvectorize(self, vector): """Transforms a vector of integers back to text""" tokens = [self._indices_token[index] for index in vector] return self._detokenize(tokens) @staticmethod def word_detokenize(tokens): # A heuristic attempt to undo the Penn Treebank tokenization above. Pass the # --pristine-output flag if no attempt at detokenizing is desired. regexes = [ # Newlines (re.compile(r'[ ]?\\n[ ]?'), r'\n'), # Contractions (re.compile(r"\b(can)\s(not)\b"), r'\1\2'), (re.compile(r"\b(d)\s('ye)\b"), r'\1\2'), (re.compile(r"\b(gim)\s(me)\b"), r'\1\2'), (re.compile(r"\b(gon)\s(na)\b"), r'\1\2'), (re.compile(r"\b(got)\s(ta)\b"), r'\1\2'), (re.compile(r"\b(lem)\s(me)\b"), r'\1\2'), (re.compile(r"\b(mor)\s('n)\b"), r'\1\2'), (re.compile(r"\b(wan)\s(na)\b"), r'\1\2'), # Ending quotes (re.compile(r"([^' ]) ('ll|'re|'ve|n't)\b"), r"\1\2"), (re.compile(r"([^' ]) ('s|'m|'d)\b"), r"\1\2"), (re.compile(r'[ ]?”'), r'"'), # Double dashes (re.compile(r'[ ]?--[ ]?'), r'--'), # Parens and brackets (re.compile(r'([\[\(\{\<]) '), r'\1'), (re.compile(r' ([\]\)\}\>])'), r'\1'), (re.compile(r'([\]\)\}\>]) ([:;,.])'), r'\1\2'), # Punctuation (re.compile(r"([^']) ' "), r"\1' "), (re.compile(r' ([?!\.])'), r'\1'), (re.compile(r'([^\.])\s(\.)([\]\)}>"\']*)\s*$'), r'\1\2\3'), (re.compile(r'([#$]) '), r'\1'), (re.compile(r' ([;%:,])'), r'\1'), # Starting quotes (re.compile(r'(“)[ ]?'), r'"') ] text = ' '.join(tokens) for regexp, substitution in regexes: text = regexp.sub(substitution, text) return text.strip() @staticmethod def word_tokenize(text): # Basic word tokenizer based on the Penn Treebank tokenization script, but # setup to handle multiple sentences. Newline aware, ie newlines are # replaced with a specific token. You may want to consider using a more robust # tokenizer as a preprocessing step, and using the --pristine-input flag. regexes = [ # Starting quotes (re.compile(r'(\s)"'), r'\1 “ '), (re.compile(r'([ (\[{<])"'), r'\1 “ '), # Punctuation (re.compile(r'([:,])([^\d])'), r' \1 \2'), (re.compile(r'([:,])$'), r' \1 '), (re.compile(r'\.\.\.'), r' ... '), (re.compile(r'([;@#$%&])'), r' \1 '), (re.compile(r'([?!\.])'), r' \1 '), (re.compile(r"([^'])' "), r"\1 ' "), # Parens and brackets (re.compile(r'([\]\[\(\)\{\}\<\>])'), r' \1 '), # Double dashes (re.compile(r'--'), r' -- '), # Ending quotes (re.compile(r'"'), r' ” '), (re.compile(r"([^' ])('s|'m|'d) "), r"\1 \2 "), (re.compile(r"([^' ])('ll|'re|'ve|n't) "), r"\1 \2 "), # Contractions (re.compile(r"\b(can)(not)\b"), r' \1 \2 '), (re.compile(r"\b(d)('ye)\b"), r' \1 \2 '), (re.compile(r"\b(gim)(me)\b"), r' \1 \2 '), (re.compile(r"\b(gon)(na)\b"), r' \1 \2 '), (re.compile(r"\b(got)(ta)\b"), r' \1 \2 '), (re.compile(r"\b(lem)(me)\b"), r' \1 \2 '), (re.compile(r"\b(mor)('n)\b"), r' \1 \2 '), (re.compile(r"\b(wan)(na)\b"), r' \1 \2 '), # Newlines (re.compile(r'\n'), r' \\n ') ] text = " " + text + " " for regexp, substitution in regexes: text = regexp.sub(substitution, text) return text.split() def _create_sequences(vector, seq_length, seq_step): # Take strips of our vector at seq_step intervals up to our seq_length # and cut those strips into seq_length sequences passes = [] for offset in range(0, seq_length, seq_step): pass_samples = vector[offset:] num_pass_samples = pass_samples.size // seq_length pass_samples = np.resize(pass_samples, (num_pass_samples, seq_length)) passes.append(pass_samples) # Stack our sequences together. This will technically leave a few "breaks" # in our sequence chain where we've looped over are entire dataset and # return to the start, but with large datasets this should be neglegable return np.concatenate(passes) def shape_for_stateful_rnn(data, batch_size, seq_length, seq_step): """ Reformat our data vector into input and target sequences to feed into our RNN. Tricky with stateful RNNs. """ # Our target sequences are simply one timestep ahead of our input sequences. # eg with an input vector "wherefore"... # targets: herefore # predicts ^ ^ ^ ^ ^ ^ ^ ^ # inputs: wherefor inputs = data[:-1] targets = data[1:] # We split our long vectors into semi-redundant seq_length sequences inputs = _create_sequences(inputs, seq_length, seq_step) targets = _create_sequences(targets, seq_length, seq_step) # Make sure our sequences line up across batches for stateful RNNs inputs = _batch_sort_for_stateful_rnn(inputs, batch_size) targets = _batch_sort_for_stateful_rnn(targets, batch_size) # Our target data needs an extra axis to work with the sparse categorical # crossentropy loss function targets = targets[:, :, np.newaxis] return inputs, targets def _batch_sort_for_stateful_rnn(sequences, batch_size): # Now the tricky part, we need to reformat our data so the first # sequence in the nth batch picks up exactly where the first sequence # in the (n - 1)th batch left off, as the RNN cell state will not be # reset between batches in the stateful model. num_batches = sequences.shape[0] // batch_size num_samples = num_batches * batch_size reshuffled = np.zeros((num_samples, sequences.shape[1]), dtype=np.int32) for batch_index in range(batch_size): # Take a slice of num_batches consecutive samples slice_start = batch_index * num_batches slice_end = slice_start + num_batches index_slice = sequences[slice_start:slice_end, :] # Spread it across each of our batches in the same index position reshuffled[batch_index::batch_size, :] = index_slice return reshuffled def load_data(data_file, word_tokens, pristine_input, pristine_output, batch_size, seq_length=50, seq_step=25): global vectorizer try: with open(data_file, encoding='utf-8') as input_file: text = input_file.read() except FileNotFoundError: print("No input.txt in data_dir") sys.exit(1) skip_validate = True # try: # with open(os.path.join(data_dir, 'validate.txt'), encoding='utf-8') as validate_file: # text_val = validate_file.read() # skip_validate = False # except FileNotFoundError: # pass # Validation text optional # Find some good default seed string in our source text. # self.seeds = find_random_seeds(text) # Include our validation texts with our vectorizer all_text = text if skip_validate else '\n'.join([text, text_val]) vectorizer = Vectorizer(all_text, word_tokens, pristine_input, pristine_output) data = vectorizer.vectorize(text) x, y = shape_for_stateful_rnn(data, batch_size, seq_length, seq_step) print("Word_tokens:", word_tokens) print('x.shape:', x.shape) print('y.shape:', y.shape) if skip_validate: return x, y, None, None, vectorizer data_val = vectorizer.vectorize(text_val) x_val, y_val = shape_for_stateful_rnn(data_val, batch_size, seq_length, seq_step) print('x_val.shape:', x_val.shape) print('y_val.shape:', y_val.shape) return x, y, x_val, y_val, vectorizer def make_model(batch_size, vocab_size, embedding_size=64, rnn_size=128, num_layers=2): # Conversely if your data is large (more than about 2MB), feel confident to increase rnn_size and train a bigger model (see details of training below). # It will work significantly better. For example with 6MB you can easily go up to rnn_size 300 or even more. model = Sequential() model.add(Embedding(vocab_size, embedding_size, batch_input_shape=(batch_size, None))) for layer in range(num_layers): model.add(LSTM(rnn_size, stateful=True, return_sequences=True)) model.add(Dropout(0.2)) model.add(TimeDistributed(Dense(vocab_size, activation='softmax'))) model.compile(loss='sparse_categorical_crossentropy', optimizer='rmsprop', metrics=['accuracy']) return model def train(model, x, y, x_val, y_val, batch_size, num_epochs): print('Training...') # print("Shape:", x.shape, y.shape) # print(num_epochs, batch_size, x[0], y[0]) train_start = time.time() validation_data = (x_val, y_val) if (x_val is not None) else None callbacks = None model.fit(x, y, validation_data=validation_data, batch_size=batch_size, shuffle=False, epochs=num_epochs, verbose=1, callbacks=callbacks) # self.update_sample_model_weights() train_end = time.time() print('Training time', train_end - train_start) def sample_preds(preds, temperature=1.0): """ Samples an unnormalized array of probabilities. Use temperature to flatten/amplify the probabilities. """ preds = np.asarray(preds).astype(np.float64) # Add a tiny positive number to avoid invalid log(0) preds += np.finfo(np.float64).tiny preds = np.log(preds) / temperature exp_preds = np.exp(preds) preds = exp_preds / np.sum(exp_preds) probas = np.random.multinomial(1, preds, 1) return np.argmax(probas) def generate(model, vectorizer, seed, length=100, diversity=0.5): seed_vector = vectorizer.vectorize(seed) # Feed in seed string print("Seed:", seed, end=' ' if vectorizer.word_tokens else '') model.reset_states() preds = None for char_index in np.nditer(seed_vector): preds = model.predict(np.array([[char_index]]), verbose=0) sampled_indices = [] # np.array([], dtype=np.int32) # Sample the model one token at a time for i in range(length): char_index = 0 if preds is not None: char_index = sample_preds(preds[0][0], diversity) sampled_indices.append(char_index) # = np.append(sampled_indices, char_index) preds = model.predict(np.array([[char_index]]), verbose=0) sample = vectorizer.unvectorize(sampled_indices) return sample if __name__ == "__main__": batch_size = 32 # Batch size for each train num_epochs = 10 # Number of epochs of training out_len = 200 # Length of the output phrase seq_length = 50 # 50 # Determines, how long phrases will be used for training use_words = False # Use words instead of characters (slower speed, bigger vocabulary) data_file = "text_habr.txt" # Source text file seed = "A" # Initial symbol of the text parser = argparse.ArgumentParser() parser.add_argument("-t", "--text", action="store", required=False, dest="text", help="Input text file") parser.add_argument("-e", "--epochs", action="store", required=False, dest="epochs", help="Number of training epochs") parser.add_argument("-p", "--phrase_len", action="store", required=False, dest="phrase_len", help="Phrase analyse length") parser.add_argument("-o", "--out_len", action="store", required=False, dest="out_len", help="Output text length") parser.add_argument("-g", "--generate", action="store_true", required=False, dest='generate', help="Generate output only without training") args = parser.parse_args() if args.text is not None: data_file = args.text if args.epochs is not None: num_epochs = int(args.epochs) if args.phrase_len is not None: seq_length = int(args.phrase_len) if args.out_len is not None: out_len = int(args.out_len) # Load text data pristine_input, pristine_output = False, False x, y, x_val, y_val, vectorizer = load_data(data_file, use_words, pristine_input, pristine_output, batch_size, seq_length) model_file = data_file.lower().replace('.txt', '.h5') if args.generate is False: # Make model model = make_model(batch_size, vectorizer.vocab_size) # Train model train(model, x, y, x_val, y_val, batch_size, num_epochs) # Save model to file model.save(filepath=model_file) model = keras.models.load_model(model_file) predict_model = make_model(1, vectorizer.vocab_size) predict_model.set_weights(model.get_weights()) # Generate phrases res = generate(predict_model, vectorizer, seed=seed, length=out_len) print(res) 


Je pense que cela s'est avéré être un générateur de texte de travail très génial , qui est utile pour écrire des articles sur Habr . Il est particulièrement intéressant de tester des textes volumineux et un grand nombre d'itérations de formation.Si quelqu'un a accès à des ordinateurs rapides, il serait intéressant de voir les résultats.

Si quelqu'un veut étudier le sujet plus en détail, une bonne description de l'utilisation de RNN avec des exemples détaillés peut être trouvée à http://karpathy.imtqy.com/2015/05/21/rnn-effectiveness/ .

PS: Et enfin, quelques versets;) Il est intéressant de noter que ce n'est pas moi qui ai fait la mise en forme du texte ou même l'ajout d'étoiles, "c'est moi-même". La prochaine étape est intéressante pour vérifier la possibilité de dessiner des images et de composer de la musique. Je pense que les réseaux de neurones sont assez prometteurs ici.

xxx

pour certains, être pris dans des biscuits - tout porte bonheur dans une cour à pain.
et sous la soirée de tamaki
sous une bougie, prenez une montagne.

xxx

bientôt fils mons à petachas en tram
la lumière invisible sent la joie
c'est pourquoi je frappe ensemble grandit
vous ne serez pas malade d'une inconnue.

coeur à cueillir dans l'ogora décalé,
ce n'est pas si vieux que les céréales mangent,
Je garde le pont vers le ballon pour voler.

de la même manière Darina à Doba,
J'entends dans mon cœur de neige sur ma main.
notre chant blanc combien doux dumina
J'ai détourné le volot de la bête de minerai.

xxx

vétérinaire crucifier fretters avec un sort
et renversé sous l'oubli.
et vous mettez, comme avec les branches de cuba
brille en elle.
o plaisir à zakoto
avec le vol de lait.

oh tu es une rose, lumière
lumière des nuages ​​à portée de main:
et roulé à l'aube
comment allez-vous, mon cavalier!

il sert le soir, pas jusqu'aux os,
la nuit à Tanya la lumière bleue
comme une sorte de tristesse.

Et les derniers versets dans l'apprentissage par mot. Ici, la rime a disparu, mais une signification est apparue (?).

et toi, de la flamme
les étoiles.
parlé à des individus éloignés.

vous inquiète rus ,, vous ,, demain.
"Pluie de colombe,
et la maison des meurtriers,
pour la princesse
son visage.

xxx

oh berger, agite les chambres
sur un bosquet au printemps.

Je traverse le coeur de la maison jusqu'à l'étang,
et les souris guilleret
Cloches de Nijni Novgorod.

mais n'ayez crainte, le vent du matin,
avec un chemin, avec un club de fer,
et pensé avec l'huître
hébergé sur un étang
en rakit appauvri.

Source: https://habr.com/ru/post/fr470035/


All Articles