Я пытаюсь найти способ сделать матрицу перехода, используя униграммы, биграммы и триграммы для заданного текста, используя python и numpy. Вероятности каждой строки должны быть равны единице. Сначала я сделал это с биграммами, и это сработало нормально:
distinct_words = list(word_dict.keys())
dwc = len(distinct_words)
matrix = np.zeros((dwc, dwc), dtype=np.float)
for i in range(len(distinct_words)):
word = distinct_words[i]
first_word_idx = i
total = 0
for bigram, count in ngrams.items():
word_1, word_2 = bigram.split(" ")
if word_1 == word:
total += count
for bigram, count in ngrams.items():
word_1, word_2 = bigram.split(" ")
if word_1 == word:
second_word_idx = index_dict[word_2]
matrix[first_word_idx,second_word_idx] = count / total
Но теперь я хочу сложить униграммы и триграммы и взвесить их вероятности (триграммы * .6, биграммы * .2, униграммы * .2). Я не думаю, что мой питон очень лаконичен, что является одной из проблем, но также я не знаю, как использовать несколько n-грамм (и веса, хотя, честно говоря, веса вторичны), чтобы я мог получить все вероятности из любой заданной строки, чтобы добавить к одному.
distinct_words = list(word_dict.keys())
dwc = len(distinct_words)
matrix = np.zeros((dwc, dwc), dtype=np.float)
for i in range(len(distinct_words)):
word = distinct_words[i]
first_word_index = i
bi_total = 0
tri_total=0
tri_prob = 0
bi_prob = 0
uni_prob = word_dict[word] / len(distinct_words)
if i < len(distinct_words)-1:
for trigram, count in trigrams.items():
word_1, word_2, word_3 = trigram.split()
if word_1 + word_2 == word + distinct_words[i+1]:
tri_total += count
for trigram, count in trigrams.items():
word_1, word_2, word_3 = trigram.split()
if word_1 + word_2 == word + distinct_words[i+1]:
second_word_index = index_dict[word_2]
tri_prob = count/bigrams[word_1 + " " + word_2]
for bigram, count in bigrams.items():
word_1, word_2 = bigram.split(" ")
if word_1 == word:
bi_total += count
for bigram, count in bigrams.items():
word_1, word_2 = bigram.split(" ")
if word_1 == word:
second_word_index = index_dict[word_2]
bi_prob = count / bi_total
matrix[first_word_index,second_word_index] = (tri_prob * .4) + (bi_prob * .2) + (word_dict[word]/len(word_dict) *.2)
Я читаю эту лекцию о том, как настроить мою матрицу вероятностей, и кажется, что это имеет смысл, но я не уверен, где я ошибаюсь.
Если это поможет, мои n_grams исходят из этого - он просто создает словарь n_gram в виде строки и ее количество.
def get_ngram(words, n):
word_dict = {}
for i, word in enumerate(words):
if i > (n-2):
n_gram = []
for num in range(n):
index = i - num
n_gram.append(words[index])
if len(n_gram) > 1:
formatted_gram = ""
for word in reversed(n_gram):
formatted_gram += word + " "
else:
formatted_gram = n_gram[0]
stripped = formatted_gram.strip() if formatted_gram else n_gram[0]
if stripped in word_dict:
word_dict[stripped] += 1
else:
word_dict[stripped] = 1
return word_dict
Я реализовал образец для вычисления униграмм, биграмм и триграмм. Вы можете использовать zip
для простого соединения предметов. Кроме того, Counter
используется для подсчета предметов, а defaultdict
используется для вероятности предметов. defaultdict
важно, когда ключ не отображается в наборе, возвращает ноль. В противном случае вы должны добавить предложение if, чтобы избежать None
.
from collections import Counter, defaultdict
def calculate_grams(items_list):
# count items in list
counts = Counter()
for item in items_list:
counts[item] += 1
# calculate probabilities, defaultdict returns 0 if not found
prob = defaultdict(float)
for item, count in counts.most_common():
prob[item] = count / len(items_list)
return prob
def calculate_bigrams(words):
# tuple first and second items
return calculate_grams(list(zip(words, words[1:])))
def calculate_trigrams(words):
# tuple first, second and third items
return calculate_grams(list(zip(words, words[1:], words[2:])))
dataset = ['a', 'b', 'b', 'c', 'a', 'a', 'a', 'b', 'e', 'e', 'c']
# create dictionary
dictionary = set(dataset)
print("Dictionary", dictionary)
unigrams = calculate_grams(dataset)
print("Unigrams", unigrams)
bigrams = calculate_bigrams(dataset)
print("Bigrams", bigrams)
trigrams = calculate_trigrams(dataset)
print("Trigrams", trigrams)
# Testing
test_words = ['a', 'b']
print("Testing", test_words)
for c in dictionary:
# calculate each probabilities
unigram_prob = unigrams[c]
bigram_prob = bigrams[(test_words[-1], c)]
trigram_prob = bigrams[(test_words[-2], test_words[-1], c)]
# calculate total probability
prob = .2 * unigram_prob + .2 * bigram_prob + .4 * trigram_prob
print(c, prob)
Выход:
Unigrams defaultdict(<class 'float'>, {'a': 0.36363636363636365, 'b': 0.2727272727272727, 'c': 0.18181818181818182, 'e': 0.18181818181818182})
Bigrams defaultdict(<class 'float'>, {('a', 'b'): 0.2, ('a', 'a'): 0.2, ('b', 'b'): 0.1, ('b', 'c'): 0.1, ('c', 'a'): 0.1, ('b', 'e'): 0.1, ('e', 'e'): 0.1, ('e', 'c'): 0.1})
Trigrams defaultdict(<class 'float'>, {('a', 'b', 'b'): 0.1111111111111111, ('b', 'b', 'c'): 0.1111111111111111, ('b', 'c', 'a'): 0.1111111111111111, ('c', 'a', 'a'): 0.1111111111111111, ('a', 'a', 'a'): 0.1111111111111111, ('a', 'a', 'b'): 0.1111111111111111, ('a', 'b', 'e'): 0.1111111111111111, ('b', 'e', 'e'): 0.1111111111111111, ('e', 'e', 'c'): 0.1111111111111111})
Testing ['a', 'b']
e 0.05636363636363637
b 0.07454545454545455
c 0.05636363636363637
a 0.07272727272727274
Попробуем сделать это на чистом Python наиболее эффективным способом, полагаясь только на списки и словари.
Предположим, у нас есть игрушечный текст, состоящий из 3 слов «а», «б» и «в»:
np.random.seed(42)
text = " ".join([np.random.choice(list("abc")) for _ in range(100)])
text
'c a c c a a c b c c c c a c b a b b b b a a b b a a a c c c b c b b c
b c c a c a c c a a c b a b b b a b a b c c a c c b a b b b b b b b a
c b b b b b b c c b c a b a a b c a b a a a a c a a a c a a'
Затем, чтобы сделать униграммы, биграммы и триграммы, вы можете действовать следующим образом:
unigrams = text.split()
unigram_counts = dict()
for unigram in unigrams:
unigram_counts[unigram] = unigram_counts.get(unigram, 0) +1
bigrams = ["".join(bigram) for bigram in zip(unigrams[:-1], unigrams[1:])]
bigram_counts = dict()
for bigram in bigrams:
bigram_counts[bigram] = bigram_counts.get(bigram, 0) +1
trigrams = ["".join(trigram) for trigram in zip(unigrams[:-2], unigrams[1:-1],unigrams[2:])]
trigram_counts = dict()
for trigram in trigrams:
trigram_counts[trigram] = trigram_counts.get(trigram, 0) +1
Чтобы включить веса и нормализовать:
weights = [.2,.2,.6]
dics = [unigram_counts, bigram_counts, trigram_counts]
weighted_counts = {k:v*w for d,w in zip(dics, weights) for k,v in d.items()}
#desired output
freqs = {k:v/sum(weighted_counts.values()) for k,v in weighted_counts.items()}
Что у нас есть:
pprint(freqs)
{'a': 0.06693711967545637,
'aa': 0.02434077079107505,
'aaa': 0.024340770791075043,
...
Наконец, проверка работоспособности:
print(sum(freqs.values()))
0.999999999999999
Этот код может быть дополнительно настроен, чтобы включить ваши правила токенизации, например, или сделать его короче, проходя по разным граммам одновременно.
Это почти отвечает на мой вопрос, но частоты здесь показывают частоту каждой униграммы, биграммы и триграммы, и я действительно хочу посмотреть, смогу ли я объединить их в матрицу. Например, как я могу сделать так, чтобы для одного слова, если есть биграмма, у нас была некоторая вероятность, но если есть триграмма, у нас была эта вероятность ПЛЮС более взвешенная вероятность триграммы? Я пытаюсь сделать взвешенную цепь Маркова
>>> but the frequencies here show the frequency of each unigram
. Не True
. Вы задали вопрос так, что сумма взвешенных вероятностей равна 1. Я ответил на него.
Ваш код не воспроизводится, пожалуйста, добавьте, откуда берется
ngrams
(API). Также предоставьте образецword_dict
из возможных.