天然语言处理和图像处理不一样,做为人类抽象出来的高级表达形式,它和图像、声音不一样,图像和声音十分直觉,好比图像的像素的颜色表达能够直接量化成数字输入到神经网络中,固然若是是通过压缩的格式jpeg等必须还要通过一个解码的过程才能变成像素的高阶矩阵的形式,而天然语言则不一样,天然语言和数字之间没有那么直接的相关关系,也就不是那么容易做为特征输入到神经网络中去了,因此,用神经网络处理天然语言,不可避免的在数据预处理方面更加繁琐,也更加细致!天然语言处理的另一个不一样之处在于语言之间的相关关系,举一个最简单的例子,在作智能助理机器人的时候,一句“我将在上午十点到达北京”和“我将在上午十点离开北京” 若是你只考虑每一个独立的词汇的话,那么“北京”究竟是做为始发地仍是目的地是不得而知的,也就是说你必须得联系上下文才可以更好的理解整个句子的意思!这也是天然语言的特别之处!固然针对语言的这种特性,也有相应的用于处理的网络——RNN(recurrent neural network)循环神经网络!编程
做为分类到Tensorflow编程实战里的一篇博客,固然以解释代码为主,具体的理论部分这里不过多解释,开门见山,本次处理的数据集是PTB数据集,它是目前语言模型学习中使用的最为普遍的数据集,PTB数据集的下载地址是: http://www.fit.vutbr.cz/~imikolov/rnnlm/simple-examples.tgz 网络
将数据集解压以后将会获得好几个文件夹,重点看/data文件夹下的三个文件,ptb.test.txt, ptb.train.txt, ptb.valid.txt 这三个数据文件已经通过了预处理,相邻的单词之间用空格隔开,以ptb.train.txt为例,看一下它里面的具体内容:ide
no it was n't black monday but while the new york stock exchange did n't fall apart friday as the dow jones industrial average plunged N points most of it in the final hour it barely managed to stay this side of chaos 学习
some circuit breakers installed after the october N crash failed their first test traders say unable to cool the selling panic in both stocks and futures
ui
the N stock specialist firms on the big board floor this
数据集中包含了9998个词汇,加上稀有词语的特殊符号 <unk>和语句的结束标记,一共是10000个! 为了可以将数据输入到神经网络,咱们首先须要作的就是这10000个词汇按照其出现的频率进行编号,造成词汇表,而后按照词汇表的对应关系,将train/test/valid数据集分别转换成编号的形式! 编码
按照单词出现的频率进行编号的代码以下:spa
1 import codecs 2 import collections 3 from operator import itemgetter 4 5 RAW_DATA = "C:\\Users\\Yang\\Desktop\\nlp\\ptb.train.txt" #数据的输入路径/ 6 VOCAB_OUTPUT ="ptb.vocab" #词汇表的输出路径 7 8 counter = collections.Counter() #counter 顾名思义是一个计数器 9 10 with codecs.open(RAW_DATA,"r","utf-8") as f: 以read的方式,utf-8编码的形式打开上述路径 11 for line in f: #读行 12 for word in line.strip().split(): #以空格做为划分,将文件里面的每个词汇都切开! strip()默认用于去掉收尾的空格和换行符 13 counter[word] += 1 #统计单词出现的次数 14 15 sorted_word_to_cnt = sorted(counter.items(),key=itemgetter(1),reverse=True) 按照itemgetter(1)属性进行排序,应该就是按照单词出现的次数排序,例如:and:981 16 sorted_words = [x[0] for x in sorted_word_to_cnt] #之因此取x[0]是由于格式是 the:356 这种形式,x[0]就是为了将单词取出来 排好序的word 17 18 sorted_words = ["<eos>"] + sorted_words #将句子的结束符添加到排好序的list中 19 #由于PTB数据中已经将低频词汇替换成了<"unk">因此下面这步不必 <"eos">是句子结束符 <"sos">是句子开始符 <"unk">是句子的低频词汇 20 #sorted_words = ["<unk>","<sos>","<eos>"] + sorted_words 21 #if len(sorted_words) >10000: 22 #sorted_words = sorted_words[:10000] 标红的这三句不必出现! 23 24 with codecs.open(VOCAB_OUTPUT,"w",'utf-8') as file_output : 25 26 for word in sorted_words: 27 file_output.write(word + "\n") #将排序好的词汇再写回输出的文件目录里面,那么就算完成了词汇对应表的构建
写完了词汇表的构建代码以后,咱们还须要一个将ptb.train/test/valid.txt文件转换成对应的编号的过程! 每个词汇对应的编号就是其在词汇表里面对应的行号! 注意到以前每个word的输出后面都跟着"\n"3d
下面就来实现将单词转换成对应的编号的部分的代码:code
1 import codecs 2 import sys 3 4 RAW_DATA = 'C:\\Users\\Yang\\Desktop\\nlp\\data\\ptb.valid.txt' #这里是待转换的文件的目录/相应的改为ptb.test.txt/ptb.train.txt能够用于其余的转换 5 VOCAB = 'ptb.vocab' #词汇表的目录 6 OUTPUT_DATA = 'ptb.valid' 用于输出的目录 7 8 9 with codecs.open(VOCAB,'r','utf-8') as f_vocab: #首先就是将词汇表以read和utf-8编码的方式打开 10 vocab = [w.strip() for w in f_vocab.readlines()] #我怎么感受这里只是读到了一行而后进行收尾空格换行符去掉的处理??? 11 #哦,我明白了 由于VOCAB中词汇的存储格式就是一个单词占一行,因此出来的就是一个个单词而不须要.split() 12 word_to_id = {k:v for (k,v) in zip(vocab,range(len(vocab)))} #转换成了单词序号的字典形式 这里是完成了词汇和其对应编号的对应! 13 14 #将词汇转换成了对应的行号序号 15 def get_id(word): 16 return word_to_id[word] if word in word_to_id else word_to_id["<unk>"] #若是是在词汇表里面的就将它转换成对应的编号,不然的话就转换成unk对应的编号 17 18 fin = codecs.open(RAW_DATA,"r","utf-8") #打开待转换文件 19 fout = codecs.open(OUTPUT_DATA,"w",'utf-8') #输出文件目录 20 21 for line in fin: 22 words = line.strip().split() + ["<eos>"] #打开的文件读取每一行而后首尾去除换行符和空格以后对空格进行切分,在末尾加上eos! 23 out_line = ' '.join([str(get_id(w)) for w in words]) +'\n' 对其的行进行转换 24 fout.write(out_line) 而后写入到对应的输出文件里面 25 26 fin.close() 27 fout.close()
咱们来看一下运行完这个代码以后咱们的ptb.train.txt变成了什么样子:
9994 9996 9974 9999 9972 9978 9981 9993 9977 9973 9985 9998 9992 9971 9997 9990 9995 9970 9989 9987 9988 9975 9980 9986 0
没错,所有变成了词汇表对应的行号编号的形式!
进行完上述两步处理以后,咱们已经完成了数据预处理部分的一半了,接下来咱们须要考虑的问题就是,处理后的编码数据不可能直接输入到网络里面去,网络接受的通常都是许多batch,可是在将数据打包成batch
的时候,又有一个十分严峻的问题须要咱们考虑,那就是到底以多长的长度来打包数据呢? 每条句子的长度是不同的,该如何打包呢? 有两种比较常见的作法,一种是根据batch_size的大小,选取里面最长的句子为
参考,剩下的padding成统一的长度!长度统一以后就能够用于batch了!
而PTB数据是一整段一整段文本,若是一句一句的进行切分的话,会破坏掉数据之间的上下文的关联,而语言模型为了利用上下文信息,必须将前面的信息传递到后面的句子中去,因此PTB一般采用的是另一种Batch的方法!
该方法将长序列切割成固定长度的子序列,而后循环神经网络处理完这个子序列以后将最后一个隐藏层的结果复制到下一个序列中做为初始值,这样在前向计算的过程当中效果就等同于一次性的读取完了所有的文档!
可是若是这样作的话会面临另一个问题,整个数据流都是串行流动的,这对于能够并行计算的tensorflow来讲简直太浪费了,因此,兼顾两者的作法是,根据batch_size的大小将整个文档划分红batch_size部分!而后让batch_size的每个位置负责一部分数据,若是处理不完,继续横向移动到下一个batch中去,也就是说,对于batch来说数据之间横向才是连贯的,纵向实际上是互不相干的!这样既可以保证数据之间的上下文关系,也可以保证tensorflow能够并行处理数据!
画一个示意图的话,大概是下面这样: