Spaces:
Sleeping
Sleeping
File size: 7,259 Bytes
d916065 |
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 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# Natural Language Toolkit: CISTEM Stemmer for German
# Copyright (C) 2001-2023 NLTK Project
# Author: Leonie Weissweiler <[email protected]>
# Tom Aarsen <> (modifications)
# Algorithm: Leonie Weissweiler <[email protected]>
# Alexander Fraser <[email protected]>
# URL: <https://www.nltk.org/>
# For license information, see LICENSE.TXT
import re
from typing import Tuple
from nltk.stem.api import StemmerI
class Cistem(StemmerI):
"""
CISTEM Stemmer for German
This is the official Python implementation of the CISTEM stemmer.
It is based on the paper
Leonie Weissweiler, Alexander Fraser (2017). Developing a Stemmer for German
Based on a Comparative Analysis of Publicly Available Stemmers.
In Proceedings of the German Society for Computational Linguistics and Language
Technology (GSCL)
which can be read here:
https://www.cis.lmu.de/~weissweiler/cistem/
In the paper, we conducted an analysis of publicly available stemmers,
developed two gold standards for German stemming and evaluated the stemmers
based on the two gold standards. We then proposed the stemmer implemented here
and show that it achieves slightly better f-measure than the other stemmers and
is thrice as fast as the Snowball stemmer for German while being about as fast
as most other stemmers.
case_insensitive is a a boolean specifying if case-insensitive stemming
should be used. Case insensitivity improves performance only if words in the
text may be incorrectly upper case. For all-lowercase and correctly cased
text, best performance is achieved by setting case_insensitive for false.
:param case_insensitive: if True, the stemming is case insensitive. False by default.
:type case_insensitive: bool
"""
strip_ge = re.compile(r"^ge(.{4,})")
repl_xx = re.compile(r"(.)\1")
strip_emr = re.compile(r"e[mr]$")
strip_nd = re.compile(r"nd$")
strip_t = re.compile(r"t$")
strip_esn = re.compile(r"[esn]$")
repl_xx_back = re.compile(r"(.)\*")
def __init__(self, case_insensitive: bool = False):
self._case_insensitive = case_insensitive
@staticmethod
def replace_to(word: str) -> str:
word = word.replace("sch", "$")
word = word.replace("ei", "%")
word = word.replace("ie", "&")
word = Cistem.repl_xx.sub(r"\1*", word)
return word
@staticmethod
def replace_back(word: str) -> str:
word = Cistem.repl_xx_back.sub(r"\1\1", word)
word = word.replace("%", "ei")
word = word.replace("&", "ie")
word = word.replace("$", "sch")
return word
def stem(self, word: str) -> str:
"""Stems the input word.
:param word: The word that is to be stemmed.
:type word: str
:return: The stemmed word.
:rtype: str
>>> from nltk.stem.cistem import Cistem
>>> stemmer = Cistem()
>>> s1 = "Speicherbehältern"
>>> stemmer.stem(s1)
'speicherbehalt'
>>> s2 = "Grenzpostens"
>>> stemmer.stem(s2)
'grenzpost'
>>> s3 = "Ausgefeiltere"
>>> stemmer.stem(s3)
'ausgefeilt'
>>> stemmer = Cistem(True)
>>> stemmer.stem(s1)
'speicherbehal'
>>> stemmer.stem(s2)
'grenzpo'
>>> stemmer.stem(s3)
'ausgefeil'
"""
if len(word) == 0:
return word
upper = word[0].isupper()
word = word.lower()
word = word.replace("ü", "u")
word = word.replace("ö", "o")
word = word.replace("ä", "a")
word = word.replace("ß", "ss")
word = Cistem.strip_ge.sub(r"\1", word)
return self._segment_inner(word, upper)[0]
def segment(self, word: str) -> Tuple[str, str]:
"""
This method works very similarly to stem (:func:'cistem.stem'). The difference is that in
addition to returning the stem, it also returns the rest that was removed at
the end. To be able to return the stem unchanged so the stem and the rest
can be concatenated to form the original word, all subsitutions that altered
the stem in any other way than by removing letters at the end were left out.
:param word: The word that is to be stemmed.
:type word: str
:return: A tuple of the stemmed word and the removed suffix.
:rtype: Tuple[str, str]
>>> from nltk.stem.cistem import Cistem
>>> stemmer = Cistem()
>>> s1 = "Speicherbehältern"
>>> stemmer.segment(s1)
('speicherbehält', 'ern')
>>> s2 = "Grenzpostens"
>>> stemmer.segment(s2)
('grenzpost', 'ens')
>>> s3 = "Ausgefeiltere"
>>> stemmer.segment(s3)
('ausgefeilt', 'ere')
>>> stemmer = Cistem(True)
>>> stemmer.segment(s1)
('speicherbehäl', 'tern')
>>> stemmer.segment(s2)
('grenzpo', 'stens')
>>> stemmer.segment(s3)
('ausgefeil', 'tere')
"""
if len(word) == 0:
return ("", "")
upper = word[0].isupper()
word = word.lower()
return self._segment_inner(word, upper)
def _segment_inner(self, word: str, upper: bool):
"""Inner method for iteratively applying the code stemming regexes.
This method receives a pre-processed variant of the word to be stemmed,
or the word to be segmented, and returns a tuple of the word and the
removed suffix.
:param word: A pre-processed variant of the word that is to be stemmed.
:type word: str
:param upper: Whether the original word started with a capital letter.
:type upper: bool
:return: A tuple of the stemmed word and the removed suffix.
:rtype: Tuple[str, str]
"""
rest_length = 0
word_copy = word[:]
# Pre-processing before applying the substitution patterns
word = Cistem.replace_to(word)
rest = ""
# Apply the substitution patterns
while len(word) > 3:
if len(word) > 5:
word, n = Cistem.strip_emr.subn("", word)
if n != 0:
rest_length += 2
continue
word, n = Cistem.strip_nd.subn("", word)
if n != 0:
rest_length += 2
continue
if not upper or self._case_insensitive:
word, n = Cistem.strip_t.subn("", word)
if n != 0:
rest_length += 1
continue
word, n = Cistem.strip_esn.subn("", word)
if n != 0:
rest_length += 1
continue
else:
break
# Post-processing after applying the substitution patterns
word = Cistem.replace_back(word)
if rest_length:
rest = word_copy[-rest_length:]
return (word, rest)
|