你如何编写一个可以自动规范化数据的django模型?

时间:2012-05-08 05:54:29

标签: django-models

我正在构建一个音乐推荐引擎,该引擎使用曲目的歌词来确定歌曲在情感上彼此之间的紧密关系。我已经使用tfidf算法(这里没有显示)为每首歌曲生成一个分数,我想在django模型字段tfidf中存储每个轨道的tfidf分数。但是我希望将每个tfidf分数标准化为0-1。

我遇到的困难在于,只要有人在管理界面输入tfidf值,就会知道如何自动规范化这些tfidf分数。因此,想象一下,您已进入管理界面,并希望将歌曲“In Da Club”添加到数据库中。您输入歌曲的名称及其tfidf分数如下:

enter image description here

我想要做的是确保只要点击“保存”按钮,它就会自动使用规范化值填充空的normalized_tfidf列。我正在使用一个简单的算法来规范化tfidf值。在我进入之前,让我告诉你这个表的样子,以便你更清楚地了解算法的作用。因此,在将“In Da Club”添加到数据库中(并且数据已经规范化)之后,表格列应如下所示:

enter image description here

Song x和song y只是我在数据库中播放的虚拟歌曲,用于设置算法的上限和下限。您看到的.50077值就是我想要自动生成的值。

算法说要找到歌曲x中特征tfidf的归一化值(nv),找出歌曲的tfidf得分与表格中最小的tfidf得分之间的差异,并将其除以最大值和最小值之间的差值表中的tfidf得分。这是数学上的。

nv(在俱乐部 tfidf )=在俱乐部 tfidf - tfidf min / tfidf max - TFIDF 分钟

这是计算:

nv(在俱乐部)= .25048 - .00010 / .50000 - .00010 = .50077

所以我正在尝试将其编码到我的模型中。问题是django似乎没有允许我按照SQL语句的方式在表中选择最小和最大tfidf值的方法。我对django很新,并不完全了解它的能力。如果我的这个表的模型看起来像我下面的那样,那么最好的方法是重写它,以便一旦你输入管理员,tfidf会自动规范化吗?

enter image description here

2 个答案:

答案 0 :(得分:1)

保存模型时有两种方法可以触发某些操作:覆盖save方法,或编写post_save侦听器。我将展示覆盖方法,因为它有点简单,并且非常适合这个用例。

要获得最大/最小值,您可以使用Django的queryset aggregation functions

from django.db.models import Max, Min


class Party(models.Model):
    ...
    def save(self, *args, **kwargs):
        max = Party.objects.all().aggregate(Max('tfidf'))['tfidf__max']
        min = Party.objects.all().aggregate(Min('tfidf'))['tfidf__min']
        self.normalized_tfidf = (self.tfidf - min) / (max - min)
        super(Party, self).save(*args, **kwargs)

覆盖save之类的默认模型方法非常简单,但如果您有兴趣,还可以获得更多信息here

请注意,如果您在任何时候执行bulk updatesParty.tfidf,则不会调用保存处理程序(或者发送post_save信号),因此您必须处理手动的所有行 - 这意味着大量的数据库写入,并且几乎使批量更新毫无意义。

答案 1 :(得分:0)

为了防止出现陈旧数据等问题,正如@klaws 在上面的评论中提到的,在添加新歌曲时计算归一化值可能并不理想。

相反,您可以使用查询让数据库在需要时计算标准化值。

您需要从 django 的 expressionsaggregates 中导入一些内容:

from django.db.models import Window, F, Min, Max

这是一个简单的例子,适用于 OP 的问题,假设不需要分组:

def query_normalized_tfidf(party_queryset):
    w_min = Window(expression=Min('tfidf'))
    w_max = Window(expression=Max('tfidf'))
    return party_queryset.annotate(
        normalized_tfidf=(F('tfidf') - w_min) / (w_max - w_min))

Window 类允许我们继续注释单个对象,如解释,例如here 和 Django 的 docs

我们也可以将其添加到自定义 model manager 中,而不是使用单独的查询函数。

如果您需要针对某些组计算归一化值(例如,如果歌曲有 genre),则可以对上述内容进行扩展和概括,如下所示:

def query_normalized_values(queryset, value_lookup, group_lookups=None):
    """
    a generalized version that normalizes data with respect to the
    extreme values within each group
    """
    partitions = None
    if group_lookups:
        partitions = [F(group_lookup) for group_lookup in group_lookups]
    w_min = Window(expression=Min(value_lookup), partition_by=partitions)
    w_max = Window(expression=Max(value_lookup), partition_by=partitions)
    return queryset.annotate(
        normalized=(F(value_lookup) - w_min) / (w_max - w_min))

这可以如下使用,假设会有一个 Party.genre 字段:

annotated_parties = query_normalized_values(
    queryset=Party.objects.all(), value_lookup='tfidf',
    group_lookups=['genre'])

这将使 tfidf 值相对于每个 tfidf 内的极端 genre 值标准化。

注意:在除以零的特殊情况下(当 w_min 等于 w_max 时),结果“归一化值”将为 None