summaryrefslogtreecommitdiff
path: root/research/knobloch/morphemes.go
blob: 226320cdd7ece3600e17ce87023134bf021b8774 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package knobloch

import (
	"encoding/gob"
	"io"
	"os"
	"sync"

	"go.jknobloc.com/x/shelf"
	"go.jknobloc.com/x/tokenizer/bpe"
)

type segmentsEntry struct {
	Value []string
	OK    bool
}

// SegmentsPath holds the morphological segmentation that decides which vocab
// tokens count as morphemes; resolved against the shelf root on first use.
var SegmentsPath shelf.Item = "results/knobloch/minipile_19_fix/segments.gob"

var morphemesCache struct {
	once sync.Once
	m    map[string]int
	err  error
}

// morphemes caches the parsed segmentation; the gob is several hundred MB and
// every plot and stats pass needs the same map.
func morphemes() (map[string]int, error) {
	morphemesCache.once.Do(func() {
		morphemesCache.m, morphemesCache.err = Morphemes(shelf.Abs(SegmentsPath))
	})

	return morphemesCache.m, morphemesCache.err
}

// isMorpheme reports whether a vocab id spells out a known morpheme; the
// returned closure holds the id-to-token map so callers build it once.
func isMorpheme(t *bpe.Tokenizer) (func(id int) bool, error) {
	m, err := morphemes()

	if err != nil {
		return nil, err
	}

	itoa := bpe.Itoa(t)

	return func(id int) bool {
		token, ok := itoa[int64(id)]

		if !ok {
			panic("unexpected token ID")
		}

		_, isMorph := m[token]

		return isMorph
	}, nil
}

func Morphemes(name string) (map[string]int, error) {
	var file *os.File

	if f, err := os.Open(name); err != nil {
		return nil, err
	} else {
		file = f

		defer file.Close()
	}

	var segments map[string]segmentsEntry

	if m, err := loadSegments(file); err != nil {
		return nil, err
	} else {
		segments = m
	}

	morphemes := make(map[string]int)

	for _, v := range segments {
		// the segmenter returns the unsegmented compound when it fails
		if !v.OK || len(v.Value) == 1 {
			continue
		}

		for _, s := range v.Value {
			morphemes[s]++
		}
	}

	return morphemes, nil

	// r := make([]int64, 0)

	// atoi := bpe.Atoi(t)
	//
	// for k, v := range morphemes {
	// 	if v < 0 {
	// 		continue
	// 	}
	//
	// 	i, ok := atoi[k]
	//
	// 	if !ok {
	// 		continue
	// 	}
	//
	// 	r = append(r, i)
	// }
	//
	// return r, nil
}

func loadSegments(r io.Reader) (map[string]segmentsEntry, error) {
	m := make(map[string]segmentsEntry)

	dec := gob.NewDecoder(r)

	if err := dec.Decode(&m); err != nil {
		return nil, err
	}

	return m, nil
}