妄想生成器:使用神经网络以任何语言创建文本

哈Ha

本文将采用“星期五”格式,今天我们将介绍NLP。 NLP不是关于地下通道出售哪些书籍的书,而是有关自然语言处理正在处理自然语言的书。 作为这种处理的示例,将使用使用神经网络的文本生成。 我们可以创建任何语言的文本,从俄语或英语到C ++。 结果非常有趣,您可能可以从图片中猜测出来。



对于那些对发生的事情感兴趣的人,其结果和源代码已被削减。

资料准备


为了进行处理,我们将使用一类特殊的神经网络-所谓的递归神经网络(RNN)。 该网络与常规网络的不同之处在于,除了常规单元之外,它还具有存储单元。 这使我们能够分析更复杂的结构的数据,实际上是更接近人类记忆的数据,因为我们也不是“从头开始”。 编写代码时,我们将使用LSTM (长期短期记忆)网络,因为Keras已支持它们。



实际上,下一个需要解决的问题是处理文本。 这里有两种方法-将符号或整个单词提交给输入。 第一种方法的原理很简单:将文本分为多个短块,其中“输入”是一段文本,而“输出”是下一个字符。 例如,对于最后一个短语,“输入是一段文本”:

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

依此类推。 因此,神经网络在输入处接收文本片段,并在输出处接收应形成的字符。

第二种方法基本相同,只使用整个单词而不是单词。 首先,编辑单词词典,然后在网络输入中输入数字而不是单词。

当然,这是一个相当简化的描述。 Keras 已经有文本生成的示例,但首先,没有对它们进行详细描述,其次,所有英语教程都使用相当抽象的文本,例如莎士比亚,这对于本地人来说很难理解。 好吧,我们正在我们强大而强大的神经网络上测试一个神经网络,它当然会更加清晰和易于理解。

网络培训


作为输入文本,我使用了... ... Habr的注释,源文件的大小为1 MB(当然,注释实际上确实更多,但是我只需要使用一部分,否则该网络将被训练一个星期,而读者在星期五之前将不会看到此文本)。 让我提醒您,只有字母被输入到神经网络的输入中,该网络对语言或其结构一无所知。 我们开始网络培训。

5分钟的训练:

到目前为止,还不清楚,但是您已经可以看到一些可识别的字母组合:

. . . «

15分钟的训练:

结果已经明显好了:



1小时的培训:

« » — « » » —

由于某种原因,所有文本都被证明没有点和大写字母,也许utf-8处理未正确完成。 但总的来说,这令人印象深刻。 通过仅分析和记住符号代码,该程序实际上“独立地”学习了俄语单词,并且可以生成看起来可信的文本。

同样有趣的是,该程序可以很好地记住文本样式。 在下面的示例中,一些法律的文字被用作教具。 网络培训时间为5分钟。

"" , , , , , , , ,

在这里,将药物的医学注释用作输入集。 网络培训时间为5分钟。



, ,

在这里,我们看到几乎整个短语。 这是由于以下事实:原始文本很短,而神经网络实际上“整体”存储了一些短语。 此效果称为“再培训”,应避免。 理想情况下,您需要在大型数据集上测试神经网络,但是在这种情况下进行的训练可能会花费很多小时,而且不幸的是,我没有额外的超级计算机。

使用这种网络的一个有趣示例是名称生成。 通过将男性和女性名字列表上传到文件中,我得到了非常有趣的新选项,这些选项非常适合科幻小说:Rlar,Laaa,Aria,Arera,Aelia,Ninran,Air。 他们身上有些感觉到埃夫雷莫夫和仙女座星云的风格...

C ++


有趣的是,总的来说,神经网络就像记忆。 下一步是检查程序如何处理源代码。 作为测试,我采用了不同的C ++源并将它们组合到一个文本文件中。

老实说,结果比俄语更令人惊讶。

5分钟的训练

该死的,这几乎是真正的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分钟的训练

 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; } 

如您所见,该程序已经“学习”了编写完整的函数。 同时,它完全“人为”地将功能与带星号的注释分开,在代码中添加注释,以及所有这些。 我想以这样的速度学习一种新的编程语言...当然,代码中有错误,当然也不会编译。 顺便说一句,我没有格式化代码,该程序还学会了在方括号和缩进“我自己”中添加文字。

当然,这些程序没有主要含义 - 含义 ,因此看起来像是在梦中写的,或者不是由一个完全健康的人编写的,因此具有超现实感。 然而,结果令人印象深刻。 也许更深入地研究不同文本的产生将有助于更好地理解真实患者的某些精神疾病。 顺便说一句,正如评论中所建议的那样,确实存在这样一种精神疾病,即一个人说出与语法相关但完全没有意义的文本( 精神分裂症 )。

结论


娱乐神经网络被认为非常有前途,与没有内存的MLP等“普通”网络相比,这确实是向前迈出了一大步。 确实,神经网络存储和处理相当复杂的结构的能力令人印象深刻。 正是在这些测试之后,当我写道未来的AI可能是“对人类最大的风险”时,我才以为Ilon Mask可能是正确的-即使简单的神经网络可以轻松记住并复制相当复杂的模式,数十亿个组件的网络能做什么? 但是另一方面,请不要忘记我们的神经网络无法思考 ,它本质上只是机械地记住字符序列,而不是理解它们的含义。 这一点很重要-即使您在超级计算机和庞大的数据集上训练神经网络,充其量也只能学会在语法上生成100%正确但完全没有意义的句子。

但是它不会在哲学上被删除,对于实践者而言,该文章仍然更多。 对于那些想要自己进行实验的人,Python 3.7中的源代码位于破坏者之下。 这段代码是来自各个github项目的汇编,不是最佳代码的示例,但似乎可以执行其任务。

使用该程序不需要编程技能,足以知道如何安装Python。 从命令行开始的示例:
-创建和训练模型以及生成文本:
python \ keras_textgen.py --text = text_habr.txt --epochs = 10 --out_len = 4000
-仅文本生成而无需模型训练:
python \ keras_textgen.py --text = text_habr.txt --epochs = 10 --out_len = 4000-生成

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) 


我认为这是一个非常时髦的工作文本生成器, 对于在Habr上写文章很有用 。 在大文本和大量迭代培训上进行测试特别有趣,如果任何人都可以使用快速计算机,则查看结果将很有趣。

如果有人想更详细地研究该主题,可以在http://karpathy.imtqy.com/2015/05/21/rnn-efficiency/上找到有关使用RNN的详细说明和详细示例。

PS:最后,还有几节经文;)有趣的是,不是我本人来格式化文本甚至添加星号,而是“是我自己”。 下一步很有趣,可以检查绘制图片和创作音乐的可能性。 我认为神经网络在这里很有前途。

xxx

对于某些人来说,它们被夹在饼干中-祝一切顺利。
晚上从玉置
在蜡烛下爬山。

xxx

很快,儿子在蒙大拿州的电车
看不见的欢乐的气味
这就是为什么我在一起成长
您不会为未知而生病。

交错的小仓鼠的心
麦片粥还没老,
我守卫通往抢球的桥梁。

用与多巴(Doba)的达里纳(Darina)相同的方式,
我听见心中有雪。
我们唱白多少柔弱的杜美
我把矿石野兽拒之门外。

xxx

兽医用咒语把油条钉死
并洒在被遗忘的下方。
和你一样,古巴的分支
在其中闪耀。
在zakoto开心
随着牛奶的飞行。

哦,你是一朵玫瑰,轻盈
手上有云光:
在黎明时滚动
你好,我的骑士!

他在晚上服务,而不是骨子里
晚上在Tanya的蓝光
像一种悲伤

最后几节经文学习模式。 在这里,韵律消失了,但是出现了一些含义(?)。

和你,从火焰中
星星。
跟远方的人说话。

担心您的罗斯,明天。
“鸽子雨,
和凶手的家,
给公主的女孩
他的脸。

xxx

噢,牧羊人,摇荡房间
在春天的树林里。

我要穿过房子的中心到池塘
和老鼠活泼
下诺夫哥罗德的钟声。

但不要担心,晨风,
一条小路,一个铁棍,
和牡蛎一起思考
藏在池塘上
在贫困的拉基特。

Source: https://habr.com/ru/post/zh-CN470035/


All Articles