以Talking Hat为例的从研究到实施的数据科学项目


一个月前,伦塔(Lenta)发起了一场竞赛 ,其中来自哈利·波特(Harry Potter)的同一个Talking Hat识别出可以访问四个学院之一的社交网络的参与者。 比赛进行得很顺利,听起来各不相同的名称由不同的系所决定,并且相似的英语和俄语名称和姓氏也以类似的方式分配。 我不知道分布是否仅取决于姓名和姓氏,是否以某种方式考虑了朋友的数量或其他因素,但是这场竞赛提出了本文的想法:尝试从头开始训练分类器,这将使用户可以分配到不同的系。


在本文中,我们将创建一个简单的ML模型,该模型将根据CRISP方法进行的小型研究过程,根据人员的姓名和姓氏将其分配给Harry Potter的部门。 即我们:


  • 我们提出问题;
  • 我们研究可能的解决方案,并制定数据要求(解决方法和数据);
  • 我们将收集必要的数据(解决方法和数据);
  • 我们将研究收集的数据集(探索性研究);
  • 从原始数据中提取特征(特征工程);
  • 让我们教一个机器学习模型(模型评估);
  • 比较获得的结果,评估解决方案的质量,并在必要时重复第2-6段;
  • 我们将解决方案打包到可以使用的服务中(生产)。


这项任务看似微不足道,因此我们将对整个过程(少于2小时)和本文(因此其阅读时间少于15分钟)施加额外的限制。


如果您已经沉迷于美丽而美妙的数据科学世界,而您又看不到Kagglite,或者(上帝禁止)喜欢在与同事会面时测量Hadup的长度,那么这篇文章对您来说似乎很简单且无趣。 而且:最终模型的质量不是本文的主要价值。 我们已警告您。 走吧


好奇的读者也可以使用包含本文中使用的代码的github存储库 。 如有错误,请打开PR。


我们制定问题


可以解决无限长的时间里没有明确的决策标准的问题,因此我们将立即决定要得到一个解决方案,该解决方案使我们能够根据输入的行来获得答案“格兰芬多”,“拉文克劳”,“赫奇帕奇”或“斯莱特林”。


实际上,我们想得到一个黑匣子:


" " => [?] => Griffindor 

最初的黑帽子根据其性质和个人素质向各部门分配了年轻的巫师。 由于我们无法获得根据任务条件获得的性格和性格数据,因此我们将使用参与者的姓名和姓氏,请记住,在这种情况下,我们应在与书中本国部门相对应的部门之间分配书中的字符。 如果我们的决定将哈利分配给赫奇帕奇或拉文克劳,陶艺师肯定会不高兴的(但是这应该使哈利以相同的可能性将哈利分配给格兰芬多和斯莱特林来传达这本书的精神)。


由于我们在谈论概率,因此我们用更严格的数学术语来形式化问题。 从数据科学的角度来看,我们解决了分类问题,即为对象(线,以名称和姓氏的形式)分配某个类(实际上,它只是一个标签或标签,可以是数字或具有yes / no值的4个变量) ) 我们知道,至少对于哈利来说,给出两个答案是正确的:格兰芬多和斯莱特林,因此最好不要预测帽子所定义的具体能力,而要预测一个人被分配给该能力的可能性,因此我们的决定将在某种功能


f<><>=Pgriffindor;Pravenclaw;Phufflpuff;Pslitherin


指标和质量评估


制定了任务和目标, 现在我们来思考如何解决 但这还不是全部。 为了开始研究,您需要输入质量指标。 换句话说,要确定我们如何将两种不同的解决方案相互比较。


生活中的一切都是美好而简单的-我们从直觉上理解,垃圾邮件检测器必须将最少的垃圾邮件传递给传入的邮件,并传递最多的必要信件,并且它当然不应将必要的信件发送给垃圾邮件。


实际上,一切都更加复杂, 对此的确认 大量 文章 ,这些文章解释了如何使用度量以及使用哪些度量。 练习有助于最好地理解这一点,但这是一个非常大的话题,我们承诺为此写一篇单独的文章并做一个开放的表,以便每个人都可以玩转并在实践中了解这有何不同。


对我们来说,“但让我们选择最好的”家庭将是ROC AUC 。 在这种情况下,这正是我们希望从度量​​标准中获得的结果:假阳性越少,实际预测越准确,ROC AUC越大。


对于理想的ROC模型,AUC为1,对于理想的随机模型,它绝对随机地定义类-0.5。


ROC AUC in[0.5;1]


演算法


我们的黑匣子应该考虑书籍英雄的分布,输入不同的名字和姓氏,然后给出结果。 要解决分类问题,可以使用不同的机器学习算法:


神经网络,分解机,线性回归或(例如)SVM。


与流行的看法相反,数据科学不仅限于神经网络,而且为了普及该思想,本文将神经网络留给好奇的读者练习 。 那些没有参加过数据分析课程(特别是主观上更好的课程)的人,或者只是阅读了有关机器学习或AI的新闻,而这些新闻现在甚至在业余渔民杂志上也发表过,这些人必须符合通用算法的名称:套袋,加强,支持向量法(SVM),线性回归。 我们将使用它们来解决问题。


更准确地说,我们比较:


  • 线性回归
  • 增强(XGboost,LightGBM)
  • 决定树木(严格来说,这是相同的提振,但我们将其单独取出:多余的树木)
  • 套袋(随机森林)
  • 支持向量机

我们可以通过定义与霍格沃茨大学学生相对应的大学系来解决将每个霍格沃茨系学生分配给其中一个系的问题,但是严格来说,此任务归结为解决确定每个班级是否属于个人的问题。 因此,在本文的框架中,我们为自己设定了获得4个模型的目标,每个模型一个。


资料


寻找正确的数据集进行培训,更重要的是,将其用于正确的目的是合法的,这是数据科学中最复杂,最耗时的任务之一。 对于我们的任务,我们将从哈利·波特世界各地的Wikia中获取数据。 例如,在此链接上,您可以找到在格兰芬多学院学习的所有角色。 在这种情况下,我们将数据用于非商业目的非常重要,因此我们不会违反本网站的许可



对于那些认为数据科学家真是太酷了的人,我将去数据科学家那里任教,我们提醒您,存在清理和准备数据的步骤。 必须手动审核下载的数据,以删除例如“格兰芬多第七郡”和半自动删除“来自格兰芬多的未知女孩”。 在实际工作中,任务的很大一部分总是与数据集中缺失值的准备,清理和恢复相关联。


稍微按Ctrl + C和Ctrl + V,然后在输出中得到4个文本文件,其中包含2种语言的字符名称:英语和俄语。


我们研究收集的数据(EDA,探索性数据分析)


在此阶段,我们有4个文件,其中包含学院学生的姓名,我们将对其进行更详细的介绍:


 $ ls ../input griffindor.txt hufflpuff.txt ravenclaw.txt slitherin.txt 

每个文件每行包含1个学生的姓名和姓氏(如果有):


 $ wc -l ../input/*.txt 250 ../input/griffindor.txt 167 ../input/hufflpuff.txt 180 ../input/ravenclaw.txt 254 ../input/slitherin.txt 851 total 

收集的数据具有以下形式:


 $ cat ../input/griffindor.txt | head -3 && cat ../input/griffindor.txt | tail -3      Charlie Stainforth Melanie Stanmore Stewart 

我们的整个想法基于这样的假设:黑匣子(或黑帽)可以在名称和姓氏中有所区别。


该算法可以按原样填充行,但效果不佳,因为基本模型将无法独立理解“ Draco”与“ Harry”的区别,因此我们需要从我们的姓名中提取符号。


数据准备(特征工程)


符号 (或特征, 英文 特征 -属性)是对象的区别属性。 过去一年中,一个人换工作的次数,左手的手指数,发动机的发动机容量,汽车的行驶里程是否超过100,000公里。 各种各样的标志分类都是由很多人发明的,在这方面没有而且不可能是一个单一的系统,因此,我们将举例说明哪些标志可以是:


  1. 有理数
  2. 类别(最多12、12-18或18+)
  3. 二进制值(是否退还第一笔贷款)
  4. 日期,颜色,份额等

特征的搜索 (或形成)( 英语为 Feature Engineering )通常作为单独的研究阶段或数据分析专家的工作而脱颖而出。 实际上,常识,经验和假设检验有助于流程本身。 立即猜测正确的标志是一个全手,基础知识和运气相结合的问题。 有时其中包含萨满教,但是一般的方法很简单:您需要先想到,然后检查是否有可能通过添加新属性来改善解决方案。 例如,作为执行任务的标志,我们可以在名称中加上嘶嘶声。


在我们模型的第一个版本中(因为真正的数据科学研究-作为杰作,永远无法完成),我们将使用以下功能来命名和使用姓氏:


  1. 1和单词的最后一个字母-元音或辅音
  2. 双元音和辅音
  3. 元音,辅音,聋人,浊音的数量
  4. 名字长度,姓氏长度
  5. ...

为此,我们将以存储库为基础并添加一个类,以便将其用于拉丁字母。 这将使我们有机会确定每个字母的发音。


 >> from Phonetic import RussianLetter, EnglishLetter >> RussianLetter('').classify() {'consonant': True, 'deaf': False, 'hard': False, 'mark': False, 'paired': False, 'shock': False, 'soft': False, 'sonorus': True, 'vowel': False} >> EnglishLetter('d').classify() {'consonant': True, 'deaf': False, 'hard': True, 'mark': False, 'paired': False, 'shock': False, 'soft': False, 'sonorus': True, 'vowel': False} 

现在,我们可以定义用于计算统计信息的简单函数,例如:


 def starts_with_letter(word, letter_type='vowel'): """   ,    . :param word:  :param letter_type: 'vowel'  'consonant'.   . :return: Boolean """ if len(word) == 0: return False return Letter(word[0]).classify()[letter_type] def count_letter_type(word): """       . :param word:  :param debug:    :return: :obj:`dict` of :obj:`str` => :int:count """ count = { 'consonant': 0, 'deaf': 0, 'hard': 0, 'mark': 0, 'paired': 0, 'shock': 0, 'soft': 0, 'sonorus': 0, 'vowel': 0 } for letter in word: classes = Letter(letter).classify() for key in count.keys(): if classes[key]: count[key] += 1 return count 

使用这些功能,我们已经可以得到第一个迹象:


 from feature_engineering import * >> print("  («»): ", len(""))   («»): 5 >> print(" («»)   : ", starts_with_letter('', 'vowel'))  («»)   : False >> print(" («»)   : ", starts_with_letter('', 'consonant'))  («»)   : True >> count_Harry = count_letter_type("") >> print ("     («»): ", count_Harry['paired'])      («»): 1 

严格来说,借助这些功能,我们可以获得字符串的一些矢量表示,即,我们获得了映射:


f<><>=>lengthnamelengthsurnames... vowelssurnames


现在,我们可以以可以输入到机器学习算法的数据集的形式显示数据:


 >> from data_loaders import load_processed_data >> hogwarts_df = load_processed_data() >> hogwarts_df.head() 


此外,结果,我们为每个学生得到以下症状:


 >> hogwarts_df[hogwarts_df.columns].dtypes 

收到的迹象
 name object surname object is_english bool name_starts_with_vowel bool name_starts_with_consonant bool name_ends_with_vowel bool name_ends_with_consonant bool name_length int64 name_vowels_count int64 name_double_vowels_count int64 name_consonant_count int64 name_double_consonant_count int64 name_paired_count int64 name_deaf_count int64 name_sonorus_count int64 surname_starts_with_vowel bool surname_starts_with_consonant bool surname_ends_with_vowel bool surname_ends_with_consonant bool surname_length int64 surname_vowels_count int64 surname_double_vowels_count int64 surname_consonant_count int64 surname_double_consonant_count int64 surname_paired_count int64 surname_deaf_count int64 surname_sonorus_count int64 is_griffindor int64 is_hufflpuff int64 is_ravenclaw int64 is_slitherin int64 dtype: object 

最后4列是针对性的-它们包含有关学生入学的信息。


算法训练


简而言之,算法就像人一样被训练:他们犯错误并从中学习。 为了了解他们犯了多少错误,算法使用错误函数(损失函数, 英语 损失函数 )。


通常,学习过程非常简单,它包含几个步骤:


  1. 做个预测。
  2. 评价错误。
  3. 更正模型参数。
  4. 重复1-3,直到达到目标,过程停止或数据结束。
  5. 评价结果模型的质量。


    当然,实际上,一切都有些复杂。 例如,存在过度拟合的现象-该算法可以从字面上记住哪些特征与答案相对应,从而使与他所训练的物体不相似的物体的结果恶化。 为避免这种情况,存在各种技术和技巧。



如上所述,我们将解决4个问题:每个系一个。 因此,我们将为Slytherin准备数据:


 #  ,     - : >> data_full = hogwarts_df.drop( [ 'name', 'surname', 'is_griffindor', 'is_hufflpuff', 'is_ravenclaw' ], axis=1).copy() #    ,   : >> X_data = data_full.drop('is_slitherin', axis=1) #     ,   1    >> y = data_full.is_slitherin 

在学习过程中,算法会不断将其结果与真实数据进行比较,因为这部分数据集已分配用于验证。 还应考虑良好音调的规则,以对算法根本看不到的单个数据评估算法的结果。 因此,现在我们将样本按70/30的比例进行划分,并训练第一个算法:


 from sklearn.cross_validation import train_test_split from sklearn.ensemble import RandomForestClassifier #      >> seed = 7 #    >> test_size = 0.3 >> X_train, X_test, y_train, y_test = train_test_split(X_data, y, test_size=test_size, random_state=seed) >> rfc = RandomForestClassifier() >> rfc_model = rfc.fit(X_train, y_train) 

做完了 现在,如果将数据提交到此模型的输入,它将产生结果。 这很有趣,所以首先我们将检查哈利中的模型是否可以识别斯莱特林。 为此,首先准备函数以获得算法的预测:


查看代码
 from data_loaders import parse_line_to_hogwarts_df import pandas as pd def get_single_student_features (name): """      :param name: string     :return: pd.DataFrame     """ featurized_person_df = parse_line_to_hogwarts_df(name) person_df = pd.DataFrame(featurized_person_df, columns=[ 'name', 'surname', 'is_english', 'name_starts_with_vowel', 'name_starts_with_consonant', 'name_ends_with_vowel', 'name_ends_with_consonant', 'name_length', 'name_vowels_count', 'name_double_vowels_count', 'name_consonant_count', 'name_double_consonant_count', 'name_paired_count', 'name_deaf_count', 'name_sonorus_count', 'surname_starts_with_vowel', 'surname_starts_with_consonant', 'surname_ends_with_vowel', 'surname_ends_with_consonant', 'surname_length', 'surname_vowels_count', 'surname_double_vowels_count', 'surname_consonant_count', 'surname_double_consonant_count', 'surname_paired_count', 'surname_deaf_count', 'surname_sonorus_count', ], index=[0] ) featurized_person = person_df.drop( ['name', 'surname'], axis = 1 ) return featurized_person def get_predictions_vector (model, person): """    :param model:   :param person: string   :return: list    """ encoded_person = get_single_student_features(person) return model.predict_proba(encoded_person)[0] 

现在,我们设置一个小的测试数据集来考虑算法的结果。


 def score_testing_dataset (model): """      . :param model:   """ testing_dataset = [ " ", "Kirill Malev", " ", "Harry Potter", " ", " ","Severus Snape", " ", "Tom Riddle", " ", "Salazar Slytherin"] for name in testing_dataset: print ("{} — {}".format(name, get_predictions_vector(model, name)[1])) score_testing_dataset(rfc_model) 

   — 0.5 Kirill Malev — 0.5   — 0.0 Harry Potter — 0.0   — 0.75   — 0.9 Severus Snape — 0.5   — 0.2 Tom Riddle — 0.5   — 0.2 Salazar Slytherin — 0.3 

结果令人怀疑。 根据这种模式,即使是该学院的创始人也不会在他的学院中。 因此,您需要评估严格的质量:查看我们一开始就要求的指标:


 from sklearn.metrics import accuracy_score, roc_auc_score, classification_report predictions = rfc_model.predict(X_test) print("Classification report: ") print(classification_report(y_test, predictions)) print("Accuracy for Random Forest Model: %.2f" % (accuracy_score(y_test, predictions) * 100)) print("ROC AUC from first Random Forest Model: %.2f" % (roc_auc_score(y_test, predictions))) 

 Classification report: precision recall f1-score support 0 0.66 0.88 0.75 168 1 0.38 0.15 0.21 89 avg / total 0.56 0.62 0.56 257 Accuracy for Random Forest Model: 62.26 ROC AUC from first Random Forest Model: 0.51 

结果如此可疑也就不足为奇了-ROC AUC约为0.51表明该模型的预测比抛硬币更好。


测试结果。 质量指标


使用上面的一个示例,我们研究了如何训练一种支持sklearn接口的算法。 其余的训练方法完全相同,因此我们只能训练所有算法,并在每种情况下选择最佳算法。



这并不复杂,对于每种算法,我们使用标准设置训练1,并且还训练整个集合,对影响算法质量的各种选项进行分类。 此阶段称为“模型调整”或“超参数优化”,其本质非常简单:选择提供最佳结果的设置集。


 from model_training import train_classifiers from data_loaders import load_processed_data import warnings warnings.filterwarnings('ignore') #   hogwarts_df = load_processed_data() #     data_full = hogwarts_df.drop( [ 'name', 'surname', 'is_griffindor', 'is_hufflpuff', 'is_ravenclaw' ], axis=1).copy() X_data = data_full.drop('is_slitherin', axis=1) y = data_full.is_slitherin #    slitherin_models = train_classifiers(data_full, X_data, y) score_testing_dataset(slitherin_models[5]) 

   — 0.09437856871661066 Kirill Malev — 0.20820536334902712   — 0.07550095601699099 Harry Potter — 0.07683794773639624   — 0.9414529336862744   — 0.9293671807790949 Severus Snape — 0.6576783576162999   — 0.18577792617672767 Tom Riddle — 0.8351835484058869   — 0.25930925139546795 Salazar Slytherin — 0.24008788903854789 

此版本中的数字在主观上比过去更好,但对于内部完美主义者来说仍然不够好。 因此,我们将下一个更深的层次,并回到工作的产品意义上:我们需要预测最有可能的师资力量,这将由散布的帽子决定英雄。 这意味着您需要为每个系训练模型。



 >> from model_training import train_all_models #      >> slitherin_models, griffindor_models, ravenclaw_models, hufflpuff_models = \ train_all_models() 

结果的长结论和多项式回归的结果
 SVM Default Report Accuracy for SVM Default: 73.93 ROC AUC for SVM Default: 0.53 Tuned SVM Report Accuracy for Tuned SVM: 72.37 ROC AUC for Tuned SVM: 0.50 KNN Default Report Accuracy for KNN Default: 70.04 ROC AUC for KNN Default: 0.58 Tuned KNN Report Accuracy for Tuned KNN: 69.65 ROC AUC for Tuned KNN: 0.58 XGBoost Default Report Accuracy for XGBoost Default: 70.43 ROC AUC for XGBoost Default: 0.54 Tuned XGBoost Report Accuracy for Tuned XGBoost: 68.09 ROC AUC for Tuned XGBoost: 0.56 Random Forest Default Report Accuracy for Random Forest Default: 73.93 ROC AUC for Random Forest Default: 0.62 Tuned Random Forest Report Accuracy for Tuned Random Forest: 74.32 ROC AUC for Tuned Random Forest: 0.54 Extra Trees Default Report Accuracy for Extra Trees Default: 69.26 ROC AUC for Extra Trees Default: 0.57 Tuned Extra Trees Report Accuracy for Tuned Extra Trees: 73.54 ROC AUC for Tuned Extra Trees: 0.55 LGBM Default Report Accuracy for LGBM Default: 70.82 ROC AUC for LGBM Default: 0.62 Tuned LGBM Report Accuracy for Tuned LGBM: 74.71 ROC AUC for Tuned LGBM: 0.53 RGF Default Report Accuracy for RGF Default: 70.43 ROC AUC for RGF Default: 0.58 Tuned RGF Report Accuracy for Tuned RGF: 71.60 ROC AUC for Tuned RGF: 0.60 FRGF Default Report Accuracy for FRGF Default: 68.87 ROC AUC for FRGF Default: 0.59 Tuned FRGF Report Accuracy for Tuned FRGF: 69.26 ROC AUC for Tuned FRGF: 0.59 SVM Default Report Accuracy for SVM Default: 70.43 ROC AUC for SVM Default: 0.50 Tuned SVM Report Accuracy for Tuned SVM: 71.60 ROC AUC for Tuned SVM: 0.50 KNN Default Report Accuracy for KNN Default: 63.04 ROC AUC for KNN Default: 0.49 Tuned KNN Report Accuracy for Tuned KNN: 65.76 ROC AUC for Tuned KNN: 0.50 XGBoost Default Report Accuracy for XGBoost Default: 69.65 ROC AUC for XGBoost Default: 0.54 Tuned XGBoost Report Accuracy for Tuned XGBoost: 68.09 ROC AUC for Tuned XGBoost: 0.50 Random Forest Default Report Accuracy for Random Forest Default: 66.15 ROC AUC for Random Forest Default: 0.51 Tuned Random Forest Report Accuracy for Tuned Random Forest: 70.43 ROC AUC for Tuned Random Forest: 0.50 Extra Trees Default Report Accuracy for Extra Trees Default: 64.20 ROC AUC for Extra Trees Default: 0.49 Tuned Extra Trees Report Accuracy for Tuned Extra Trees: 70.82 ROC AUC for Tuned Extra Trees: 0.51 LGBM Default Report Accuracy for LGBM Default: 67.70 ROC AUC for LGBM Default: 0.56 Tuned LGBM Report Accuracy for Tuned LGBM: 70.82 ROC AUC for Tuned LGBM: 0.50 RGF Default Report Accuracy for RGF Default: 66.54 ROC AUC for RGF Default: 0.52 Tuned RGF Report Accuracy for Tuned RGF: 65.76 ROC AUC for Tuned RGF: 0.53 FRGF Default Report Accuracy for FRGF Default: 65.76 ROC AUC for FRGF Default: 0.53 Tuned FRGF Report Accuracy for Tuned FRGF: 69.65 ROC AUC for Tuned FRGF: 0.52 SVM Default Report Accuracy for SVM Default: 74.32 ROC AUC for SVM Default: 0.50 Tuned SVM Report Accuracy for Tuned SVM: 74.71 ROC AUC for Tuned SVM: 0.51 KNN Default Report Accuracy for KNN Default: 69.26 ROC AUC for KNN Default: 0.48 Tuned KNN Report Accuracy for Tuned KNN: 73.15 ROC AUC for Tuned KNN: 0.49 XGBoost Default Report Accuracy for XGBoost Default: 72.76 ROC AUC for XGBoost Default: 0.49 Tuned XGBoost Report Accuracy for Tuned XGBoost: 74.32 ROC AUC for Tuned XGBoost: 0.50 Random Forest Default Report Accuracy for Random Forest Default: 73.93 ROC AUC for Random Forest Default: 0.52 Tuned Random Forest Report Accuracy for Tuned Random Forest: 74.32 ROC AUC for Tuned Random Forest: 0.50 Extra Trees Default Report Accuracy for Extra Trees Default: 73.93 ROC AUC for Extra Trees Default: 0.52 Tuned Extra Trees Report Accuracy for Tuned Extra Trees: 73.93 ROC AUC for Tuned Extra Trees: 0.50 LGBM Default Report Accuracy for LGBM Default: 73.54 ROC AUC for LGBM Default: 0.52 Tuned LGBM Report Accuracy for Tuned LGBM: 74.32 ROC AUC for Tuned LGBM: 0.50 RGF Default Report Accuracy for RGF Default: 73.54 ROC AUC for RGF Default: 0.51 Tuned RGF Report Accuracy for Tuned RGF: 73.93 ROC AUC for Tuned RGF: 0.50 FRGF Default Report Accuracy for FRGF Default: 73.93 ROC AUC for FRGF Default: 0.53 Tuned FRGF Report Accuracy for Tuned FRGF: 73.93 ROC AUC for Tuned FRGF: 0.50 SVM Default Report Accuracy for SVM Default: 80.54 ROC AUC for SVM Default: 0.50 Tuned SVM Report Accuracy for Tuned SVM: 80.93 ROC AUC for Tuned SVM: 0.52 KNN Default Report Accuracy for KNN Default: 78.60 ROC AUC for KNN Default: 0.50 Tuned KNN Report Accuracy for Tuned KNN: 80.16 ROC AUC for Tuned KNN: 0.51 XGBoost Default Report Accuracy for XGBoost Default: 80.54 ROC AUC for XGBoost Default: 0.50 Tuned XGBoost Report Accuracy for Tuned XGBoost: 77.04 ROC AUC for Tuned XGBoost: 0.52 Random Forest Default Report Accuracy for Random Forest Default: 77.43 ROC AUC for Random Forest Default: 0.49 Tuned Random Forest Report Accuracy for Tuned Random Forest: 80.54 ROC AUC for Tuned Random Forest: 0.50 Extra Trees Default Report Accuracy for Extra Trees Default: 76.26 ROC AUC for Extra Trees Default: 0.48 Tuned Extra Trees Report Accuracy for Tuned Extra Trees: 78.60 ROC AUC for Tuned Extra Trees: 0.50 LGBM Default Report Accuracy for LGBM Default: 75.49 ROC AUC for LGBM Default: 0.51 Tuned LGBM Report Accuracy for Tuned LGBM: 80.54 ROC AUC for Tuned LGBM: 0.50 RGF Default Report Accuracy for RGF Default: 78.99 ROC AUC for RGF Default: 0.52 Tuned RGF Report Accuracy for Tuned RGF: 75.88 ROC AUC for Tuned RGF: 0.55 FRGF Default Report Accuracy for FRGF Default: 76.65 ROC AUC for FRGF Default: 0.50 #   ,        

 from sklearn.linear_model import LogisticRegression clf = LogisticRegression(random_state=0, solver='lbfgs', multi_class='multinomial') hogwarts_df = load_processed_data_multi() #     data_full = hogwarts_df.drop( [ 'name', 'surname', ], axis=1).copy() X_data = data_full.drop('faculty', axis=1) y = data_full.faculty clf.fit(X_data, y) score_testing_dataset(clf) 

   — [0.3602361 0.16166944 0.16771712 0.31037733] Kirill Malev — [0.47473072 0.16051924 0.13511385 0.22963619]   — [0.38697926 0.19330242 0.17451052 0.2452078 ] Harry Potter — [0.40245098 0.16410043 0.16023278 0.27321581]   — [0.13197025 0.16438855 0.17739254 0.52624866]   — [0.17170203 0.1205678 0.14341742 0.56431275] Severus Snape — [0.15558044 0.21589378 0.17370406 0.45482172]   — [0.39301231 0.07397324 0.1212741 0.41174035] Tom Riddle — [0.26623969 0.14194379 0.1728505 0.41896601]   — [0.24843037 0.21632736 0.21532696 0.3199153 ] Salazar Slytherin — [0.09359144 0.26735897 0.2742305 0.36481909] 

confusion_matrix:


 confusion_matrix(clf.predict(X_data), y) 

 array([[144, 68, 64, 78], [ 8, 9, 8, 6], [ 22, 18, 31, 20], [ 77, 73, 78, 151]]) 

 def get_predctions_vector (models, person): predictions = [get_predictions_vector (model, person)[1] for model in models] return { 'slitherin': predictions[0], 'griffindor': predictions[1], 'ravenclaw': predictions[2], 'hufflpuff': predictions[3] } def score_testing_dataset (models): testing_dataset = [ " ", "Kirill Malev", " ", "Harry Potter", " ", " ","Severus Snape", " ", "Tom Riddle", " ", "Salazar Slytherin"] data = [] for name in testing_dataset: predictions = get_predctions_vector(models, name) predictions['name'] = name data.append(predictions) scoring_df = pd.DataFrame(data, columns=['name', 'slitherin', 'griffindor', 'hufflpuff', 'ravenclaw']) return scoring_df # Data Science —    ,       top_models = [ slitherin_models[3], griffindor_models[3], ravenclaw_models[3], hufflpuff_models[3] ] score_testing_dataset(top_models) 

  name slitherin griffindor hufflpuff ravenclaw 0   0.349084 0.266909 0.110311 0.091045 1 Kirill Malev 0.289914 0.376122 0.384986 0.103056 2   0.338258 0.400841 0.016668 0.124825 3 Harry Potter 0.245377 0.357934 0.026287 0.154592 4   0.917423 0.126997 0.176640 0.096570 5   0.969693 0.106384 0.150146 0.082195 6 Severus Snape 0.663732 0.259189 0.290252 0.074148 7   0.268466 0.579401 0.007900 0.083195 8 Tom Riddle 0.639731 0.541184 0.084395 0.156245 9   0.653595 0.147506 0.172940 0.137134 10 Salazar Slytherin 0.647399 0.169964 0.095450 0.26126 


, . ROC AUC , 0.5. , :


  • ;
  • ;
  • , ;
  • , ;
  • , , .

, , , , XGBoost CV , .


重要! , 70% . , 4 .


 from model_training import train_production_models from xgboost import XGBClassifier best_models = [] for i in range (0,4): best_models.append(XGBClassifier(base_score=0.5, booster='gbtree', colsample_bylevel=1, colsample_bytree=0.7, gamma=0, learning_rate=0.05, max_delta_step=0, max_depth=6, min_child_weight=11, missing=-999, n_estimators=1000, n_jobs=1, nthread=4, objective='binary:logistic', random_state=0, reg_alpha=0, reg_lambda=1, scale_pos_weight=1, seed=1337, silent=1, subsample=0.8)) slitherin_model, griffindor_model, ravenclaw_model, hufflpuff_model = \ train_production_models(best_models) top_models = slitherin_model, griffindor_model, ravenclaw_model, hufflpuff_model score_testing_dataset(top_models) 

 name slitherin griffindor hufflpuff ravenclaw 0   0.273713 0.372337 0.065923 0.279577 1 Kirill Malev 0.401603 0.761467 0.111068 0.023902 2   0.031540 0.616535 0.196342 0.217829 3 Harry Potter 0.183760 0.422733 0.119393 0.173184 4   0.945895 0.021788 0.209820 0.019449 5   0.950932 0.088979 0.084131 0.012575 6 Severus Snape 0.634035 0.088230 0.249871 0.036682 7   0.426440 0.431351 0.028444 0.083636 8 Tom Riddle 0.816804 0.136530 0.069564 0.035500 9   0.409634 0.213925 0.028631 0.252723 10 Salazar Slytherin 0.824590 0.067910 0.111147 0.085710 

, , .


, , . .


 import pickle pickle.dump(slitherin_model, open("../output/slitherin.xgbm", "wb")) pickle.dump(griffindor_model, open("../output/griffindor.xgbm", "wb")) pickle.dump(ravenclaw_model, open("../output/ravenclaw.xgbm", "wb")) pickle.dump(hufflpuff_model, open("../output/hufflpuff.xgbm", "wb")) 


, . , , , .


, , . , . , Data Scientist — -.


:


  • ;
  • json- json;
  • .

, docker-, python-. , flask.


 from __future__ import print_function # In python 2.7 import os import subprocess import json import re from flask import Flask, request, jsonify from inspect import getmembers, ismethod import numpy as npb import pandas as pd import math import os import pickle import xgboost as xgb import sys from letter import Letter from talking_hat import * from sklearn.ensemble import RandomForestClassifier import warnings def prod_predict_classes_for_name (full_name): featurized_person = parse_line_to_hogwarts_df(full_name) person_df = pd.DataFrame(featurized_person, columns=[ 'name', 'surname', 'is_english', 'name_starts_with_vowel', 'name_starts_with_consonant', 'name_ends_with_vowel', 'name_ends_with_consonant', 'name_length', 'name_vowels_count', 'name_double_vowels_count', 'name_consonant_count', 'name_double_consonant_count', 'name_paired_count', 'name_deaf_count', 'name_sonorus_count', 'surname_starts_with_vowel', 'surname_starts_with_consonant', 'surname_ends_with_vowel', 'surname_ends_with_consonant', 'surname_length', 'surname_vowels_count', 'surname_double_vowels_count', 'surname_consonant_count', 'surname_double_consonant_count', 'surname_paired_count', 'surname_deaf_count', 'surname_sonorus_count', ], index=[0] ) slitherin_model = pickle.load(open("models/slitherin.xgbm", "rb")) griffindor_model = pickle.load(open("models/griffindor.xgbm", "rb")) ravenclaw_model = pickle.load(open("models/ravenclaw.xgbm", "rb")) hufflpuff_model = pickle.load(open("models/hufflpuff.xgbm", "rb")) predictions = get_predctions_vector([ slitherin_model, griffindor_model, ravenclaw_model, hufflpuff_model ], person_df.drop(['name', 'surname'], axis=1)) return { 'slitherin': float(predictions[0][1]), 'griffindor': float(predictions[1][1]), 'ravenclaw': float(predictions[2][1]), 'hufflpuff': float(predictions[3][1]) } def predict(params): fullname = params['fullname'] print(params) return prod_predict_classes_for_name(fullname) def create_app(): app = Flask(__name__) functions_list = [predict] @app.route('/<func_name>', methods=['POST']) def api_root(func_name): for function in functions_list: if function.__name__ == func_name: try: json_req_data = request.get_json() if json_req_data: res = function(json_req_data) else: return jsonify({"error": "error in receiving the json input"}) except Exception as e: data = { "error": "error while running the function" } if hasattr(e, 'message'): data['message'] = e.message elif len(e.args) >= 1: data['message'] = e.args[0] return jsonify(data) return jsonify({"success": True, "result": res}) output_string = 'function: %s not found' % func_name return jsonify({"error": output_string}) return app if __name__ == '__main__': app = create_app() app.run(host='0.0.0.0') 

Dockerfile:


 FROM datmo/python-base:cpu-py35 #  python3-wheel,        RUN apt-get update; apt-get install -y python3-pip python3-numpy python3-scipy python3-wheel ADD requirements.txt / RUN pip3 install -r /requirements.txt RUN mkdir /code;mkdir /code/models COPY ./python_api.py ./talking_hat.py ./letter.py ./request.py /code/ COPY ./models/* /code/models/ WORKDIR /code CMD python3 /code/python_api.py 

:


 docker build -t talking_hat . && docker rm talking_hat && docker run --name talking_hat -p 5000:5000 talking_hat 


— . , Apache Benchmark . , . — .


 $ ab -p data.json -T application/json -c 50 -n 10000 http://0.0.0.0:5000/predict 

ab
 This is ApacheBench, Version 2.3 <$Revision: 1807734 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking 0.0.0.0 (be patient) Completed 1000 requests Completed 2000 requests Completed 3000 requests Completed 4000 requests Completed 5000 requests Completed 6000 requests Completed 7000 requests Completed 8000 requests Completed 9000 requests Completed 10000 requests Finished 10000 requests Server Software: Werkzeug/0.14.1 Server Hostname: 0.0.0.0 Server Port: 5000 Document Path: /predict Document Length: 141 bytes Concurrency Level: 50 Time taken for tests: 238.552 seconds Complete requests: 10000 Failed requests: 0 Total transferred: 2880000 bytes Total body sent: 1800000 HTML transferred: 1410000 bytes Requests per second: 41.92 [#/sec] (mean) Time per request: 1192.758 [ms] (mean) Time per request: 23.855 [ms] (mean, across all concurrent requests) Transfer rate: 11.79 [Kbytes/sec] received 7.37 kb/s sent 19.16 kb/s total Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.1 0 3 Processing: 199 1191 352.5 1128 3352 Waiting: 198 1190 352.5 1127 3351 Total: 202 1191 352.5 1128 3352 Percentage of the requests served within a certain time (ms) 50% 1128 66% 1277 75% 1378 80% 1451 90% 1668 95% 1860 98% 2096 99% 2260 100% 3352 (longest request) 

, :


 def prod_predict_classes_for_name (full_name): <...> predictions = get_predctions_vector([ app.slitherin_model, app.griffindor_model, app.ravenclaw_model, app.hufflpuff_model ], person_df.drop(['name', 'surname'], axis=1)) return { 'slitherin': float(predictions[0][1]), 'griffindor': float(predictions[1][1]), 'ravenclaw': float(predictions[2][1]), 'hufflpuff': float(predictions[3][1]) } def create_app(): <...> with app.app_context(): app.slitherin_model = pickle.load(open("models/slitherin.xgbm", "rb")) app.griffindor_model = pickle.load(open("models/griffindor.xgbm", "rb")) app.ravenclaw_model = pickle.load(open("models/ravenclaw.xgbm", "rb")) app.hufflpuff_model = pickle.load(open("models/hufflpuff.xgbm", "rb")) return app 

:


 $ docker build -t talking_hat . && docker rm talking_hat && docker run --name talking_hat -p 5000:5000 talking_hat $ ab -p data.json -T application/json -c 50 -n 10000 http://0.0.0.0:5000/predict 

ab
 This is ApacheBench, Version 2.3 <$Revision: 1807734 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking 0.0.0.0 (be patient) Completed 1000 requests Completed 2000 requests Completed 3000 requests Completed 4000 requests Completed 5000 requests Completed 6000 requests Completed 7000 requests Completed 8000 requests Completed 9000 requests Completed 10000 requests Finished 10000 requests Server Software: Werkzeug/0.14.1 Server Hostname: 0.0.0.0 Server Port: 5000 Document Path: /predict Document Length: 141 bytes Concurrency Level: 50 Time taken for tests: 219.812 seconds Complete requests: 10000 Failed requests: 3 (Connect: 0, Receive: 0, Length: 3, Exceptions: 0) Total transferred: 2879997 bytes Total body sent: 1800000 HTML transferred: 1409997 bytes Requests per second: 45.49 [#/sec] (mean) Time per request: 1099.062 [ms] (mean) Time per request: 21.981 [ms] (mean, across all concurrent requests) Transfer rate: 12.79 [Kbytes/sec] received 8.00 kb/s sent 20.79 kb/s total Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.1 0 2 Processing: 235 1098 335.2 1035 3464 Waiting: 235 1097 335.2 1034 3462 Total: 238 1098 335.2 1035 3464 Percentage of the requests served within a certain time (ms) 50% 1035 66% 1176 75% 1278 80% 1349 90% 1541 95% 1736 98% 1967 99% 2141 100% 3464 (longest request) 

做完了 . , .


结论


, . - .


, :


  • feature engineering- ( ), , Soundex .
  • PyTorch . , , .
  • flask Quart , , .
  • - -, .

, , . , !


没有开放数据科学社区,就不会发表这篇文章,该社区聚集了数据分析领域的大量俄语专家。

Source: https://habr.com/ru/post/zh-CN430006/


All Articles