X-Git-Url: http://jsfdemo.indexdata.com/?a=blobdiff_plain;f=src%2Frelevance.c;h=b08d217706bb047a6b318fc35c31101c46474cd6;hb=758bd14da56233550801d486d0e7c6e8790cfe11;hp=9d2f47d3cbc12a3d65fea134c605ea32619efdae;hpb=4d37a7d84107f77bd61f5eb057ed99c59b84607d;p=pazpar2-moved-to-github.git diff --git a/src/relevance.c b/src/relevance.c index 9d2f47d..b08d217 100644 --- a/src/relevance.c +++ b/src/relevance.c @@ -1,7 +1,5 @@ -/* $Id: relevance.c,v 1.12 2007-05-10 09:26:19 adam Exp $ - Copyright (c) 2006-2007, Index Data. - -This file is part of Pazpar2. +/* This file is part of Pazpar2. + Copyright (C) 2006-2013 Index Data Pazpar2 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free @@ -14,277 +12,276 @@ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License -along with Pazpar2; see the file LICENSE. If not, write to the -Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA -02111-1307, USA. - */ +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -#include -#include -#include +*/ #if HAVE_CONFIG_H -#include +#include #endif +#include +#include +#include + #include "relevance.h" -#include "pazpar2.h" +#include "session.h" -#define USE_TRIE 0 +#ifdef WIN32 +#define log2(x) (log(x)/log(2)) +#endif struct relevance { int *doc_frequency_vec; + int *term_frequency_vec_tmp; + int *term_pos; int vec_len; -#if USE_TRIE - struct word_trie *wt; -#else struct word_entry *entries; -#endif + pp2_charset_token_t prt; + int rank_cluster; + double follow_factor; + double lead_decay; + int length_divide; NMEM nmem; }; -#define raw_char(c) (((c) >= 'a' && (c) <= 'z') ? (c) - 'a' : -1) - - -#if USE_TRIE -// We use this data structure to recognize terms in input records, -// and map them to record term vectors for counting. -struct word_trie -{ - struct - { - struct word_trie *child; - int termno; - } list[26]; +struct word_entry { + const char *norm_str; + const char *display_str; + int termno; + char *ccl_field; + struct word_entry *next; }; -static struct word_trie *create_word_trie_node(NMEM nmem) +static struct word_entry *word_entry_match(struct relevance *r, + const char *norm_str, + const char *rank, int *weight) { - struct word_trie *res = nmem_malloc(nmem, sizeof(struct word_trie)); - int i; - for (i = 0; i < 26; i++) + int i = 1; + struct word_entry *entries = r->entries; + for (; entries; entries = entries->next, i++) { - res->list[i].child = 0; - res->list[i].termno = -1; + if (*norm_str && !strcmp(norm_str, entries->norm_str)) + { + const char *cp = 0; + int no_read = 0; + sscanf(rank, "%d%n", weight, &no_read); + rank += no_read; + while (*rank == ' ') + rank++; + if (no_read > 0 && (cp = strchr(rank, ' '))) + { + if ((cp - rank) == strlen(entries->ccl_field) && + memcmp(entries->ccl_field, rank, cp - rank) == 0) + *weight = atoi(cp + 1); + } + return entries; + } } - return res; + return 0; } -static void word_trie_addterm(NMEM nmem, struct word_trie *n, const char *term, int num) +void relevance_countwords(struct relevance *r, struct record_cluster *cluster, + const char *words, const char *rank, + const char *name) { + int *w = r->term_frequency_vec_tmp; + const char *norm_str; + int i, length = 0; + double lead_decay = r->lead_decay; + struct word_entry *e; + WRBUF wr = cluster->relevance_explain1; + int printed_about_field = 0; + + pp2_charset_token_first(r->prt, words, 0); + for (e = r->entries, i = 1; i < r->vec_len; i++, e = e->next) + { + w[i] = 0; + r->term_pos[i] = 0; + } - while (*term) { - int c = tolower(*term); - if (c < 'a' || c > 'z') - term++; - else + assert(rank); + while ((norm_str = pp2_charset_token_next(r->prt))) + { + int local_weight = 0; + e = word_entry_match(r, norm_str, rank, &local_weight); + if (e) { - c -= 'a'; - if (!*(++term)) - n->list[c].termno = num; - else + int res = e->termno; + int j; + + if (!printed_about_field) { - if (!n->list[c].child) + printed_about_field = 1; + wrbuf_printf(wr, "field=%s content=", name); + if (strlen(words) > 50) { - struct word_trie *new = create_word_trie_node(nmem); - n->list[c].child = new; + wrbuf_xmlputs_n(wr, words, 49); + wrbuf_puts(wr, " ..."); } - word_trie_addterm(nmem, n->list[c].child, term, num); + else + wrbuf_xmlputs(wr, words); + wrbuf_puts(wr, ";\n"); } - break; + assert(res < r->vec_len); + w[res] += local_weight / (1 + log2(1 + lead_decay * length)); + wrbuf_printf(wr, "%s: w[%d] += w(%d) / " + "(1+log2(1+lead_decay(%f) * length(%d)));\n", + e->display_str, res, local_weight, lead_decay, length); + j = res - 1; + if (j > 0 && r->term_pos[j]) + { + int d = length + 1 - r->term_pos[j]; + wrbuf_printf(wr, "%s: w[%d] += w[%d](%d) * follow(%f) / " + "(1+log2(d(%d));\n", + e->display_str, res, res, w[res], + r->follow_factor, d); + w[res] += w[res] * r->follow_factor / (1 + log2(d)); + } + for (j = 0; j < r->vec_len; j++) + r->term_pos[j] = j < res ? 0 : length + 1; } + length++; } -} -static int word_trie_match(struct word_trie *t, const char *word, int *skipped) -{ - int c = raw_char(tolower(*word)); - - if (!*word) - return 0; - - word++; - (*skipped)++; - if (!*word || raw_char(*word) < 0) + for (e = r->entries, i = 1; i < r->vec_len; i++, e = e->next) { - if (t->list[c].termno > 0) - return t->list[c].termno; - else - return 0; - } - else - { - if (t->list[c].child) + if (length == 0 || w[i] == 0) + continue; + wrbuf_printf(wr, "%s: tf[%d] += w[%d](%d)", e->display_str, i, i, w[i]); + switch (r->length_divide) { - return word_trie_match(t->list[c].child, word, skipped); + case 0: + cluster->term_frequency_vecf[i] += (double) w[i]; + break; + case 1: + wrbuf_printf(wr, " / log2(1+length(%d))", length); + cluster->term_frequency_vecf[i] += + (double) w[i] / log2(1 + length); + break; + case 2: + wrbuf_printf(wr, " / length(%d)", length); + cluster->term_frequency_vecf[i] += (double) w[i] / length; } - else - return 0; + cluster->term_frequency_vec[i] += w[i]; + wrbuf_printf(wr, " (%f);\n", cluster->term_frequency_vecf[i]); } + cluster->term_frequency_vec[0] += length; } - -static struct word_trie *build_word_trie(NMEM nmem, const char **terms) +static void pull_terms(struct relevance *res, struct ccl_rpn_node *n) { - struct word_trie *res = create_word_trie_node(nmem); - const char **p; + char **words; + int numwords; + char *ccl_field; int i; - for (i = 1, p = terms; *p; p++, i++) - word_trie_addterm(nmem, res, *p, i); - return res; -} - -#else - -struct word_entry { - const char *norm_str; - int termno; - struct word_entry *next; -}; - -static void add_word_entry(NMEM nmem, - struct word_entry **entries, - const char *norm_str, - int term_no) -{ - struct word_entry *ne = nmem_malloc(nmem, sizeof(*ne)); - ne->norm_str = nmem_strdup(nmem, norm_str); - ne->termno = term_no; - - ne->next = *entries; - *entries = ne; -} - - -int word_entry_match(struct word_entry *entries, const char *norm_str) -{ - for (; entries; entries = entries->next) + switch (n->kind) { - if (!strcmp(norm_str, entries->norm_str)) - return entries->termno; - } - return 0; -} + case CCL_RPN_AND: + case CCL_RPN_OR: + case CCL_RPN_NOT: + case CCL_RPN_PROX: + pull_terms(res, n->u.p[0]); + pull_terms(res, n->u.p[1]); + break; + case CCL_RPN_TERM: + nmem_strsplit(res->nmem, " ", n->u.t.term, &words, &numwords); + for (i = 0; i < numwords; i++) + { + const char *norm_str; -static struct word_entry *build_word_entries(NMEM nmem, - const char **terms) -{ - int termno = 1; /* >0 signals THERE is an entry */ - struct word_entry *entries = 0; - const char **p = terms; - WRBUF norm_str = wrbuf_alloc(); + ccl_field = nmem_strdup_null(res->nmem, n->u.t.qual); - for (; *p; p++) - { - const char *cp = *p; - for (; *cp; cp++) - { - int c = raw_char(*cp); - if (c >= 0) - wrbuf_putc(norm_str, c); - else + pp2_charset_token_first(res->prt, words[i], 0); + while ((norm_str = pp2_charset_token_next(res->prt))) { - if (wrbuf_len(norm_str)) - add_word_entry(nmem, &entries, wrbuf_cstr(norm_str), - termno); - wrbuf_rewind(norm_str); + struct word_entry **e = &res->entries; + while (*e) + e = &(*e)->next; + *e = nmem_malloc(res->nmem, sizeof(**e)); + (*e)->norm_str = nmem_strdup(res->nmem, norm_str); + (*e)->ccl_field = ccl_field; + (*e)->termno = res->vec_len++; + (*e)->display_str = nmem_strdup(res->nmem, words[i]); + (*e)->next = 0; } } - if (wrbuf_len(norm_str)) - add_word_entry(nmem, &entries, wrbuf_cstr(norm_str), termno); - wrbuf_rewind(norm_str); - termno++; + break; + default: + break; } - wrbuf_destroy(norm_str); - return entries; } +struct relevance *relevance_create_ccl(pp2_charset_fact_t pft, + struct ccl_rpn_node *query, + int rank_cluster, + double follow_factor, double lead_decay, + int length_divide) +{ + NMEM nmem = nmem_create(); + struct relevance *res = nmem_malloc(nmem, sizeof(*res)); + int i; + res->nmem = nmem; + res->entries = 0; + res->vec_len = 1; + res->rank_cluster = rank_cluster; + res->follow_factor = follow_factor; + res->lead_decay = lead_decay; + res->length_divide = length_divide; + res->prt = pp2_charset_token_create(pft, "relevance"); -#endif + pull_terms(res, query); + res->doc_frequency_vec = nmem_malloc(nmem, res->vec_len * sizeof(int)); + for (i = 0; i < res->vec_len; i++) + res->doc_frequency_vec[i] = 0; + // worker array + res->term_frequency_vec_tmp = + nmem_malloc(res->nmem, + res->vec_len * sizeof(*res->term_frequency_vec_tmp)); -struct relevance *relevance_create(NMEM nmem, const char **terms, int numrecs) -{ - struct relevance *res = nmem_malloc(nmem, sizeof(struct relevance)); - const char **p; - int i; + res->term_pos = + nmem_malloc(res->nmem, res->vec_len * sizeof(*res->term_pos)); - for (p = terms, i = 0; *p; p++, i++) - ; - res->vec_len = ++i; - res->doc_frequency_vec = nmem_malloc(nmem, res->vec_len * sizeof(int)); - memset(res->doc_frequency_vec, 0, res->vec_len * sizeof(int)); - res->nmem = nmem; -#if USE_TRIE - res->wt = build_word_trie(nmem, terms); -#else - res->entries = build_word_entries(nmem, terms); -#endif return res; } -void relevance_newrec(struct relevance *r, struct record_cluster *rec) +void relevance_destroy(struct relevance **rp) { - if (!rec->term_frequency_vec) + if (*rp) { - rec->term_frequency_vec = nmem_malloc(r->nmem, r->vec_len * sizeof(int)); - memset(rec->term_frequency_vec, 0, r->vec_len * sizeof(int)); + pp2_charset_token_destroy((*rp)->prt); + nmem_destroy((*rp)->nmem); + *rp = 0; } } - -// FIXME. The definition of a word is crude here.. should support -// some form of localization mechanism? -void relevance_countwords(struct relevance *r, struct record_cluster *cluster, - const char *words, int multiplier) +void relevance_newrec(struct relevance *r, struct record_cluster *rec) { -#if !USE_TRIE - WRBUF norm_str = wrbuf_alloc(); -#endif - while (*words) + if (!rec->term_frequency_vec) { - char c; - int res; -#if USE_TRIE - int skipped = 0; -#endif - while (*words && (c = raw_char(tolower(*words))) < 0) - words++; - if (!*words) - return; -#if USE_TRIE - res = word_trie_match(r->wt, words, &skipped); - if (res) - { - words += skipped; - cluster->term_frequency_vec[res] += multiplier; - } - else - { - while (*words && (c = raw_char(tolower(*words))) >= 0) - words++; - } -#else - while (*words && (c = raw_char(tolower(*words))) >= 0) - { - wrbuf_putc(norm_str, c); - words++; - } - res = word_entry_match(r->entries, wrbuf_cstr(norm_str)); - if (res) - cluster->term_frequency_vec[res] += multiplier; - wrbuf_rewind(norm_str); -#endif - cluster->term_frequency_vec[0]++; + int i; + + // term frequency [1,..] . [0] is total length of all fields + rec->term_frequency_vec = + nmem_malloc(r->nmem, + r->vec_len * sizeof(*rec->term_frequency_vec)); + for (i = 0; i < r->vec_len; i++) + rec->term_frequency_vec[i] = 0; + + // term frequency divided by length of field [1,...] + rec->term_frequency_vecf = + nmem_malloc(r->nmem, + r->vec_len * sizeof(*rec->term_frequency_vecf)); + for (i = 0; i < r->vec_len; i++) + rec->term_frequency_vecf[i] = 0.0; } -#if !USE_TRIE - wrbuf_destroy(norm_str); -#endif } void relevance_donerecord(struct relevance *r, struct record_cluster *cluster) @@ -298,37 +295,13 @@ void relevance_donerecord(struct relevance *r, struct record_cluster *cluster) r->doc_frequency_vec[0]++; } -#ifdef GAGA -#ifdef FLOAT_REL -static int comp(const void *p1, const void *p2) -{ - float res; - struct record **r1 = (struct record **) p1; - struct record **r2 = (struct record **) p2; - res = (*r2)->relevance - (*r1)->relevance; - if (res > 0) - return 1; - else if (res < 0) - return -1; - else - return 0; -} -#else -static int comp(const void *p1, const void *p2) -{ - struct record_cluster **r1 = (struct record_cluster **) p1; - struct record_cluster **r2 = (struct record_cluster **) p2; - return (*r2)->relevance - (*r1)->relevance; -} -#endif -#endif - // Prepare for a relevance-sorted read void relevance_prepare_read(struct relevance *rel, struct reclist *reclist) { int i; float *idfvec = xmalloc(rel->vec_len * sizeof(float)); + reclist_enter(reclist); // Calculate document frequency vector for each term. for (i = 1; i < rel->vec_len; i++) { @@ -336,45 +309,65 @@ void relevance_prepare_read(struct relevance *rel, struct reclist *reclist) idfvec[i] = 0; else { - // This conditional may be terribly wrong - // It was there to address the situation where vec[0] == vec[i] - // which leads to idfvec[i] == 0... not sure about this - // Traditional TF-IDF may assume that a word that occurs in every - // record is irrelevant, but this is actually something we will - // see a lot - if ((idfvec[i] = log((float) rel->doc_frequency_vec[0] / - rel->doc_frequency_vec[i])) < 0.0000001) - idfvec[i] = 1; + /* add one to nominator idf(t,D) to ensure a value > 0 */ + idfvec[i] = log((float) (1 + rel->doc_frequency_vec[0]) / + rel->doc_frequency_vec[i]); } } // Calculate relevance for each document - for (i = 0; i < reclist->num_records; i++) + while (1) { - int t; - struct record_cluster *rec = reclist->flatlist[i]; - float relevance; - relevance = 0; - for (t = 1; t < rel->vec_len; t++) + int relevance = 0; + WRBUF w; + struct word_entry *e = rel->entries; + struct record_cluster *rec = reclist_read_record(reclist); + if (!rec) + break; + w = rec->relevance_explain2; + wrbuf_rewind(w); + wrbuf_puts(w, "relevance = 0;\n"); + for (i = 1; i < rel->vec_len; i++) { - float termfreq; - if (!rec->term_frequency_vec[0]) - break; - termfreq = (float) rec->term_frequency_vec[t] / rec->term_frequency_vec[0]; - relevance += termfreq * idfvec[t]; + float termfreq = (float) rec->term_frequency_vecf[i]; + int add = 100000 * termfreq * idfvec[i]; + + wrbuf_printf(w, "idf[%d] = log(((1 + total(%d))/termoccur(%d));\n", + i, rel->doc_frequency_vec[0], + rel->doc_frequency_vec[i]); + wrbuf_printf(w, "%s: relevance += 100000 * tf[%d](%f) * " + "idf[%d](%f) (%d);\n", + e->display_str, i, termfreq, i, idfvec[i], add); + relevance += add; + e = e->next; } - rec->relevance = (int) (relevance * 100000); + if (!rel->rank_cluster) + { + struct record *record; + int cluster_size = 0; + + for (record = rec->records; record; record = record->next) + cluster_size++; + + wrbuf_printf(w, "score = relevance(%d)/cluster_size(%d);\n", + relevance, cluster_size); + relevance /= cluster_size; + } + else + { + wrbuf_printf(w, "score = relevance(%d);\n", relevance); + } + rec->relevance_score = relevance; } -#ifdef GAGA - qsort(reclist->flatlist, reclist->num_records, sizeof(struct record*), comp); -#endif - reclist->pointer = 0; + reclist_leave(reclist); xfree(idfvec); } /* * Local variables: * c-basic-offset: 4 + * c-file-style: "Stroustrup" * indent-tabs-mode: nil * End: * vim: shiftwidth=4 tabstop=8 expandtab */ +