在上一篇文章中,我讨论了针对类似应用程序的搜索引擎 。 推出后,我们开始收到第一批评论。 分析师喜欢并推荐了一些建议,有些则没有。
为了继续前进并找到更好的模型,有必要首先评估当前模型的性能。 还必须选择可以将两个模型相互比较的标准。
根据削减,我将谈论:
- 收集有关建议的反馈
- 制定评估建议质量的指标
- 建立模型优化周期
- 获得见识和新模式
意见收集
收集分析人员的明确反馈将是理想的:每个建议事件的建议的相关性如何。 这将使我们了解当前的情况,并继续基于定量指标来改进系统。
决定以一种非常简单的格式收集评论:
- 我们正在分析的事件数
- 推荐事件编号
- 推荐评论:好/不好
“投票”(一个接受带有参数的GET请求并将信息存储在文件中的小项目)直接放置在建议区域中,以便分析人员只需单击以下一个链接即可立即留下反馈:“好”或“坏”。
此外,为了对建议进行回顾性审查,提出了一个非常简单的解决方案:
- 针对大量历史数据,启动了一个模型;
- 收集的建议以几个独立的HTML文件的形式呈现,其中使用了相同的“投票”。
- 准备好的文件已分发给分析人员,以查看50-100次事件的结果。
因此,有可能收集大约4000多对事件建议的数据。
初步审查分析
最初的指标是“一般”的-同事们认为,“好的”建议的份额仅为25%。
第一个模型的主要问题:
- 有关“新”问题的事件没有得到系统的建议; 事实证明,在上诉内容没有一致的情况下,系统选择了与联系员工所在部门附近的事件。
- 对一个系统上的事件的建议会影响其他系统上的事件。 上诉中使用的措词相似,但描述了其他系统的问题并且有所不同。
选择了提高建议质量的可能方法:
- 调整最终向量中包含的治疗属性的组成和权重
- 矢量化设置的选择
TfidfVectorizer
- 选择建议的“截止”距离
制定质量标准和评估方法
为了搜索模型的改进版本,有必要确定评估模型结果质量的原理。 这将使您可以定量比较两个模型并选择最佳模型。
可以从收集的评论中获得什么
我们有许多形式的元组:“事件”,“推荐事件”,“推荐评估”。
- “推荐等级”( v )-设置为二进制:“良好” | 较差(1 / -1);
- “事件”和“推荐事件”只是事件编号。 在它们上,您可以在数据库中找到事件。
有了这些数据,您可以计算:
n_inc_total
有建议的事件总数n_inc_good
有“良好”建议的事件数avg_inc_good
针对事件的“良好”建议的平均数量n_rec_total
建议总数n_rec_good
“良好”建议的总数pct_inc_good
有“良好”建议的事件份额
pct_inc_good = n_inc_good / n_inc_total
pct_rec_good
“良好”建议的总份额
pct_rec_good = n_rec_good / n_rec_total
这些指标是根据用户的估算得出的,可以视为原始模型的“基本指标”。 我们将与之比较该模型新版本的类似指标。
从m中获取所有唯一的“事件”,并驱动它们通过新模型。
结果,我们得到了m *个元组:“事件”,“推荐事件”,“距离”。
此处,“距离”是NearestNeighbor中定义的度量。 在我们的模型中,这是余弦距离。 值“ 0”对应于向量的完全重合。
选择“截止距离”
用来自m的初始估计值的v的真实估计值补充建议m *的信息集,我们得到了该模型的距离d和v的实际估计值之间的对应关系。
通过设置( d , v ),可以选择最佳截止水平t ,对于d <= t,推荐将为“好”,而对于d> t-“不好”。 t的选择可以通过在最参数t上优化v = -1 if d>t else 1
优化最简单的二进制分类器v = -1 if d>t else 1
并使用例如AUC ROC作为度量来完成。
获得的t值可用于过滤建议。
当然,这种方法仍然可以跳过“不好的”建议,而切断“好的”建议。 因此,在此阶段,我们始终显示“前5名”建议,但是考虑到找到的t ,我们特别标记那些被认为是“好”的建议。
备选:如果找到至少一个“好”推荐,则仅显示“好”。 否则,显示所有可用的(也-“前N个”)。
假设比较模型
对于训练模型,使用相同的事件案例。
假设如果先前找到了“良好”建议,那么新模型还应该为同一事件找到“良好”建议。 特别是,新模型可能会找到与旧模型相同的“良好”建议。 但是,对于新模型,我们预计“不良”建议的数量将会减少。
然后,考虑新模型的建议m *的相同指标,可以将它们与m的相应指标进行比较。 根据比较,您可以选择最佳模型。
有两种方法可以考虑对m *集的“良好”建议:
- 基于找到的t :认为m *中d < t的所有建议都是“好的”,并在计算度量时将它们考虑在内
- 根据来自集合m的相应真实估计:从建议m *中,仅选择在m中具有真实估计的那些,然后舍弃其余的。
在第一种情况下,新模型的“绝对”指示符( n_inc_good
, n_rec_good
)应大于基本模型的指示符。 在第二种情况下,指标应接近基本模型的指标。
第二种方法的问题:如果新模型优于原始模型,并且发现以前未知的内容,则在计算中将不考虑这样的建议。
选择模型比较选项
选择新模型时,我希望指标与现有模型相比有所改善:
- 每个事件的平均“良好”建议数量(
avg_inc_good
) - 有“良好”建议的事件数(
n_inc_good
)。
为了与原始模型进行比较,我们将使用新模型和原始模型的这些参数之间的关系。 因此,如果新模型和旧模型的参数之比大于1,则新模型更好。
benchmark_agv_inc_good = avg_inc_good* / avg_inc_good benchmark_n_inc_good = n_inc_good* / n_inc_good
为了简化选择,最好使用单个参数。 我们采用各个相对指标的谐波均值,并将其用作新模型的唯一综合质量标准。
composite = 2 / ( 1/benchmark_agv_inc_good + 1/benchmark_n_inc_good)
新模型及其优化
对于新模型,在表示事件的最终向量中,添加负责“事件区域”的组件(由我们的团队提供服务的多个系统之一)。
有关创建事件的员工的单位和位置的信息也放置在单独的向量组件中。 所有分量在最终向量中都有权重。
p = Pipeline( steps=[ ('grp', ColumnTransformer( transformers=[ ('text', Pipeline(steps=[ ('pp', CommentsTextTransformer(n_jobs=-1)), ("tfidf", TfidfVectorizer(stop_words=get_stop_words(), ngram_range=(1, 3), max_features=10000, min_df=0)) ]), ['short_description', 'comments'] ), ('area', OneHotEncoder(handle_unknown='ignore'), ['area'] ), ('dept', OneHotEncoder(handle_unknown='ignore'), ['u_impacted_department'] ), ('loc', OneHotEncoder(handle_unknown='ignore'), ['u_impacted_location'] ) ], transformer_weights={'text': 1, 'area': 0.5, 'dept': 0.1, 'loc': 0.1}, n_jobs=-1 )), ('norm', Normalizer()), ("nn", NearestNeighborsTransformer(n_neighbors=10, metric='cosine')) ], memory=None)
模型超参数有望影响模型目标。 在选定的模型架构中,我们将考虑超参数:
- TF-IDF矢量化参数-使用的n-gram(ngram_range),字典大小(max_features),最小术语驱动(min_df)
- 分量对最终矢量的贡献-变压器权重。
文本向量化超参数的初始值取自先前的模型。 初始分量权重是根据专家判断来选择的。
参数选择周期
已经确定了如何比较,选择失火等级以及相互比较模型。 现在我们可以通过选择超参数来进行优化。

param_grid = { 'grp__text__tfidf__ngram_range': [(1, 1), (1, 2), (1, 3), (2, 2)], 'grp__text__tfidf__max_features': [5000, 10000, 20000], 'grp__text__tfidf__min_df': [0, 0.0001, 0.0005, 0.001], 'grp__transformer_weights': [{'text': 1, 'area': 0.5, 'dept': 0.1, 'loc': 0.1}, {'text': 1, 'area': 0.75, 'dept': 0.1, 'loc': 0.1}, {'text': 1, 'area': 0.5, 'dept': 0.3, 'loc': 0.3}, {'text': 1, 'area': 0.75, 'dept': 0.3, 'loc': 0.3}, {'text': 1, 'area': 1, 'dept': 0.1, 'loc': 0.1}, {'text': 1, 'area': 1, 'dept': 0.3, 'loc': 0.3}, {'text': 1, 'area': 1, 'dept': 0.5, 'loc': 0.5}], } for param in ParameterGrid(param_grid=param_grid): p.set_params(**param) p.fit(x) ...
优化结果
下表显示了获得有趣结果的实验结果-受控指标的前5个最佳值和最差值。

表格中带有指示器的单元格标记为:
- 深绿色是所有实验中最好的指示
- 浅绿色-指标值在前5名
- 深红色-所有实验中最差的指标
- 浅红色-指示器处于最坏状态5
对于具有以下参数的模型,可获得最佳综合指标:
ngram_range = (1,2) min_df = 0.0001 max_features = 20000 transformer_weights = {'text': 1, 'area': 1, 'dept': 0.1, 'loc': 0.1}
与原始模型相比,具有这些参数的模型显示的综合指标有所改善24%
一些观察和结论
根据优化结果:
使用ngram_range = (1,3)
组( ngram_range = (1,3)
)似乎不合理。 与双字词相比,它们会使字典膨胀,并稍微提高准确性。
仅使用双ngram_range = (2,2)
( ngram_range = (2,2)
)构建字典时的一个有趣行为:建议的“准确性”增加,而发现的建议数量减少。 就像分类器中的精确度/召回率平衡一样。 在选择截断水平t时观察到类似的行为-对于二元组,截断的“圆锥”较窄,并且“好”和“坏”建议的更好分离是特征。
非零参数min_df以及双字组提高了建议的准确性。 它们开始基于至少出现几次的术语。 随着参数的增加,字典开始迅速收缩。 对于小样本(如我们的情况),使用文档数量(整数值min_df)进行操作比包含该术语的文档分数(分数值min_df)更容易理解。
当最终向量中包含负责“区域”的事件属性的权重等于或接近文本分量时,将获得良好的结果。 较低的值会导致“不良”建议的比例增加,原因是在其他领域的文档中发现了类似的词语。 但是在我们的案例中,客户所在地的标志并不会很好地影响建议的结果。
一些新的想法出现了:
- 添加“时间”组件,以便最近的事件优先于类似的事件。
- 看看max_df参数的引入将如何影响-尽管对于tf-idf而言,根据定义,语料库的通用词不应具有显着权重。
- 最后,尝试使用其他方式对内容进行矢量化处理,例如,基于单词到矢量,或基于使用网络的tf-idf视图卷积。