小型语言模型生成制造应用合成表格数据:架构比较分析

2024年11月08日 由 alex 发表 139 0

介绍

合成数据生成解决了多个根本性挑战:数据集中的类别不平衡、数据隐私要求、数据采集成本优化以及实验周期的加速。传统方法如SMOTE[1]通过现有数据点之间的插值来为少数类别生成合成样本。


本文研究了利用小型语言模型(SLM)生成合成表格数值数据的新方法。为了与先前的研究保持连续性,我们专注于单一的表格数据,特别是分析了来自NASA Ames预测卓越中心的Turbofan发动机退化模拟数据集。


本研究探讨了四种关键方法:

  1. 带有领域特定约束的SLM微调
  2. 带有数值分词器和自定义损失函数的高级微调
  3. Transformer GAN和条件Transformer GAN架构
  4. 语言模型GAN(LM-GAN)实现


小型语言模型(SLM)

在自然语言处理领域,语言模型被归类为“小型”具有时间上的变异性。一个显著的例子是GPT-2,它在2019年发布时拥有15亿参数,被归类为大型模型,但按当代标准来看,现在被认为是小型模型。当前的分类(2024年)将SLM定义为包含3至100亿参数的模型,而大型语言模型(LLM)通常包含数百亿个参数。SLM针对资源效率和边缘部署场景进行了优化,代表性模型包括Phi 3[8]、Galactica和Gemma。


SLM的架构多样性与其大型对应物相似,融入了各种注意力机制:

— 多头注意力(MHA)

— 多查询注意力(MQA)

— 分组查询注意力(GQA)

— 多头潜在注意力(MLA)


这些模型在结构组件上表现出显著差异,包括:

  1. 前馈神经网络实现(标准FFN、门控FFN)
  2. 激活函数选择(ReLU、GELU、GELUtanh、SiLU)
  3. 词汇量范围(<5万至25万以上)
  4. 训练数据量(从数百万到6万亿个标记)


本研究专注于将这些SLM架构应用于合成数据生成应用。


数据生成技术

本研究评估了使用小型语言模型(SLM)生成合成表格数据的四种先进方法,并简要概述了带有结构化约束的提示工程。这些方法包括针对制造特定目标的SLM微调、结合自定义损失函数的高级微调、将Transformer与GAN相结合的混合架构以及新颖的语言模型GAN(LM-GAN)框架。实施过程使用了微软的Phi-3.5 mini进行微调实验,使用了DistilGPT-2进行自定义损失函数集成,并为Transformer GAN和LM-GAN模型开发了专用架构。每种方法都逐步建立在SLM的能力之上,最终形成了LM-GAN架构,该架构在合成制造数据中表现出卓越的统计特性保持能力。


提示工程

提示工程是合成数据生成的一种基本但有效的方法。该方法需要为语言模型构建结构化的输入,包括字段描述和规格、领域特定约束以及带有少样本学习的示例数据样本。该方法允许指定生成参数,包括输出量和条件约束。研究已经展示了两种主要的提示范式:自然语言描述和结构化CSV格式,每种范式都优化了标记利用效率的不同方面。高级提示技术结合了随机值替换以增强数据多样性,以及层次分组机制以进行条件生成。虽然提示工程在合成数据生成方面具有巨大潜力,但本文重点关注更先进的方法框架,将提示技术的全面探索留给现有文献,并鼓励读者进一步探索提示工程。


SLM微调

我们探索的第一种技术是微调语言模型。为此,本文考虑了微软的Phi 3.5 mini指令模型。它是一个仅解码器的Transformer架构模型,拥有38.2亿参数,在3.4万亿个标记上进行了训练。它具有12.8万个标记的上下文长度,并且特别针对基本推理任务、代码生成和数学问题解决进行了优化。


为了进一步推进,让我们正式地构建表格数值数据生成的问题框架。


设D为训练数据集,包含n个样本D = {X1, X2, X3, …, Xn}。每个样本都有一组m个键值对,其中字段名称为键。


14


这些内容分布在n行中,其中每对元素之间由一个特殊标记分隔。在本次练习中,选择了“:::”作为该标记。连接操作符C执行此操作并生成一个组合字符串。


15


其中δ表示特殊标记。生成的文本会进一步被分词。分词函数T将连接后的字符串映射为一系列标记:


16


最后,这些标记被用于微调语言模型,该模型会根据初始标记来预测接下来的标记。


对于合成数据生成,给定一个初始标记s,模型会从学习到的分布X^中进行采样。


17

18


上图解释了整个过程。标注为F{i}的步骤是微调过程,标注为G{i}的步骤是生成过程。


初始标记的选择基于模型是如何被微调的。如果在连接操作符之前字段是随机化的,那么初始标记可以是任何字段名。或者,它也可以基于一组固定的字段名来确定其他字段的分布。在文献中,你可以发现这两种技术都已经被评估过,并且结果因数据集而异。虽然这篇文章使用了通用的字段名,但已有研究表明,使用描述性的字段名可以进一步提高合成样本的真实性。


为了微调模型,我们使用了公开可用(通过Huggingface)的Phi 3.5迷你模型的权重作为预训练权重。模型使用AdamW优化器进行微调,该优化器配备了恒定学习率调度器和2e-4的学习率。与标准Adam相比,AdamW优化器在微调任务中提供了更好的泛化性能,这得益于其将权重衰减与跟踪一阶和二阶矩及其各自的权重衰减相分离。在微调过程中,我们使用了Huggingface transformers[19]库的trainer类,并结合了低秩适应和BitsAndBytes进行量化。


以下配置提供了微调设置的快速概览:


model_id: &model_id "microsoft/Phi-3.5-mini-instruct"
tokenizer_config:
    max_length: 350
    truncation: True
    padding: "max_length"
training_env:
    model_dir: "opt/ml/phi-model" #directory for fine tuned model weights
    cache_dir: &cache_dir "/tmp/.cache" #directory for storing pretrained model weights(downloaded from huggingface)
    merge_dir: "/tmp/phi-model" #directory for storing merged model weights 
model_config:
    trust_remote_code: True
    cache_dir: *cache_dir
    device_map: "auto"
    torch_dtype: "float16"
    attn_implementation: "flash_attention_2"
bnb_config:
    load_in_4bit: True
    bnb_4bit_use_double_quant: True
    bnb_4bit_quant_type: "nf4"
    bnb_4bit_compute_dtype: "bfloat16"
lora_config:
    r: 8
    lora_alpha: 16
    lora_dropout: 0.1
    bias: "none"
    task_type: "CAUSAL_LM"
training_config:
    per_device_train_batch_size: 4
    per_device_eval_batch_size: 1
    gradient_accumulation_steps: 2
    gradient_checkpointing: True    
    learning_rate: 0.0002
    lr_scheduler_type: "constant"      
    num_train_epochs: 1
    logging_strategy: "steps"
    logging_steps: 10
    log_on_each_node: False
    bf16: True
    ddp_find_unused_parameters: False
    fsdp: "" #fsdp turned off 
    fsdp_config: null
    save_strategy: "no"
    output_dir: "outputs"
    report_to: none
    optim: adamw_torch       
    save_strategy: epoch
    max_grad_norm: 0.3 
    warmup_ratio: 0.03


下图展示了原始(测试)分布与合成分布之间的Kullback-Leibler(KL)散度。KL散度的值跨越了大约两个数量级,范围从0.5到40,这表明在不同设置下合成数据的质量存在显著差异。对于其他字段,观察到较低的KL散度值,表明合成数据的保真度较高。


19


高级微调

接下来,本文研究了在训练过程中考虑KL散度并将其作为损失函数一部分的影响。在本节中,我们将探讨使用DistilGPT-2语言模型进行合成数据生成的高级微调策略的实施。DistilGPT-2是GPT2的压缩版本,通过知识蒸馏开发而成。它拥有8200万个参数,是在1.24亿参数版本的GPT-2的监督下预训练的,大小约为原始模型的一半。其架构包括6个Transformer块(GPT2有12个),同时保留了每个块中的12个注意力头和768的嵌入维度。该模型保留了1024个标记的上下文窗口和50257个标记的词汇量。


本文探索了两种策略来微调GPT2:


数值分词器

数值分词器的动机有两个方面。首先,与默认分词器相比,它为数值中的所有数字提供了相等的权重,而默认分词器可能会在不同的数字处拆分以生成标记。第二个动机是为列值、分隔符标记(:::)和字段分隔符(,)创建专用的单一标记。它可以正式定义为GPT-2基础分词器Tb的扩展,如下所示:


20


Tn是数值分词函数,用于拆分数值。


21


分词器的词汇表定义为,


22


其中V是最终的词汇表,Vb是GPT-2基础词汇表,F是字段名的集合,D是数字和小数点的集合。


以下是相关的代码实现:


from transformers import AutoTokenizer
import re
from typing import List, Union, Dict
class NumTokenizer:
    def __init__(self,model_id,reqd_cols,sep_token):
        self.base_tokenizer = AutoTokenizer.from_pretrained(model_id)
        self.base_tokenizer.pad_token = self.base_tokenizer.eos_token # Set Tokenizer pad Token
        self.base_tokenizer.special_tokens = [sep_token]
        self.base_tokenizer.special_tokens.extend(reqd_cols)
        self.base_tokenizer.special_tokens.extend(',')
        self.num_pattern = re.compile(rf'{sep_token.strip()}\d+(?:\.\d+)?')
    def tokenize_num(self,num_text):
        return [t for t in num_text]
    
    def __call__(self,text: Union[str,List[str]],padding:bool=True,truncation:bool=True,max_length:int=None,return_tensors:str=None, **kwargs) -> Dict:
        if isinstance(text,str):
            tl = [text]
        else:
            tl = text
        encoded_inputs = []
        for t in tl:
            encoded = self.encode(t)
            encoded_inputs.append(encoded)
        if max_length is None:
            max_length = max(len(e) for e in encoded_inputs)
        if padding:
            encoded_inputs = [enc + [self.base_tokenizer.pad_token_id] * (max_length - len(enc)) for enc in encoded_inputs]
        if truncation:
            encoded_inputs = [enc[:max_length] for enc in encoded_inputs]
        
        if isinstance(text,str):
            output = {"input_ids": encoded_inputs[0], "attention_mask" : [1] * len(encoded_inputs[0])}
        else:
            output = {"input_ids": encoded_inputs, "attention_mask" : [[1] * len(enc) for enc in encoded_inputs]}
        
        if return_tensors:
            output = {k: torch.tensor(v) for k,v in output.items()}
        return output
        
    def tokenize(self,text):
        col_names = self.num_pattern.split(text)
        col_values = [n.replace(sep_token.strip(),'').strip() for n in self.num_pattern.findall(text)]
        tokens = []
        for col_name, col_value in zip (col_names,col_values + ['']):
            tokens.extend(self.base_tokenizer.tokenize(col_name))
            tokens.extend(self.tokenize_num(col_value))
        return tokens
    def encode(self,text, **kwargs):
        tokens = self.tokenize(text)
        return self.base_tokenizer.convert_tokens_to_ids(tokens)
                
    def decode(self,token_ids, **kwargs):
        return self.base_tokenizer.decode(token_ids)
        
    def __getattr__(self,name):
        return getattr(self.base_tokenizer,name)


带有KL散度损失的自定义训练器

第二个动机引入了一个混合损失函数,旨在平衡保持语言结构(字段名)和数值结构(字段值)。该损失函数结合了用于文本连贯性的交叉熵损失和用于数值准确性的KL散度,分别通过参数α和β进行加权,其值分别设置为0.6和0.4。这种公式使模型能够同时学习表格数据表示的结构模式和数值的底层分布,这种方法旨在解决合成数据生成中的根本挑战:同时保留字段之间的语义关系和数值的统计属性。


文本损失Ltext是通过交叉熵函数计算的,该函数计算给定每个元素的预测概率分布时真实类别标签的负对数似然,然后计算所有这些损失在所有元素上的平均值。


对于数值,计算预测分布和真实分布之间的KL散度Lnum,


23


实施如下:


import torch.nn.functional as F

def kl_div(p, q):
    return (p * torch.log(p / q)).sum(-1)
    
class CustomTrainer(transformers.Trainer):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
    
    def compute_loss(self,model,inputs,num_items_in_batch,return_outputs=False):
        labels = inputs.pop("labels")
        labels = torch.where(labels == -100, tokenizer.pad_token_id,labels)
        outputs = model(**inputs)
        logits = outputs.logits
        logits_m =  torch.argmax(logits[:, :-1, :], dim=-1)
        labels_m = labels[:, 1:].contiguous()
        
        pred_texts = tokenizer.batch_decode(logits_m,skip_special_tokens=False)
        label_texts = tokenizer.batch_decode(labels_m,skip_special_tokens=False)
        pred_list = text2dict(pred_texts,df_meta_data,sep_token)
        label_list = text2dict(label_texts,df_meta_data,sep_token)
        shift_logits = logits[:, :-1, :]
        shift_labels = labels[:, 1:]
        
        #column names losses  
        text_loss = F.cross_entropy(
            shift_logits.reshape(-1, shift_logits.size(-1)),
            shift_labels.reshape(-1),
            ignore_index=tokenizer.pad_token_id,)
        
        #column val loss with KL diveregence
        num_losses = []
        for pred_dict, label_dict in zip(pred_list, label_list):
            for key in pred_dict.keys():
                if key != '' and key in label_dict:
                    pred_value = pred_dict[key]
                    true_value = label_dict[key]
                    if pred_value is not None and true_value is not None:
                        num_losses.append(kl_div(torch.tensor([pred_value], device=logits.device), torch.tensor([true_value], device=logits.device)))
        num_loss = torch.mean(torch.stack(num_losses)) if num_losses else 0
        alpha = 0.6  # Weightage for text loss
        beta = 0.4  # Weightage for numerical loss
        combined_loss = alpha * text_loss + beta * num_loss
        if return_outputs:
            return combined_loss, outputs
        else:
            return combined_loss


对不同列进行的KL散度测量揭示了模型在捕捉底层分布方面存在的显著差异。总体而言,与常规微调相比,分布的质量有所提高,但字段s16和s18存在异常值,表明这些特征的原始分布与合成分布之间存在显著差异。值得注意的是,模型并未使用不同的超参数进行评估,且仅在3个训练周期内进行了微调。


24


Transformer GAN(生成对抗网络)

已有研究利用Transformer模型来生成合成表格数据。本文介绍了将Transformer架构与生成对抗网络(GAN)相结合用于合成表格数据生成的方法。所提出的Transformer GAN框架利用Transformer强大的自注意力机制作为生成器,并配备了一个定制的判别器网络。


我们探索了两种不同的变体:一种是普通的TransformerGAN,另一种是条件Transformer GAN,它们各自为合成数据生成提供了独特的功能。普通架构采用基于Transformer的生成器,该生成器通过多个自注意力层处理输入噪声向量,而条件变体则融入了额外的上下文信息来指导生成过程。此外,该架构还整合了位置编码,以帮助模型更好地理解表格数据中的序列模式。


下图提供了Transformer GAN及其已实现组件的概览。


25


Vanilla Transformer GAN

正式解释这一点,训练过程以对抗性的方式涉及两个神经网络:一个生成器和一个判别器。Transformer作为生成器网络,记为G,它接收一个潜在向量z作为输入,并生成合成数据样本x^ = G(z)。


26


在这里,Femb代表输入嵌入层,PE代表位置编码,而Fout代表输出线性层。


为了生成像表格数据这样的结构化输出,我们的模型需要按照字段及其序列的正确顺序进行训练。这是通过位置编码来实现的,它提供了相对和绝对的位置信息。从上面的描述中可以看到,位置编码与输入嵌入层具有相同的维度,因此这两个嵌入可以相加。对于给定的模型维度dmodel,位置pos和维度i的位置编码定义为:


27


在这个实现中,i 的取值范围从 0 到 dmodel/2。这里采用的是正弦位置编码,其中每个位置和维度对的编码是通过交替使用正弦和余弦函数来计算的。对于偶数维度(2i),编码使用正弦函数,而奇数维度(2i+1)则使用余弦函数。这个过程对每个位置都重复进行。在我们处理表格数据的上下文中,这种方法特别有价值,因为它使模型能够同时捕捉局部和全局的位置关系。


class PositionalEncoding(nn.Module):
    def __init__(self,d_model,max_positions=1024,n=10000):
        super(PositionalEncoding,self).__init__()
        pe = torch.zeros(max_positions*d_model).reshape(max_positions, d_model) 
        k = torch.arange(0,max_positions).unsqueeze(1)
        i = torch.arange(d_model//2)
        div_term = (n ** ((2*i)/d_model))   
        theta = 1/div_term
        pe[:, 0::2] = torch.sin(k * theta) 
        pe[:, 1::2] = torch.cos(k * theta)
        self.pe = pe.to(device)
    def forward(self,x):
        x = x + self.pe[:x.size()[0],:]
        return x


判别器网络,记为D,接收一个数据样本x作为输入,并预测其为真实数据还是从(0,1)集合中生成的合成数据的概率D(x)。本质上,判别器可以被定义为一系列线性变换。


28


其中 σ 表示 LeakyReLU 激活,除了最后一层使用 sigmoid。


class TransformerGenerator(nn.Module):
    def __init__(self, input_dim, model_dim,num_heads,num_layers,feedforward_dim):
        super(TransformerGenerator, self).__init__()
        self.embedding = nn.Linear(input_dim,model_dim)
        self.pos_encoding = PositionalEncoding(d_model=model_dim)
        encoder_layer = nn.TransformerEncoderLayer(model_dim,num_heads,feedforward_dim,dropout=0.2)
        self.transformer_encoder = nn.TransformerEncoder(encoder_layer,num_layers)
        self.fc_out = nn.Linear(model_dim,input_dim)
 
    def forward(self, x):
        emb = self.embedding(x)
        pe = self.pos_encoding(emb)
        x = emb + pe
        x = self.transformer_encoder(x)
        return self.fc_out(x)
class Discriminator(nn.Module):
    def __init__(self, input_dim):
        super(Discriminator,self).__init__()
        self.model = nn.Sequential(
            nn.Linear(input_dim, 1024),
            nn.LeakyReLU(0.2),
            nn.Linear(1024, 512),
            nn.LeakyReLU(0.2),
            nn.Linear(512, 256),
            nn.LeakyReLU(0.2),
            nn.Linear(256, 128),
            nn.LeakyReLU(0.2),
            nn.Linear(128, 1),
            nn.Sigmoid()
        )
    def forward(self, x):
        return self.model(x)


用于训练网络的超参数如下。


# Set hyperparams
batch_size = 32
num_epochs = 40
lr = 0.0001
condition_dim = 3
input_dim = len(df.columns) - condition_dim
model_dim = 512
num_heads = 16
num_layers = 18
feedforward_dim = 512


训练循环将传统的GAN训练与专为表格数据合成设计的额外优化技术相结合。在初始训练后实施的额外策略导致了模式崩溃。这导致生成器损失变得极大且为负值,而判别器损失则变得很小并趋近于零。为了解决这个问题,对判别器损失应用了标签平滑(1 — 平滑),以防止过拟合。判别器的损失(d_loss)计算为真实样本和假样本的二元交叉熵损失之和,以优化判别器区分真实制造数据和合成数据的能力。


在生成器训练阶段,引入了几种复杂的技术来提高合成数据的质量。输入噪声向量使用0到2之间的随机因子进行动态缩放,从而在生成的样本中引入变异性。生成器的损失函数包含两个组成部分:一个二元交叉熵损失(g_loss_bce),它鼓励生成器产生能够欺骗判别器的样本;以及一个Kullback-Leibler散度项(g_loss_kl),它衡量生成数据和真实数据分布之间的统计距离。KL散度项通过kl_div_weight进行加权,以平衡其对总损失的贡献。生成器和判别器的优化器都使用学习率调度器,在训练过程中自适应地调整学习率,从而可能提高收敛的稳定性。


for epoch in range(num_epochs):
    for batch in dataloader:
        real_data = batch[0].to(device)
        batch_size = real_data.size(0)
        optimizer_D.zero_grad()
        real_labels = torch.ones(batch_size, 1,device=device)* (1 - smoothing)
        real_outputs = model.discriminator(real_data)
        real_loss = criterion_bce(real_outputs, real_labels)
        z = torch.randn(batch_size, input_dim,device=device)
        fake_data = model.generator(z).to(device)
        fake_labels = torch.zeros(batch_size, 1,device=device)* smoothing
        fake_outputs = model.discriminator(fake_data)
        fake_loss = criterion_bce(fake_outputs, fake_labels)
        d_loss = real_loss + fake_loss
        d_loss.backward()
        optimizer_D.step()
        optimizer_G.zero_grad()
        z = torch.randn(batch_size, input_dim,device=device)
        scale = torch.rand(batch_size, 1, device=device) * 2  # Random scale between 0 and 2
        z = z * scale
        
        fake_data = model.generator(z)
        fake_outputs = model.discriminator(fake_data)
        g_loss_bce = criterion_bce(fake_outputs, real_labels)

        # KL divergence 
        g_loss_kl =  torch.abs(criterion_kl(fake_data, real_data))
        g_loss = g_loss_bce + kl_div_weight * g_loss_kl 
        g_loss.backward()        
        optimizer_G.step()
        scheduler_G.step()
        scheduler_D.step()
        
    print(f"Epoch [{epoch+1}/{num_epochs}], D Loss: {d_loss.item():.4f}, G Loss: {g_loss.item():.4f}")   


条件Transformer GAN

给定真实数据分布X和条件空间C,以及潜在空间Z,条件生成器G和判别器D定义为:


29


条件生成器的架构融合了双重嵌入策略,既包括潜在输入信息,也包括条件信息。


30


其中,Femb是输入嵌入层,Fcond是条件嵌入层,PE是位置编码。在特定的数据集中,settings1、settings2和settings3构成了条件空间。此外,它还包含了一个可选的基于CNN的嵌入路径,可以通过卷积层处理输入数据,提供一种替代的特征提取机制。


条件判别器同时处理真实/生成的样本和条件:


31


其中 [x; c] 表示输入和条件向量的连接。条件向量 c 是从输入的前三个分量中提取的

。c = x1:3,x′ = x4:n


class ConditionalTransformerGenerator(nn.Module):
    def __init__(self, input_dim,condition_dim, model_dim,num_heads,num_layers,feedforward_dim):
        super(ConditionalTransformerGenerator, self).__init__()
        self.embedding = nn.Linear(input_dim,model_dim)
        self.condition_embedding = nn.Linear(condition_dim, model_dim)
        self.pos_encoding = PositionalEncoding(d_model=model_dim)
        encoder_layer = nn.TransformerEncoderLayer(model_dim,num_heads,feedforward_dim,dropout=0.2)
        self.transformer_encoder = nn.TransformerEncoder(encoder_layer,num_layers)
        self.fc_out = nn.Linear(model_dim,input_dim)
        self.cnn_embedding = nn.Sequential(
            nn.Conv1d(1, 32, kernel_size=3, stride=1, padding=1),
            nn.LeakyReLU(0.2),
            nn.Conv1d(32, 64, kernel_size=3, stride=1, padding=1),
            nn.LeakyReLU(0.2),
            nn.MaxPool1d(2)
        )
        self.linear_proj = nn.Linear(640, model_dim)
 
    def forward(self, x,condition):
        if cnn_embeddings:
            x = x.unsqueeze(1)  #for CNN
            emb = self.cnn_embedding(x)
            emb = emb.view(emb.size(0), -1)  # flatten
            emb = self.linear_proj(emb)
        else:
            emb = self.embedding(x)
        condition_emb = self.condition_embedding(condition)
        x = emb + condition_emb
        pe = self.pos_encoding(x)
        x = x + pe
        x = self.transformer_encoder(x)
        return self.fc_out(x)
class ConditionalDiscriminator(nn.Module):
    def __init__(self, input_dim,condition_dim):
        super(ConditionalDiscriminator,self).__init__()
        self.condition_dim = condition_dim
        self.input_dim = input_dim
        self.model = nn.Sequential(
            nn.Linear(input_dim + condition_dim, 1024),
            nn.LeakyReLU(0.2),
            nn.Linear(1024, 512),
            nn.LeakyReLU(0.2),
            nn.Linear(512, 256),
            nn.LeakyReLU(0.2),
            nn.Linear(256, 128),
            nn.LeakyReLU(0.2),
            nn.Linear(128, 1),
            nn.Sigmoid()
        )

    def forward(self, x,condition):
        if condition.ndim == 1:
            condition = condition.unsqueeze(0).repeat(x.size(0), 1)
        x = torch.cat([x, condition], dim=1)
        return self.model(x)


训练循环与Transformer GAN相似。条件Transformer GAN与标准Transformer GAN训练循环之间的主要区别在于数据处理方式。在条件Transformer GAN的训练循环中,输入数据被策略性地划分为条件向量(操作设置)和目标特征(传感器值),其中前三个特征作为指导生成过程的条件。在每次训练迭代中,这些条件向量会与真实数据和生成数据明确地配对,从而影响生成器的合成过程和判别器的评估。判别器则会通过同时考虑生成/真实样本及其对应的条件(这些条件和样本会被拼接在一起)来评估数据的真实性,并在操作参数的上下文中做出决策。这与标准Transformer GAN的训练循环不同,在后者中,生成器仅基于随机噪声输入进行操作,而判别器则在没有任何条件上下文的情况下评估数据。


LM-GAN

本文接下来将Transformer GAN的思想扩展到语言模型,特别是SLM,并通过利用预训练的distilGPT2语言模型作为生成器,引入了语言模型GAN(LM-GAN)架构。随后的实证分析,包括KL散度测量、主成分分析(PCA)和直方图,都证明了LM-GAN的优越性。本文认为,LM-GAN架构生成的合成样本能够保持原始传感器测量的统计特性和多模态特性,同时避免模式崩溃,这是GAN训练中常见的挑战。


32


LM-GAN的公式化在很大程度上与Transformer GAN相似。在这种语言模型GAN架构中,生成器网络利用了预训练的DistilGPT2模型。关键的不同之处在于,作为生成器一部分的DistilGPT2是由参数θ来参数化的。


33


其中,Z表示输入标记空间,X表示输出逻辑空间(或称为未归一化的预测概率空间)。生成器接收输入标记序列及其对应的注意力掩码,并通过多个由自注意力机制和前馈神经网络组成的Transformer块对它们进行处理。该模型架构保持了DistilGPT2的原始配置,但对标记嵌入进行了修改,以适应特定于制造业的词汇表,包括数值和列标识符。


class Generator(nn.Module):
    def __init__(self, model):
        super(Generator, self).__init__()
        self.model = model # pretrained distil GPT2 model
        
    def forward(self, input_ids, attention_mask):
        outputs = self.model(
            input_ids=input_ids,
            attention_mask=attention_mask,
            labels=input_ids,
            #max_length = max_length
        )
        return outputs.logits.squeeze(1), outputs.loss


生成器的输出是


34


其中,Xsyn表示合成数据的逻辑值(或称为未归一化的预测值),Lgen是语言建模损失。在前向传播过程中,生成器产生两个输出:表示序列中每个位置词汇表上概率分布的逻辑值,以及通过教师强制(teacher forcing)计算得到的语言建模损失。逻辑值的形状为(批量大小,序列长度,词汇表大小),它们捕捉了模型对每个标记位置的预测,而语言建模损失则有助于保持预训练期间学到的语言结构。在GAN训练过程中,生成器的参数会得到微调,使其能够将预先学到的表示适应制造业传感器数据中存在的特定模式和分布,同时保持其生成连贯序列的能力。


参数为ϕ的判别器Dϕ定义为:


35


其中:— e : Rn×v → Rn×d 是嵌入层,— hlstm : Rn×d → R2d 是双向LSTM[32]。fϕ是分类层,σ是sigmoid激活函数。判别器是一种混合架构,结合了嵌入层、双向LSTM(256个隐藏单元)和密集神经网络,以区分真实数据和合成数据。最终的分类阶段由一系列带有LeakyReLU激活函数的密集层组成。在处理生成样本时,判别器首先通过argmax操作将生成器的逻辑值转换为标记ID。


class Discriminator(nn.Module):
    def __init__(self, vocab_size):
        super(Discriminator, self).__init__()
        self.embedding = nn.Embedding(vocab_size, 128)
        self.lstm = nn.LSTM(128, 256, batch_first=True, bidirectional=True)
        self.classifier = nn.Sequential(
            nn.Linear(512, 256),
            nn.LeakyReLU(0.2),
            nn.Linear(256, 1),
            nn.Sigmoid()
        )
        
    def forward(self, input_ids):
        if input_ids.dim() == 3:  # If input is logits (batch_size, sequence_length, vocab_size)
            input_ids = torch.argmax(input_ids, dim=-1)  # Convert to token ids
        
        embedded = self.embedding(input_ids.int())
        lstm_out, _ = self.lstm(embedded)
        lstm_out = lstm_out[:, -1, :]  # take last hidden state
        validity = self.classifier(lstm_out)
        return validity


训练过程使用了Adam优化器,为生成器和判别器网络均设置了2e-5的初始学习率,并结合了ReduceLROnPlateau调度器,该调度器能够根据损失轨迹动态调整学习率。


g_scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(g_optimizer, mode='min', factor=0.5, patience=2)'min', factor=0.5, patience=2)
d_scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(d_optimizer, mode='min', factor=0.5, patience=2)


该过程始于上文高级微调部分中提到的数值分词器。该分词器使用自定义的分隔标记来处理数值和列标识符,从而确保对制造测量数据的精确表示。在每次训练迭代中,首先对真实数据序列进行分词,并将其输入到判别器中,判别器学习如何为真实的制造数据模式分配高概率。生成器则利用DistilGPT2架构生成合成序列,这些序列由判别器进行评估,并使用二元交叉熵计算对抗损失。


 for batch_idx, batch in enumerate(dataloader):for batch_idx, batch in enumerate(dataloader):
    real_texts = batch['input_ids'].to(device)
    attention_mask = batch['attention_mask'].to(device)
    batch_size = real_texts.size(0)
    
    #Generator
    g_optimizer.zero_grad()
    
    gen_logits, causal_lm_loss = generator(real_texts, attention_mask)
    fake_checks = discriminator(gen_logits)
    r = real_texts.squeeze().float()
    g =  torch.argmax(gen_logits, dim=-1).float()
    
    w_loss = wasserstein_loss(fake_checks)
    k_loss = kth_order_loss(g, r, k=2)
    
    g_loss = (
        1.0 * causal_lm_loss +  # lm loss
        0.8 * w_loss + # Wasserstein loss 
        0.2 * k_loss            # k-th order loss for numerical accuracy
    )
    
    g_loss.backward()
    torch.nn.utils.clip_grad_norm_(generator.parameters(), 1.0)            
    g_optimizer.step()
    # Discriminator 
    d_optimizer.zero_grad()
    
    real_checks = discriminator(real_texts)
    fake_checks = discriminator(g)
    
    d_loss = wasserstein_loss(fake_checks) - wasserstein_loss(real_checks)
    
    gradient_penalty = compute_gradient_penalty(discriminator, r, g)
    d_loss += 10 * gradient_penalty
    torch.nn.utils.clip_grad_norm_(discriminator.parameters(), 1.0)
    d_optimizer.step()
    d_loss.backward()    


训练过程为生成器采用了教师强制方法,将语言建模损失与对抗损失相结合,以在适应制造数据分布的同时保持连贯的序列生成。ReduceLROnPlateau调度器会监控损失指标(耐心值为2个周期),当改进达到平台期时,将学习率减半,这有助于稳定训练并防止振荡。梯度惩罚计算和梯度裁剪是LM-GAN训练过程中至关重要的稳定机制。梯度惩罚是通过在真实样本和生成样本之间进行插值来计算的。此外,还对生成器和判别器的参数应用了torch.nn.utils.clip_grad_norm_,最大范数阈值为1.0,以防止梯度爆炸。这种双重优化过程结合专门的分词策略,使模型能够同时学习数据的统计特性和不同传感器测量值之间的潜在关系,而调度机制则确保了整个训练过程中两个网络的稳健收敛。


对合成制造数据和原始制造数据的比较分析表明,LM-GAN模型在捕捉传感器测量的统计特性和潜在模式方面具有卓越的能力。分布图显示了所有21个传感器通道中真实数据和合成数据之间的一致性,合成数据(以橙色虚线表示)紧密跟随原始测量值(蓝色实线)的时间动态。


36


直方图比较显示不同传感器范围内分布匹配一致,特别是在操作设置(s1-s3)和关键传感器测量中尤为明显。


37


值得注意的是,PCA(主成分分析)可视化展示了数据流形得到了极好的保持,合成样本(红点)与真实数据点(蓝点)在主成分上紧密交织,这表明模型已成功捕捉到了不同传感器测量值之间的复杂相关性。


38


这一全面的评估验证了LM-GAN架构不仅保留了单个传感器的边缘分布,而且还维持了制造过程数据中存在的复杂关系和操作模式。


消融研究

为了定量评估合成数据生成方法的保真度,我们进行了全面的消融研究,测量了原始数据集和合成数据集之间的统计相似性。分析采用了三个主要的评估框架:


比较原始数据集和合成数据集之间的统计量(最小值、最大值、平均值和标准差)。


39


聚合保真度评分:Kolmogorov-Smirnov TestKolmogorov-Smirnov scores评分。


40


通过相关矩阵比较来评估特征关系的保持情况

以下是LM-GAN架构生成的原始分布与合成分布之间的相关矩阵。


41

42


结论

这两部分的研究系列对工业制造应用中的合成数据生成技术进行了全面分析。第一部分通过评估包括GAN、VAE、高斯Copula、贝叶斯网络和CTGAN在内的传统生成模型,建立了基线性能指标,展示了贝叶斯网络和高斯Copula等概率方法在保持统计分布方面的优越性能。


第二部分通过引入小型语言模型(SLM)在合成数据生成中的新颖应用,推动了该领域的发展。研究从基础的提示工程发展到复杂的架构,如LM-GAN,展示了合成数据保真度的不断提升。比较分析显示,采用自定义损失函数的高级微调显著改进了基本的SLM方法,而提出的LM-GAN架构在保持边缘分布和复杂的特征间关系方面达到了最先进的性能。



文章来源:https://medium.com/@gopikwork/synthetic-tabular-data-generation-using-small-language-models-cab13fa861c0
欢迎关注ATYUN官方公众号
商务合作及内容投稿请联系邮箱:bd@atyun.com
评论 登录
热门职位
Maluuba
20000~40000/月
Cisco
25000~30000/月 深圳市
PilotAILabs
30000~60000/年 深圳市
写评论取消
回复取消