lolipop12

[SNZ]Klasifikacija na dokumenti

Dec 26th, 2019
182
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 60.06 KB | None | 0 0
  1. import math
  2. import re
  3.  
  4.  
  5. def get_words(doc):
  6.     """Поделба на документот на зборови. Стрингот се дели на зборови според
  7.    празните места и интерпукциските знаци
  8.  
  9.    :param doc: документ
  10.    :type doc: str
  11.    :return: множество со зборовите кои се појавуваат во дадениот документ
  12.    :rtype: set(str)
  13.    """
  14.     # подели го документот на зборови и конвертирај ги во мали букви
  15.     # па потоа стави ги во резултатот ако нивната должина е >2 и <20
  16.     words = list()
  17.     for word in re.split('\\W+', doc):
  18.         if 2 < len(word) < 20:
  19.             words.append(word.lower())
  20.     return words
  21.  
  22.  
  23. def get_vocabulary(documents):
  24.     """Враќа множество од сите зборови кои се појавуваат во документите.
  25.  
  26.    :param documents: листа со документи
  27.    :type documents: list(str)
  28.    :return: множество зборови
  29.    :rtype: set(str)
  30.    """
  31.     vocab = set()
  32.     for doc_text in documents:
  33.         words = get_words(doc_text)
  34.         words_set = set(words)
  35.         vocab.update(words_set)
  36.     return sorted(vocab)
  37.  
  38.  
  39. def cosine(v1, v2):
  40.     """Ја враќа косинусната сличност помеѓу два вектори v1 и v2.
  41.  
  42.    :param v1: вектор1
  43.    :type v1: list(float)
  44.    :param v2: вектор2
  45.    :type v2: list(float)
  46.    :return: сличност помеѓу вектор и вектор2
  47.    :rtype: float
  48.    """
  49.     sumxx, sumxy, sumyy = 0, 0, 0
  50.     for i in range(len(v1)):
  51.         x = v1[i]
  52.         y = v2[i]
  53.         sumxx += x * x
  54.         sumyy += y * y
  55.         sumxy += x * y
  56.     return sumxy / math.sqrt(sumxx * sumyy)
  57.  
  58.  
  59. def pearson(v1, v2):
  60.     """ Го враќа коефициентот на Пирсонова корелација помеѓу два вектори v1 и v2.
  61.  
  62.    :param v1: вектор1
  63.     :type v1: list(float)
  64.    :param v2: вектор2
  65.    :type v2: list(float)
  66.    :return: сличност помеѓу вектор и вектор2
  67.    :rtype: float
  68.    """
  69.     sum1 = 0
  70.     sum2 = 0
  71.     sum1Sq = 0
  72.     sum2Sq = 0
  73.     pSum = 0
  74.     n = len(v1)
  75.     for i in range(n):
  76.         x1 = v1[i]
  77.         x2 = v2[i]
  78.         sum1 += x1
  79.         sum1Sq += x1 ** 2
  80.         sum2 += x2
  81.         sum2Sq += x2 ** 2
  82.         pSum += x1 * x2
  83.     num = pSum - (sum1 * sum2 / n)
  84.     den = math.sqrt((sum1Sq - sum1 ** 2 / n) * (sum2Sq - sum2 ** 2 / n))
  85.     if den == 0: return 0
  86.     r = num / den
  87.     return r
  88.  
  89.  
  90. def calculate_document_frequencies(documents):
  91.     """Враќа речник со број на појавување на зборовите.
  92.  
  93.    :param documents: листа со документи
  94.    :type documents: list(str)
  95.    :return: речник со број на појавување на зборовите
  96.    :rtype: dict(str, int)
  97.    """
  98.     df = {}
  99.     documents_words = []
  100.     for doc_text in documents:
  101.         words = get_words(doc_text)
  102.         documents_words.append(words)
  103.         words_set = set(words)
  104.         for word in words_set:
  105.             df.setdefault(word, 0)
  106.             df[word] += 1
  107.     return df
  108.  
  109.  
  110. def calc_vector(cur_tf_idf, vocab):
  111.     """Пресметува tf-idf вектор за даден документ од дадениот вокабулар.
  112.  
  113.    :param cur_tf_idf: речник со tf-idf тежини
  114.    :type cur_tf_idf: dict(str, float)
  115.    :param vocab: множество од сите зборови кои се појавуваат во барем еден документ
  116.    :type vocab: set(str)
  117.    :return: tf-idf вектор за дадениот документ
  118.    """
  119.     vec = []
  120.     for word in vocab:
  121.         tf_idf = cur_tf_idf.get(word, 0)
  122.         vec.append(tf_idf)
  123.     return vec
  124.  
  125.  
  126. def process_document(doc, df, N, vocab):
  127.     """Пресметува tf-idf за даден документ.
  128.  
  129.    :param doc: документ
  130.    :type doc: str
  131.    :param df: речник со фреквенции на зборовите во дадениот документ
  132.    :type df: dict(str, int)
  133.    :param N: вкупен број на документи
  134.    :param vocab: множество од сите зборови кои се појавуваат во барем еден документ
  135.    :type vocab: set(str)
  136.    :return: tf-idf вектор за дадениот документ
  137.    """
  138.     if isinstance(doc, str):
  139.         words = get_words(doc)
  140.     else:
  141.         words = doc
  142.     idf = {}
  143.     for word, cdf in df.items():
  144.         idf[word] = math.log(N / cdf)
  145.     f = {}  # колку пати се јавува секој збор во овој документ
  146.     for word in words:
  147.         f.setdefault(word, 0)
  148.         f[word] += 1
  149.     max_f = max(f.values())  # колку пати се појавува најчестиот збор во овој документ
  150.     tf_idf = {}
  151.     for word, cnt in f.items():
  152.         ctf = cnt * 1.0 / max_f
  153.         tf_idf[word] = ctf * idf.get(word, 0)
  154.     vec = calc_vector(tf_idf, vocab)
  155.     return vec
  156.  
  157.  
  158. def rank_documents(doc, documents, sim_func=cosine):
  159.     """Враќа најслични документи со дадениот документ.
  160.  
  161.    :param doc: документ
  162.    :type doc: str
  163.    :param documents: листа со документи
  164.    :type documents: list(str)
  165.    :param sim_func: функција за сличност
  166.    :return: листа со најслични документи
  167.    """
  168.     df = calculate_document_frequencies(documents)
  169.     N = len(documents)
  170.     vocab = get_vocabulary(documents)
  171.     doc_vectors = []
  172.     for document in documents:
  173.         vec = process_document(document, df, N, vocab)
  174.         doc_vectors.append(vec)
  175.     query_vec = process_document(doc, df, N, vocab)
  176.     similarities = []
  177.     for i, doc_vec in enumerate(doc_vectors):
  178.         dist = sim_func(query_vec, doc_vec)
  179.         similarities.append((dist, i))
  180.     similarities.sort(reverse=True)
  181.     return similarities
  182.  
  183.  
  184. def create_dataset(documents, labels):
  185.     """Формира податочно множество со tf-idf тежини и класи, соодветно за класификација со дрва на одлука.
  186.  
  187.    :param documents: листа со документи
  188.    :type documents: list(str)
  189.    :param labels: листа со класи
  190.    :type labels: list
  191.    :return: податочно множество со tf-idf тежини и класи, речник со френвенции на појавување на зборовите,
  192.            број на документи во множеството, вокабулар од даденото множество на аборови
  193.    :rtype: list(list), dict(str, int), int, set(word)
  194.    """
  195.     dataset = []
  196.     doc_vectors = []
  197.     df = calculate_document_frequencies(documents)
  198.     N = len(documents)
  199.     vocab = get_vocabulary(documents)
  200.     for document in documents:
  201.         vec = process_document(document, df, N, vocab)
  202.         doc_vectors.append(vec)
  203.     for doc_vec, label in zip(doc_vectors, labels):
  204.         doc_vec.append(label)
  205.         dataset.append(doc_vec)
  206.     return dataset, df, N, vocab
  207.  
  208. def get_words(doc):
  209.     """Поделба на документот на зборови. Стрингот се дели на зборови според
  210.    празните места и интерпукциските знаци
  211.  
  212.    :param doc: документ
  213.    :type doc: str
  214.    :return: множество со зборовите кои се појавуваат во дадениот документ
  215.    :rtype: set(str)
  216.    """
  217.     # подели го документот на зборови и конвертирај ги во мали букви
  218.     # па потоа стави ги во резултатот ако нивната должина е >2 и <20
  219.     words = set()
  220.     for word in re.split('\\W+', doc):
  221.         if 2 < len(word) < 20:
  222.             words.add(word.lower())
  223.     return words
  224.  
  225.  
  226. class DocumentClassifier:
  227.     def __init__(self, get_features):
  228.         # број на парови атрибут/категорија (feature/category)
  229.         self.feature_counts_per_category = {}
  230.         # број на документи во секоја категорија
  231.         self.category_counts = {}
  232.         # функција за добивање на атрибутите (зборовите) во документот
  233.         self.get_features = get_features
  234.  
  235.     def increment_feature_counts_per_category(self, current_feature, current_category):
  236.         """Зголемување на бројот на парови атрибут/категорија
  237.  
  238.        :param current_feature: даден атрибут
  239.        :param current_category: дадена категорија
  240.        :return: None
  241.        """
  242.         self.feature_counts_per_category.setdefault(current_feature, {})
  243.         self.feature_counts_per_category[current_feature].setdefault(current_category, 0)
  244.         self.feature_counts_per_category[current_feature][current_category] += 1
  245.  
  246.     def increment_category_counts(self, cat):
  247.         """Зголемување на бројот на предмети (документи) во категорија
  248.  
  249.        :param cat: категорија
  250.        :return: None
  251.        """
  252.         self.category_counts.setdefault(cat, 0)
  253.         self.category_counts[cat] += 1
  254.  
  255.     def get_feature_counts_per_category(self, current_feature, current_category):
  256.         """Добивање на бројот колку пати одреден атрибут се има појавено во
  257.        одредена категорија
  258.  
  259.        :param current_feature: атрибут
  260.        :param current_category: категорија
  261.        :return: None
  262.        """
  263.         if current_feature in self.feature_counts_per_category \
  264.                 and current_category in self.feature_counts_per_category[current_feature]:
  265.             return float(self.feature_counts_per_category[current_feature][current_category])
  266.         return 0.0
  267.  
  268.     def get_category_count(self, current_category):
  269.         """Добивање на бројот на предмети (документи) во категорија
  270.  
  271.        :param current_category: категорија
  272.        :return: број на предмети (документи)
  273.        """
  274.         if current_category in self.category_counts:
  275.             return float(self.category_counts[current_category])
  276.         return 0
  277.  
  278.     def get_total_count(self):
  279.         """Добивање на вкупниот број на предмети"""
  280.         return sum(self.category_counts.values())
  281.  
  282.     def categories(self):
  283.         """Добивање на листа на сите категории"""
  284.         return self.category_counts.keys()
  285.  
  286.     def train(self, item, current_category):
  287.         """Тренирање на класификаторот. Новиот предмет (документ)
  288.  
  289.        :param item: нов предмет (документ)
  290.        :param current_category: категорија
  291.        :return: None
  292.        """
  293.         # Се земаат атрибутите (зборовите) во предметот (документот)
  294.         features = self.get_features(item)
  295.         # Се зголемува бројот на секој атрибут во оваа категорија
  296.         for current_feature in features:
  297.             self.increment_feature_counts_per_category(current_feature, current_category)
  298.  
  299.         # Се зголемува бројот на предмети (документи) во оваа категорија
  300.         self.increment_category_counts(current_category)
  301.  
  302.     def get_feature_per_category_probability(self, current_feature, current_category):
  303.         """Веројатноста е вкупниот број на пати кога даден атрибут f (збор) се појавил во
  304.        дадена категорија поделено со вкупниот број на предмети (документи) во категоријата
  305.  
  306.        :param current_feature: атрибут
  307.        :param current_category: карактеристика
  308.        :return: веројатност на појавување
  309.        """
  310.         if self.get_category_count(current_category) == 0:
  311.             return 0
  312.         return self.get_feature_counts_per_category(current_feature, current_category) \
  313.                / self.get_category_count(current_category)
  314.  
  315.     def weighted_probability(self, current_feature, current_category, prf, weight=1.0, ap=0.5):
  316.         """Пресметка на тежински усогласената веројатност
  317.  
  318.        :param current_feature: атрибут
  319.        :param current_category: категорија
  320.        :param prf: функција за пресметување на основната веројатност
  321.        :param weight: тежина
  322.        :param ap: претпоставена веројатност
  323.        :return: тежински усогласена веројатност
  324.        """
  325.         # Пресметај ја основната веројатност
  326.         basic_prob = prf(current_feature, current_category)
  327.         # Изброј колку пати се има појавено овој атрибут (збор) во сите категории
  328.         totals = sum([self.get_feature_counts_per_category(current_feature, currentCategory) for currentCategory in
  329.                       self.categories()])
  330.         # Пресметај ја тежински усредената веројатност
  331.         bp = ((weight * ap) + (totals * basic_prob)) / (weight + totals)
  332.         return bp
  333.  
  334.  
  335. class NaiveBayes(DocumentClassifier):
  336.     def __init__(self, get_features):
  337.         super().__init__(get_features)
  338.         self.thresholds = {}
  339.  
  340.     def set_threshold(self, current_category, threshold):
  341.         """Поставување на праг на одлучување за категорија
  342.  
  343.        :param current_category: категорија
  344.        :param threshold: праг на одлучување
  345.        :return: None
  346.        """
  347.         self.thresholds[current_category] = threshold
  348.  
  349.     def get_threshold(self, current_category):
  350.         """Добивање на прагот на одлучување за дадена класа
  351.  
  352.        :param current_category: категорија
  353.        :return: праг на одлучување за дадената категорија
  354.        """
  355.         if current_category not in self.thresholds:
  356.             return 1.0
  357.         return self.thresholds[current_category]
  358.  
  359.     def calculate_document_probability_in_class(self, item, current_category):
  360.         """Ја враќа веројатноста на документот да е од класата current_category
  361.        (current_category е однапред позната)
  362.  
  363.        :param item: документ
  364.        :param current_category: категорија
  365.        :return:
  366.        """
  367.         # земи ги зборовите од документот item
  368.         features = self.get_features(item)
  369.         # помножи ги веројатностите на сите зборови
  370.         p = 1
  371.         for current_feature in features:
  372.             p *= self.weighted_probability(current_feature, current_category,
  373.                                            self.get_feature_per_category_probability)
  374.  
  375.         return p
  376.  
  377.     def get_category_probability_for_document(self, item, current_category):
  378.         """Ја враќа веројатноста на класата ако е познат документот
  379.  
  380.        :param item: документ
  381.        :param current_category: категорија
  382.        :return: веројатност за документот во категорија
  383.        """
  384.         cat_prob = self.get_category_count(current_category) / self.get_total_count()
  385.         calculate_document_probability_in_class = self.calculate_document_probability_in_class(item, current_category)
  386.         # Bayes Theorem
  387.         return calculate_document_probability_in_class * cat_prob / (1.0 / self.get_total_count())
  388.  
  389.     def classify_document(self, item, default=None):
  390.         """Класифицирање на документ
  391.  
  392.        :param item: документ
  393.        :param default: подразбирана (default) класа
  394.        :return:
  395.        """
  396.         probs = {}
  397.         # најди ја категоријата (класата) со најголема веројатност
  398.         max = 0.0
  399.         for cat in self.categories():
  400.             probs[cat] = self.get_category_probability_for_document(item, cat)
  401.             if probs[cat] > max:
  402.                 max = probs[cat]
  403.                 best = cat
  404.  
  405.         # провери дали веројатноста е поголема од threshold*next best (следна најдобра)
  406.         for cat in probs:
  407.             if cat == best:
  408.                 continue
  409.             if probs[cat] * self.get_threshold(best) > probs[best]: return default
  410.  
  411.         return best
  412. from math import log
  413.  
  414.  
  415. def unique_counts(rows):
  416.     """Креирај броење на можни резултати (последната колона
  417.    во секоја редица е класата)
  418.  
  419.    :param rows: dataset
  420.    :type rows: list
  421.    :return: dictionary of possible classes as keys and count
  422.             as values
  423.    :rtype: dict
  424.    """
  425.     results = {}
  426.     for row in rows:
  427.         # Клацата е последната колона
  428.         r = row[len(row) - 1]
  429.         if r not in results:
  430.             results[r] = 0
  431.         results[r] += 1
  432.     return results
  433.  
  434.  
  435. def gini_impurity(rows):
  436.     """Probability that a randomly placed item will
  437.    be in the wrong category
  438.  
  439.    :param rows: dataset
  440.    :type rows: list
  441.    :return: Gini impurity
  442.    :rtype: float
  443.    """
  444.     total = len(rows)
  445.     counts = unique_counts(rows)
  446.     imp = 0
  447.     for k1 in counts:
  448.         p1 = float(counts[k1]) / total
  449.         for k2 in counts:
  450.             if k1 == k2:
  451.                 continue
  452.             p2 = float(counts[k2]) / total
  453.             imp += p1 * p2
  454.     return imp
  455.  
  456.  
  457. def entropy(rows):
  458.     """Ентропијата е сума од p(x)log(p(x)) за сите
  459.    можни резултати
  460.  
  461.    :param rows: податочно множество
  462.    :type rows: list
  463.    :return: вредност за ентропијата
  464.    :rtype: float
  465.    """
  466.     log2 = lambda x: log(x) / log(2)
  467.     results = unique_counts(rows)
  468.     # Пресметка на ентропијата
  469.     ent = 0.0
  470.     for r in results.keys():
  471.         p = float(results[r]) / len(rows)
  472.         ent = ent - p * log2(p)
  473.     return ent
  474.  
  475.  
  476. class DecisionNode:
  477.     def __init__(self, col=-1, value=None, results=None, tb=None, fb=None):
  478.         """
  479.        :param col: индексот на колоната (атрибутот) од тренинг множеството
  480.                    која се претставува со оваа инстанца т.е. со овој јазол
  481.        :type col: int
  482.        :param value: вредноста на јазолот според кој се дели дрвото
  483.        :param results: резултати за тековната гранка, вредност (различна
  484.                        од None) само кај јазлите-листови во кои се донесува
  485.                        одлуката.
  486.        :type results: dict
  487.        :param tb: гранка која се дели од тековниот јазол кога вредноста е
  488.                   еднаква на value
  489.        :type tb: DecisionNode
  490.        :param fb: гранка која се дели од тековниот јазол кога вредноста е
  491.                   различна од value
  492.        :type fb: DecisionNode
  493.        """
  494.         self.col = col
  495.         self.value = value
  496.         self.results = results
  497.         self.tb = tb
  498.         self.fb = fb
  499.  
  500.  
  501. def compare_numerical(row, column, value):
  502.     """Споредба на вредноста од редицата на посакуваната колона со
  503.    зададена нумеричка вредност
  504.  
  505.    :param row: дадена редица во податочното множество
  506.    :type row: list
  507.    :param column: индекс на колоната (атрибутот) од тренирачкото множество
  508.    :type column: int
  509.    :param value: вредност на јазелот во согласност со кој се прави
  510.                  поделбата во дрвото
  511.    :type value: int or float
  512.    :return: True ако редицата >= value, инаку False
  513.    :rtype: bool
  514.    """
  515.     return row[column] >= value
  516.  
  517.  
  518. def compare_nominal(row, column, value):
  519.     """Споредба на вредноста од редицата на посакуваната колона со
  520.    зададена номинална вредност
  521.  
  522.    :param row: дадена редица во податочното множество
  523.    :type row: list
  524.    :param column: индекс на колоната (атрибутот) од тренирачкото множество
  525.    :type column: int
  526.    :param value: вредност на јазелот во согласност со кој се прави
  527.                  поделбата во дрвото
  528.    :type value: str
  529.    :return: True ако редицата == value, инаку False
  530.    :rtype: bool
  531.    """
  532.     return row[column] == value
  533.  
  534.  
  535. def divide_set(rows, column, value):
  536.     """Поделба на множеството според одредена колона. Може да се справи
  537.    со нумерички или номинални вредности.
  538.  
  539.    :param rows: тренирачко множество
  540.    :type rows: list(list)
  541.    :param column: индекс на колоната (атрибутот) од тренирачкото множество
  542.    :type column: int
  543.    :param value: вредност на јазелот во зависност со кој се прави поделбата
  544.                  во дрвото за конкретната гранка
  545.    :type value: int or float or str
  546.    :return: поделени подмножества
  547.    :rtype: list, list
  548.    """
  549.     # Направи функција која ни кажува дали редицата е во
  550.     # првата група (True) или втората група (False)
  551.     if isinstance(value, int) or isinstance(value, float):
  552.         # ако вредноста за споредба е од тип int или float
  553.         split_function = compare_numerical
  554.     else:
  555.         # ако вредноста за споредба е од друг тип (string)
  556.         split_function = compare_nominal
  557.  
  558.     # Подели ги редиците во две подмножества и врати ги
  559.     # за секој ред за кој split_function враќа True
  560.     set1 = [row for row in rows if
  561.             split_function(row, column, value)]
  562.     # set1 = []
  563.     # for row in rows:
  564.     #     if not split_function(row, column, value):
  565.     #         set1.append(row)
  566.     # за секој ред за кој split_function враќа False
  567.     set2 = [row for row in rows if
  568.             not split_function(row, column, value)]
  569.     return set1, set2
  570.  
  571.  
  572. def build_tree(rows, scoref=entropy):
  573.     """Градење на дрво на одлука.
  574.  
  575.    :param rows: тренирачко множество
  576.    :type rows: list(list)
  577.    :param scoref: функција за одбирање на најдобар атрибут во даден чекор
  578.    :type scoref: function
  579.    :return: коренот на изграденото дрво на одлука
  580.    :rtype: DecisionNode object
  581.    """
  582.     if len(rows) == 0:
  583.         return DecisionNode()
  584.     current_score = scoref(rows)
  585.  
  586.     # променливи со кои следиме кој критериум е најдобар
  587.     best_gain = 0.0
  588.     best_criteria = None
  589.     best_sets = None
  590.  
  591.     column_count = len(rows[0]) - 1
  592.     for col in range(0, column_count):
  593.         # за секоја колона (col се движи во интервалот од 0 до
  594.         # column_count - 1)
  595.         # Следниов циклус е за генерирање на речник од различни
  596.         # вредности во оваа колона
  597.         column_values = {}
  598.         for row in rows:
  599.             column_values[row[col]] = 1
  600.         # за секоја редица се зема вредноста во оваа колона и се
  601.         # поставува како клуч во column_values
  602.         for value in column_values.keys():
  603.             (set1, set2) = divide_set(rows, col, value)
  604.  
  605.             # Информациона добивка
  606.             p = float(len(set1)) / len(rows)
  607.             gain = current_score - p * scoref(set1) - (1 - p) * scoref(set2)
  608.             if gain > best_gain and len(set1) > 0 and len(set2) > 0:
  609.                 best_gain = gain
  610.                 best_criteria = (col, value)
  611.                 best_sets = (set1, set2)
  612.  
  613.     # Креирај ги подгранките
  614.     if best_gain > 0:
  615.         true_branch = build_tree(best_sets[0], scoref)
  616.         false_branch = build_tree(best_sets[1], scoref)
  617.         return DecisionNode(col=best_criteria[0], value=best_criteria[1],
  618.                             tb=true_branch, fb=false_branch)
  619.     else:
  620.         return DecisionNode(results=unique_counts(rows))
  621.  
  622.  
  623. def print_tree(tree, indent=''):
  624.     """Принтање на дрво на одлука
  625.  
  626.    :param tree: коренот на дрвото на одлучување
  627.    :type tree: DecisionNode object
  628.    :param indent:
  629.    :return: None
  630.    """
  631.     # Дали е ова лист јазел?
  632.     if tree.results:
  633.         print(str(tree.results))
  634.     else:
  635.         # Се печати условот
  636.         print(str(tree.col) + ':' + str(tree.value) + '? ')
  637.         # Се печатат True гранките, па False гранките
  638.         print(indent + 'T->', end='')
  639.         print_tree(tree.tb, indent + '  ')
  640.         print(indent + 'F->', end='')
  641.         print_tree(tree.fb, indent + '  ')
  642.  
  643.  
  644. def classify(observation, tree):
  645.     """Класификација на нов податочен примерок со изградено дрво на одлука
  646.  
  647.    :param observation: еден ред од податочното множество за предвидување
  648.    :type observation: list
  649.    :param tree: коренот на дрвото на одлучување
  650.    :type tree: DecisionNode object
  651.    :return: речник со класите како клуч и бројот на појавување во листот на дрвото
  652.    за класификација како вредност во речникот
  653.    :rtype: dict
  654.    """
  655.     if tree.results:
  656.         return tree.results
  657.     else:
  658.         value = observation[tree.col]
  659.         if isinstance(value, int) or isinstance(value, float):
  660.             compare = compare_numerical
  661.         else:
  662.             compare = compare_nominal
  663.  
  664.         if compare(observation, tree.col, tree.value):
  665.             branch = tree.tb
  666.         else:
  667.             branch = tree.fb
  668.  
  669.         return classify(observation, branch)
  670. import math
  671. import re
  672.  
  673.  
  674. def get_words(doc):
  675.     """Поделба на документот на зборови. Стрингот се дели на зборови според
  676.    празните места и интерпукциските знаци
  677.  
  678.    :param doc: документ
  679.    :type doc: str
  680.    :return: множество со зборовите кои се појавуваат во дадениот документ
  681.    :rtype: set(str)
  682.    """
  683.     # подели го документот на зборови и конвертирај ги во мали букви
  684.     # па потоа стави ги во резултатот ако нивната должина е >2 и <20
  685.     words = list()
  686.     for word in re.split('\\W+', doc):
  687.         if 2 < len(word) < 20:
  688.             words.append(word.lower())
  689.     return words
  690.  
  691.  
  692. def get_vocabulary(documents):
  693.     """Враќа множество од сите зборови кои се појавуваат во документите.
  694.  
  695.    :param documents: листа со документи
  696.    :type documents: list(str)
  697.    :return: множество зборови
  698.    :rtype: set(str)
  699.    """
  700.     vocab = set()
  701.     for doc_text in documents:
  702.         words = get_words(doc_text)
  703.         words_set = set(words)
  704.         vocab.update(words_set)
  705.     return sorted(vocab)
  706.  
  707.  
  708. def cosine(v1, v2):
  709.     """Ја враќа косинусната сличност помеѓу два вектори v1 и v2.
  710.  
  711.    :param v1: вектор1
  712.    :type v1: list(float)
  713.    :param v2: вектор2
  714.    :type v2: list(float)
  715.    :return: сличност помеѓу вектор и вектор2
  716.    :rtype: float
  717.    """
  718.     sumxx, sumxy, sumyy = 0, 0, 0
  719.     for i in range(len(v1)):
  720.         x = v1[i]
  721.         y = v2[i]
  722.         sumxx += x * x
  723.         sumyy += y * y
  724.         sumxy += x * y
  725.     return sumxy / math.sqrt(sumxx * sumyy)
  726.  
  727.  
  728. def pearson(v1, v2):
  729.     """ Го враќа коефициентот на Пирсонова корелација помеѓу два вектори v1 и v2.
  730.  
  731.    :param v1: вектор1
  732.     :type v1: list(float)
  733.    :param v2: вектор2
  734.    :type v2: list(float)
  735.    :return: сличност помеѓу вектор и вектор2
  736.    :rtype: float
  737.    """
  738.     sum1 = 0
  739.     sum2 = 0
  740.     sum1Sq = 0
  741.     sum2Sq = 0
  742.     pSum = 0
  743.     n = len(v1)
  744.     for i in range(n):
  745.         x1 = v1[i]
  746.         x2 = v2[i]
  747.         sum1 += x1
  748.         sum1Sq += x1 ** 2
  749.         sum2 += x2
  750.         sum2Sq += x2 ** 2
  751.         pSum += x1 * x2
  752.     num = pSum - (sum1 * sum2 / n)
  753.     den = math.sqrt((sum1Sq - sum1 ** 2 / n) * (sum2Sq - sum2 ** 2 / n))
  754.     if den == 0: return 0
  755.     r = num / den
  756.     return r
  757.  
  758.  
  759. def calculate_document_frequencies(documents):
  760.     """Враќа речник со број на појавување на зборовите.
  761.  
  762.    :param documents: листа со документи
  763.    :type documents: list(str)
  764.    :return: речник со број на појавување на зборовите
  765.    :rtype: dict(str, int)
  766.    """
  767.     df = {}
  768.     documents_words = []
  769.     for doc_text in documents:
  770.         words = get_words(doc_text)
  771.         documents_words.append(words)
  772.         words_set = set(words)
  773.         for word in words_set:
  774.             df.setdefault(word, 0)
  775.             df[word] += 1
  776.     return df
  777.  
  778.  
  779. def calc_vector(cur_tf_idf, vocab):
  780.     """Пресметува tf-idf вектор за даден документ од дадениот вокабулар.
  781.  
  782.    :param cur_tf_idf: речник со tf-idf тежини
  783.    :type cur_tf_idf: dict(str, float)
  784.    :param vocab: множество од сите зборови кои се појавуваат во барем еден документ
  785.    :type vocab: set(str)
  786.    :return: tf-idf вектор за дадениот документ
  787.    """
  788.     vec = []
  789.     for word in vocab:
  790.         tf_idf = cur_tf_idf.get(word, 0)
  791.         vec.append(tf_idf)
  792.     return vec
  793.  
  794.  
  795. def process_document(doc, df, N, vocab):
  796.     """Пресметува tf-idf за даден документ.
  797.  
  798.    :param doc: документ
  799.    :type doc: str
  800.    :param df: речник со фреквенции на зборовите во дадениот документ
  801.    :type df: dict(str, int)
  802.    :param N: вкупен број на документи
  803.    :param vocab: множество од сите зборови кои се појавуваат во барем еден документ
  804.    :type vocab: set(str)
  805.    :return: tf-idf вектор за дадениот документ
  806.    """
  807.     if isinstance(doc, str):
  808.         words = get_words(doc)
  809.     else:
  810.         words = doc
  811.     idf = {}
  812.     for word, cdf in df.items():
  813.         idf[word] = math.log(N / cdf)
  814.     f = {}  # колку пати се јавува секој збор во овој документ
  815.     for word in words:
  816.         f.setdefault(word, 0)
  817.         f[word] += 1
  818.     max_f = max(f.values())  # колку пати се појавува најчестиот збор во овој документ
  819.     tf_idf = {}
  820.     for word, cnt in f.items():
  821.         ctf = cnt * 1.0 / max_f
  822.         tf_idf[word] = ctf * idf.get(word, 0)
  823.     vec = calc_vector(tf_idf, vocab)
  824.     return vec
  825.  
  826.  
  827. def rank_documents(doc, documents, sim_func=cosine):
  828.     """Враќа најслични документи со дадениот документ.
  829.  
  830.    :param doc: документ
  831.    :type doc: str
  832.    :param documents: листа со документи
  833.    :type documents: list(str)
  834.    :param sim_func: функција за сличност
  835.    :return: листа со најслични документи
  836.    """
  837.     df = calculate_document_frequencies(documents)
  838.     N = len(documents)
  839.     vocab = get_vocabulary(documents)
  840.     doc_vectors = []
  841.     for document in documents:
  842.         vec = process_document(document, df, N, vocab)
  843.         doc_vectors.append(vec)
  844.     query_vec = process_document(doc, df, N, vocab)
  845.     similarities = []
  846.     for i, doc_vec in enumerate(doc_vectors):
  847.         dist = sim_func(query_vec, doc_vec)
  848.         similarities.append((dist, i))
  849.     similarities.sort(reverse=True)
  850.     return similarities
  851.  
  852.  
  853. def create_dataset(documents, labels):
  854.     """Формира податочно множество со tf-idf тежини и класи, соодветно за класификација со дрва на одлука.
  855.  
  856.    :param documents: листа со документи
  857.    :type documents: list(str)
  858.    :param labels: листа со класи
  859.    :type labels: list
  860.    :return: податочно множество со tf-idf тежини и класи, речник со френвенции на појавување на зборовите,
  861.            број на документи во множеството, вокабулар од даденото множество на аборови
  862.    :rtype: list(list), dict(str, int), int, set(word)
  863.    """
  864.     dataset = []
  865.     doc_vectors = []
  866.     df = calculate_document_frequencies(documents)
  867.     N = len(documents)
  868.     vocab = get_vocabulary(documents)
  869.     for document in documents:
  870.         vec = process_document(document, df, N, vocab)
  871.         doc_vectors.append(vec)
  872.     for doc_vec, label in zip(doc_vectors, labels):
  873.         doc_vec.append(label)
  874.         dataset.append(doc_vec)
  875.     return dataset, df, N, vocab
  876.  
  877. def unique_counts(rows):
  878.     """Креирај броење на можни резултати (последната колона
  879.    во секоја редица е класата)
  880.  
  881.    :param rows: dataset
  882.    :type rows: list
  883.    :return: dictionary of possible classes as keys and count
  884.             as values
  885.    :rtype: dict
  886.    """
  887.     results = {}
  888.     for row in rows:
  889.         # Клацата е последната колона
  890.         r = row[len(row) - 1]
  891.         if r not in results:
  892.             results[r] = 0
  893.         results[r] += 1
  894.     return results
  895.  
  896.  
  897. def gini_impurity(rows):
  898.     """Probability that a randomly placed item will
  899.    be in the wrong category
  900.  
  901.    :param rows: dataset
  902.    :type rows: list
  903.    :return: Gini impurity
  904.    :rtype: float
  905.    """
  906.     total = len(rows)
  907.     counts = unique_counts(rows)
  908.     imp = 0
  909.     for k1 in counts:
  910.         p1 = float(counts[k1]) / total
  911.         for k2 in counts:
  912.             if k1 == k2:
  913.                 continue
  914.             p2 = float(counts[k2]) / total
  915.             imp += p1 * p2
  916.     return imp
  917.  
  918.  
  919. def entropy(rows):
  920.     """Ентропијата е сума од p(x)log(p(x)) за сите
  921.    можни резултати
  922.  
  923.    :param rows: податочно множество
  924.    :type rows: list
  925.    :return: вредност за ентропијата
  926.    :rtype: float
  927.    """
  928.     log2 = lambda x: log(x) / log(2)
  929.     results = unique_counts(rows)
  930.     # Пресметка на ентропијата
  931.     ent = 0.0
  932.     for r in results.keys():
  933.         p = float(results[r]) / len(rows)
  934.         ent = ent - p * log2(p)
  935.     return ent
  936.  
  937.  
  938. class DecisionNode:
  939.     def __init__(self, col=-1, value=None, results=None, tb=None, fb=None):
  940.         """
  941.        :param col: индексот на колоната (атрибутот) од тренинг множеството
  942.                    која се претставува со оваа инстанца т.е. со овој јазол
  943.        :type col: int
  944.        :param value: вредноста на јазолот според кој се дели дрвото
  945.        :param results: резултати за тековната гранка, вредност (различна
  946.                        од None) само кај јазлите-листови во кои се донесува
  947.                        одлуката.
  948.        :type results: dict
  949.        :param tb: гранка која се дели од тековниот јазол кога вредноста е
  950.                   еднаква на value
  951.        :type tb: DecisionNode
  952.        :param fb: гранка која се дели од тековниот јазол кога вредноста е
  953.                   различна од value
  954.        :type fb: DecisionNode
  955.        """
  956.         self.col = col
  957.         self.value = value
  958.         self.results = results
  959.         self.tb = tb
  960.         self.fb = fb
  961.  
  962.  
  963. def compare_numerical(row, column, value):
  964.     """Споредба на вредноста од редицата на посакуваната колона со
  965.    зададена нумеричка вредност
  966.  
  967.    :param row: дадена редица во податочното множество
  968.    :type row: list
  969.    :param column: индекс на колоната (атрибутот) од тренирачкото множество
  970.    :type column: int
  971.    :param value: вредност на јазелот во согласност со кој се прави
  972.                  поделбата во дрвото
  973.    :type value: int or float
  974.    :return: True ако редицата >= value, инаку False
  975.    :rtype: bool
  976.    """
  977.     return row[column] >= value
  978.  
  979.  
  980. def compare_nominal(row, column, value):
  981.     """Споредба на вредноста од редицата на посакуваната колона со
  982.    зададена номинална вредност
  983.  
  984.    :param row: дадена редица во податочното множество
  985.    :type row: list
  986.    :param column: индекс на колоната (атрибутот) од тренирачкото множество
  987.    :type column: int
  988.    :param value: вредност на јазелот во согласност со кој се прави
  989.                  поделбата во дрвото
  990.    :type value: str
  991.    :return: True ако редицата == value, инаку False
  992.    :rtype: bool
  993.    """
  994.     return row[column] == value
  995.  
  996.  
  997. def divide_set(rows, column, value):
  998.     """Поделба на множеството според одредена колона. Може да се справи
  999.    со нумерички или номинални вредности.
  1000.  
  1001.    :param rows: тренирачко множество
  1002.    :type rows: list(list)
  1003.    :param column: индекс на колоната (атрибутот) од тренирачкото множество
  1004.    :type column: int
  1005.    :param value: вредност на јазелот во зависност со кој се прави поделбата
  1006.                  во дрвото за конкретната гранка
  1007.    :type value: int or float or str
  1008.    :return: поделени подмножества
  1009.    :rtype: list, list
  1010.    """
  1011.     # Направи функција која ни кажува дали редицата е во
  1012.     # првата група (True) или втората група (False)
  1013.     if isinstance(value, int) or isinstance(value, float):
  1014.         # ако вредноста за споредба е од тип int или float
  1015.         split_function = compare_numerical
  1016.     else:
  1017.         # ако вредноста за споредба е од друг тип (string)
  1018.         split_function = compare_nominal
  1019.  
  1020.     # Подели ги редиците во две подмножества и врати ги
  1021.     # за секој ред за кој split_function враќа True
  1022.     set1 = [row for row in rows if
  1023.             split_function(row, column, value)]
  1024.     # set1 = []
  1025.     # for row in rows:
  1026.     #     if not split_function(row, column, value):
  1027.     #         set1.append(row)
  1028.     # за секој ред за кој split_function враќа False
  1029.     set2 = [row for row in rows if
  1030.             not split_function(row, column, value)]
  1031.     return set1, set2
  1032.  
  1033.  
  1034. def build_tree(rows, scoref=entropy):
  1035.     if len(rows) == 0:
  1036.         return DecisionNode()
  1037.     current_score = scoref(rows)
  1038.  
  1039.     # променливи со кои следиме кој критериум е најдобар
  1040.     best_gain = 0.0
  1041.     best_criteria = None
  1042.     best_sets = None
  1043.  
  1044.     column_count = len(rows[0]) - 1
  1045.     for col in range(0, column_count):
  1046.         # за секоја колона (col се движи во интервалот од 0 до
  1047.         # column_count - 1)
  1048.         # Следниов циклус е за генерирање на речник од различни
  1049.         # вредности во оваа колона
  1050.         column_values = {}
  1051.         for row in rows:
  1052.             column_values[row[col]] = 1
  1053.         # за секоја редица се зема вредноста во оваа колона и се
  1054.         # поставува како клуч во column_values
  1055.         for value in column_values.keys():
  1056.             (set1, set2) = divide_set(rows, col, value)
  1057.  
  1058.             # Информациона добивка
  1059.             p = float(len(set1)) / len(rows)
  1060.             gain = current_score - p * scoref(set1) - (1 - p) * scoref(set2)
  1061.             if gain > best_gain and len(set1) > 0 and len(set2) > 0:
  1062.                 best_gain = gain
  1063.                 best_criteria = (col, value)
  1064.                 best_sets = (set1, set2)
  1065.  
  1066.     # Креирај ги подгранките
  1067.     if best_gain > 0:
  1068.         true_branch = build_tree(best_sets[0], scoref)
  1069.         false_branch = build_tree(best_sets[1], scoref)
  1070.         return DecisionNode(col=best_criteria[0], value=best_criteria[1],
  1071.                             tb=true_branch, fb=false_branch)
  1072.     else:
  1073.         return DecisionNode(results=unique_counts(rows))
  1074.  
  1075.  
  1076. def print_tree(tree, indent=''):
  1077.     # Дали е ова лист јазел?
  1078.     if tree.results:
  1079.         print(str(tree.results))
  1080.     else:
  1081.         # Се печати условот
  1082.         print(str(tree.col) + ':' + str(tree.value) + '? ')
  1083.         # Се печатат True гранките, па False гранките
  1084.         print(indent + 'T->', end='')
  1085.         print_tree(tree.tb, indent + '  ')
  1086.         print(indent + 'F->', end='')
  1087.         print_tree(tree.fb, indent + '  ')
  1088.  
  1089.  
  1090. def classify(observation, tree):
  1091.     if tree.results:
  1092.         return tree.results
  1093.     else:
  1094.         value = observation[tree.col]
  1095.         if isinstance(value, int) or isinstance(value, float):
  1096.             compare = compare_numerical
  1097.         else:
  1098.             compare = compare_nominal
  1099.  
  1100.         if compare(observation, tree.col, tree.value):
  1101.             branch = tree.tb
  1102.         else:
  1103.             branch = tree.fb
  1104.  
  1105.         return classify(observation, branch)
  1106. def get_words(doc):
  1107.     """Поделба на документот на зборови. Стрингот се дели на зборови според
  1108.    празните места и интерпукциските знаци
  1109.  
  1110.    :param doc: документ
  1111.    :type doc: str
  1112.    :return: множество со зборовите кои се појавуваат во дадениот документ
  1113.    :rtype: set(str)
  1114.    """
  1115.     # подели го документот на зборови и конвертирај ги во мали букви
  1116.     # па потоа стави ги во резултатот ако нивната должина е >2 и <20
  1117.     words = set()
  1118.     for word in re.split('\\W+', doc):
  1119.         if 2 < len(word) < 20:
  1120.             words.add(word.lower())
  1121.     return words
  1122. def get_words_with_ignore(doc,words_to_ignore=None):
  1123.     """Поделба на документот на зборови. Стрингот се дели на зборови според
  1124.    празните места и интерпукциските знаци
  1125.  
  1126.    :param doc: документ
  1127.    :type doc: str
  1128.    :return: множество со зборовите кои се појавуваат во дадениот документ
  1129.    :rtype: set(str)
  1130.    """
  1131.     # подели го документот на зборови и конвертирај ги во мали букви
  1132.     # па потоа стави ги во резултатот ако нивната должина е >2 и <20
  1133.     words = set()
  1134.     for word in re.split('\\W+', doc):
  1135.         if 2 < len(word) < 20:
  1136.             if words_to_ignore is not None and word.lower() not in words_to_ignore:
  1137.              words.add(word.lower())
  1138.     return words
  1139.  
  1140.  
  1141. class DocumentClassifier:
  1142.     def __init__(self, get_features):
  1143.         # број на парови атрибут/категорија (feature/category)
  1144.         self.feature_counts_per_category = {}
  1145.         # број на документи во секоја категорија
  1146.         self.category_counts = {}
  1147.         # функција за добивање на атрибутите (зборовите) во документот
  1148.         self.get_features = get_features
  1149.  
  1150.     def increment_feature_counts_per_category(self, current_feature, current_category):
  1151.         """Зголемување на бројот на парови атрибут/категорија
  1152.  
  1153.        :param current_feature: даден атрибут
  1154.        :param current_category: дадена категорија
  1155.        :return: None
  1156.        """
  1157.         self.feature_counts_per_category.setdefault(current_feature, {})
  1158.         self.feature_counts_per_category[current_feature].setdefault(current_category, 0)
  1159.         self.feature_counts_per_category[current_feature][current_category] += 1
  1160.  
  1161.     def increment_category_counts(self, cat):
  1162.         """Зголемување на бројот на предмети (документи) во категорија
  1163.  
  1164.        :param cat: категорија
  1165.        :return: None
  1166.        """
  1167.         self.category_counts.setdefault(cat, 0)
  1168.         self.category_counts[cat] += 1
  1169.  
  1170.     def get_feature_counts_per_category(self, current_feature, current_category):
  1171.         """Добивање на бројот колку пати одреден атрибут се има појавено во
  1172.        одредена категорија
  1173.  
  1174.        :param current_feature: атрибут
  1175.        :param current_category: категорија
  1176.        :return: None
  1177.        """
  1178.         if current_feature in self.feature_counts_per_category \
  1179.                 and current_category in self.feature_counts_per_category[current_feature]:
  1180.             return float(self.feature_counts_per_category[current_feature][current_category])
  1181.         return 0.0
  1182.  
  1183.     def get_category_count(self, current_category):
  1184.         """Добивање на бројот на предмети (документи) во категорија
  1185.  
  1186.        :param current_category: категорија
  1187.        :return: број на предмети (документи)
  1188.        """
  1189.         if current_category in self.category_counts:
  1190.             return float(self.category_counts[current_category])
  1191.         return 0
  1192.  
  1193.     def get_total_count(self):
  1194.         """Добивање на вкупниот број на предмети"""
  1195.         return sum(self.category_counts.values())
  1196.  
  1197.     def categories(self):
  1198.         """Добивање на листа на сите категории"""
  1199.         return self.category_counts.keys()
  1200.  
  1201.     def train(self, item, current_category):
  1202.         """Тренирање на класификаторот. Новиот предмет (документ)
  1203.  
  1204.        :param item: нов предмет (документ)
  1205.        :param current_category: категорија
  1206.        :return: None
  1207.        """
  1208.         # Се земаат атрибутите (зборовите) во предметот (документот)
  1209.         features = self.get_features(item)
  1210.         # Се зголемува бројот на секој атрибут во оваа категорија
  1211.         for current_feature in features:
  1212.             self.increment_feature_counts_per_category(current_feature, current_category)
  1213.  
  1214.         # Се зголемува бројот на предмети (документи) во оваа категорија
  1215.         self.increment_category_counts(current_category)
  1216.  
  1217.     def get_feature_per_category_probability(self, current_feature, current_category):
  1218.         """Веројатноста е вкупниот број на пати кога даден атрибут f (збор) се појавил во
  1219.        дадена категорија поделено со вкупниот број на предмети (документи) во категоријата
  1220.  
  1221.        :param current_feature: атрибут
  1222.        :param current_category: карактеристика
  1223.        :return: веројатност на појавување
  1224.        """
  1225.         if self.get_category_count(current_category) == 0:
  1226.             return 0
  1227.         return self.get_feature_counts_per_category(current_feature, current_category) \
  1228.                / self.get_category_count(current_category)
  1229.  
  1230.     def weighted_probability(self, current_feature, current_category, prf, weight=1.0, ap=0.5):
  1231.         """Пресметка на тежински усогласената веројатност
  1232.  
  1233.        :param current_feature: атрибут
  1234.        :param current_category: категорија
  1235.        :param prf: функција за пресметување на основната веројатност
  1236.        :param weight: тежина
  1237.        :param ap: претпоставена веројатност
  1238.        :return: тежински усогласена веројатност
  1239.        """
  1240.         # Пресметај ја основната веројатност
  1241.         basic_prob = prf(current_feature, current_category)
  1242.         # Изброј колку пати се има појавено овој атрибут (збор) во сите категории
  1243.         totals = sum([self.get_feature_counts_per_category(current_feature, currentCategory) for currentCategory in
  1244.                       self.categories()])
  1245.         # Пресметај ја тежински усредената веројатност
  1246.         bp = ((weight * ap) + (totals * basic_prob)) / (weight + totals)
  1247.         return bp
  1248.  
  1249.  
  1250. class NaiveBayes(DocumentClassifier):
  1251.     def __init__(self, get_features):
  1252.         super().__init__(get_features)
  1253.         self.thresholds = {}
  1254.  
  1255.     def set_threshold(self, current_category, threshold):
  1256.         """Поставување на праг на одлучување за категорија
  1257.  
  1258.        :param current_category: категорија
  1259.        :param threshold: праг на одлучување
  1260.        :return: None
  1261.        """
  1262.         self.thresholds[current_category] = threshold
  1263.  
  1264.     def get_threshold(self, current_category):
  1265.         """Добивање на прагот на одлучување за дадена класа
  1266.  
  1267.        :param current_category: категорија
  1268.        :return: праг на одлучување за дадената категорија
  1269.        """
  1270.         if current_category not in self.thresholds:
  1271.             return 1.0
  1272.         return self.thresholds[current_category]
  1273.  
  1274.     def calculate_document_probability_in_class(self, item, current_category):
  1275.         """Ја враќа веројатноста на документот да е од класата current_category
  1276.        (current_category е однапред позната)
  1277.  
  1278.        :param item: документ
  1279.        :param current_category: категорија
  1280.        :return:
  1281.        """
  1282.         # земи ги зборовите од документот item
  1283.         features = self.get_features(item)
  1284.         # помножи ги веројатностите на сите зборови
  1285.         p = 1
  1286.         for current_feature in features:
  1287.             p *= self.weighted_probability(current_feature, current_category,
  1288.                                            self.get_feature_per_category_probability)
  1289.  
  1290.         return p
  1291.  
  1292.     def get_category_probability_for_document(self, item, current_category):
  1293.         """Ја враќа веројатноста на класата ако е познат документот
  1294.  
  1295.        :param item: документ
  1296.        :param current_category: категорија
  1297.        :return: веројатност за документот во категорија
  1298.        """
  1299.         cat_prob = self.get_category_count(current_category) / self.get_total_count()
  1300.         calculate_document_probability_in_class = self.calculate_document_probability_in_class(item, current_category)
  1301.         # Bayes Theorem
  1302.         return calculate_document_probability_in_class * cat_prob / (1.0 / self.get_total_count())
  1303.  
  1304.     def classify_document(self, item, default=None):
  1305.         """Класифицирање на документ
  1306.  
  1307.        :param item: документ
  1308.        :param default: подразбирана (default) класа
  1309.        :return:
  1310.        """
  1311.         probs = {}
  1312.         # најди ја категоријата (класата) со најголема веројатност
  1313.         max = 0.0
  1314.         for cat in self.categories():
  1315.             probs[cat] = self.get_category_probability_for_document(item, cat)
  1316.             if probs[cat] > max:
  1317.                 max = probs[cat]
  1318.                 best = cat
  1319.  
  1320.         # провери дали веројатноста е поголема од threshold*next best (следна најдобра)
  1321.         for cat in probs:
  1322.             if cat == best:
  1323.                 continue
  1324.             if probs[cat] * self.get_threshold(best) > probs[best]: return default
  1325.  
  1326.         return best
  1327.  
  1328.  
  1329.  
  1330. train_data = [
  1331.     ("""I like Rhythm and Blue music.""", 'formal'),
  1332.     ("""Back in my day Emo was a comedian :/""", 'informal'),
  1333.     ("""Why sit and listen to Locke, Jack, or Syead?""", 'informal'),
  1334.     ("""There's nothing he needs to change.""", 'formal'),
  1335.     ("""It does not exist.""", 'formal'),
  1336.     ("""I like when the Prime Minister goes door to door to find the girl!""", 'informal'),
  1337.     ("""Mine is book by Steve Martin called 'The Pleasure of my Company'.""", 'formal'),
  1338.     ("""What differentiates a mosquitoo from a blonde?""", 'formal'),
  1339.     ("""They're pretty good. Also, that's a good song.""", 'formal'),
  1340.     ("""And every time I hear that song I get butterflies in my stomach!""", 'informal'),
  1341.     ("""It's the biggest load of crap I've seen for ages.""", 'informal'),
  1342.     ("""I do not think Beyonce can sing, dance, or act. You mentioned Rihanna, who is that?""", 'formal'),
  1343.     ("""as i lay dying is far far away from christ definitaly!""", 'informal'),
  1344.     ("""I was unaware that you were in law enforcement, as well.""", 'formal'),
  1345.     ("""I might be seeing them in a few months!""", 'informal'),
  1346.     ("""I called to say 'I Love You""", 'formal'),
  1347.     ("""that´s why they needed to open that hatch so much!""", 'informal'),
  1348.     ("""I would most likely not vote for him, although I believe Melania would be the most attractive First Lady in our country's history.""", 'formal'),
  1349.     ("""I do not hate him.""", 'formal'),
  1350.     ("""He's supposed to be in jail!""", 'informal'),
  1351.     ("""i thought that she did an outstanding job in the movie""", 'informal'),
  1352.     ("""Nicole Kidman, I love her eyes""", 'informal'),
  1353.     ("""Youtube.com also features many of the current funny ads.""", 'formal'),
  1354.     ("""I enjoy watching my companion attempt to role-play with them.""", 'formal'),
  1355.     ("""omg i love that song im listening to it right now""", 'informal'),
  1356.     ("""Some of my favorite television series are Monk, The Dukes of Hazzard, Miami Vice, and The Simpsons.""", 'formal'),
  1357.     ("""I have a desire to produce videos on Full Metal Alchemist.""", 'formal'),
  1358.     ("""tell him you want a 3 way with another hot girl""", 'informal'),
  1359.     ("""I would travel to that location and physically assault you at this very moment, however, I am unable to swim.""", 'formal'),
  1360.     ("""No, no, no that was WITNESS...""", 'informal'),
  1361.     ("""aneways shonenjump.com is cool and yeah narutos awsum""", 'informal'),
  1362.     ("""Your mother is so unintelligent that she was hit by a cup and told the police that she was mugged.""", 'formal'),
  1363.     ("""You must be creative and find something to challange us.""", 'formal'),
  1364.     ("""i think they would have, quite a shame isn't it""", 'informal'),
  1365.     ("""I am watching it right now.""", 'formal'),
  1366.     ("""I do not know; the person who invented the names had attention deficit disorder.""", 'formal'),
  1367.     ("""im a huge green day fan!!!!!""", 'informal'),
  1368.     ("""I believe, rather, that they are not very smart on this topic.""", 'formal'),
  1369.     ("""Of course it is Oprah, because she has been providing better advice for a longer time.""", 'formal'),
  1370.     ("""Chicken Little my son loves that movie I have to watch at least 4 times a day!""", 'informal'),
  1371.     ("""That is the key point, that you fell asleep.""", 'formal'),
  1372.     ("""A brunette female, a blonde, and person with red hair walked down a street.""", 'formal'),
  1373.     ("""who is your best bet for american idol season five""", 'informal'),
  1374.     ("""That is funny.  Girls need to be a part of everything.""", 'formal'),
  1375.     ("""In point of fact, Chris's performance looked like the encoure performed at a Genesis concert.""", 'formal'),
  1376.     ("""In my time, Emo was a comedian.""", 'formal'),
  1377.     ("""my age gas prices and my blood pressure  LOL""", 'informal'),
  1378.     ("""Moriarty and so forth, but what character did the Peruvian actor portray?""", 'formal'),
  1379.     ("""What did the beaver say to the log?""", 'formal'),
  1380.     ("""Where in the world do you come up with these questions????""", 'informal'),
  1381.     ("""even though i also agree that the girls on Love Hina are pretty scrumptious""", 'informal'),
  1382.     ("""I miss Aaliyah, she was a great singer.""", 'formal'),
  1383.     ("""and the blond says Great they already put me on my first murder mystery case""", 'informal'),
  1384. ]
  1385.  
  1386.  
  1387. if __name__ == '__main__':
  1388.     sample = input()
  1389.     documents = []
  1390.     labels = []
  1391.     for x in train_data:
  1392.         documents.append(x[0])
  1393.         labels.append(x[1])
  1394.        
  1395.     dataset, df, N, vocab = create_dataset(documents, labels)
  1396.  
  1397.     vocab=get_vocabulary(documents)
  1398.    
  1399.     tree = build_tree(dataset,entropy)
  1400.     test_case = process_document(sample, df, N, vocab)
  1401.     print(max(classify(test_case, tree).items(), key=lambda x: x[1])[0])
  1402.  
  1403.     clas = NaiveBayes(get_words)
  1404.     for x in train_data:
  1405.         clas.train(x[0], x[1])
  1406.        
  1407.     print(clas.classify_document(sample))
Add Comment
Please, Sign In to add comment