tensorflow實現 聊天AI--PigPig養成記(1)

Chapter1.代碼詳解

完整代碼github鏈接,Untitled.ipynb文件內。
【裏面的測試是還沒訓練完的時候測試的,今晚會更新訓練完成後的測試結果】
修復了網上一些代碼的bug,解決了由於tensorflow版本不同引起的一些問題。

數據集鏈接 ,下載數據集後,解壓提取dgk_shooter_min.conv文件,最好進行轉碼操作。建議用記事本打開後將其另存爲,選擇編碼爲utf-8後進行保存。


代碼詳解

(1)數據預處理

#coding=utf-8
#(1)數據預處理
import os
import random
from io import open
conv_path = 'dgk_shooter_min.conv.txt'
#判斷數據集是否存在?
if not os.path.exists(conv_path):
    print('數據集不存在')
    exit()
 
# 數據集格式
"""
E
M 畹/華/吾/侄/
M 你/接/到/這/封/信/的/時/候/
M 不/知/道/大/伯/還/在/不/在/人/世/了/
E
M 咱/們/梅/家/從/你/爺/爺/起/
M 就/一/直/小/心/翼/翼/地/唱/戲/
M 侍/奉/宮/廷/侍/奉/百/姓/
M 從/來/不/曾/遭/此/大/禍/
M 太/後/的/萬/壽/節/誰/敢/不/穿/紅/
M 就/你/膽/兒/大/
M 唉/這/我/舅/母/出/殯/
M 我/不/敢/穿/紅/啊/
M 唉/呦/唉/呦/爺/
M 您/打/得/好/我/該/打/
M 就/因/爲/沒/穿/紅/讓/人/賞/咱/一/紙/枷/鎖/
M 爺/您/別/給/我/戴/這/紙/枷/鎖/呀/
E
M 您/多/打/我/幾/下/不/就/得/了/嗎/
M 走/
M 這/是/哪/一/出/啊/…/ / /這/是/
M 撕/破/一/點/就/弄/死/你/
M 唉/
M 記/着/唱/戲/的/再/紅/
M 還/是/讓/人/瞧/不/起/
M 大/伯/不/想/讓/你/挨/了/打/
M 還/得/跟/人/家/說/打/得/好/
M 大/伯/不/想/讓/你/再/戴/上/那/紙/枷/鎖/
M 畹/華/開/開/門/哪/
E
...
"""
 
# 我首先使用文本編輯器sublime把dgk_shooter_min.conv文件編碼轉爲UTF-8,一下子省了不少麻煩
convs = []  # 對話集合
with open(conv_path, encoding="utf8") as f:
    one_conv = []  # 一次完整對話
    for line in f:
        line = line.strip('\n').replace('/', '')#將分隔符去掉
        if line == '':
           continue
        if line[0] == 'E':
           if one_conv:
              convs.append(one_conv)
           one_conv = []
        elif line[0] == 'M':
           one_conv.append(line.split(' ')[1])
#將對話轉成utf-8格式,並將其保存在dgk_shooter_min.conv文件中
 

print(convs[:3])  # 個人感覺對白數據集有點不給力啊
#[ ['畹華吾侄', '你接到這封信的時候', '不知道大伯還在不在人世了'],
#  ['咱們梅家從你爺爺起', '就一直小心翼翼地唱戲', '侍奉宮廷侍奉百姓', '從來不曾遭此大禍', '太后的萬壽節誰敢不穿紅', '就你膽兒大', '唉這我舅母出殯', '我不敢穿紅啊', '唉呦唉呦爺', '您打得好我該打', '就因爲沒穿紅讓人賞咱一紙枷鎖', '爺您別給我戴這紙枷鎖呀'],
#  ['您多打我幾下不就得了嗎', '走', '這是哪一齣啊 ', '撕破一點就弄死你', '唉', '記着唱戲的再紅', '還是讓人瞧不起', '大伯不想讓你捱了打', '還得跟人家說打得好', '大伯不想讓你再戴上那紙枷鎖', '畹華開開門哪'], ....]

 
# 把對話分成問與答
ask = []        # 問
response = []   # 答
for conv in convs:
  if len(conv) == 1:
     continue
  if len(conv) % 2 != 0:  # 奇數對話數, 轉爲偶數對話
     conv = conv[:-1]
  for i in range(len(conv)):
     if i % 2 == 0:
        ask.append(conv[i])#偶數對,填寫問題
     else:
        response.append(conv[i])#回答
 

print(len(ask), len(response))
print(ask[:3])
print(response[:3])
#['畹華吾侄', '咱們梅家從你爺爺起', '侍奉宮廷侍奉百姓']
#['你接到這封信的時候', '就一直小心翼翼地唱戲', '從來不曾遭此大禍']

 
 
def convert_seq2seq_files(questions, answers, TESTSET_SIZE=8000):
    # 創建文件
    train_enc = open('train.enc', 'w',encoding='utf-8')  # 問
    train_dec = open('train.dec', 'w',encoding='utf-8')  # 答
    test_enc = open('test.enc', 'w',encoding='utf-8')  # 問
    test_dec = open('test.dec', 'w',encoding='utf-8')  # 答
 
    # 選擇8000數據作爲測試數據
    test_index = random.sample([i for i in range(len(questions))], TESTSET_SIZE)
 
    for i in range(len(questions)):
        if i in test_index:#創建測試文件
            test_enc.write(questions[i] + '\n')
            test_dec.write(answers[i] + '\n')
        else:#創建訓練文件
            train_enc.write(questions[i] + '\n')
            train_dec.write(answers[i] + '\n')
        if i % 1000 == 0:#表示處理了多少個i
            print(len(range(len(questions))), '處理進度:', i)
 
    train_enc.close()
    train_dec.close()
    test_enc.close()
    test_dec.close()
 
 
convert_seq2seq_files(ask, response)
# 生成的*.enc文件保存了問題
# 生成的*.dec文件保存了回答

將數據集進行處理後分成問與答的形式進行保存,選擇其中的8000數據作爲測試數據。處理完畢後生成的.enc文件保存了問題,.dec文件保存了回答。
問題文件*.enc預覽:

爺爺您戲改得真好
您怎麼不進去呀
王老闆
見過
地球再也無法承受人類的數量
我現在是和摩蘭達說話嗎?
我們不是告訴他們應該想什麼

回答文件*.dec預覽:

這回跟您可真是一棵菜了
我等人拿鑰匙呢
唉
什麼事
我們發現了一個新的太陽系
不是
我們僅僅是想告訴他們應該怎麼想


(2)創建詞彙表

#coding=utf-8
#(2)創建詞彙表
# 前一步生成的問答文件路徑
train_encode_file = 'train.enc'
train_decode_file = 'train.dec'
test_encode_file = 'test.enc'
test_decode_file = 'test.dec'
 
print('開始創建詞彙表...')
# 特殊標記,用來填充標記對話
PAD = "__PAD__"
GO = "__GO__"
EOS = "__EOS__"  # 對話結束
UNK = "__UNK__"  # 標記未出現在詞彙表中的字符
START_VOCABULART = [PAD, GO, EOS, UNK]
PAD_ID = 0
GO_ID = 1
EOS_ID = 2
UNK_ID = 3
# 參看tensorflow.models.rnn.translate.data_utils
 
vocabulary_size = 5000
 
 
# 生成詞彙表文件
def gen_vocabulary_file(input_file, output_file):
    vocabulary = {}
    with open(input_file, encoding="utf8") as f:
        counter = 0
        for line in f:
           counter += 1
           tokens = [word for word in line.strip()]
           for word in tokens:
               if word in vocabulary:
                  vocabulary[word] += 1
               else:
                  vocabulary[word] = 1
        vocabulary_list = START_VOCABULART + sorted(vocabulary, key=vocabulary.get, reverse=True)
        # 取前5000個常用漢字, 應該差不多夠用了(額, 好多無用字符, 最好整理一下. 我就不整理了)
        if len(vocabulary_list) > 5000:
           vocabulary_list = vocabulary_list[:5000]
        print(input_file + " 詞彙表大小:", len(vocabulary_list))
        with open(output_file, "w", encoding="utf8") as ff:
           for word in vocabulary_list:
               ff.write(word + "\n")
 
 
gen_vocabulary_file(train_encode_file, "train_encode_vocabulary")
gen_vocabulary_file(train_decode_file, "train_decode_vocabulary")
 
train_encode_vocabulary_file = 'train_encode_vocabulary'
train_decode_vocabulary_file = 'train_decode_vocabulary'
 
print("對話轉向量...")
 
 
# 把對話字符串轉爲向量形式
def convert_to_vector(input_file, vocabulary_file, output_file):
    tmp_vocab = []
    with open(vocabulary_file, "r", encoding="utf8") as f:
        tmp_vocab.extend(f.readlines())
    tmp_vocab = [line.strip() for line in tmp_vocab]
    vocab = dict([(x, y) for (y, x) in enumerate(tmp_vocab)])
    # {'碩': 3142, 'v': 577, 'I': 4789, '\ue796': 4515, '拖': 1333, '疤': 2201 ...}
    output_f = open(output_file, 'w')
    with open(input_file, 'r', encoding="utf8") as f:
        for line in f:
            line_vec = []
            for words in line.strip():
                line_vec.append(vocab.get(words, UNK_ID))
            output_f.write(" ".join([str(num) for num in line_vec]) + "\n")
    output_f.close()
 
 
convert_to_vector(train_encode_file, train_encode_vocabulary_file, 'train_encode.vec')
convert_to_vector(train_decode_file, train_decode_vocabulary_file, 'train_decode.vec')
 
convert_to_vector(test_encode_file, train_encode_vocabulary_file, 'test_encode.vec')
convert_to_vector(test_decode_file, train_decode_vocabulary_file, 'test_decode.vec')

提取前5000個常用的漢字創建詞彙表
詞彙表文件*_vocabulary預覽:

__PAD__
__GO__
__EOS__
__UNK__
我
的
你
是
,
不
了
們

對話轉向量,把對話字符串轉爲向量形式
向量文件*.vec預覽:

6 269 31 13 1022 157 5 60 190
28 14 226 92 113 2047 2047 98 909 724
137 22 9 644 1331 278 63 1685
28 6 1363 118 63
4 9 652 514 824 88
433 131 51 24 4 127 131
1093 433 94 81 4 884 13 840 3435 1010 366

生成的train_encode.vec和train_decode.vec用於訓練,對應的詞彙表train_encode_vocabulary和train_decode_vocabulary。

(3)訓練

這裏選取部分代碼進行講解,完整代碼鏈接
導入向量文件進行訓練,定義一個read_data的函數對訓練集與測試集的問題向量文件encode.vec,回答向量文件decode.vec,進行讀取。

讀取的時候將問題向量文件encode.vec中的每一行默認以空格爲分隔符,構成一個源序列。將回答向量文件decode.vec中的每一行默認以空格爲分隔符,構成一個目標序列。然後將兩個序列添加到data_set中。對文件中的每一行都進行處理與添加後,將得到的data_set返回。

# 讀取*encode.vec和*decode.vec數據(數據還不算太多, 一次讀入到內存)
def read_data(source_path, target_path, max_size=None):
    data_set = [[] for _ in buckets]#生成了[[],[],[],[]],即當值與參數不一樣
    with tf.gfile.GFile(source_path, mode="r") as source_file:#以讀格式打開源文件(source_file)
        with tf.gfile.GFile(target_path, mode="r") as target_file:#以讀格式打開目標文件
            source, target = source_file.readline(), target_file.readline()#只讀取一行
            counter = 0#計數器爲0
            while source and target and ( not max_size or counter < max_size):#當讀入的還存在時
                counter += 1
                source_ids = [int(x) for x in source.split()]#source的目標序列號,默認分隔符爲空格,組成了一個源序列
                target_ids = [int(x) for x in target.split()]#target組成一個目標序列,爲目標序列
                target_ids.append(EOS_ID)#加上結束標記的序列號
                for bucket_id, (source_size, target_size) in enumerate(buckets):#enumerate()遍歷序列中的元素和其下標
                    if len(source_ids) < source_size and len(target_ids) < target_size:#判斷是否超越了最大長度
                        data_set[bucket_id].append([source_ids, target_ids])#讀取到數據集文件中區
                        break#一次即可,跳出當前循環
                source, target = source_file.readline(), target_file.readline()#讀取了下一行
    return data_set

構建模型

model = seq2seq_model.Seq2SeqModel(source_vocab_size=vocabulary_encode_size, target_vocab_size=vocabulary_decode_size,
                                   buckets=buckets, size=layer_size, num_layers=num_layers, max_gradient_norm=5.0,
                                   batch_size=batch_size, learning_rate=0.5, learning_rate_decay_factor=0.97,
                                   forward_only=False)

開始訓練

with tf.Session(config=config) as sess:
    # 恢復前一次訓練
    ckpt = tf.train.get_checkpoint_state('.')
    if ckpt != None:
        print(ckpt.model_checkpoint_path)
        model.saver.restore(sess, ckpt.model_checkpoint_path)
    else:
        sess.run(tf.global_variables_initializer())
 
    train_set = read_data(train_encode_vec, train_decode_vec)
    test_set = read_data(test_encode_vec, test_decode_vec)
 
    train_bucket_sizes = [len(train_set[b]) for b in range(len(buckets))]#分別計算出訓練集中的長度【1,2,3,4】
    train_total_size = float(sum(train_bucket_sizes))#訓練實例總數
    train_buckets_scale = [sum(train_bucket_sizes[:i + 1]) / train_total_size for i in range(len(train_bucket_sizes))]#計算了之前所有的數的首戰百分比
 
    loss = 0.0#損失置位0
    total_step = 0
    previous_losses = []
    # 一直訓練,每過一段時間保存一次模型
    while True:
        random_number_01 = np.random.random_sample()#每一次循環結果不一樣
        #選出最小的大於隨機採樣的值的索引號
        bucket_id = min([i for i in range(len(train_buckets_scale)) if train_buckets_scale[i] > random_number_01])
 
        encoder_inputs, decoder_inputs, target_weights = model.get_batch(train_set, bucket_id)
        #get_batch()函數首先獲取bucket的encoder_size與decoder_size
        _, step_loss, _ = model.step(sess, encoder_inputs, decoder_inputs, target_weights, bucket_id, False)#損失
 
        loss += step_loss / 500
        total_step += 1
 
        print(total_step)
        if total_step % 500 == 0:
            print(model.global_step.eval(), model.learning_rate.eval(), loss)
 
    # 如果模型沒有得到提升,減小learning rate
            if len(previous_losses) > 2 and loss > max(previous_losses[-3:]):#即損失比以前的大則降低學習率
                sess.run(model.learning_rate_decay_op)
            previous_losses.append(loss)
    # 保存模型
            checkpoint_path = "./chatbot_seq2seq.ckpt"
            model.saver.save(sess, checkpoint_path, global_step=model.global_step)
            #返回路徑checkpoint_file = "%s-%s" % (save_path, "{:08d}".format(global_step))
            loss = 0.0#置當前損失爲0
    # 使用測試數據評估模型
            for bucket_id in range(len(buckets)):
                if len(test_set[bucket_id]) == 0:
                    continue
                #獲取當前bucket的encoder_inputs, decoder_inputs, target_weights
                encoder_inputs, decoder_inputs, target_weights = model.get_batch(test_set, bucket_id)
                #計算bucket_id的損失權重
                _, eval_loss, _ = model.step(sess, encoder_inputs, decoder_inputs, target_weights, bucket_id, True)
                eval_ppx = math.exp(eval_loss) if eval_loss < 300 else float('inf')
                print(bucket_id, eval_ppx)#輸出的是bucket_id與eval_ppx


(4)模型測試

#coding=utf-8
#(4)使用訓練好的模型
import tensorflow as tf  # 0.12
# from tensorflow.models.rnn.translate import seq2seq_model
from tensorflow.models.tutorials.rnn.chatbot import seq2seq_model#注意 seq2seq_model這個需要自己去下載,根據自己的路徑進行導入
# 本人將seq2seq_model模塊下載後 複製到tensorflow/models/tutorials/rnn/chatbot/路徑下,所以才這樣進行導入
import os
import numpy as np
 
PAD_ID = 0
GO_ID = 1
EOS_ID = 2
UNK_ID = 3
tf.reset_default_graph()
#詞彙表路徑path
train_encode_vocabulary = 'train_encode_vocabulary'
train_decode_vocabulary = 'train_decode_vocabulary'
 
#讀取詞彙表
def read_vocabulary(input_file):
    tmp_vocab = []
    with open(input_file, "r",encoding='utf-8') as f:
        tmp_vocab.extend(f.readlines())#打開的文件全部讀入input_file中
    tmp_vocab = [line.strip() for line in tmp_vocab]#轉換成列表
    vocab = dict([(x, y) for (y, x) in enumerate(tmp_vocab)])
    return vocab, tmp_vocab#返回字典,列表
 
 
vocab_en, _, = read_vocabulary(train_encode_vocabulary)#得到詞彙字典
_, vocab_de, = read_vocabulary(train_decode_vocabulary)#得到詞彙列表
 
# 詞彙表大小5000
vocabulary_encode_size = 5000
vocabulary_decode_size = 5000
 
buckets = [(5, 10), (10, 15), (20, 25), (40, 50)]
layer_size = 256  # 每層大小
num_layers = 3  # 層數
batch_size = 1
 
model = seq2seq_model.Seq2SeqModel(source_vocab_size=vocabulary_encode_size, target_vocab_size=vocabulary_decode_size,
                                   buckets=buckets, size=layer_size, num_layers=num_layers, max_gradient_norm=5.0,
                                   batch_size=batch_size, learning_rate=0.5, learning_rate_decay_factor=0.99,
                                   forward_only=True)
#模型說明:源,目標詞彙尺寸=vocabulary_encode(decode)_size;batch_size:訓練期間使用的批次的大小;#forward_only:僅前向不傳遞誤差
 
model.batch_size = 1#batch_size=1
 
with tf.Session() as sess:#打開作爲一次會話
    # 恢復前一次訓練
    ckpt = tf.train.get_checkpoint_state('.')#從檢查點文件中返回一個狀態(ckpt)
    #如果ckpt存在,輸出模型路徑
    if ckpt != None:
        print(ckpt.model_checkpoint_path)
        model.saver.restore(sess, ckpt.model_checkpoint_path)#儲存模型參數
    else:
        print("沒找到模型")
    #測試該模型的能力
    while True:
        input_string = input('me > ')
    # 退出
        if input_string == 'quit':
           exit()
 
        input_string_vec = []#輸入字符串向量化
        for words in input_string.strip():
            input_string_vec.append(vocab_en.get(words, UNK_ID))#get()函數:如果words在詞表中,返回索引號;否則,返回UNK_ID
        bucket_id = min([b for b in range(len(buckets)) if buckets[b][0] > len(input_string_vec)])#保留最小的大於輸入的bucket的id
        encoder_inputs, decoder_inputs, target_weights = model.get_batch({bucket_id: [(input_string_vec, [])]}, bucket_id)
        #get_batch(A,B):兩個參數,A爲大小爲len(buckets)的元組,返回了指定bucket_id的encoder_inputs,decoder_inputs,target_weights
        _, _, output_logits = model.step(sess, encoder_inputs, decoder_inputs, target_weights, bucket_id, True)
        #得到其輸出
        outputs = [int(np.argmax(logit, axis=1)) for logit in output_logits]#求得最大的預測範圍列表
        if EOS_ID in outputs:#如果EOS_ID在輸出內部,則輸出列表爲[,,,,:End]
            outputs = outputs[:outputs.index(EOS_ID)]
 
        response = "".join([tf.compat.as_str(vocab_de[output]) for output in outputs])#轉爲解碼詞彙分別添加到回覆中
        print('AI--PigPig > ' + response)#輸出回覆


測試結果:

以下爲訓練5500步後的測試結果:
【最終結果有待更新】

傲嬌屬性get
圖片描述



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章