Press "Enter" to skip to content

使用遗传算法在Python中优化电视节目调度

使用遗传算法在Python中优化电视节目安排的实践教程

Photo by Glenn Carstens-Peters on Unsplash

我已经很久没有在VoAGI上发布新文章了。在过去的两年里,我一直在研究如何通过机器学习和深度学习在传统媒体行业中进行改进。其中之一就是优化技术。在每个行业中,优化都是必不可少的,媒体行业也不例外。因此,在本文中,我想通过将电视节目规划与进化算法(遗传算法)相结合,来分享一下电视节目规划的方法。请记住,这只是一个简单的实现。现实生活远远超出了这种简单性。

什么是优化,为什么我们需要它?

我想从这个问题开始。优化是寻找最小化或最大化给定目标函数的值。那目标函数呢?目标函数是我们试图最大化或最小化的性能度量的数学表示。如果问题是一个最小化问题,我们可以称之为成本函数;如果问题是一个最大化问题,我们可以称之为适应度函数。

让我们用一个例子来丰富这个解释。停下来想象一下,你拥有一家餐馆。我们的目标是通过修改菜单(我指的是菜单上的菜肴)来最大化其利润。首先想到的方法是使用更便宜的原料。通过降低使用的原料质量,你可以获得更多的利润。但在现实生活中,并不是这样的。当你降低产品质量时,顾客对使用低质量原料制作的菜肴的需求也会减少。因此,无法达到预期的目标。

你可以想象,对于餐馆老板来说,创建一个能够最大化利润的菜单是一个优化问题。例如,餐馆老板可以分析哪些菜在什么时间卖出,然后制定一个路线图。优化技术将使餐馆老板能够基于数据做出决策,并实现最佳结果。

现在想象一下,你是一家电视台的节目策划人。记住,你的竞争对手很强大,但你仍然有一些可以竞争的节目。需要决策的主要问题是在什么时间播放哪个节目。看起来很简单,只需要一支笔和一些纸。真的是这样吗?

尽管电视节目策划似乎不复杂,但涉及到各种因素后变得非常复杂。以下是其中的一些因素:

观众喜好:观众喜欢什么样的电视内容类型?

时间段:观众在什么时期喜欢什么样的节目?

前导和后导节目:有些节目会将在播出期间吸引到的观众转移到下一个节目。

竞争对手频道的节目偏好:竞争对手频道在什么时间播放什么节目?

假期、特殊场合和季节性趋势:观众的喜好如何变化?是否存在一些现有的趋势?

新鲜和旧内容:播出的节目是否是新的?还是重播?

故事情节和悬念:节目是否有故事情节?或者悬念?

这只是一些因素。你可能已经注意到,数十个因素会影响电视节目策划。因此,优化算法可以解决这样的问题。

评估和遗传算法是什么?

我将在这部分简要解释评估和遗传算法。进化算法(EAs)是一种优化技术,可以解决许多具有挑战性的优化问题,而不需要对问题的结构具有特定的知识;换句话说,它们是问题无关的。进化算法(EAs)可以处理线性和非线性目标函数,而不需要有关问题结构的信息。另一方面,遗传算法属于搜索算法家族,并使用进化的原理。通过实施繁殖和自然选择过程,它可以产生高质量的解。遗传算法是解决优化问题的一种非常有效的技术。

您可以在下面看到一个简单的遗传算法流程图。我们的第一步是创建初始种群。初始种群包含随机选择的染色体(更明确地说,初始种群是一组染色体)。种群创建完成后,为每个个体计算适应度函数值。遗传算法使用染色体来表示每个个体。每个个体的适应度值是独立的,这样可以同时进行多次计算。在计算出适应度值后,遗传算法的三个不同阶段开始发挥作用——选择、交叉和变异。选择阶段负责从种群中选择染色体,目的是创建更好的后代。交叉过程负责从选定的个体中生成新的后代。通常情况下,通过每次选取两个选定的个体,并交换它们染色体的部分来创建代表后代的两个新染色体。最后,变异阶段改变一个或多个基因。这种变化的概率非常低。变异阶段最重要的特点是防止系统陷入局部最小值。

遗传算法流程图(Eser Saygın)

实现

我刚刚给出了关于遗传算法的简单信息。现在我将使用Python逐步解释遗传算法。如标题所示,我们的问题是在何时播放哪个节目。首先,我要强调一个重要的观点。我们即将实现的问题是一个简单例子。正如我提到的,许多因素影响着在现实生活中实现问题。因此,问题识别阶段是最耗时的部分。

步骤

首先,我们从定义我们的数据集开始。如我之前所述,下面的集合是一个简单的例子。数据集显示了在18个小时(06:00-24:00)内各个节目的收视率。在现实生活中,需要在每个时间段播出以测量不同时间段内节目的收视得分。

# 每个时间段样本节目评分数据集。ratings = {    '新闻': [0.1, 0.1, 0.4, 0.3, 0.5, 0.4, 0.3, 0.2, 0.1, 0.2, 0.3, 0.5, 0.5, 0.4, 0.3, 0.2, 0.1, 0.2],    '实况足球': [0.0, 0.0, 0.0, 0.2, 0.1, 0.3, 0.2, 0.1, 0.4, 0.3, 0.4, 0.5, 0.4, 0.6, 0.4, 0.3, 0.4, 0.3],    '电影A': [0.1, 0.1, 0.2, 0.4, 0.3, 0.2, 0.1, 0.2, 0.3, 0.4, 0.5, 0.4, 0.3, 0.4, 0.3, 0.5, 0.3, 0.4],    '电影B': [0.2, 0.1, 0.1, 0.3, 0.2, 0.1, 0.2, 0.3, 0.4, 0.5, 0.4, 0.3, 0.4, 0.5, 0.4, 0.3, 0.4, 0.5],    '真人秀': [0.3, 0.4, 0.3, 0.4, 0.4, 0.5, 0.3, 0.4, 0.5, 0.4, 0.3, 0.2, 0.1, 0.2, 0.3, 0.2, 0.2, 0.3],    '电视剧A': [0.2, 0.3, 0.2, 0.1, 0.1, 0.2, 0.2, 0.4, 0.4, 0.3, 0.3, 0.3, 0.5, 0.6, 0.4, 0.5, 0.4, 0.3],    '电视剧B': [0.1, 0.2, 0.3, 0.3, 0.2, 0.3, 0.3, 0.1, 0.4, 0.3, 0.4, 0.3, 0.5, 0.3, 0.4, 0.6, 0.4, 0.3],    '音乐节目': [0.3, 0.3, 0.3, 0.2, 0.2, 0.1, 0.2, 0.4, 0.3, 0.3, 0.3, 0.3, 0.2, 0.3, 0.2, 0.3, 0.5, 0.3],    '纪录片': [0.3, 0.3, 0.4, 0.3, 0.2, 0.2, 0.3, 0.4, 0.4, 0.3, 0.2, 0.2, 0.2, 0.1, 0.1, 0.3, 0.3, 0.2],    '拳击': [0.4, 0.3, 0.3, 0.2, 0.2, 0.1, 0.1, 0.1, 0.1, 0.3, 0.3, 0.3, 0.2, 0.3, 0.4, 0.3, 0.4, 0.6]}

下面,您可以看到其他变量。这些变量是遗传算法中要使用的超参数。我还创建了两个不同的列表以供后续使用。

GEN = 100POP = 50CO_R = 0.8MUT_R = 0.2EL_S = 2all_programs = list(ratings.keys()) # 所有节目all_time_slots = list(range(6, 24)) # 时间段

正如我们在文章中提到的,我们的第一项工作是初始化种群。您可以在下面找到我为此目的创建的函数。正如您所看到的,函数需要两个输入列表:一个节目列表和一个时间段列表。我们已经在上面定义了这些列表。该函数生成所有可能的日程安排。

def initialize_pop(programs, time_slots):    if not programs:        return [[]]    all_schedules = []    for i in range(len(programs)):        for schedule in initialize_pop(programs[:i] + programs[i + 1:], time_slots):            all_schedules.append([programs[i]] + schedule)    return all_schedules

接下来,我们将定义我们的适应度函数。适应度函数负责衡量每个日程安排的质量。它以日程安排作为输入,并返回总评分。 (我们称之为日程安排的列表是由电视节目组成的广播日程安排。)

def fitness_function(schedule):    total_rating = 0    for time_slot, program in enumerate(schedule):        total_rating += ratings[program][time_slot]    return total_rating

在定义适应度函数之后,我们可以进入选择阶段。选择阶段旨在找到最优日程安排。为此,我们可以使用下面创建的函数。该函数检查每个日程安排的适应度值,并选择具有最高值的日程安排。

def finding_best_schedule(all_schedules):    best_schedule = []    max_ratings = 0    for schedule in all_schedules:        total_ratings = fitness_function(schedule)        if total_ratings > max_ratings:            max_ratings = total_ratings            best_schedule = schedule    return best_schedule

选择阶段之后是交叉阶段。在交叉阶段,通过遗传算法,将两个父代解合并以形成新的后代。在电视节目安排问题中,此过程改变了两个解中找到的节目(基因)。此过程创建了各种电视节目的组合。您可以在下面看到交叉函数。

def crossover(schedule1, schedule2):    crossover_point = random.randint(1, len(schedule1) - 2)    child1 = schedule1[:crossover_point] + schedule2[crossover_point:]    child2 = schedule2[:crossover_point] + schedule1[crossover_point:]    return child1, child2

最后一个阶段是突变阶段。如前所述,在突变阶段,通过改变染色体的遗传物质形成新的后代。在电视节目优化问题中,我们可以将其视为随机更改节目。请记住,突变的概率非常低。此外,您可以将此概率分配为超参数。

def mutate(schedule):    mutation_point = random.randint(0, len(schedule) - 1)    new_program = random.choice(all_programs)    schedule[mutation_point] = new_program    return schedule

现在我们已经定义了所有的函数,我们可以运行适应度函数。

# 调用适应度函数def evaluate_fitness(schedule):    return fitness_function(schedule)

我们遗传算法所需的数据已经准备好了。现在我们可以定义算法。该算法将使用initial_schedule、generations、population_size、crossover_rate、mutation_rate和elitism_size。我们之前已经对这些进行了描述。由于它们是超参数,我们可以修改它们,但不需要。该函数首先使用提供的初始日程创建初始种群,然后添加随机日程。然后,它运行指定数量的代数的循环,并使用选择、交叉和突变操作为每一代生成新的种群。精英主义有助于基于其适应度分数保留上一代中最成功的个体。一旦种群更新,它将成为下一代的当前种群。然后,函数返回上一代中的最佳日程安排。

def genetic_algorithm(initial_schedule, generations=GEN, population_size=POP, crossover_rate=CO_R, mutation_rate=MUT_R, elitism_size=EL_S):        population = [initial_schedule]    for _ in range(population_size - 1):        random_schedule = initial_schedule.copy()        random.shuffle(random_schedule)        population.append(random_schedule)    for generation in range(generations):        new_population = []        # 精英主义        population.sort(key=lambda schedule: fitness_function(schedule), reverse=True)        new_population.extend(population[:elitism_size])        while len(new_population) < population_size:            parent1, parent2 = random.choices(population, k=2)            if random.random() < crossover_rate:                child1, child2 = crossover(parent1, parent2)            else:                child1, child2 = parent1.copy(), parent2.copy()            if random.random() < mutation_rate:                child1 = mutate(child1)            if random.random() < mutation_rate:                child2 = mutate(child2)            new_population.extend([child1, child2])        population = new_population    return population[0]

现在我们准备好获取结果了。

initial_best_schedule = finding_best_schedule(all_possible_schedules)rem_t_slots = len(all_time_slots) - len(initial_best_schedule)genetic_schedule = genetic_algorithm(initial_best_schedule, generations=GEN, population_size=POP, elitism_size=EL_S)final_schedule = initial_best_schedule + genetic_schedule[:rem_t_slots]print("\n最终最佳日程:")for time_slot, program in enumerate(final_schedule):    print(f"时间段 {all_time_slots[time_slot]:02d}:00 - 节目 {program}")print("总评分:", fitness_function(final_schedule))

在遗传算法运行后,我们将初始最佳日程和遗传日程结合起来创建最终最佳日程。最后,我们打印最佳日程及其分配的节目,显示时间段、相应的节目以及在最终最佳日程中达到的总评分。

使用遗传算法在Python中优化电视节目调度 四海 第3张

结论

节目规划对于传统媒体领域的电视频道来说至关重要,竞争激烈。在本案例中,我们展示了如何利用遗传算法来改进电视节目安排,这是一个可以帮助最大化观众评分的强大工具。考虑使用遗传算法来优化节目安排等调度问题。凭借其强大的能力,它可以帮助您创建一个最大程度上提高观众参与度和评分的日程安排。

在我即将发布的文章中,我计划探索各种遗传算法,如竞争性共进化算法(CCQGA)和量子算法(QGA)。我还可能在其中包含其他内容。

感谢您花时间阅读本文。如果您想与我联系,请随时在 LinkedIn 上添加我。

https://www.linkedin.com/in/esersaygin/

参考资料

《使用Python进行实践遗传算法:将遗传算法应用于解决现实世界的深度学习和人工智能问题》by Eyal Wirsansky (作者)

《应用于工程师的进化算法:使用Python 1st Edition》by Leonardo Azevedo Scardua (作者)

完整代码

import random##################################### 定义参数和数据集 ############################################################## 每个时间段的节目评分数据集ratings = {    'news': [0.1, 0.1, 0.4, 0.3, 0.5, 0.4, 0.3, 0.2, 0.1, 0.2, 0.3, 0.5, 0.5, 0.4, 0.3, 0.2, 0.1, 0.2],    'live_soccer': [0.0, 0.0, 0.0, 0.2, 0.1, 0.3, 0.2, 0.1, 0.4, 0.3, 0.4, 0.5, 0.4, 0.6, 0.4, 0.3, 0.4, 0.3],    'movie_a': [0.1, 0.1, 0.2, 0.4, 0.3, 0.2, 0.1, 0.2, 0.3, 0.4, 0.5, 0.4, 0.3, 0.4, 0.3, 0.5, 0.3, 0.4],    'movie_b': [0.2, 0.1, 0.1, 0.3, 0.2, 0.1, 0.2, 0.3, 0.4, 0.5, 0.4, 0.3, 0.4, 0.5, 0.4, 0.3, 0.4, 0.5],    'reality_show': [0.3, 0.4, 0.3, 0.4, 0.4, 0.5, 0.3, 0.4, 0.5, 0.4, 0.3, 0.2, 0.1, 0.2, 0.3, 0.2, 0.2, 0.3],    'tv_series_a': [0.2, 0.3, 0.2, 0.1, 0.1, 0.2, 0.2, 0.4, 0.4, 0.3, 0.3, 0.3, 0.5, 0.6, 0.4, 0.5, 0.4, 0.3],    'tv_series_b': [0.1, 0.2, 0.3, 0.3, 0.2, 0.3, 0.3, 0.1, 0.4, 0.3, 0.4, 0.3, 0.5, 0.3, 0.4, 0.6, 0.4, 0.3],    'music_program': [0.3, 0.3, 0.3, 0.2, 0.2, 0.1, 0.2, 0.4, 0.3, 0.3, 0.3, 0.3, 0.2, 0.3, 0.2, 0.3, 0.5, 0.3],    'documentary': [0.3, 0.3, 0.4, 0.3, 0.2, 0.2, 0.3, 0.4, 0.4, 0.3, 0.2, 0.2, 0.2, 0.1, 0.1, 0.3, 0.3, 0.2],    'Boxing': [0.4, 0.3, 0.3, 0.2, 0.2, 0.1, 0.1, 0.1, 0.1, 0.3, 0.3, 0.3, 0.2, 0.3, 0.4, 0.3, 0.4, 0.6]}GEN = 100POP = 50CO_R = 0.8MUT_R = 0.2EL_S

Leave a Reply

Your email address will not be published. Required fields are marked *