rdf2vecgpu 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
rdf2vecgpu/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ __version__ = "0.1.0"
2
+
3
+ __all__ = [
4
+ "GPU_RDF2Vec"
5
+ ]
File without changes
@@ -0,0 +1,459 @@
1
+ import cupy as cp
2
+ import cudf
3
+ import dask_cudf as dcudf
4
+ from loguru import logger
5
+ from cugraph import uniform_random_walks, filter_unreachable, bfs
6
+ import cugraph
7
+ from cugraph import Graph # single-GPU Graph
8
+ from cugraph.dask import uniform_random_walks as dask_uniform_random_walks
9
+ from cugraph.dask.traversal.bfs import bfs as dask_bfs
10
+
11
+ class single_gpu_walk_corpus:
12
+ def __init__(self, graph: Graph):
13
+ self.G = graph
14
+
15
+ def _replace_entities_with_tokens(self, tokeninzation: cudf.Series, word: cudf.Series, edge_df: cudf.DataFrame) -> tuple[cudf.DataFrame, cudf.DataFrame]:
16
+ """_summary_
17
+
18
+ Args:
19
+ tokeninzation (cudf.Series): _description_
20
+ word (cudf.Series): _description_
21
+ edge_df (cudf.DataFrame): _description_
22
+
23
+ Returns:
24
+ tuple[cudf.DataFrame, cudf.DataFrame]: _description_
25
+ """
26
+ word2idx = cudf.concat([cudf.Series(tokeninzation), cudf.Series(word)], axis=1)
27
+ word2idx.columns = ["token", "word"]
28
+ edge_df["subject"] = edge_df.merge(word2idx, left_on="subject", right_on="word", how="left")["token"]
29
+ edge_df["predicate"] = edge_df.merge(word2idx, left_on="predicate", right_on="word", how="left")["token"]
30
+ edge_df["object"] = edge_df.merge(word2idx, left_on="object", right_on="word", how="left")["token"]
31
+ edge_df = edge_df.astype("int32")
32
+ return edge_df, word2idx
33
+
34
+ def _triples_to_tokens(self, df: cudf.DataFrame, min_count) -> cudf.DataFrame:
35
+ """_summary_
36
+
37
+ Args:
38
+ df (cudf.DataFrame): _description_
39
+
40
+ Returns:
41
+ cudf.DataFrame: _description_
42
+ """
43
+ df = df.sort_values(['walk_id', 'step'])
44
+
45
+ # predicate tokens --------------------------------------------------------
46
+ pred_tok = cudf.DataFrame({
47
+ 'walk_id': df.walk_id,
48
+ 'pos': df.step * 2 + 1,
49
+ 'token': df.predicate
50
+ })
51
+
52
+ # dst tokens --------------------------------------------------------------
53
+ dst_tok = cudf.DataFrame({
54
+ 'walk_id': df.walk_id,
55
+ 'pos': df.step * 2 + 2,
56
+ 'token': df.dst
57
+ })
58
+
59
+ # src tokens (only for the first row in each walk) ------------------------
60
+ first_rows = df[df.step == 0]
61
+ src_tok = cudf.DataFrame({
62
+ 'walk_id': first_rows.walk_id,
63
+ 'pos': [0] * len(first_rows),
64
+ 'token': first_rows.src
65
+ })
66
+
67
+ # concat & sort ----------------------------------------------------------
68
+ tokens = cudf.concat([src_tok, pred_tok, dst_tok])
69
+ token_counts = tokens.groupby('token').size()
70
+ token_counts = token_counts[token_counts >= min_count]
71
+ token_counts = token_counts.reset_index()
72
+ tokens = tokens.loc[tokens["token"].isin(token_counts["token"])]
73
+ return tokens.sort_values(['walk_id', 'pos'])
74
+
75
+ def _skipgram_pairs(self, tokens: cudf.DataFrame, window: int):
76
+ pairs = []
77
+
78
+ # one inner merge per offset (all happen on-GPU)
79
+ for d in range(-window, window + 1):
80
+ if d == 0:
81
+ continue
82
+ left = tokens.rename(columns={'token': 'center'})
83
+ right = tokens.rename(columns={'token': 'context'})
84
+ right = right.assign(pos=right.pos - d) # shift
85
+
86
+ pairs.append(
87
+ left.merge(right, on=['walk_id', 'pos'],
88
+ how='inner')[['center', 'context']]
89
+ )
90
+ return cudf.concat(pairs, ignore_index=True)
91
+
92
+
93
+ def _cbow_pairs(self, tokens: cudf.DataFrame, window: int):
94
+ """
95
+ For each position in each walk, collect all context tokens within the given window
96
+ into a list, paired with the centre token.
97
+
98
+ Returns a DataFrame with columns:
99
+ - 'context' : list of context tokens
100
+ - 'center' : the centre token
101
+ """
102
+ pairs = []
103
+
104
+ # for each offset, shift tokens to become a 'context' column
105
+ for d in range(-window, window + 1):
106
+ if d == 0:
107
+ continue
108
+
109
+ # rename token → context, and shift its pos so it lines up with the centre at pos+d
110
+ ctx = (
111
+ tokens
112
+ .rename(columns={'token': 'context'})
113
+ .assign(pos=tokens.pos - d)
114
+ )
115
+
116
+ # join centre and this slice of context
117
+ centre = tokens.rename(columns={'token': 'center'})
118
+ merged = centre.merge(
119
+ ctx,
120
+ on=['walk_id', 'pos'],
121
+ how='inner'
122
+ )[['walk_id', 'pos', 'center', 'context']]
123
+
124
+ pairs.append(merged)
125
+
126
+ # stack all (centre, single-context) rows
127
+ all_pairs = cudf.concat(pairs, ignore_index=True)
128
+
129
+ # aggregate each centre into a list of contexts
130
+ cbow = (
131
+ all_pairs
132
+ .groupby(['walk_id', 'pos', 'center'])
133
+ .agg_list('context') # now each row has a 'context' list
134
+ .reset_index()
135
+ )
136
+ # return just the two columns we need for training
137
+ return cbow[['context', 'center']]
138
+
139
+ def bfs_walk(self, edge_df: cudf.DataFrame, walk_vertices: cudf.Series, walk_depth: int, random_state: int, word2vec_model: str, min_count: int) -> cudf.DataFrame:
140
+ out = []
141
+ max_walk_id = 0
142
+ for v in walk_vertices.to_cupy():
143
+ bfs_extraction = bfs(self.G, start=v, depth_limit=walk_depth)
144
+ bfs_extraction_filtered = filter_unreachable(bfs_extraction)
145
+ bfs_edges = (bfs_extraction_filtered[bfs_extraction_filtered.predecessor != -1].rename(columns={"predecessor": "subject", "vertex": "object"}).reset_index(drop=True))
146
+ outdeg = bfs_edges.groupby("subject").size().rename("out_deg")
147
+ leave_intermediate = bfs_extraction_filtered[["vertex"]].merge(outdeg, left_on="vertex", right_index=True, how="left")
148
+ leaves = leave_intermediate[leave_intermediate.out_deg.isnull()].rename(columns={"vertex": "object"}).reset_index(drop=True)
149
+ walk_id_list = list(range(max_walk_id, len(leaves) + max_walk_id))
150
+ leaves["walk_id"] = walk_id_list
151
+ max_walk_id = max(walk_id_list) + 1
152
+ walk_edges = cudf.DataFrame(columns=["subject", "object", "walk_id"])
153
+ frontier = leaves[["object", "walk_id"]]
154
+ while len(frontier):
155
+ # join to find each frontier vertex’s parent
156
+ step = frontier.merge(bfs_edges, on="object", how="left") # adds `source`
157
+ step = step.dropna(subset=["subject"]) # reached the root?
158
+
159
+ # collect the edges that belong to these walks
160
+ walk_edges = cudf.concat([walk_edges, step[["subject", "object", "walk_id"]]],
161
+ ignore_index=True)
162
+
163
+ # next frontier is this layer’s parents
164
+ frontier = step[["subject", "walk_id"]].rename(columns={"subject": "object"})
165
+ walk_edges = (
166
+ walk_edges.astype({"subject": "int32", "object": "int32", "walk_id": "int32"})
167
+ .sort_values(["walk_id", "subject"])
168
+ .reset_index(drop=True)
169
+ )
170
+ out.append(walk_edges)
171
+ bfs_all = cudf.concat(out, ignore_index=True)
172
+ bfs_all["step"] = bfs_all.groupby("walk_id").cumcount()
173
+ bfs_all = bfs_all.merge(edge_df, left_on=["subject", "object"], right_on=["subject", "object"], how="left")[["subject", "predicate", "object", "walk_id", "step"]]
174
+ triple_to_token_df = self._triples_to_tokens(bfs_all, min_count)
175
+ if word2vec_model == "skipgram":
176
+ skipgram_df = self._skipgram_pairs(triple_to_token_df, window=5)
177
+ return skipgram_df
178
+ elif word2vec_model == "cbow":
179
+ cbow_df = self._cbow_pairs(triple_to_token_df, window=5)
180
+ return cbow_df
181
+ else:
182
+ raise ValueError("word2vec_model should be either 'skipgram' or 'cbow'")
183
+
184
+ def random_walk(self, edge_df: cudf.DataFrame, walk_vertices: cudf.Series, walk_depth: int, random_state: int, word2vec_model: str, min_count: int) -> cudf.DataFrame:
185
+ """_summary_
186
+
187
+ Args:
188
+ edge_df (cudf.DataFrame): _description_
189
+ walk_vertices (cudf.Series): _description_
190
+
191
+ Raises:
192
+ NotImplementedError: _description_
193
+ ValueError: _description_
194
+
195
+ Returns:
196
+ cudf.DataFrame: _description_
197
+ """
198
+ random_walks, _, max_length = uniform_random_walks(self.G, start_vertices=walk_vertices, max_depth=walk_depth,random_state=random_state)
199
+ group_keys = cudf.Series(range(len(random_walks))) // max_length
200
+ transformed_random_walk = random_walks.to_frame(name="src")
201
+ transformed_random_walk["walk_id"] = group_keys
202
+ transformed_random_walk["dst"] = transformed_random_walk['src'].shift(-1)
203
+ transformed_random_walk = transformed_random_walk.mask(transformed_random_walk == -1, [None, None, None]).dropna()
204
+
205
+ transformed_random_walk["step"] = transformed_random_walk.groupby("walk_id").cumcount()
206
+ merged_walks = transformed_random_walk.merge(edge_df, left_on=["src", "dst"], right_on= ["subject", "object"], how="left")[["src", "predicate", "dst", "walk_id", "step"]]
207
+ merged_walks = merged_walks.dropna()
208
+ merged_walks.sort_values(["walk_id", "step"])
209
+ triple_to_token_df = self._triples_to_tokens(merged_walks, min_count)
210
+ if word2vec_model == "skipgram":
211
+ skipgram_df = self._skipgram_pairs(triple_to_token_df, window=5)
212
+ return skipgram_df
213
+ elif word2vec_model == "cbow":
214
+ cbow_df = self._cbow_pairs(triple_to_token_df, window=5)
215
+ return cbow_df
216
+ else:
217
+ raise ValueError("word2vec_model should be either 'skipgram' or 'cbow'")
218
+
219
+
220
+ def _ensure_dask_frame(df, nparts=None):
221
+ """Return a dask_cudf.DataFrame (zero-copy if already dask)."""
222
+ if isinstance(df, dcudf.DataFrame):
223
+ return df
224
+ return dcudf.from_cudf(df, npartitions=nparts or 1)
225
+
226
+ def _dst_per_partition(df: cudf.DataFrame):
227
+ """Partition-safe dst = src.shift(-1) and drop last row."""
228
+ df["dst"] = df["src"].shift(-1)
229
+ return df.iloc[:-1]
230
+
231
+
232
+ class multi_gpu_walk_corpus:
233
+ """
234
+ A fully Dask/cuDF/cuGraph implementation of random-walk and BFS
235
+ corpora that scales to many GPUs without triggering the 32-bit
236
+ `size_type` overflow.
237
+ """
238
+
239
+ def __init__(self, graph: Graph):
240
+ # Convert single-GPU Graphs to distributed Graphs on demand
241
+ self.G = graph
242
+
243
+ # --------------- Low-level helpers (unchanged API) ------------------- #
244
+
245
+ def _triples_to_tokens(self, df: dcudf.DataFrame, min_count: int) -> dcudf.DataFrame:
246
+ df = df.sort_values(['walk_id', 'step'])
247
+
248
+ # predicate tokens --------------------------------------------------
249
+ pred_tok = dcudf.DataFrame({
250
+ 'walk_id': df.walk_id,
251
+ 'pos': df.step * 2 + 1,
252
+ 'token': df.predicate
253
+ })
254
+
255
+ # dst tokens --------------------------------------------------------
256
+ dst_tok = dcudf.DataFrame({
257
+ 'walk_id': df.walk_id,
258
+ 'pos': df.step * 2 + 2,
259
+ 'token': df.dst
260
+ })
261
+
262
+ # src tokens (first row of each walk) ------------------------------
263
+ first_rows = df[df.step == 0]
264
+ src_tok = dcudf.DataFrame({
265
+ 'walk_id': first_rows.walk_id,
266
+ 'pos': 0,
267
+ 'token': first_rows.src
268
+ })
269
+
270
+ tokens = dcudf.concat([src_tok, pred_tok, dst_tok])
271
+
272
+ # frequency filter
273
+ tok_counts = tokens.groupby('token').size()
274
+ tok_counts = tok_counts[tok_counts >= min_count].reset_index()
275
+ tokens = tokens.merge(tok_counts[['token']], on='token')
276
+
277
+ return tokens.sort_values(['walk_id', 'pos'])
278
+
279
+ def _skipgram_pairs(self, tokens: dcudf.DataFrame, window: int):
280
+ pairs = []
281
+ for d in range(-window, window + 1):
282
+ if d == 0:
283
+ continue
284
+ left = tokens.rename(columns={'token': 'center'})
285
+ right = tokens.rename(columns={'token': 'context'})
286
+ right = right.assign(pos=right.pos - d)
287
+
288
+ pairs.append(
289
+ left.merge(right, on=['walk_id', 'pos'],
290
+ how='inner')[['center', 'context']]
291
+ )
292
+ return dcudf.concat(pairs, ignore_index=True)
293
+
294
+ def _cbow_pairs(self, tokens: dcudf.DataFrame, window: int):
295
+ """
296
+ For CBOW we emit one pair per (target, context) exactly like Skip-Gram
297
+ but invert the columns so downstream code can treat the left column as
298
+ the *label* (word to predict) and the right column as a single context
299
+ word. If your trainer expects aggregated contexts, groupby/aggregate
300
+ after `.compute()`.
301
+ """
302
+ pairs = []
303
+ for d in range(-window, window + 1):
304
+ if d == 0:
305
+ continue
306
+ left = tokens.rename(columns={'token': 'target'}) # word to predict
307
+ right = tokens.rename(columns={'token': 'context'})
308
+ right = right.assign(pos=right.pos - d)
309
+
310
+ pairs.append(
311
+ left.merge(right, on=['walk_id', 'pos'],
312
+ how='inner')[['target', 'context']]
313
+ )
314
+ return dcudf.concat(pairs, ignore_index=True)
315
+
316
+ # ---------------------- Random-walk corpus -------------------------- #
317
+
318
+ def random_walk(
319
+ self,
320
+ edge_df: cudf.DataFrame | dcudf.DataFrame,
321
+ walk_vertices: cudf.Series | dcudf.Series,
322
+ walk_depth: int,
323
+ random_state: int,
324
+ word2vec_model: str,
325
+ min_count: int,
326
+ ):
327
+ """Multi-GPU random-walk corpus builder (Skip-Gram / CBOW)."""
328
+
329
+ # ---- Step 0 – inputs must be distributed ----
330
+ edge_ddf = _ensure_dask_frame(edge_df)
331
+ start_vertices = _ensure_dask_frame(
332
+ cudf.DataFrame({'v': walk_vertices})
333
+ )['v']
334
+
335
+ # ---- Step 1 – distributed random walks ----
336
+ logger.info("Running uniform_random_walks on {} GPUs …", dcudf.get_device_count())
337
+ walks_s, _, max_len = dask_uniform_random_walks(
338
+ self.G,
339
+ start_vertices=start_vertices,
340
+ max_depth=walk_depth,
341
+ random_state=random_state,
342
+ ) # walks_s: dask_cudf.Series
343
+
344
+ walks = walks_s.to_frame(name="src").reset_index(drop=False)
345
+ walks = walks.rename(columns={'index': 'global_pos'})
346
+
347
+ # walk_id derived from the GLOBAL index – no overflow, no giant range
348
+ walks["walk_id"] = (walks["global_pos"].astype("int64") // max_len)
349
+
350
+ # ---- Step 2 – dst, step columns (partition-safe) ----
351
+ walks = walks.map_partitions(_dst_per_partition)
352
+ walks["step"] = walks.groupby("walk_id").cumcount()
353
+
354
+ # ---- Step 3 – join predicates ------------------------
355
+ merged = walks.merge(
356
+ edge_ddf,
357
+ left_on=["src", "dst"],
358
+ right_on=["subject", "object"],
359
+ how="left",
360
+ ).dropna(subset=["predicate"])
361
+
362
+ # keep needed columns only
363
+ merged = merged[["src", "predicate", "dst", "walk_id", "step"]]
364
+
365
+ # ---- Step 4 – tokens & (skip-gram | CBOW) pairs ----
366
+ tokens = self._triples_to_tokens(merged, min_count)
367
+
368
+ if word2vec_model == "skipgram":
369
+ pairs = self._skipgram_pairs(tokens, window=5)
370
+ elif word2vec_model == "cbow":
371
+ pairs = self._cbow_pairs(tokens, window=5)
372
+ else:
373
+ raise ValueError("word2vec_model must be 'skipgram' or 'cbow'")
374
+
375
+ return pairs # still a dask_cudf.DataFrame – call .compute() when needed
376
+
377
+ # -------------------------- BFS corpus ----------------------------- #
378
+ # (Algorithmically unchanged – wrapped for Dask simplicity) #
379
+ # ------------------------------------------------------------------ #
380
+
381
+ def bfs_walk(
382
+ self,
383
+ edge_df: cudf.DataFrame | dcudf.DataFrame,
384
+ walk_vertices: cudf.Series | dcudf.Series,
385
+ walk_depth: int,
386
+ random_state: int,
387
+ word2vec_model: str,
388
+ min_count: int,
389
+ ):
390
+ """
391
+ Distributed BFS-to-leaf walks. The inner loop is identical to the
392
+ single-GPU version but runs once **per worker** in parallel because
393
+ each start vertex is scheduled on the GPU that owns it.
394
+ """
395
+
396
+ edge_ddf = _ensure_dask_frame(edge_df)
397
+ start_vertices = _ensure_dask_frame(
398
+ cudf.DataFrame({'v': walk_vertices})
399
+ )['v']
400
+
401
+ # Helper that runs on a single GPU – we wrap the old single-GPU BFS
402
+ def _bfs_from_vertex(v):
403
+ bfs_extraction = cugraph.bfs(self.G.to_delayed(), start_vertices=v, max_depth=walk_depth)
404
+ bfs_extraction_filtered = cugraph.filter_unreachable(bfs_extraction)
405
+
406
+ bfs_edges = (
407
+ bfs_extraction_filtered[bfs_extraction_filtered.predecessor != -1]
408
+ .rename(columns={"predecessor": "subject", "vertex": "object"})
409
+ .reset_index(drop=True)
410
+ )
411
+
412
+ outdeg = bfs_edges.groupby("subject").size().rename("out_deg")
413
+ leaves = (
414
+ bfs_extraction_filtered[["vertex"]]
415
+ .merge(outdeg, left_on="vertex", right_index=True, how="left")
416
+ )
417
+ leaves = leaves[leaves.out_deg.isnull()].rename(columns={"vertex": "object"}).reset_index(drop=True)
418
+
419
+ # build walks root→leaf (same as before) ------------------
420
+ walk_edges = cudf.DataFrame(columns=["subject", "object", "walk_id"])
421
+ frontier = leaves.assign(walk_id=cp.arange(len(leaves), dtype="int32"))
422
+
423
+ while len(frontier):
424
+ step = frontier.merge(bfs_edges, on="object", how="left")
425
+ step = step.dropna(subset=["subject"])
426
+
427
+ walk_edges = cudf.concat(
428
+ [walk_edges, step[["subject", "object", "walk_id"]]],
429
+ ignore_index=True
430
+ )
431
+
432
+ frontier = step[["subject", "walk_id"]].rename(columns={"subject": "object"})
433
+
434
+ walk_edges["step"] = walk_edges.groupby("walk_id").cumcount()
435
+ return walk_edges
436
+
437
+ # Apply the helper across the Dask Series of start vertices
438
+ walks = start_vertices.map_partitions(
439
+ lambda s: cudf.concat([_bfs_from_vertex(v) for v in s.to_cupy()]),
440
+ )
441
+
442
+ merged = walks.merge(
443
+ edge_ddf,
444
+ left_on=["subject", "object"],
445
+ right_on=["subject", "object"],
446
+ how="left",
447
+ ).dropna(subset=["predicate"])
448
+
449
+ merged = merged.rename(columns={'subject': 'src', 'object': 'dst'})
450
+ tokens = self._triples_to_tokens(merged[["src", "predicate", "dst", "walk_id", "step"]], min_count)
451
+
452
+ if word2vec_model == "skipgram":
453
+ pairs = self._skipgram_pairs(tokens, window=5)
454
+ elif word2vec_model == "cbow":
455
+ pairs = self._cbow_pairs(tokens, window=5)
456
+ else:
457
+ raise ValueError("word2vec_model must be 'skipgram' or 'cbow'")
458
+
459
+ return pairs
File without changes
@@ -0,0 +1,106 @@
1
+ import torch
2
+ import torch.nn as nn
3
+ from torch.optim import Adam
4
+ import lightning as L
5
+ import numpy as np
6
+
7
+ torch.cuda.set_per_process_memory_fraction(0.9)
8
+
9
+ class SkipGram(L.LightningModule):
10
+ def __init__(self, vocab_size, embedding_dim, neg_samples, learning_rate=0.003, use_sparse=True):
11
+ super().__init__()
12
+ self.vocab_size = vocab_size
13
+ self.learning_rate = learning_rate
14
+
15
+ self.in_embeddings = nn.Embedding(vocab_size, embedding_dim, sparse=use_sparse)
16
+ self.output_embeddings = nn.Embedding(vocab_size, embedding_dim, sparse=use_sparse)
17
+
18
+ bound = 1.0 / embedding_dim
19
+
20
+ nn.init.uniform_(self.in_embeddings.weight, -bound, bound)
21
+ nn.init.uniform_(self.output_embeddings.weight, -bound, bound)
22
+
23
+ self.neg_samples = neg_samples
24
+ self.loss_fn = nn.BCEWithLogitsLoss(reduction='none')
25
+ self.use_sparse = use_sparse
26
+
27
+ def forward(self, center, context, negative):
28
+ center_embed = self.in_embeddings(center) # (batch, embed)
29
+ context_embed = self.output_embeddings(context) # (batch, embed)
30
+ negative_embed = self.output_embeddings(negative) # (batch, neg, embed)
31
+
32
+ # Positive score
33
+ pos_logits = (center_embed * context_embed).sum(dim=1)
34
+ pos_targets = torch.ones_like(pos_logits)
35
+ # pos_score = torch.sum(torch.mul(center_embed, context_embed), dim=1)
36
+ pos_loss = self.loss_fn(pos_logits, pos_targets)
37
+
38
+ # Negative score
39
+ neg_score = torch.bmm(negative_embed, center_embed.unsqueeze(-1)).squeeze(-1)
40
+ neg_targets = torch.zeros_like(neg_score)
41
+ neg_loss = self.loss_fn(neg_score, neg_targets).sum(dim=1)
42
+
43
+ return torch.mean(pos_loss + neg_loss)
44
+
45
+ def training_step(self, batch, batch_idx):
46
+ center, context = batch
47
+ negative = torch.randint(0, self.vocab_size, (center.size(0), self.neg_samples), device=self.device)
48
+ loss = self(center, context, negative)
49
+ self.log("train_loss", loss, prog_bar=True)
50
+ return loss
51
+
52
+ def configure_optimizers(self):
53
+ opt_function = torch.optim.SparseAdam if self.use_sparse else torch.optim.Adam
54
+ return opt_function(self.parameters(), lr=self.learning_rate)
55
+
56
+
57
+ class CBOW(L.LightningModule):
58
+ def __init__(self, vocab_size, embedding_dim, learning_rate, use_sparse=True):
59
+ super().__init__()
60
+ self.save_hyperparameters()
61
+ self.vocab_size = vocab_size
62
+ self.learning_rate = learning_rate
63
+ self.use_sparse = use_sparse
64
+
65
+ self.in_embeddings = nn.Embedding(vocab_size, embedding_dim)
66
+ self.output_embeddings = nn.Embedding(vocab_size, embedding_dim)
67
+
68
+ bound = 1.0 / embedding_dim
69
+ nn.init.uniform_(self.in_embeddings.weight, -bound, bound)
70
+ nn.init.uniform_(self.output_embeddings.weight, -bound, bound)
71
+ self.loss_fn = nn.BCEWithLogitsLoss(reduction='none')
72
+
73
+ def forward(self, center, context, negative):
74
+ center_embed = self.in_embeddings(center) # (batch, embed)
75
+ context_embed = self.output_embeddings(context) # (batch, embed)
76
+ negative_embed = self.output_embeddings(negative)
77
+
78
+
79
+ pos_logits = torch.sum(torch.mul(center_embed, context_embed), dim=-1)
80
+ pos_targets = torch.ones_like(pos_logits)
81
+ pos_loss = self.loss_fn(pos_logits, pos_targets)
82
+
83
+ neg_logits = torch.bmm(center_embed, context_embed.unsqueeze(-1)).squeeze(-1)
84
+ neg_targets = torch.zeros_like(neg_logits)
85
+ neg_loss = self.loss_fn(neg_logits, neg_targets)
86
+
87
+ return torch.mean(pos_loss + neg_loss)
88
+
89
+ def training_step(self, batch, batch_idx):
90
+ center, context = batch
91
+ batch_size, device = center.size(0), center.device
92
+
93
+ negative = torch.randint(0, self.vocab_size, (batch_size, self.window_size), device=device)
94
+ loss = self(center, context, negative)
95
+ self.log("train_loss", loss, prog_bar=True)
96
+ return loss
97
+
98
+ def configure_optimizers(self):
99
+ opt_function = torch.optim.SparseAdam if self.use_sparse else torch.optim.Adam
100
+ return opt_function(self.parameters(), lr=self.learning_rate)
101
+
102
+
103
+ class OrderAwareSkipgram:
104
+ def __init__(self):
105
+ super().__init__()
106
+ pass