我有一个Track模型,目前我通过模型ID进行嵌套循环以获取该对,然后将其传递给一个函数以计算这两个非等效轨道对象之间的相似性
track_set = Track.objects.all()
track_ids = [track.id for track in track_set]
pointer_a = 0
pointer_b = 1
for pointer_a in range(len(track_ids) - 1):
for pointer_b in range(pointer_a + 1, len(track_ids)):
track_a = Track.objects.get(pk=track_ids[pointer_a])
track_b = Track.objects.get(pk=track_ids[pointer_b])
counter += 1
count_it_sim(track_a, track_b)
我认为我获取对象的方式不是很有效,有什么方法可以优化它吗?
编辑:此count_it_sim
将计算track_a和track_b之间的相似度值,我需要针对Track模型中的所有对计算相似度值
models.py
class Tag(models.Model):
name = models.CharField(max_length=255, unique=True)
class Tagged(models.Model):
track = models.ForeignKey(Track, on_delete=models.CASCADE)
tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
frequency = models.IntegerField(
default=0,
validators=[MinValueValidator(0)],
)
class Meta:
unique_together = ('track', 'tag')
class Track(models.Model):
track_id = models.CharField(max_length=24)
title = models.CharField(max_length=120)
link = models.URLField(max_length=120, blank=True)
tags = models.ManyToManyField(Tag, through='Tagged', blank=True)
similarity = models.ManyToManyField(
'self',
blank=True,
through='Similar',
related_name='similar_to',
symmetrical=False
)
users = models.ManyToManyField(User, through='PlayTrack', blank=True)
class Similar(models.Model):
track1 = models.ForeignKey(Track, on_delete=models.CASCADE, related_name='track1')
track2 = models.ForeignKey(Track, on_delete=models.CASCADE, related_name='track2')
similarity = models.FloatField(
validators=[MinValueValidator(0), MaxValueValidator(1)],
)
count_it_sim会做的是,它将通过关联实体(即标记模型)获得所有标签的track_a和track_b频率,并进行计算以获取track_a和track_b之间的相似性值
def count_it_sim(track_a: Track, track_b: Track):
tag_set = Tag.objects.all()
part1 = 0
part2 = 0
part3 = 0
for tag in tag_set:
try:
freq_tag_of_track_a = Tagged.objects.get(track=track_a, tag=tag).frequency
except Tagged.DoesNotExist:
continue
try:
freq_tag_of_track_b = Tagged.objects.get(track=track_b, tag=tag).frequency
except Tagged.DoesNotExist:
continue
part1 += freq_tag_of_track_a * freq_tag_of_track_b
part2 += freq_tag_of_track_a ** 2
part3 += freq_tag_of_track_b ** 2
try:
it_sim = part1 / (math.sqrt(part2) * math.sqrt(part3))
except ZeroDivisionError:
it_sim = None
编辑2:在count_it_sim上,我不遍历Tag.objects.all()
中的所有标签,而是只查询Tagged
中存在的那些标签,并且结果更快比以前的代码,这是我当前的代码
def count_it_sim(track_a: Track, track_b: Track):
filtered_tagged = Tagged.objects.filter(Q(track=track_a) | Q(track=track_b))
tag_ids = filtered_tagged.values_list('tag', flat=True).distinct()
part1 = 0
part2 = 0
part3 = 0
for tag_id in tag_ids:
try:
freq_tag_of_track_a = filtered_tagged.get(track=track_a, tag__id=tag_id).frequency
except Tagged.DoesNotExist:
freq_tag_of_track_a = 0
try:
freq_tag_of_track_b = filtered_tagged.get(track=track_b, tag__id=tag_id).frequency
except Tagged.DoesNotExist:
freq_tag_of_track_b = 0
part1 += freq_tag_of_track_a * freq_tag_of_track_b
part2 += freq_tag_of_track_a ** 2
part3 += freq_tag_of_track_b ** 2
try:
it_sim = part1 / (math.sqrt(part2) * math.sqrt(part3))
except ZeroDivisionError:
it_sim = None
编辑3:模型中有一些更改。现在不用计算frequency
中每个tag
的{{1}},而是通过计算有多少track
标记了{{1} }和特定的frequency
。这是更新
users
和count_it_sim函数变为
track
答案 0 :(得分:3)
我认为我获取对象的方式不是很有效,有什么方法可以优化它吗?
好吧,您已经有了对象(在track_set
中),因此您无需再次获取它们;您只需要获取对象对即可。
我需要的是(1,2)(1,3)(1,4)(2,3)(2,4)(3,4)
为此,您可以使用itertools.combinations
:
import itertools
for a,b in itertools.combinations(track_set, 2):
count_it_sim(a, b)
您将必须确保以正确的顺序从数据库中获取对象;因为不能保证如何退回物品:
如果查询未指定顺序,则返回结果 从数据库以未指定的顺序。特定的顺序是 仅在通过一组唯一的字段进行订购时才能保证 识别结果中的每个对象。
在您的情况下,似乎您需要按主键顺序使用它们;所以我将初始查询修改为:
track_set = Track.objects.order_by('pk')
queryset documentation包含有关order_by
的详细信息,而model reference包含有关指定默认顺序的详细信息。
答案 1 :(得分:1)
根据我们在评论中的讨论,目的是使用最少数量的数据库查询来获取所需的数据,并且该结构应易于允许进一步的数据处理。
我们将尝试在以下字典结构中获取与每个Tagged.frequency
对应的所有Track
数据:
{
track_id1: list(all_related_frequencies),
track_id2: list(all_related_frequencies),
}
这个想法是在名为Track
的属性中获取所有Tagged
实例及其prefetch的相关prefetched_tagged
实例。
Track
的每个实例都有一个属性tagged_set
,该属性允许reverse access到与其相关的所有Tagged
实例。这是预取它们的查询:
from django.db.models import Prefetch
the_data = (
Track.objects.prefetch_related(
Prefetch(
'tagged_set',
to_attr='prefetched_tagged',
)
).all()
)
现在,我们将the_data
的所有实例保存在Track
变量中,其中每个实例都有一个属性prefetched_tagged
,其中包含所有list
实例的Tagged
与每个Track
实例相关。
通过以下字典理解,我们迭代the_data
变量以创建一个以所有Track.track_id
为键的字典。每个键都有一个list
,因为它的值将包含所有与其相关的Tagged.frequency
。
要创建列表,我们将在字典理解中使用列表理解:
result = {
each_track.track_id: [
each_tagged.frequency for each_tagged in each_track.prefetched_tagged
] for each_track in the_data
}
现在,变量result
在我们需要的结构中包含数据,以便进一步操作它们。为了将所有数据库数据加载到内存中,需要2次数据库命中。这是整个代码,也可以衡量数据库的点击量:
from django import db
from django.db.models import Prefetch
db.reset_queries()
the_data = (
Track.objects.prefetch_related(
Prefetch(
'tagged_set',
to_attr='prefetched_tagged',
)
).all()
)
result = {
each_track.track_id: [
each_tagged.frequency for each_tagged in each_track.prefetched_tagged
] for each_track in the_data
}
print(result)
print ("Queries Used: {0}".format(len(db.connection.queries))