cihatyldz commited on
Commit
562ffb3
·
verified ·
1 Parent(s): 807809b

Upload pusula_perturb.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. pusula_perturb.py +150 -0
pusula_perturb.py ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ pusula_perturb.py — Turkce sorgular icin kontrollu, tekrarlanabilir bozulmalar.
3
+
4
+ Her bozulma: f(text, rng, **params) -> text
5
+ Rastgelelik sorgu kimligi + bozulma adindan turetilir: calisma sirasi sonucu degistirmez.
6
+ """
7
+ import hashlib
8
+ import re
9
+
10
+ import numpy as np
11
+
12
+ # ═══ Turkce harf yardimcilari (uzunluk koruyan) ═══
13
+ ASCII_MAP = str.maketrans({
14
+ "ç": "c", "Ç": "C", "ğ": "g", "Ğ": "G", "ı": "i", "İ": "I",
15
+ "ö": "o", "Ö": "O", "ş": "s", "Ş": "S", "ü": "u", "Ü": "U",
16
+ "â": "a", "Â": "A", "î": "i", "Î": "I", "û": "u", "Û": "U",
17
+ })
18
+ DIACRITICS = set("çÇğĞıİöÖşŞüÜâÂîÎûÛ")
19
+
20
+
21
+ def tr_lower(s):
22
+ return s.replace("I", "ı").replace("İ", "i").lower()
23
+
24
+
25
+ def tr_upper(s):
26
+ return s.replace("i", "İ").replace("ı", "I").upper()
27
+
28
+
29
+ # ═══ Turkce Q klavye komsuluklari (yazim hatasi icin) ═══
30
+ _ROWS = ["qwertyuıopğü", "asdfghjklşi", "zxcvbnmöç"]
31
+ KEY_NEIGHBORS = {}
32
+ for r, row in enumerate(_ROWS):
33
+ for c, ch in enumerate(row):
34
+ nb = set()
35
+ for dr in (-1, 0, 1):
36
+ rr = r + dr
37
+ if 0 <= rr < len(_ROWS):
38
+ for dc in (-1, 0, 1):
39
+ cc = c + dc
40
+ if (dr or dc) and 0 <= cc < len(_ROWS[rr]):
41
+ nb.add(_ROWS[rr][cc])
42
+ KEY_NEIGHBORS[ch] = sorted(nb)
43
+
44
+
45
+ def make_rng(query_id, perturbation, seed=0):
46
+ h = hashlib.sha256(f"{seed}|{perturbation}|{query_id}".encode()).digest()
47
+ return np.random.default_rng(int.from_bytes(h[:8], "little"))
48
+
49
+
50
+ # ═══ Bozulmalar ═══
51
+ def ascii_full(text, rng=None):
52
+ """Tum Turkce karakterler ASCII karsiligina (Ingilizce klavye)."""
53
+ return text.translate(ASCII_MAP)
54
+
55
+
56
+ def ascii_partial(text, rng, p=0.5):
57
+ """Her Turkce karakter p olasilikla ASCII'ye (karisik yazim)."""
58
+ return "".join(ch.translate(ASCII_MAP) if ch in DIACRITICS and rng.random() < p else ch for ch in text)
59
+
60
+
61
+ def upper(text, rng=None):
62
+ """Turkce klavyeyle BUYUK HARF."""
63
+ return tr_upper(text)
64
+
65
+
66
+ def _typo_word(word, rng):
67
+ if len(word) < 4:
68
+ return word
69
+ i = int(rng.integers(1, len(word))) # ilk harfe dokunma
70
+ op = rng.choice(["sub", "del", "swap", "dup"])
71
+ ch = word[i]
72
+ if op == "sub":
73
+ nbs = KEY_NEIGHBORS.get(tr_lower(ch))
74
+ if not nbs:
75
+ return word
76
+ rep = rng.choice(nbs)
77
+ rep = tr_upper(rep) if ch != tr_lower(ch) else rep
78
+ return word[:i] + rep + word[i + 1:]
79
+ if op == "del":
80
+ return word[:i] + word[i + 1:]
81
+ if op == "swap" and i < len(word) - 1:
82
+ return word[:i] + word[i + 1] + word[i] + word[i + 2:]
83
+ return word[:i] + ch + word[i:] # dup (swap son harfteyse de buraya duser)
84
+
85
+
86
+ def typo(text, rng, rate=0.2):
87
+ """Kelime basina rate olasilikla tek bir klavye hatasi (komsu tus, silme, yer degistirme, cift basma)."""
88
+ return re.sub(r"\w+", lambda m: _typo_word(m.group(0), rng) if rng.random() < rate else m.group(0), text)
89
+
90
+
91
+ # Gundelik yazim: -yor + kisi eki birlikte kisalir (kullaniyorum -> kullaniyom, geliyorsun -> geliyosun)
92
+ _YOR_SUFFIX = {"": "", "um": "m", "uz": "z", "sun": "sun", "sunuz": "sunuz", "lar": "lar"}
93
+
94
+ # (desen, yerine [str veya fonksiyon], aciklama). Her eslesme rate olasilikla uygulanir.
95
+ COLLOQUIAL_RULES = [
96
+ (r"\bnerede\b", "nerde", "nerede → nerde"),
97
+ (r"\bneredeyse\b", "nerdeyse", "neredeyse → nerdeyse"),
98
+ (r"\bbir\b", "bi", "bir → bi"),
99
+ (r"\bdeğil\b", "diil", "değil → diil"),
100
+ (r"\bne yapacağım\b", "napcam", "ne yapacağım → napcam"),
101
+ (r"\bne yapıyorsun\b", "napıyosun", "ne yapıyorsun → napıyosun"),
102
+ (r"(\w+?[ıiuü])yor(sunuz|sun|um|uz|lar)?\b",
103
+ lambda m: m.group(1) + "yo" + _YOR_SUFFIX[m.group(2) or ""], "-yor(um) → -yo(m)"),
104
+ (r"(\w+)acağım\b", r"\1acam", "-acağım → -acam"),
105
+ (r"(\w+)eceğim\b", r"\1ecem", "-eceğim → -ecem"),
106
+ (r"(\w+)\s(m[ıiuü](?:s[ıiuü]n|y[ıiuü]m|d[ıiuü]r)?)\b", r"\1\2", "soru eki bitişik (var mı → varmı)"),
107
+ (r"\b(ben|sen|biz|siz|o|onlar)\s(de|da)\b", r"\1\2", "zamir + de/da bitişik (ben de → bende)"),
108
+ ]
109
+
110
+
111
+ def _keep_case(original, replaced):
112
+ """Eslesmenin ilk harfi buyukse sonucun ilk harfini de buyut (Nerede -> Nerde)."""
113
+ if original[:1] and original[:1] != tr_lower(original[:1]) and replaced:
114
+ return tr_upper(replaced[0]) + replaced[1:]
115
+ return replaced
116
+
117
+
118
+ def colloquial(text, rng, rate=0.8):
119
+ out = text
120
+ for pattern, repl, _ in COLLOQUIAL_RULES:
121
+ def sub(m, repl=repl):
122
+ if rng.random() >= rate:
123
+ return m.group(0)
124
+ new = repl(m) if callable(repl) else m.expand(repl)
125
+ return _keep_case(m.group(0), new)
126
+ out = re.sub(pattern, sub, out, flags=re.IGNORECASE)
127
+ return out
128
+
129
+
130
+ def mobile(text, rng, typo_rate=0.15):
131
+ """Aceleyle Ingilizce klavyeli telefondan yazilmis sorgu: kucuk harf + gundelik + hata + ASCII.
132
+ ASCII en SONDA: yazim hatasi Turkce komsu tus (ğ, ş) uretse bile cikti tutarli bicimde ASCII olur."""
133
+ out = colloquial(tr_lower(text), rng)
134
+ return ascii_full(typo(out, rng, rate=typo_rate))
135
+
136
+
137
+ PERTURBATIONS = {
138
+ "clean": (lambda t, rng: t, "Temiz (referans)"),
139
+ "ascii_full": (lambda t, rng: ascii_full(t), "Tüm Türkçe karakterler ASCII"),
140
+ "ascii_partial": (lambda t, rng: ascii_partial(t, rng), "Türkçe karakterlerin yarısı ASCII"),
141
+ "upper": (lambda t, rng: upper(t), "BÜYÜK HARF"),
142
+ "typo": (lambda t, rng: typo(t, rng), "Kelime başına %20 klavye hatası"),
143
+ "colloquial": (lambda t, rng: colloquial(t, rng), "Gündelik yazım (nerde, geliyo, varmı)"),
144
+ "mobile": (lambda t, rng: mobile(t, rng), "Hepsi: küçük harf + gündelik + ASCII + hata"),
145
+ }
146
+
147
+
148
+ def perturb(text, query_id, name, seed=0):
149
+ fn, _ = PERTURBATIONS[name]
150
+ return fn(text, make_rng(query_id, name, seed))