遞歸神經(jīng)網(wǎng)絡(luò)可存儲記憶神經(jīng)網(wǎng)絡(luò),LSTM是其中一種,在NLP領(lǐng)域應(yīng)用效果不錯(cuò)。
遞歸神經(jīng)網(wǎng)絡(luò)(RNN),時(shí)間遞歸神經(jīng)網(wǎng)絡(luò)(recurrent neural network),結(jié)構(gòu)遞歸神經(jīng)網(wǎng)絡(luò)(recursive neural network)。時(shí)間遞歸神經(jīng)網(wǎng)絡(luò)神經(jīng)元間連接構(gòu)成有向圖,結(jié)構(gòu)遞歸神經(jīng)網(wǎng)絡(luò)利用相似神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)遞歸構(gòu)造更復(fù)雜深度網(wǎng)絡(luò)。兩者訓(xùn)練屬同一算法變體。
時(shí)間遞歸神經(jīng)網(wǎng)絡(luò)。傳統(tǒng)神經(jīng)網(wǎng)絡(luò)FNN(Feed-Forward Neural Networks),前向反饋神經(jīng)網(wǎng)絡(luò)。RNN引入定向循環(huán),神經(jīng)元為節(jié)點(diǎn)組成有向環(huán),可表達(dá)前后關(guān)聯(lián)關(guān)系。隱藏層節(jié)點(diǎn)間構(gòu)成全連接,一個(gè)隱藏層節(jié)點(diǎn)輸出可作另一個(gè)隱藏層節(jié)點(diǎn)或自己的輸入。U、V、W是變換概率矩陣,x是輸入,o是輸出。RNN關(guān)鍵是隱藏層,隱藏層捕捉序列信息,記憶能力。RNN中U、V、W參數(shù)共享,每一步都在做相同事情,輸入不同,降低參數(shù)個(gè)數(shù)和計(jì)算量。RNN在NLP應(yīng)用較多,語言模型在已知已出現(xiàn)詞情況下預(yù)測下一個(gè)詞概率,是時(shí)序模型,下一個(gè)詞出現(xiàn)取決于前幾個(gè)詞,對應(yīng)RNN隱藏層間內(nèi)部連接。
RNN的訓(xùn)練方法。用BP誤差反向傳播算法更新訓(xùn)練參數(shù)。從輸入到輸出經(jīng)過步驟不確定,利用時(shí)序方式做前向計(jì)算,假設(shè)x表示輸入值,s表示輸入x經(jīng)過U矩陣變換后值,h表示隱藏層激活值,o表示輸出層值, f表示隱藏層激活函數(shù),g表示輸出層激活函數(shù)。當(dāng)t=0時(shí),輸入為x0, 隱藏層為h0。當(dāng)t=1時(shí),輸入為x1, s1 = Ux1+Wh0, h1 = f(s1), o1 = g(Vh1)。當(dāng)t=2時(shí),s2 = Ux2+Wh1, h2 = f(s2), o2 = g(Vh2)。st = Uxt + Wh(t-1), ht = f(st), ot = g(Vht)。h=f(現(xiàn)有的輸入+過去記憶總結(jié)),對RNN記憶能力全然體現(xiàn)。
UVW變換概率矩陣,x輸入,s xU矩陣變換后值,f隱藏層激活函數(shù),h隱藏層激活值,g輸出層激活函數(shù),o輸出。時(shí)間、輸入、變換(輸入、前隱藏)、隱藏(變換)、輸出(隱藏)。輸出(隱藏(變換(時(shí)間、輸入、前隱藏)))。反向修正參數(shù),每一步輸出o和實(shí)際o值誤差,用誤差反向推導(dǎo),鏈?zhǔn)角髮?dǎo)求每層梯度,更新參數(shù)。
LSTM(Long Short Tem Momery networks)。RNN存在長序列依賴(Long-Term Dependencies)問題。下一個(gè)詞出現(xiàn)概率和非常久遠(yuǎn)之前詞有關(guān),考慮到計(jì)算量,限制依賴長度。http://colah.github.io/posts/2015-08-Understanding-LSTMs 。傳統(tǒng)RNN示意圖,只包含一個(gè)隱藏層,tanh為激發(fā)函數(shù),“記憶”體現(xiàn)在t滑動窗口,有多少個(gè)t就有多少記憶。
LSTM設(shè)計(jì),神經(jīng)網(wǎng)絡(luò)層(權(quán)重系數(shù)和激活函數(shù),σ表示sigmoid激活函數(shù),tanh表示tanh激活函數(shù)),矩陣運(yùn)算(矩陣乘或矩陣加)。歷史信息傳遞和記憶,調(diào)大小閥門(乘以一個(gè)0到1之間系數(shù)),第一個(gè)sigmoid層計(jì)算輸出0到1之間系數(shù),作用到×門,這個(gè)操作表達(dá)上一階段傳遞過來的記憶保留多少,忘掉多少。忘掉記憶多少取決上一隱藏層輸出h{t-1}和本層的輸入x{t}。上一層輸出h{t-1}和本層的輸入x{t}得出新信息,存到記憶。計(jì)算輸出值Ct部分tanh神經(jīng)元和計(jì)算比例系數(shù)sigmoid神經(jīng)元(sigmoid取值范圍是[0,1]作比例系數(shù),tanh取值范圍[-1,1]作一個(gè)輸出值)。隱藏層輸出h計(jì)算,考慮當(dāng)前全部信息(上一時(shí)序隱藏層輸出、本層輸入x和當(dāng)前整體記憶信息),本單元狀態(tài)部分C通過tanh激活并做一個(gè)過濾(上一時(shí)序輸出值和當(dāng)前輸入值通過sigmoid激活系數(shù))。一句話詞是不同時(shí)序輸入x,在某一時(shí)間t出現(xiàn)詞A概率可LSTM計(jì)算,詞A出現(xiàn)概率取決前面出現(xiàn)過詞,取決前面多少個(gè)詞不確定,LSTM存儲記憶信息C,得出較接近概率。
聊天機(jī)器人是范問答系統(tǒng)。
語料庫獲取。范問答系統(tǒng),一般從互聯(lián)網(wǎng)收集語料信息,比如百度、谷歌,構(gòu)建問答對組成語料庫。語料庫分成多訓(xùn)練集、開發(fā)集、測試集。問答系統(tǒng)訓(xùn)練在一堆答案里找一個(gè)正確答案模型。訓(xùn)練過程不把所有答案都放到一個(gè)向量空間,做分組,在語料庫里采集樣本,收集每一個(gè)問題對應(yīng)500個(gè)答案集合,500個(gè)里面有正向樣本,隨機(jī)選些負(fù)向樣本,突出正向樣本作用。
基于CNN系統(tǒng)設(shè)計(jì),sparse interaction(稀疏交互),parameter sharing(參數(shù)共享),equivalent respresentation(等價(jià)表示),適合自動問答系統(tǒng)答案選擇模型訓(xùn)練。
通用訓(xùn)練方法。訓(xùn)練時(shí)獲取問題詞向量Vq(詞向量可用google word2vec訓(xùn)練,和一個(gè)正向答案詞向量Va+,和一個(gè)負(fù)向答案詞向量Va-, 比較問題和兩個(gè)答案相似度,兩個(gè)相似度差值大于一個(gè)閾值m更新模型參數(shù),在候選池里選答案,小于m不更新模型。參數(shù)更新,梯度下降、鏈?zhǔn)角髮?dǎo)。測試數(shù)據(jù),計(jì)算問題和候選答案cos距離,相似度最大是正確答案預(yù)測。
神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)設(shè)計(jì)。HL hide layer隱藏層,激活函數(shù)z = tanh(Wx+B),CNN 卷積層,P 池化層,池化步長 1,T tanh層,P+T輸出是向量表示,最終輸出兩個(gè)向量cos相似度。HL或CNN連起來表示共享相同權(quán)重。CNN輸出維數(shù)取決做多少卷積特征。論文《Applying Deep Learning To Answer Selection- A Study And An Open Task》。
深度學(xué)習(xí)運(yùn)用到聊天機(jī)器人中,1. 神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)選擇、組合、優(yōu)化。2. 自然語言處理,機(jī)器識別詞向量。3. 相似或匹配關(guān)系考慮相似度計(jì)算,典型方法 cos距離。4. 文本序列全局信息用CNN或LSTM。5. 精度不高可加層。6. 計(jì)算量過大,參數(shù)共享和池化。
聊天機(jī)器人學(xué)習(xí),需要海量聊天語料庫。美劇字幕。外文電影或電視劇字幕文件是天然聊天語料,對話比較多美劇最佳。字幕庫網(wǎng)站www.zimuku.net。
自動抓取字幕。抓取器代碼(https://github.com/warmheartli/ChatBotCourse)。在subtitle下創(chuàng)建目錄result,scrapy.Request
方法調(diào)用時(shí)增加傳參 dont_filter=True:
# coding:utf-8
import sys
import importlib
importlib.reload(sys)
import scrapy
from subtitle_crawler.items import SubtitleCrawlerItem
class SubTitleSpider(scrapy.Spider):
name = "subtitle"
allowed_domains = ["zimuku.net"]
start_urls = [
"http://www.zimuku.net/search?q=&t=onlyst&ad=1&p=20",
"http://www.zimuku.net/search?q=&t=onlyst&ad=1&p=21",
"http://www.zimuku.net/search?q=&t=onlyst&ad=1&p=22",
]
def parse(self, response):
hrefs = response.selector.xpath('//div[contains(@class, "persub")]/h1/a/@href').extract()
for href in hrefs:
url = response.urljoin(href)
request = scrapy.Request(url, callback=self.parse_detail, dont_filter=True)
yield request
def parse_detail(self, response):
url = response.selector.xpath('//li[contains(@class, "dlsub")]/div/a/@href').extract()[0]
print("processing: ", url)
request = scrapy.Request(url, callback=self.parse_file, dont_filter=True)
yield request
def parse_file(self, response):
body = response.body
item = SubtitleCrawlerItem()
item['url'] = response.url
item['body'] = body
return item
# -*- coding: utf-8 -*-
class SubtitleCrawlerPipeline(object):
def process_item(self, item, spider):
url = item['url']
file_name = url.replace('/','_').replace(':','_')+'.rar'
fp = open('result/'+file_name, 'wb+')
fp.write(item['body'])
fp.close()
return item
ls result/|head -1 , ls result/|wc -l , du -hs result/ 。
字幕文件解壓,linux直接執(zhí)行unzip file.zip。linux解壓rar文件,http://www.rarlab.com/download.htm 。wget http://www.rarlab.com/rar/rarlinux-x64-5.4.0.tar.gz 。tar zxvf rarlinux-x64-5.4.0.tar.gz
./rar/unrar 。解壓命令,unrar x file.rar 。linux解壓7z文件,http://downloads.sourceforge.net/project/p7zip 下載源文件,解壓執(zhí)行make編譯 bin/7za可用,用法 bin/7za x file.7z。
程序和腳本在https://github.com/warmheartli/ChatBotCourse 。第一步:爬取影視劇字幕。第二步:壓縮格式分類。文件多無法ls、文件名帶特殊字符、文件名重名誤覆蓋、擴(kuò)展名千奇百怪,python腳本mv_zip.py:
import glob
import os
import fnmatch
import shutil
import sys
def iterfindfiles(path, fnexp):
for root, dirs, files in os.walk(path):
for filename in fnmatch.filter(files, fnexp):
yield os.path.join(root, filename)
i=0
for filename in iterfindfiles(r"./input/", "*.ZIP"):
i=i+1
newfilename = "zip/" + str(i) + "_" + os.path.basename(filename)
print(filename + " <===> " + newfilename)
shutil.move(filename, newfilename)
#sys.exit(-1)
擴(kuò)展名根據(jù)壓縮文件修改.rar、.RAR、.zip、.ZIP。第三步:解壓。根據(jù)操作系統(tǒng)下載不同解壓工具,建議unrar和unzip,腳本來實(shí)現(xiàn)批量解壓:
i=0; for file in `ls`; do mkdir output/${i}; echo "unzip $file -d output/${i}";unzip -P abc $file -d output/${i} > /dev/null; ((i++)); done
i=0; for file in `ls`; do mkdir output/${i}; echo "${i} unrar x $file output/${i}";unrar x $file output/${i} > /dev/null; ((i++)); done
第四步:srt、ass、ssa字幕文件分類整理。字幕文件類型srt、lrc、ass、ssa、sup、idx、str、vtt。第五步:清理目錄。自動清理空目錄腳本clear_empty_dir.py :
import glob
import os
import fnmatch
import shutil
import sys
def iterfindfiles(path, fnexp):
for root, dirs, files in os.walk(path):
if 0 == len(files) and len(dirs) == 0:
print(root)
os.rmdir(root)
iterfindfiles(r"./input/", "*.srt")
第六步:清理非字幕文件。批量刪除腳本del_file.py :
import glob
import os
import fnmatch
import shutil
import sys
def iterfindfiles(path, fnexp):
for root, dirs, files in os.walk(path):
for filename in fnmatch.filter(files, fnexp):
yield os.path.join(root, filename)
for suffix in ("*.mp4", "*.txt", "*.JPG", "*.htm", "*.doc", "*.docx", "*.nfo", "*.sub", "*.idx"):
for filename in iterfindfiles(r"./input/", suffix):
print(filename)
os.remove(filename)
第七步:多層解壓縮。第八步:舍棄剩余少量文件。無擴(kuò)展名、特殊擴(kuò)展名、少量壓縮文件,總體不超過50M。第九步:編碼識別與轉(zhuǎn)碼。utf-8、utf-16、gbk、unicode、iso8859,統(tǒng)一utf-8,get_charset_and_conv.py :
import chardet
import sys
import os
if __name__ == '__main__':
if len(sys.argv) == 2:
for root, dirs, files in os.walk(sys.argv[1]):
for file in files:
file_path = root + "/" + file
f = open(file_path,'r')
data = f.read()
f.close()
encoding = chardet.detect(data)["encoding"]
if encoding not in ("UTF-8-SIG", "UTF-16LE", "utf-8", "ascii"):
try:
gb_content = data.decode("gb18030")
gb_content.encode('utf-8')
f = open(file_path, 'w')
f.write(gb_content.encode('utf-8'))
f.close()
except:
print("except:", file_path)
第十步:篩選中文。extract_sentence_srt.py :
# coding:utf-8
import chardet
import os
import re
cn=ur"([u4e00-u9fa5]+)"
pattern_cn = re.compile(cn)
jp1=ur"([u3040-u309F]+)"
pattern_jp1 = re.compile(jp1)
jp2=ur"([u30A0-u30FF]+)"
pattern_jp2 = re.compile(jp2)
for root, dirs, files in os.walk("./srt"):
file_count = len(files)
if file_count > 0:
for index, file in enumerate(files):
f = open(root + "/" + file, "r")
content = f.read()
f.close()
encoding = chardet.detect(content)["encoding"]
try:
for sentence in content.decode(encoding).split('n'):
if len(sentence) > 0:
match_cn = pattern_cn.findall(sentence)
match_jp1 = pattern_jp1.findall(sentence)
match_jp2 = pattern_jp2.findall(sentence)
sentence = sentence.strip()
if len(match_cn)>0 and len(match_jp1)==0 and len(match_jp2) == 0 and len(sentence)>1 and len(sentence.split(' ')) < 10:
print(sentence.encode('utf-8'))
except:
continue
第十一步:字幕中句子提取。
# coding:utf-8
import chardet
import os
import re
cn=ur"([u4e00-u9fa5]+)"
pattern_cn = re.compile(cn)
jp1=ur"([u3040-u309F]+)"
pattern_jp1 = re.compile(jp1)
jp2=ur"([u30A0-u30FF]+)"
pattern_jp2 = re.compile(jp2)
for root, dirs, files in os.walk("./ssa"):
file_count = len(files)
if file_count > 0:
for index, file in enumerate(files):
f = open(root + "/" + file, "r")
content = f.read()
f.close()
encoding = chardet.detect(content)["encoding"]
try:
for line in content.decode(encoding).split('n'):
if line.find('Dialogue') == 0 and len(line) < 500:
fields = line.split(',')
sentence = fields[len(fields)-1]
tag_fields = sentence.split('}')
if len(tag_fields) > 1:
sentence = tag_fields[len(tag_fields)-1]
match_cn = pattern_cn.findall(sentence)
match_jp1 = pattern_jp1.findall(sentence)
match_jp2 = pattern_jp2.findall(sentence)
sentence = sentence.strip()
if len(match_cn)>0 and len(match_jp1)==0 and len(match_jp2) == 0 and len(sentence)>1 and len(sentence.split(' ')) < 10:
sentence = sentence.replace('N', '')
print(sentence.encode('utf-8'))
except:
continue
第十二步:內(nèi)容過濾。過濾特殊unicode字符、關(guān)鍵詞、去除字幕樣式標(biāo)簽、html標(biāo)簽、連續(xù)特殊字符、轉(zhuǎn)義字符、劇集信息:
# coding:utf-8
import sys
import re
import chardet
if __name__ == '__main__':
#illegal=ur"([u2000-u2010]+)"
illegal=ur"([u0000-u2010]+)"
pattern_illegals = [re.compile(ur"([u2000-u2010]+)"), re.compile(ur"([u0090-u0099]+)")]
filters = ["字幕", "時(shí)間軸:", "校對:", "翻譯:", "后期:", "監(jiān)制:"]
filters.append("時(shí)間軸:")
filters.append("校對:")
filters.append("翻譯:")
filters.append("后期:")
filters.append("監(jiān)制:")
filters.append("禁止用作任何商業(yè)盈利行為")
filters.append("http")
htmltagregex = re.compile(r'<[^>]+>',re.S)
brace_regex = re.compile(r'{.*}',re.S)
slash_regex = re.compile(r'\w',re.S)
repeat_regex = re.compile(r'[-=]{10}',re.S)
f = open("./corpus/all.out", "r")
count=0
while True:
line = f.readline()
if line:
line = line.strip()
# 編碼識別,不是utf-8就過濾
gb_content = ''
try:
gb_content = line.decode("utf-8")
except Exception as e:
sys.stderr.write("decode error: ", line)
continue
# 中文識別,不是中文就過濾
need_continue = False
for pattern_illegal in pattern_illegals:
match_illegal = pattern_illegal.findall(gb_content)
if len(match_illegal) > 0:
sys.stderr.write("match_illegal error: %sn" % line)
need_continue = True
break
if need_continue:
continue
# 關(guān)鍵詞過濾
need_continue = False
for filter in filters:
try:
line.index(filter)
sys.stderr.write("filter keyword of %s %sn" % (filter, line))
need_continue = True
break
except:
pass
if need_continue:
continue
# 去掉劇集信息
if re.match('.*第.*季.*', line):
sys.stderr.write("filter copora %sn" % line)
continue
if re.match('.*第.*集.*', line):
sys.stderr.write("filter copora %sn" % line)
continue
if re.match('.*第.*幀.*', line):
sys.stderr.write("filter copora %sn" % line)
continue
# 去html標(biāo)簽
line = htmltagregex.sub('',line)
# 去花括號修飾
line = brace_regex.sub('', line)
# 去轉(zhuǎn)義
line = slash_regex.sub('', line)
# 去重復(fù)
new_line = repeat_regex.sub('', line)
if len(new_line) != len(line):
continue
# 去特殊字符
line = line.replace('-', '').strip()
if len(line) > 0:
sys.stdout.write("%sn" % line)
count+=1
else:
break
f.close()
pass
參考資料:
《Python 自然語言處理》
http://www.shareditor.com/blogshow?blogId=103
http://www.shareditor.com/blogshow?blogId=104
http://www.shareditor.com/blogshow?blogId=105
http://www.shareditor.com/blogshow?blogId=112
歡迎推薦上海機(jī)器學(xué)習(xí)工作機(jī)會,我的微信:qingxingfengzi