WaveDetect / wavedetect_hf.py
KaitongQin's picture
Upload folder using huggingface_hub
d38df62 verified
Raw
History Blame Contribute Delete
5.61 kB
import math
from pathlib import Path
import torch
import torch.nn as nn
from transformers import (
AutoConfig,
AutoModelForCausalLM,
AutoTokenizer,
PreTrainedModel,
PretrainedConfig,
ResNetConfig,
ResNetModel,
)
class WaveDetectConfig(PretrainedConfig):
model_type = "wavedetect"
def __init__(self, base_model_path, num_scales=16, kernel_size=128, n_labels=2, **kwargs):
super().__init__(**kwargs)
self.base_model_path = base_model_path
self.num_scales = num_scales
self.kernel_size = kernel_size
self.n_labels = n_labels
def build_resnet18_config():
return ResNetConfig(
num_channels=3,
embedding_size=64,
hidden_sizes=[64, 128, 256, 512],
depths=[2, 2, 2, 2],
layer_type="basic",
hidden_act="relu",
downsample_in_first_stage=False,
)
class WaveletTransform(nn.Module):
def __init__(self, num_scales=16, kernel_size=128):
super().__init__()
self.num_scales = num_scales
self.kernel_size = kernel_size
self.sigma = 1.0
self.freq0 = 5.0
self.register_buffer("time", torch.linspace(-2, 2, steps=kernel_size))
self.register_buffer("scales", torch.linspace(1.0, num_scales, num_scales))
def morlet(self, scale):
t = self.time / scale
wave = torch.exp(-0.5 * (t / self.sigma) ** 2) * torch.cos(2 * math.pi * self.freq0 * t)
wave = wave / (wave.abs().sum() + 1e-6)
return wave.view(1, 1, -1)
def forward(self, token_probs, attention_mask):
batch_size, seq_len = token_probs.shape
spectra = []
for i in range(batch_size):
valid_len = attention_mask[i].sum().long().item()
x = token_probs[i, :valid_len].view(1, 1, -1)
rows = []
for scale in self.scales:
kernel = self.morlet(scale.to(x.device)).to(x.dtype)
rows.append(torch.nn.functional.conv1d(x, kernel, padding=self.kernel_size // 2))
spectrum = torch.stack(rows, dim=2)
if valid_len < seq_len:
spectrum = torch.nn.functional.pad(spectrum, (0, seq_len - valid_len))
spectra.append(spectrum)
return torch.cat(spectra, dim=0).abs()
class WaveDetect(PreTrainedModel):
config_class = WaveDetectConfig
def __init__(self, config):
super().__init__(config)
dtype = torch.bfloat16 if torch.cuda.is_available() else torch.float32
base_config = AutoConfig.from_pretrained(config.base_model_path, trust_remote_code=True)
self.qwen_model = AutoModelForCausalLM.from_config(base_config).to(dtype)
self.wavelet = WaveletTransform(config.num_scales, config.kernel_size)
self.cnn = ResNetModel(build_resnet18_config()).to(dtype)
self.classifier = nn.Linear(512, config.n_labels)
self.post_init()
def forward(self, input_ids, attention_mask):
outputs = self.qwen_model(input_ids=input_ids, attention_mask=attention_mask)
logits = outputs.logits[:, :-1]
labels = input_ids[:, 1:]
mask = attention_mask[:, :-1]
log_probs = torch.log_softmax(logits, dim=-1)
token_probs = torch.exp(log_probs.gather(2, labels.unsqueeze(-1)).squeeze(-1)) * mask
spectrum = self.wavelet(token_probs, mask).repeat(1, 3, 1, 1)
cnn_out = self.cnn(spectrum, return_dict=True)
pooled = cnn_out.last_hidden_state.mean(dim=[2, 3]).to(self.classifier.weight.dtype)
return self.classifier(pooled)
def clean_state_dict(state_dict):
if "state_dict" in state_dict:
state_dict = state_dict["state_dict"]
return {k.removeprefix("module.").removeprefix("_orig_mod."): v for k, v in state_dict.items()}
class WaveDetectPredictor:
def __init__(self, model_dir=None, device=None, max_length=1024):
self.model_dir = Path(model_dir or Path(__file__).resolve().parent)
self.device = torch.device(device or ("cuda" if torch.cuda.is_available() else "cpu"))
self.max_length = max_length
self.tokenizer = AutoTokenizer.from_pretrained(self.model_dir, trust_remote_code=True)
if self.tokenizer.pad_token_id is None:
self.tokenizer.pad_token = self.tokenizer.eos_token
config = WaveDetectConfig(base_model_path=str(self.model_dir))
self.model = WaveDetect(config).to(self.device)
state_dict = torch.load(self.model_dir / "wavedetect_all.bin", map_location="cpu")
self.model.load_state_dict(clean_state_dict(state_dict), strict=True)
self.model.eval()
@torch.no_grad()
def predict(self, text):
inputs = self.tokenizer(
text,
return_tensors="pt",
truncation=True,
max_length=self.max_length,
padding=True,
).to(self.device)
logits = self.model(inputs["input_ids"], inputs["attention_mask"])
probs = torch.softmax(logits, dim=-1)[0]
ai_score = float(probs[1].detach().cpu())
return {
"label": int(ai_score >= 0.5),
"label_name": "ai_generated" if ai_score >= 0.5 else "human",
"ai_score": ai_score,
}
def predict(text, model_dir=None):
return WaveDetectPredictor(model_dir=model_dir).predict(text)
def main():
predictor = WaveDetectPredictor()
text = "Clara folded the letter and stood up. The hallway was dark, but a thin line of light shone from beneath the locked door upstairs."
print(predictor.predict(text))
if __name__ == "__main__":
main()