在构建大语言模型的旅途中,模型构建是至关重要的一步。大语言模型通常基于深度学习技术,特别是循环神经网络(RNN)及其变体,如长短期记忆网络(LSTM)或门控循环单元(GRU),这些网络擅长处理序列数据。(我们在上一章中提到过,见上一章【拓展】部分)
1. 定义模型构建函数
首先,我们需要一个函数来定义并构建我们的模型。这个函数将接收多个参数,如词汇表大小(vocab_size
)、嵌入维度(embedding_dim
)、RNN单元数(rnn_units
)、批量大小(batch_size
)以及模型版本(mt
)和窗口大小(window
,目前他没用但以后我们优化模型的时候会用到)。
这是GRU的简单流程图:
输入层: 接收当前时间步的输入数据 重置门: 即sigmoid函数 决定新输入与旧记忆的结合 更新门: sigmoid函数并控制旧状态信息的保留程度 候选隐藏状态: 基于重置门输出和当前输入计算 最终隐藏状态: 结合更新门输出候选隐藏状态和旧隐藏状态
python
def build_model(vocab_size, embedding_dim, rnn_units, batch_size, mt=2.2, window=128):
if mt == 1:
# mt:模型版本,目前我们只有1版本
model = tf.keras.Sequential([
tf.keras.layers.Embedding(vocab_size, embedding_dim,
batch_input_shape=[batch_size, None]),
tf.keras.layers.GRU(rnn_units,
return_sequences=True,
stateful=True,
recurrent_initializer='glorot_uniform'),#GRU层,整个模型的核心部分。你也可以换成LSTM等
tf.keras.layers.Dropout(0.2), # 添加Dropout防止过拟合
tf.keras.layers.Dense(vocab_size) # 输出层,对应词汇表大小
])
return model
2. 损失函数
损失函数,简单来说,就是用来衡量我们模型预测结果和实际结果之间差异的一个"尺子"。在机器学习和深度学习中,我们的目标是让模型学会从输入数据中预测出正确的输出。但是,模型一开始并不知道怎么预测,它会通过不断地学习和调整自己的参数来尽可能地减少预测结果和实际结果之间的差异。
这个"差异"的大小,就是由损失函数来计算的。如果预测结果和实际结果完全一样,那么损失函数的值就是0(或者一个非常接近0的数),表示没有差异;如果预测结果和实际结果有差异,那么损失函数的值就会大于0,差异越大,损失函数的值就越大。
损失函数就像是一个"教练",它告诉模型:"你看,你这次预测错了这么多,你得继续努力,调整你的参数,减少这个差异。"模型就会根据损失函数的反馈,不断地调整自己的参数,直到损失函数的值降到很低,也就是预测结果和实际结果之间的差异变得很小。
不同的任务会使用不同的损失函数。比如,在分类任务中,我们常用的损失函数有交叉熵损失函数;在回归任务中,我们常用的损失函数有均方误差损失函数。这些损失函数都是根据任务的特点来设计的,能够很好地衡量模型预测结果和实际结果之间的差异。
对于语言模型,我们通常使用交叉熵损失函数来衡量预测结果与实际标签之间的差异。在TensorFlow中,sparse_categorical_crossentropy
是处理此类问题的理想选择,特别是当标签是整数时。
python
def loss(labels, logits):
return tf.keras.losses.sparse_categorical_crossentropy(labels, logits, from_logits=True)
3. 加载模型权重
在训练过程中,我们可能希望从之前的检查点加载模型权重,以继续训练或进行预测。这可以通过model.load_weights()
方法实现。
python
# fwidx与loadmodel在上一章的函数定义传参部分已经定义了
if fwidx > 0 or loadmodel:
model = build_model(vocab_size, embedding_dim, rnn_units, batch_size=BATCH_SIZE, mt=mt, window=window)
model.load_weights(checkpoint_path)
else:
model = build_model(vocab_size, embedding_dim, rnn_units, batch_size=BATCH_SIZE, mt=mt, window=window)
4. 编译模型
在模型训练之前,我们需要使用优化器和损失函数来编译模型。这里,我们使用Adam优化器,它自动调整学习率,非常适合大多数深度学习场景。
python
model.compile(optimizer=tf.keras.optimizers.Adam(lr=LR), loss=loss)
5. 模型概览
使用model.summary()
方法可以打印出模型的概览,包括每层的名称、输出形状和参数数量。
python
try:
model.summary()
except ValueError:
print('Model Can not summary, We will summary it after a batch of train.')
如果因为某些原因(如模型未接收到输入数据的维度信息)而无法打印概览,可以在训练开始后再尝试。
总结
下一章我们就要开始正式的训练模型了!
上一章与本章的所有完整代码:
python
def train(
mt=3,
big_file=False,#是否采用大文件加载策略
#微调数据集
path_to_file = r'en_novel.txt',
ntype_='_en',#保存为微调模型名称
#设置vocab版本
vtype_='_lx',#type_#
fen=50,#数据量分几份
fwidx=0,#第几份
BATCH_SIZE = 64,
loadmodel=False,
pass_=-1,
ste=0,
):'''
多出的参数不必理会,后面会用到
'''
global LR,param_data,p_ntype
p_ntype=ntype_
if ntype_[0]!='_':ntype_='_'+ntype_
type_=ntype_
print('path_to_file',path_to_file)
print('LR',LR)
import os
#dataset与vocab是配对的!
if not os.path.exists(r'E:\小思框架\论文\ganskchat\vocab'+vtype_+'.txt'):
raise Exception("can't reading vocab from "+r'E:\小思框架\论文\ganskchat\vocab'+vtype_+'.txt')
else:
with open('E:\\小思框架\\论文\\ganskchat\\vocab'+vtype_+'.txt','r',encoding='utf-8') as f:
vocab=eval(f.read())
UNK=0
unkli=[]
char2idx = {u:i for i, u in enumerate(vocab)}
idx2char = np.array(vocab)
print('{')
for char,_ in zip(char2idx, range(20)):
print(' {:4s}: {:3d},'.format(repr(char), char2idx[char]))
print(' ...\n}')
# 设定每个输入句子长度的最大值
seq_length = dic[mt][2]
def split_input_target(chunk):
input_text = chunk[:-1]
target_text = chunk[1:]
return input_text, target_text
import tensorflow as tf
import pickle
# 假设 BATCH_SIZE 和 BUFFER_SIZE 已经定义好
if 1:
# 设定缓冲区大小,以重新排列数据集
BUFFER_SIZE = 50000
# 词集的长度
vocab_size = len(vocab)
# 嵌入的维度
embedding_dim = dic[mt][0]#int(1024*2*1)
# RNN 的单元数量
rnn_units = dic[mt][1]#int(1024*4*2)
window = dic[mt][2]
# 加载保存的数据集
def load_dataset(path):
dataset = tf.data.experimental.load(path)
return dataset
if os.path.exists(r'E:\小思框架\论文\ganskchat\dataset'+ntype_+'_'+str(fwidx)):#换了batch后要重新处理数据集
print('loading dataset')
# 加载已经打乱过的数据集
dataset = load_dataset(r'E:\小思框架\论文\ganskchat\dataset'+ntype_+'_'+str(fwidx))
else:
if big_file:
text=[]
with open(path_to_file, 'r',encoding='utf-8') as f:
idxlen=0
print('getting length\n')
for _ in tqdm.tqdm(f):
idxlen+=1
st=idxlen//fen*fwidx
ed=idxlen//fen*(fwidx+1)
with open(path_to_file, 'r',encoding='utf-8') as f:
idx=0
print('\n\nrunning data\n')
for _ in tqdm.tqdm(f):
if idx<st:continue
if idx>=ed:
break
text.append(_)
idx+=1
text=''.join(text)
else:
text = open(path_to_file, 'r',encoding='utf-8').read()
idxlen=len(text)
st=idxlen//fen*fwidx
ed=idxlen//fen*(fwidx+1)
text=text[st:ed]
print('data size:',len(text))
#text_as_int = np.array([char2idx[c] for c in text])
text_as_int=[]
cks=list(char2idx.keys())
unk_li=set()
len_=0
for c in tqdm.tqdm(text):
if c in cks:
text_as_int.append(char2idx[c])
else:
c=cc.convert(c)
if c in cks:#转为简体再次尝试
text_as_int.append(char2idx[c])
else:
if not is_add:
if not c in unk_li:
with open('unk'+ntype_+'.txt','w',encoding='utf-8') as f:
f.write(str(len_)+'\n'+str(list(unk_li)))#print('unknow:',repr(c))
unk_li.add(c)
len_+=1
text_as_int.append(UNK)
text_as_int=np.array(text_as_int)
#------------------------------------------------------------------------------------
# 创建训练样本 / 目标
char_dataset = tf.data.Dataset.from_tensor_slices(text_as_int)
if 0:
for i in char_dataset.take(5):
print(i.numpy())
print(idx2char[i.numpy()])
sequences = char_dataset.batch(seq_length+1, drop_remainder=True)
for item in sequences.take(5):
print(repr(''.join(idx2char[(item.numpy())])))
dataset = sequences.map(split_input_target)
dataset = dataset.shuffle(BUFFER_SIZE).batch(BATCH_SIZE, drop_remainder=True)
tf.data.Dataset.save(dataset,'./dataset'+ntype_+'_'+str(fwidx) )
# 以上是上一章代码
######################这是分界线#######################
# 以下是本章的代码
def build_model(vocab_size, embedding_dim, rnn_units, batch_size,mt=2.2,window=128):
#global mt
if mt==1:
model = tf.keras.Sequential([
tf.keras.layers.Embedding(vocab_size, embedding_dim,
batch_input_shape=[batch_size, None]),
tf.keras.layers.GRU(rnn_units,
return_sequences=True,
stateful=True,
recurrent_initializer='glorot_uniform'),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Dense(vocab_size)
])
return model
else:
raise Exception('MT Error!')
def loss(labels, logits):
return tf.keras.losses.sparse_categorical_crossentropy(labels, logits, from_logits=True)
model.compile(optimizer=tf.keras.optimizers.Adam(lr=0.0035), loss=loss)
def preprocess_data_for_training(dd):
input_sequences, target_sequences = dd
return input_sequences, target_sequences
EPOCHS = 50000
import os
import tensorflow as tf
checkpoint_dir = r'./dataset/ckpt'+type_
if not os.path.exists(checkpoint_dir):
os.makedirs(checkpoint_dir)
steps_per_epoch = len(dataset) // BATCH_SIZE
ct()
checkpoint_path = tf.train.latest_checkpoint(checkpoint_dir)
ncheckpoint_dir = r'./dataset/ckpt'+ntype_
if not os.path.exists(ncheckpoint_dir):
os.makedirs(ncheckpoint_dir)
ncheckpoint_path = tf.train.latest_checkpoint(ncheckpoint_dir)
ct()
if fwidx>0 or loadmodel:
model = build_model(vocab_size, embedding_dim, rnn_units, batch_size=BATCH_SIZE,mt=mt,window=window)
# 直接加载权重到模型中
model.load_weights(checkpoint_path)
else:
#传入新的mt
model = build_model(vocab_size, embedding_dim, rnn_units, batch_size=BATCH_SIZE,mt=mt,window=window)
try:
model.summary()
ns=0
except ValueError:
print('Model Can not summary, We will summary it after a batch of train.')
ns=1
model.compile(optimizer=tf.keras.optimizers.Adam(lr=LR), loss=loss)