2

我正在构建一个音乐推荐引擎,它使用曲目的歌词来确定歌曲在情感上彼此之间的关联程度。我已使用 tfidf 算法(此处未显示)为每首歌曲生成分数,我想将每首曲目的 tfidf 分数存储在名为tfidf. 但我想将每个 tfidf 分数标准化为 0-1。

我遇到的困难是弄清楚如何在有人在管理界面中输入 tfidf 值后立即自动标准化这些 tfidf 分数。所以想象一下,你已经进入了管理界面,想要将歌曲“In Da Club”添加到数据库中。您输入歌曲的名称及其 tfidf 分数,如下所示:

在此处输入图像描述

我想做的是确保一旦你点击保存按钮,它就会自动normalized_tfidf用标准化值填充空列。我正在使用一个简单的算法来规范化 tfidf 值。不过,在我开始之前,让我向您展示这张表的外观,以便您更清楚地了解算法正在做什么。因此,在将“In Da Club”添加到数据库后(并且数据已被规范化),表格列应如下所示:

在此处输入图像描述

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

该算法说要找到歌曲 x 中特征 tfidf 的归一化值 (nv),求歌曲的 tfidf 分数与表中最小 tfidf 分数之间的差值,然后除以表中最大和最小 tfidf 分数之间的差值桌子。这是数学上的。

nv(In da club tfidf ) = (In da club tfidf – tfidf min ) / (tfidf max – tfidf min )

这是计算:

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

所以我试图将它编码到我的模型中。问题是 django 似乎没有允许我像使用 SQL 语句那样在表中选择最小和最大 tfidf 值的方法。我对 django 很陌生,并不完全了解它的功能。如果我的这个表的模型看起来像我下面的模型,那么重写它以便在将 tfidf 输入管理员后自动标准化的最佳方法是什么?

class Party(models.Model):
    song = models.CharField(max_length=30)
    tfidf = models.FloatField()
    normalized_tfidf = models.FloatField()
4

2 回答 2

2

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

要获得最大值/最小值,您可以使用 Django 的查询集聚合函数

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非常简单,但如果您有兴趣,这里还有更多信息。

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

于 2012-05-14T14:38:50.670 回答
0

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

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

你需要从 django 的表达式聚合中导入一些东西:

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允许我们继续注释单个对象,如此和 Django文档中所述。

除了使用单独的查询函数,我们还可以将其添加到自定义模型管理器中。

如果您需要针对某些组计算归一化值(例如,如果歌曲有 a 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.

于 2021-07-05T15:51:52.667 回答