利用公司年报中的自然语言处理预测濒临破产
公司年报的语言能预测股价暴跌吗?
在交易固定收益和货币多年后,我正在寻找一个项目,作为自然语言处理(NLP)的介绍。在目睹了金融和整个企业界如此多的丑闻后,我被这样一种观点吸引住了,即公司年报的措辞可能带有欺诈或不负责任的法庭特征。这种情况下,要看我说的话,而不是我报告的数字。
最大提款是给定时期内投资组合或证券的最大市值损失,是一种广泛使用的风险管理指标。该项目的目标是看看是否可以从公司年度报告的文本中预测急剧和极端的股本减少,作为破产、信贷风险和治理的一个代理,并将其与使用财务指标(FIN)和市场指标(MKT)的更传统的衡量标准进行比较。
正式调教
该问题被设置为一个二元分类问题,目标是在发布年度报告后的一年中股票价格的最大滚动 20 天下降。当提款大于或等于 80%时,目标登记“几乎破产”(正面)事件。该特征集是通过预处理公司年度报告的文本并在术语-频率-逆-文档-频率(tf-idf)矩阵中表示这些文本而得到的。
数据库
Sharadar 提供价格合理的订阅,涵盖 14,000 多家美国公司的 20 多年历史,可通过 Quandl API 访问。对于我们的应用程序来说,重要的是 Sharadar 股票数据库包括破产公司和其他退市公司。上市公司需要向 SEC 提交年度报告(10K ),这些报告可从 SEC 网站上的 EDGAR 数据库中获得。一个现有的 Python 包被用来抓取这些数据。股票价格数据库提供了 160,926 个潜在目标事件,其中 38,807 个可以与下载的年度报告数据库相匹配。
数据库示意图(图片由作者提供)
文本预处理
鉴于数据跨度长达数十年,年度报告采用多种格式,包括文本、html 和 XBRL。因此,使用 Regex 来解析报告并删除特殊字符:
def remove_html_tags_char(text):
'''Takes in string and removes special characters '''
#Define special Chars
clean1 = re.compile('\n')
clean2 = re.compile('\r')
clean3 = re.compile(' ')
clean4 = re.compile(' ')
clean5 = re.compile(' ')
#Define html tags
clean6 = re.compile('<.*?>')
#remove special characters and html tags
text = re.sub(clean1,' ', text)
text = re.sub(clean2,' ',text)
text = re.sub(clean3,' ',text)
text = re.sub(clean4,' ',text)
text = re.sub(clean5,' ',text)
text = re.sub(clean6,' ',text)
# check spacing
final_text = ' '.join(text.split())return final_text
为了与现代方法保持一致,文本经过了最低限度的处理,没有词汇化、词干化或停用词删除。这遵循了这样的想法,即有了足够大的数据集,应该让模型来确定这些细微差别(例如时态)对于手头的问题是否重要。
数据探索
数据高度不平衡:
跨管道少数民族阶层比例的演变(图片由作者提供)
毫不奇怪,“几乎破产”超过了美国经济衰退(与美国实际 GDP 的关系更加微妙——详见 GitHub):
作者图片
像许多其他市场价格变量一样,自相关性或持续性在股本缩减中很明显:
作者图片
这最后一个观察导致了市场(MKT)模型的引入,用于基线比较的目的。MKT 只是将一家公司之前的年度支出四分位数排名作为唯一特征。
交叉验证
方法
从 2015 年开始的数据被用作保留集,而训练数据从 1997 年运行到 2014 年。使用扩展窗口方法,将 CV 集分成 5 等份:
CV 集从 1997 年运行到 2015 年,并使用扩展窗口方法分成 4 次迭代(图片由作者提供)
tf-idf 矢量器在 CV 训练数据上形成,然后使用变换将测试文档转换成其 tf-idf 矩阵。宏(谐波)召回被用作 CV 分数,并且与训练集中的数据量成比例地加权(即,CV1 具有 10%的权重,CV2 具有 20%的权重,等等)。在需要加试的情况下,使用 CV 集得分的标准差。
结构
使用欠采样、过采样和类别权重来解决数据不平衡问题。考虑的另一个样本偏差是财务报表随时间的不平等分布可能会损害模型的通用性。事实上,我们可能会认为,过去 23 年中不断变化的监管、会计、法律和经济格局将在年度报告的语言和结构中得到反映。为了对此进行测试,对(I)多数类数据的随机选择和(ii)保持每年多数类事件比率等于少数类事件比率的预处理样本进行了验证(时间均衡)。最后,测试了各种概率阈值和集合。
机器学习实现示意图(图片由作者提供)
主要结果
最佳微调下各种模型的 CV 性能指标(图片由作者提供)
欠采样比过采样表现更好
时间均衡将欠采样分数从 62%提高到 66%
梯度增强的性能优于随机森林和对数回归
欠采样有利于肯定回忆(灵敏度),而过采样有利于否定回忆(特异性)
最佳模型是比率为 25% / 75%的过采样/欠采样的集合,两种模型都使用梯度增强
拒绝测试
结果
最优模型(NLP)应用于基于年度扩展窗口的维持集。然后将综合结果与基线财务比率(FIN)和市场(MKT)模型进行比较:
与 FIN 和 MKT 相比,NLP 的聚合维持结果(图片由作者提供)
NLP 混淆矩阵是:
作者图片
分析
在二元目标的基础上使用连续变量的好处之一是,我们可以通过计算混淆矩阵中每个类别的下降统计来计算模型误差的成本:
假阳性捕捉关于连续目标空间的有用信息(图片由作者提供)
尽管假阳性的比例相对较高并且相关联的精度较低,但是模型误差揭示了关于分类空间的有趣信息
这可以更实际地在领域空间中通过形成预测真实、预测负面和所有的平均加权的投资组合来充实:
模型实用程序的领域视角(图片由作者提供)
解读
对这些模型的解释有些挑战,但是我们可以看看在独立的基础上,维持集中的哪些词具有最高的概率。这是通过形成文档矩阵来实现的,其中每个文档只有一个唯一的单词,然后通过模型预测来运行该矩阵。需要注意的是,一个单词可能有很高的概率,但它只适用于一个或很少几个文档。在这方面,并不是所有的话都会被笼统地预测到。
从元数据开始,最大单个单词概率低于 50%,99.9%的单词在 20%到 30%之间。这表明是在文档中找到的单词的组合而不是单个隐含单词产生了高概率的文档。
独立单词概率表示单词组合驱动模型(图片由作者提供)
下面列出的热门词汇被手动分类。在这些类别中:会计/信贷、顾问发言/业务重组和负面情绪词汇直观上令人愉悦。
会计/信贷和顾问演讲/业务重组是直观的类别(图片由作者提供)
进一步的思考
看到一个相对简单的模型如 tf-idf 与基线财务比率模型相比表现如此之好,多少有些令人惊讶。信贷和业务重组的热门词汇类别表明,该模型擅长提取与财务比率中包含的信息类似的信息。该项目的下一阶段是探索与财务比率强劲的几乎破产事件相关的语言。这种对“额外性”的关注可能需要更复杂的算法(如 BERT)来更好地解析上下文,但可能能够揭示财务报表中不存在的会计欺诈或其他不诚实行为。
识别弱势公司是一回事,但知道这种弱势何时会在积极事件中表现出来要难得多,而且还取决于复杂的外部环境。这是对高假阳性率的合理解释。看看报告文本是否包含比已经摘录的更多的关于时间的信息将是令人着迷的。一种尝试的方法是余弦相似性,但这可能会受到维数灾难的影响。另一种方法是将单个公司的 tf-idf 矩阵本身的年度变化包括在特征集中。在总体水平上,人们可以试着检验年度 tf-idf 矩阵是否能预测下一年几乎破产数量的变化。
Github
该模型连同其他项目文件可在 GitHub 上获得。玩得开心,请随时分享反馈!
用非负矩阵分解对公司进行分类。
从非财务标准中提取有价值的信息。
乔希·里默尔在 Unsplash 上的照片
介绍
公司是随着时间发展的复杂实体。作为一名参与投资的数据科学家,我长期以来一直在问自己这样一个问题:评估企业数据建模的最合适维度:这些东西存在于什么空间?没有比这个更好的答案了:“太多了!”。这个问题的另一种表述是询问有多少个独立标准足以描述一家公司。作为标准的例子,我们可以想到市场资本总额、工业部门、雇员人数、当前收入水平、碳足迹、金融分析师的意见等等:可用标准的数量很容易超过 100 个。这些标准不是独立的,相反,它们被一个隐含相关性的整体网络联系在一起。这使得选择相关的变量子集成为一项非常困难的任务。
分组与预测
投资从业者通过两种主要方式将机器学习应用于这些数据:
- 通过或多或少复杂的回归,根据不同标准的历史数据预测公司的未来行为,通常是其股票市场价值的演变。这就是监督学习。
- 自动将公司分组为同质集合,从而能够将这些公司与其邻居进行比较,而无需适当的标签:这种练习被称为无监督学习。金融业经常使用的一个集合的例子是工业部门的概念。
这两个练习——分组和预测——追求不同的目标,但随着更多标准的使用,这两个练习在实施时都更加困难和不稳定。这是统计学习中众所周知的现象的表现之一,被称为“维数灾难”。
因此,自然需要通过减少所用标准的数量来缩小问题的规模。说起来容易做起来难。如果我们添加一个可解释性约束,在实践中就更难实现了。
在本文中,我们将看到非负因式分解算法(NMF) 如何让这两个目标更接近。
现在让我们把这个问题形式化一点。
非负矩阵分解
假设可用数据由(n,f)类型的 X 矩阵表示,即 n 行和 f 列。我们假设这些数据是正的或零的,并且是有界的——这种假设可以放宽,但这就是精神所在。
X 的非负因式分解是 X 的一种近似分解:
其中 W 为(n,c)型,H 为(f,c)型,满足约束 W ≥ 0,H ≥ 0。
需要记住的一些要点:
- 这种分解是近似的,而不是相等的。解 w 和 h 矩阵最小化了真实数据 x 和它们的近似 X^.之间的二次误差
- 这种分解不一定是唯一的,即使有积极性的约束。人们确实可以以正对角 D 矩阵的形式构造单位变换矩阵,然后获得以下形式的相同分解:
d 及其逆都是正系数的对角线
- c ≤ min(n,f)是一个整数,表示所选元件的数量。与 K-均值聚类方法一样,该参数表示方法的参数自由度。确定组件的最佳数量需要一个额外的标准。
- 在文献中通常将 W 矩阵的线称为因子。
在这里可以找到关于 NMF 的精彩介绍。
如何解读分解
通常将 X 线视为同一类别(如客户、患者、公司等)对象的 n 次观察。每个对象都有 f 个属性,矩阵中包含的数据对应于相应对象的属性所取的值。
这种分解的主要效果是减少描述一个观察结果所必需的信息。以近似为代价,X 矩阵的原始观测值可以恢复为具有正系数的线性组合——H 矩阵的线。因此实现了大小的减小:包含在原始矩阵中的部分信息,包括每个个体的 f 数据,可以由每个个体的更小的 c 数据集合来近似概括。
非负分解的另一个有趣的效果是观察和属性的自然聚集的出现。潜在的数学原理在这篇论文中会带我们走得太远,但是你可以在这篇文章【2】中找到一个非常清晰的表述。直观地,可以根据主导因子,即 c 个因子中具有最高值的因子,对观察值进行聚类。同样,可以根据对其影响最大的因素对原始特征进行分组。
这种聚集的自然可能性与系数的正定性密切相关。这是与主成分分析(PCA)的一个重要区别,在 PCA 中,要求因子成对正交,这使得不可能控制系数的符号。
企业外部财务数据的应用
为投资者收集和发布非金融企业数据本身就是一个行业,参与者是评级机构。每个机构都有自己的标准和自己的评级方法,但从一个机构到另一个机构可以找到大量的标准。
我们感兴趣的是根据环境(E)、社会(S)和治理(G)这三个主要支柱来描述公司行为的数据。这些数据被称为行为得分。他们根据男女同酬、董事会中独立董事的比例、水污染、碳足迹等标准,对公司的表现进行评分,分值范围为 0 到 100 分。
这些分数通常由评级机构每年审核一到两次,主要由公司在申报的基础上进行审核。我们将把这些分数中包含的信息用于:
- 根据比行业部门更多的内在信息确定同类公司群体,
- 将变量分组,以减少影响因素的数量。
本文使用的数据是来自 Vigeo-Eiris research 的 77 个行为得分,Vigeo-Eiris research 是穆迪评级机构的子公司,专门从事全球上市公司的财务外评级。我们在此展示的数值经验是基于欧洲 500 只最大股票的得分,在 2011 年 9 月至 2020 年 6 月期间进行平均评估。
Vigeo 的行为评分分为 6 个方面:
- 环境
- 公司治理
- 社交:商业行为
- 社会:人权
- 社会:人力资源
- 社会:社区参与
关于评分方法的更多内容可在此处找到。
这种数据的多样性和粒度使得有可能设想一个比简单基于工业部门的更相关的公司分组。我们将通过对分数数据应用 NMF 来检验这一假设。
缺失数据的替换
并非所有部门的所有行动都有行为得分,因此该系列有缺失值。
各种分数的部门平均填充率的图形视图。全球平均水平为 81.6%。
图表最左侧的分数具有 100%的填充率。例如,与商业行为(以 C_S 开头)或公司治理(以 C_G 开头)相关的分数就是如此。另一方面,高度专业化的得分,如当地污染治理(名为 EnV2_6)的填充率最低,在一些主题相关性较低的部门(金融、房地产),填充率可降至 0。
像大量基于矩阵代数的方法一样,NMF 不允许丢失数据。因此,我们将用相同分数的平均值替换原始矩阵中每个分数缺失的值,该平均值取自填写了该分数的所有公司。
填充缺失数据后要素的相关矩阵-平均成对相关为 41%。两个分数之间的相关性是跨 500 只股票计算的。
寻找最佳维度
我们现在几乎准备好启动 NMF 分解。我们现在要做的就是为参数 c 选择一个值。我们对该参数的最佳值知之甚少,但我们希望以下方案是正确的:
- c == >数据保存的小值和不精确的模型,
- c == >数据密集的大值和更精确的模型。
这个原则将帮助我们定义一个目标函数,该函数综合了稀疏度、稳定性和**精度的概念。**这些概念确实是预测系统可解释性的关键因素,正如这里的【3】,【4】所解释的。为了简单起见,在本文中我们将把稳定性放在一边,重点放在稀疏性和精确性上。
为了量化稀疏性,让我们观察大小为(n,f)的初始矩阵已经被分别大小为(n,c)和(c,f)的两个矩阵所代替。因此,我们可以定义由以下公式给出的稀疏分数:
sp©是 c 分量的稀疏分数,
当然,对于 c = 1,这个分数将是最小的,在理论情况下,所有的观测值将大致是单个向量的倍数。
为了量化精度,很自然地通过以下公式定义类似于线性回归 R 的精度分数 err(c ):
逼近误差的归一化度量。
我们可以通过结合精确性和稀疏性最终定义一个可解释性得分:
这个分数是一个介于 0 和 1 之间的数字,按照惯例 0 是可解释性的最佳水平,1 是最差水平。
当 c = 1 或 c = c时,分数被校准为 100%最大值;常数 c起着最大可接受维数的作用,这里设置为 20。
这里有几行你必须写的代码,计算一个给定维度 c 的 NMF 分解:
**>>> import** **numpy** **as** **np
>>> import pandas as pd
>>> from** **sklearn.decomposition** **import** NMF**>>>** X = pd.read_csv (scores_file_path) # reading the score data
**>>>** c = 4 **>>>** model = NMF(n_components=c, init='random', random_state=0)
**>>>** W = model.fit_transform(X)
**>>>** H = model.components_
**>>>** err = model.reconstruction_err_
矩阵 H 和 W 是计算的主要输出,需要它们来重建近似矩阵和误差函数 err©。
下图是通过绘制 sp©、err©和 inter score©的值获得的。
c = 6 时可解释性得分最佳
该图清楚地显示了精度和简约性之间的权衡,c 在 2 到 6 之间急剧下降,随后随着 c 值的增大而缓慢加速上升,在下文中,出于示例的目的,我们保留 c=6 作为元件数量。
收集 NMF 福利
特征收缩
我们从非负因式分解中获得的最明显的好处是,最初的 77 个特征被 6 个正线性组合的缩减子集,即 元特征 所取代。
随着等级的增加,特性显得更有选择性。
该图显示了 6 个新的元特征在最初的 77 个特征上的系数分布(“负载”)。正如所料,这些系数分布在几个特征上,线性组合可能难以解释。元特征#1 尤其如此,随着元特征等级的增加,系数越来越集中。
将主题分配给元特征
这个数组显示了每个元特性的 6 个最具影响力的特性。有趣的是,这些流入特征的主题是相互关联的。因此,我们可以为每个元特征分配一个主题。例如,特写 V1 讲述的故事大多是关于人权 (hrts)和人力资源(hr),而 V4 讲得很清楚的是环境 (env)。
NMF 交易了维度和可解释性,通过大幅降低维度(从 77 到 6),代价是获得更多的但仍有有意义的特征。
公司集群
有趣的是,NMF 的对称公式还提供了一种将所有公司分组为 6 个可能集群的自然方法:当股票的最大价值在列 I 中时,即当其在元特征#i 上的得分最高时,我们简单地将集群#i 分配给股票。让我们来看看这种将公司分组的新方法如何对应于通常的行业:
NC = 6 个分量的扇区/ NMF 簇对应关系
基于 NMF 的聚类带来了不包含在单独的部门中的信息。我们可以将前面图中的每一行作为各个元特征的每个扇区的“签名”。例如,能源部门在与环境主题相关的元特征#4 中得分最低。
缺失数据
记住,我们从一个包含缺失数据的 X 矩阵开始。我们已经用每一列的全局平均值填充了空的单元格,并用填充后的矩阵填充了我们的 NMF 算法。
既然我们已经有了股票的聚类,为什么我们不尝试通过用每列的平均值代替空单元格来改进缺失数据的填充,用相同聚类的股票来代替所有股票呢?这将提供对 X 的新估计,该估计又可以用作因式分解的输入。如此等等…
使用 NMF 对缺失数据进行自举可以减少 20%的近似误差。
与基于均值的先验知识相比,NMF 提供的线聚类允许以更高的精度插值数据矩阵的缺失值。这种方法在许多领域非常有用,主要优点是相对于像扇区这样的外部聚类的独立性:它是完全数据驱动的吗?
结论
本文旨在说明并支持如何使用非负矩阵分解作为一种强大的工具来创建观察值和特征的自然分组。这一点在生命科学领域早就被认识到,特别是在基因组学领域,减少特征的数量同时保持分析的可解释性是至关重要的。
将 NMF 应用于额外的金融数据是提取这些数据集中包含的有价值信息的一种创新方法。我们使用了特定提供商 Vigeo-Eiris 的数据,但好消息是,当使用多个数据源来展示公司的基本属性时,这种方法将非常稳健。
进一步阅读的参考资料
[1]丁,钟汉卿;陶;Jordan,M.I .凸和半非负矩阵分解(2010 年)。IEEE Trans。模式
肛门。马赫。Intelli。拱门。2010, 32, 44–55.
[2] P.Fogel,Y. Gaston-Mathé等人,使用非负矩阵分解的新型聚类方法在公共卫生环境
研究中的应用(2016),国际环境研究和公共卫生杂志。
[3] B. Yu 和 K. Kumbier,数据科学的三个原则:可预测性、可计算性和稳定性(pcs) (2019),arXiv 预印本 arXiv:1901.08152。
[4]诉玛戈特。比较可解释性的严格方法(2020),arXiv 预印本 arXiv:2004.01570。
[5] Vigeo-Eiris,网站(2020 年)
使用零样本来形成决策空间和防御敌对攻击
一种更自然的模型训练方法
在机器学习分类模型中,各个类别的最佳决策空间可能是复杂且不相交的,并且仅占据整个输入空间的很小一部分。尽管上图中的绳索纠缠在一起,但人们可以很容易地发现图像中不属于任何绳索的点(这是绝大多数的点)。然而,我们在机器学习社区中不允许我们传统训练的机器学习模型做同样的事情。在这幅图中,这样的模型会愚蠢地将每个点分配给一条绳索。这种不自然的条件可能是这些模型易受恶意(欺骗)攻击的根本原因。
这篇文章是我最近在 arXiv.org 上发表的一篇技术报告的摘要。在这里阅读完整的报告(和正式的引文):https://arxiv.org/abs/2002.10084
介绍
大约在 2012 年计算机视觉的新一波神经网络模型到来后不久[Krizhevsky,2012;LeCun,2015 年],人们发现,这种网络可以被巧妙设计的图像所欺骗,这些图像对人类来说,看起来与原始图像的改变很小,如果有的话[Szegedy,2013 年]。这种所谓的敌对例子给许多需要抵御敌对行为者的计算机视觉应用带来了问题。
通过向熊猫图像添加一点点噪声,常规训练的神经网络以很高的可信度将熊猫图像误认为长臂猿图像[Goodfellow,2014]。
显然,卷积神经网络(CNN)模型做出决定的方式与人类或任何其他哺乳动物都非常不同。
问题的一个可能来源是,模型被设计和训练为将图像分类为属于 N 类(对象)之一,而不管图像。例如,如果一个模型被训练来对从 0 到 9 的孤立数字的图像进行分类,然后显示一个“T”或一个“$”或一只驴子,它会将该图像分类为其中一个数字-这就是它被训练做的所有事情。
这意味着在可能图像的高维输入空间中,每个点都被分配给一个对象类。各个类的决策空间共同填充整个空间,尽管对于给定的对象类,真实样本仅位于学习决策空间的一个小的子区域内。决策空间彼此邻接,即使空间内的真实样本被很好地分开。
下面介绍的是一个玩具模型和任务,它允许这种情况的可视化,它使传统模型容易受到敌对攻击的方式,以及缓解该问题的策略。
典型的例子
图 1:玩具示例的最佳决策空间
在我们的玩具环境中,输入“图像”是三个像素的线性阵列,其中可能存在两个对象中的一个,每个对象是一对具有特定值的相邻像素。上图显示了两类物体(洋红色和青色)可能存在的位置。空间位置与类别身份无关,因此单个类别的对象样本存在于 3D 输入空间的两个隔离区域中。彩色区域代表两个类别的“最佳”决策空间,即在洋红色和青色区域之外不存在训练样本。
我们在这个分类任务上训练了一个简单、传统的 CNN,然后用跨越整个输入空间的样本来探测该模型。产生的决策空间如下所示。
图 2:传统 CNN 模型的学习决策空间
传统 CNN 的决策空间完全填充了输入空间——对于给定的架构和训练,它们必须如此——并且各个类的空间(图 2 的中间和右边的面板)比真实对象样本(图 1)的空间大得多。不难想象,当添加到真实对象样本中时,一点点噪声可能会将图像推过决策边界。
缓解这个问题的一个方法是(1)向模型添加一个 (N +1)ᵗʰ输出——该输出指示一个空类,而不是 n 个对象类中的一个,以及(2)在训练期间使用空样本以及对象样本。对于玩具示例,我们使用均匀噪声的三像素图像作为零样本。
图 3:零训练 CNN 模型的学习决策空间
在训练完这个零模型后,我们像对传统模型一样可视化决策空间。如上所示,决策空间已经大大缩小了。该图仅显示了两个对象类的决策空间。视觉上的空白空间是 null 类的决策空间。换句话说,如果输入图像没有落入其中一个对象类的决策空间,则允许该模型将输入图像“未分类”。
我们推测,随着输入空间维度的增加,玩具示例中传统模型所展示的情况只会变得更糟。习得的决策空间是错综复杂的,真实的对象样本可能位于与对象的因果特征不相关的决策边界附近——也就是说,使其成为其类成员的特征。
MNIST 数字识别的零训练模型
虽然 MNIST 数字分类通常被认为是一个“简单”的分类基准任务,CNN 模型仍然容易受到敌对攻击。已经取得了进展[Madry,2017],但即使是最好的常规 CNN 模型也仍然很脆弱[Schott,2018]。有人可能会说,其他模型类型已经“解决”了这个问题,例如 Schott 等人的综合分析模型【Schott,2018】。我们从这种模型中获得了有益的见解,但它们在计算上非常昂贵,对于使用 2020 年硬件处理照片级逼真图像的应用程序来说,不太可能是可行的解决方案。
因此,我们有动力在 MNIST 基准测试任务上测试我们的方法。我们首先训练了一个常规(或“基线”)CNN,在未受干扰的 MNIST 测试集上实现了 99%以上的准确率。然后,我们使用快速梯度符号方法(FGSM) [Goodfellow,2014]来创建具有足够噪声(噪声乘数的最小可能值,ε)的对立图像,以便基线模型将数字分类为除未受干扰的源图像之外的数字。示例如下所示。
图 4:从基线传统模型中创建的对立例子。带有红色边框的图像是那些模型犯了错误分类错误的图像,这是所有这些图像的设计。每个图上方的数字依次为:源图像标签、模型预测标签和预测标签的得分(概率)。
然后,我们训练了一个零模型,使用三种类型的零样本——均匀噪声、混合数字图像和平铺混洗图像。在训练期间,所有的空图像都被赋予空目标标签。
图 5:在零模型的训练中使用的零图像的例子。顶行:均匀噪声。中间一行:混合数字图像。底部一行:不同大小的随机图像。
在训练零模型之后,我们在从基线模型创建的敌对图像上测试它。如下图所示,大多数对抗性图像被归类为空图像(即“未分类”),而不是错误地归类为不同的数字。
图 6:从基线模型中创建的对抗性例子,并由空模型评分。图标题如图 4 所示。边框-绿色:数字分类正确。红色:分类错误的数字,蓝色:未分类。
最后,我们评估了一组基线模型和几组零模型的性能(每一组都用不同类型的零样本训练)。我们不是简单地在一组固定的对立例子上进行测试,而是通过将噪声乘数ε的范围从 0 变到 1 来改变扰动的程度。
图 7:基线和模型在一系列扰动下的性能(ε值)。左:零模型训练的混合数字零样本。中间:在混排数位零样本上训练的零模型。右图:在混合和混排样本上训练的空模型。
如上所述,用混合数位零样本(左图)训练的零模型有效地防止了对具有低扰动的图像的错误分类,优先将这样的图像分类为零。相比之下,用混洗数位零样本(中间的面板)训练的模型在防止对具有高扰动的图像的错误分类方面是有效的。当对这两种类型的零样本(右图)进行训练时,不管扰动的大小如何,零模型很少产生错误分类。
结论
在混合数字和混洗数字零样本上训练的 MNIST 零模型很少在扰动图像上犯数字误分类错误,并且它们准确地分类未扰动的数字图像。此外,我们的零训练方法在训练和推理期间都是计算有效的。
然而,应该强调的是,尽管这些图像中的数字很容易被人类识别,但是零模型不能正确地对具有适度扰动的图像进行分类。相反,这些模型用错误分类换取无效分类。这种折衷对于许多应用程序来说可能是完全可以接受的,因为错误分类可能是灾难性的,而空分类可以被忽略或以其他方式处理。
为了开发既能很好地概括又能抵御敌对攻击的模型,还需要额外的研究。一种策略可能是将我们的零训练方法与受损图像训练相结合,最近的研究表明,如果元参数值得到适当调整,就可以提高泛化能力[Rusak,2020]。
使用 ODBC 将任何数据库直接连接到 Jupyter notebook。
嘘,如果你不喜欢 ODBC,也可以选择
无论您是将数据存储在本地还是远程系统中,无论是哪种类型的数据库管理系统, ODBC 都是解决方案。
介绍
ODBC 是一个开放的接口,使用 ODBC 驱动程序可以连接几乎所有的数据库管理系统。ODBC 驱动程序是由数据库管理系统提供的,它有助于连接特定的数据库系统。
因此,当数据科学家或分析师需要在他们喜欢的 Jupyter NB 中快速运行查询时,ODBC 就派上了用场。
现在对于 R 中 ODBC 的连接,你可以跟随其他教程,因为我将只演示 python 实现。
注意除了 ODBC 之外,您可以利用其他的包进行连接,但是大多数包只能连接特定的数据库管理系统。
用于 ODBC 的 python 包: pyodbc
微软 SQL
安装 MS-SQL 驱动程序—仅当数据在远程机器上时才需要安装
根据您的操作系统下载 MS-SQL 驱动
窗户
- 对于 windows,只需点击一下鼠标即可轻松安装。MS-SQL 的 ODBC 在这里可用。
2.安装。msi 文件按照你的位。
Linux — Ubuntu
sudo su
curl [https://packages.microsoft.com/keys/microsoft.asc](https://packages.microsoft.com/keys/microsoft.asc) | apt-key add -#Download appropriate package for the OS version
#Choose only ONE of the following, corresponding to your OS version#Ubuntu 14.04
curl [https://packages.microsoft.com/config/ubuntu/14.04/prod.list](https://packages.microsoft.com/config/ubuntu/14.04/prod.list) > /etc/apt/sources.list.d/mssql-release.list#Ubuntu 16.04
curl [https://packages.microsoft.com/config/ubuntu/16.04/prod.list](https://packages.microsoft.com/config/ubuntu/16.04/prod.list) > /etc/apt/sources.list.d/mssql-release.list#Ubuntu 18.04
curl [https://packages.microsoft.com/config/ubuntu/18.04/prod.list](https://packages.microsoft.com/config/ubuntu/18.04/prod.list) > /etc/apt/sources.list.d/mssql-release.list#Ubuntu 18.10
curl [https://packages.microsoft.com/config/ubuntu/18.10/prod.list](https://packages.microsoft.com/config/ubuntu/18.10/prod.list) > /etc/apt/sources.list.d/mssql-release.list#Ubuntu 19.04
curl [https://packages.microsoft.com/config/ubuntu/19.04/prod.list](https://packages.microsoft.com/config/ubuntu/19.04/prod.list) > /etc/apt/sources.list.d/mssql-release.listexit
sudo apt-get update
sudo ACCEPT_EULA=Y apt-get install msodbcsql17
# optional: for bcp and sqlcmd
sudo ACCEPT_EULA=Y apt-get install mssql-tools
echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bash_profile
echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bashrc
source ~/.bashrc
# optional: for unixODBC development headers
sudo apt-get install unixodbc-dev
Linux — Debian
sudo su
curl [https://packages.microsoft.com/keys/microsoft.asc](https://packages.microsoft.com/keys/microsoft.asc) | apt-key add -#Download appropriate package for the OS version
#Choose only ONE of the following, corresponding to your OS version#Debian 8
curl [https://packages.microsoft.com/config/debian/8/prod.list](https://packages.microsoft.com/config/debian/8/prod.list) > /etc/apt/sources.list.d/mssql-release.list#Debian 9
curl [https://packages.microsoft.com/config/debian/9/prod.list](https://packages.microsoft.com/config/debian/9/prod.list) > /etc/apt/sources.list.d/mssql-release.list#Debian 10
curl [https://packages.microsoft.com/config/debian/10/prod.list](https://packages.microsoft.com/config/debian/10/prod.list) > /etc/apt/sources.list.d/mssql-release.listexit
sudo apt-get update
sudo ACCEPT_EULA=Y apt-get install msodbcsql17
# optional: for bcp and sqlcmd
sudo ACCEPT_EULA=Y apt-get install mssql-tools
echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bash_profile
echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bashrc
source ~/.bashrc
# optional: for unixODBC development headers
sudo apt-get install unixodbc-dev
# optional: kerberos library for debian-slim distributions
sudo apt-get install libgssapi-krb5-2
Linux — RedHat
sudo su#Download appropriate package for the OS version
#Choose only ONE of the following, corresponding to your OS version#RedHat Enterprise Server 6
curl [https://packages.microsoft.com/config/rhel/6/prod.repo](https://packages.microsoft.com/config/rhel/6/prod.repo) > /etc/yum.repos.d/mssql-release.repo#RedHat Enterprise Server 7
curl [https://packages.microsoft.com/config/rhel/7/prod.repo](https://packages.microsoft.com/config/rhel/7/prod.repo) > /etc/yum.repos.d/mssql-release.repo#RedHat Enterprise Server 8
curl [https://packages.microsoft.com/config/rhel/8/prod.repo](https://packages.microsoft.com/config/rhel/8/prod.repo) > /etc/yum.repos.d/mssql-release.repoexit
sudo yum remove unixODBC-utf16 unixODBC-utf16-devel #to avoid conflicts
sudo ACCEPT_EULA=Y yum install msodbcsql17
# optional: for bcp and sqlcmd
sudo ACCEPT_EULA=Y yum install mssql-tools
echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bash_profile
echo 'export PATH="$PATH:/opt/mssql-tools/bin"' >> ~/.bashrc
source ~/.bashrc
# optional: for unixODBC development headers
sudo yum install unixODBC-devel
苹果操作系统
/usr/bin/ruby -e "$(curl -fsSL [https://raw.githubusercontent.com/Homebrew/install/master/install](https://raw.githubusercontent.com/Homebrew/install/master/install))"
brew tap microsoft/mssql-release [https://github.com/Microsoft/homebrew-mssql-release](https://github.com/Microsoft/homebrew-mssql-release)
brew update
brew install msodbcsql17 mssql-tools
安装 python 的 pyodbc — ODBC 包
- 安装 pyodbc 包。
pip install pyodbc
2.要检查驱动程序是否安装正确,请找到所有连接到 pyodbc 的驱动程序。
import pyodbc
pyodbc.drivers()
对于 MS-SQL,它将导致
['ODBC Driver 17 for SQL Server']
随着更多的驱动程序添加到您的系统,更多的驱动程序将被添加到列表中。
连接到数据库
- 用于远程连接。
# enter ip address and port number of the system where the database resides.
server = 'tcp:31.288.186.65,49170'
database = 'database_name' # enter database name
username = 'user_name'
password = 'pass_word' # add appropriate driver name
cnxn = pyodbc.connect('DRIVER={ODBC Driver 17 for SQL Server};SERVER='+server+';DATABASE='+database+';UID='+username+';PWD='+ password)cursor = cnxn.cursor()
2.用于本地连接(如果数据在本地计算机中)。
server = 'tcp:31.288.186.65,49170'
database = 'database_name' # enter database namecnxn = pyodbc.connect('DRIVER={SQL Server};SERVER='+server+';DATABASE='+database+';Trusted_Connection=yes;')cursor = cnxn.cursor()
查询数据库
- 你可以在你的笔记本上查询数据库,即选择、插入、更新或删除。
您的查询可以直接转换为熊猫数据框架。
import pandas as pd# select command
query = ''' SELECT RecordID FROM tables''';
data = pd.read_sql(query, cnxn)
data.head()
替代品
或者,您可以使用 pymssql ,它的工作原理相同,但已经停止使用。不过,如果你想使用它,你必须安装
pip install "pymssql<3.0"
神谕
Oracle 也支持 ODBC 数据库。你需要根据你的操作系统为 oracle 安装一个 ODBC 驱动。
- 安装 Oracle ODBC 客户端。
从 oracle 网站下载 instant client basic 和 odbc 包。
2.如果下载的包是 zip,使用 wget 解压或者 rpm 获取 rpm 文件。
3.设置 ORACLE_HOME 和 LD_LIBRARY_PATH
export ORACLE_HOME=/usr/lib/oracle/12.2/client64
export LD_LIBRARY_PATH=/usr/lib/oracle/12.2/client64/lib
根据您的版本更改路径“/12.2/”。
4.在/etc/odbcinst.ini 集中:
[oracle_db]
Description=Oracle ODBC driver for Oracle 12c
Driver=/usr/lib/oracle/12.2/client64/lib/libsqora.so.12.1
FileUsage=1
Driver Logging=7
UsageCount=1
5.现在,打开 Jupyter NB,您可以轻松连接到您的 oracle 数据库。
import pyodbc
conn = pyodbc.connect('DRIVER={oracle_db};Host=1.1.1.1;Port=1521;Service Name=orcl.local;User ID=test1;Password=test1')
替代品
或者,您可以使用 Cx_Oracle ,它的工作方式类似,但只适用于 Oracle 数据库。
我的 SQL
- 对于 MySQL,它与上面的配置非常相似,您需要安装合适的 MySQL 驱动程序。
import pyodbc
conn = pyodbc.connect('DRIVER {MySQL};SERVER=localhost;DATABASE=test;UID=root;PWD=abc;')
替代品
对于 Mysql,有许多包可以连接到 python,比如
- MySQLdb
- mysql.connector
MONGO 数据库
- 对于 MongoDB ,安装 MongoDB 驱动并连接到 pyodbc。
可供选择的事物
或者,您可以使用 pymongo 来连接 python。
结论
根据连接性,有多个包来连接数据库,但是如果您只想要一个通用包, pyodbc 就是您想要的包,它使用 ODBC 接口来访问独立于数据库系统的数据。
此外,对于任何澄清,评论,帮助,赞赏或建议,只需张贴在评论中,我会帮助你。
如果你喜欢,你可以跟着我。
阅读 Gaurav Chauhan 在媒体上的文章。hopscotch.health 的数据科学家,每天,Gaurav Chauhan 和数千人…
medium.com](https://medium.com/@gauravc2708)
此外,如果你想要更多基于数据科学、人工智能或数据管道的内容,可以在我的社交媒体上连接我。
使用 Pandas 方法链接提高代码可读性
熊猫方法链接的最佳实践教程
我们一直在讨论使用 Pandas 管道函数来提高代码可读性。在本文中,我们来看看熊猫方法链接。
在数据处理中,经常需要对某一行或某一列执行操作以获得新的数据。而不是写作
df = pd.read_csv('data.csv')
df = df.fillna(...)
df = df.query('some_condition')
df['new_column'] = df.cut(...)
df = df.pivot_table(...)
df = df.rename(...)
我们能做的
(pd.read_csv('data.csv')
.fillna(...)
.query('some_condition')
.assign(new_column = df.cut(...))
.pivot_table(...)
.rename(...)
)
方法链接在 Pandas 中一直可用,但是通过添加新的“可链接”方法,对链接的支持增加了。例如query()
、assign()
、pivot_table()
,特别是[pipe()](/using-pandas-pipe-function-to-improve-code-readability-96d66abfaf8)
、,用于在方法链中允许用户自定义方法。
方法链是一种编程风格,它按顺序调用多个方法调用,每个调用对同一个对象执行一个操作并返回该操作。
它消除了在每个中间步骤命名变量的认知负担。 Fluent 接口,一种创建面向对象 API 的方法依赖于方法级联(又名方法链)。这类似于 Unix 系统中的管道。
阿迪亚曼·科尔提
方法链接大大增加了代码的可读性。让我们深入教程,看看它如何提高我们的代码可读性。
源代码请访问我的 Github 笔记本。
数据集准备
在本教程中,我们将处理来自 Kaggle 的泰坦尼克号数据集。这是一个非常著名的数据集,通常是学生学习数据科学的第一步。让我们导入一些库并加载数据来开始。
import pandas as pd
import sys
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline
%config InlineBackend.figure_format = 'svg'**df = pd.read_csv('data/train.csv')**
df.head()
我们将 train.csv 文件加载到熊猫数据帧中
泰坦尼克号数据预览
来自 Kaggle 的数据字典
让我们从检查缺失值开始。我们可以使用 seaborn 创建一个简单的热图来查看哪里缺少值
sns.heatmap(**df.isnull()**,
yticklabels=False,
cbar=False,
cmap='viridis')
缺失值的 seaborn 热图输出
年龄 , 船舱 , 上船 有缺失值。 年龄 缺失的比例很可能小到足以用某种形式的插补进行合理替换。看着 舱 栏,好像缺了很多值。 着手 缺失的比例很小。
工作
假设我们被要求看一看从南安普顿出发的乘客,并计算出不同年龄组和p 等级 的存活率。
让我们把这项任务分成几个步骤,一步一步地完成。
- 数据清理:用某种形式的插补替换缺失的 年龄
- 选择从南安普敦出发的乘客
- 将年龄转换为年龄范围组:≤12 岁、青少年(≤ 18 岁)、成人(≤ 60 岁)和老年人(> 60 岁)
- 创建一个数据透视表来显示不同年龄组的存活率和 Pclass
- 通过重命名轴标签和格式化值来改进数据透视表的显示。
酷,让我们继续使用熊猫方法链接来完成它们。
1.用某种形式的插补替代缺失的年龄
正如在数据准备中提到的,我们希望用某种形式的插补来替换缺失的 年龄 。一种方法是填写所有乘客的平均年龄。然而,我们可以更聪明地处理这个问题,按乘客级别检查平均年龄。例如:
sns.boxplot(x='Pclass',
y='Age',
data=df,
palette='winter')
我们可以看到,在较高的阶层中,较富裕的乘客往往年龄较大,这是有道理的。我们将根据年龄的 Pclass 使用这些平均年龄值进行估算。
pclass_age_map = {
**1: 37,
2: 29,
3: 24,**
}def replace_age_na(x_df, fill_map):
**cond=x_df['Age'].isna()
res=x_df.loc[cond,'Pclass'].map(fill_map)
x_df.loc[cond,'Age']=res** return x_df
x_df['Age'].isna()
选择 年龄 栏,检测缺失值。然后,x_df.loc[cond, 'Pclass']
用于有条件地访问 Pclass 值,并调用熊猫[map()](/introduction-to-pandas-apply-applymap-and-map-5d3e044e93ff)
将每个值替换为另一个值。最后,x_df.loc[cond, 'Age']=res
有条件地用res
替换所有缺失的年龄值。
运行以下代码
res = (
pd.read_csv('data/train.csv')
**.pipe(replace_age_na, pclass_age_map)**
)res.head()
应根据年龄的 Pclass 替换所有缺失的年龄。让我们通过运行res
上的热图来检查一下。
sns.heatmap(**res.isnull()**,
yticklabels=False,
cbar=False,
cmap='viridis')
太好了,成功了!
2.选择从南安普敦出发的乘客
根据泰坦尼克号数据字典,从南安普顿出发的乘客应该是乘坐 登上 ,价值S
。让我们使用 Pandas query()
函数进行查询。
res = (
pd.read_csv('data/train.csv')
.pipe(replace_age_na, pclass_age_map)
**.query('Embarked == "S"')**
)res.head()
为了评估查询结果,我们可以使用value_counts()
进行检查
res.Embarked.value_counts()**S 644**
Name: Embarked, dtype: int64
3.将年龄转换为年龄范围组:≤12 岁、青少年(≤ 18 岁)、成人(≤ 60 岁)和老年人(> 60 岁)
我们在文章 Pandas pipe function 中使用了一个自定义函数。或者,我们可以使用 Pandas 内置函数assign()
向 DataFrame 添加新列。让我们从assign()
开始。
**bins=[0, 13, 19, 61, sys.maxsize]
labels=['<12', 'Teen', 'Adult', 'Older']**res = (
pd.read_csv('data/train.csv')
.pipe(replace_age_na, pclass_age_map)
.query('Embarked == "S"')
**.assign(ageGroup = lambda df: pd.cut(df['Age'], bins=bins, labels=labels))**
)res.head()
熊猫assign()
用于创建新列 年龄组 。新列是用 lambda 函数和 Pandas cut()
一起创建的,用于将年龄转换为范围组。
通过运行代码,我们应该得到如下输出:
4.创建一个数据透视表来显示不同年龄组和p 类 的存活率
数据透视表让我们能够洞察我们的数据。让我们用它来计算存活率。
bins=[0, 13, 19, 61, sys.maxsize]
labels=['<12', 'Teen', 'Adult', 'Older'](
pd.read_csv('data/train.csv')
.pipe(replace_age_na, pclass_age_map)
.query('Embarked == "S"')
.assign(ageGroup = lambda df: pd.cut(df['Age'], bins=bins, labels=labels))
**.pivot_table(
values='Survived',
columns='Pclass',
index='ageGroup',
aggfunc='mean')**
)
第一个参数values='Survived'
指定要聚合的幸存列。由于存活的值是1
或0
,我们可以使用聚合函数mean
来计算存活率,因此使用了aggfunc='mean'
。index='ageGroup'
和columns='Pclass'
会在输出表中将 年龄组 显示为行,将 Pclass 显示为列。
通过运行代码,我们应该得到如下输出:
5.通过重命名轴标签和格式化值来改进数据透视表的显示。
到目前为止,我们得到的结果不是很清楚。让我们继续改进显示。
bins=[0, 13, 19, 61, sys.maxsize]
labels=['<12', 'Teen', 'Adult', 'Older'](
pd.read_csv('data/train.csv')
.pipe(replace_age_na, pclass_age_map)
.query('Embarked == "S"')
.assign(ageGroup = lambda df: pd.cut(df['Age'], bins=bins, labels=labels))
.pivot_table(
values='Survived',
columns='Pclass',
index='ageGroup',
aggfunc='mean')
**.rename_axis('', axis='columns')
.rename('Class {}'.format, axis='columns')
.style.format('{:.2%}')**
)
rename_axis()
用于清除列标签。之后,rename('Class {}'.format, axis='columns')
用于格式化列标签。最后,style.format('{:.2%}')
用于将值格式化为带两位小数的百分比。
通过运行代码,我们应该得到如下输出
性能和缺点
在性能方面,根据 DataSchool [2],方法链提前告诉 pandas 所有事情,因此 pandas 可以更有效地计划其操作,因此它应该比常规方法有更好的性能。
方法链接可读性更好。然而,一个非常长的方法链可能可读性较差,特别是当在链中调用其他函数时,例如,在我们的教程中,在assign()
方法中使用了cut()
。
此外,使用方法链的一个主要缺点是调试会更加困难,尤其是在非常长的链中。如果最后看起来有问题,你没有中间值来检查。
关于这个主题的更长的讨论,请参见汤姆·奥格斯伯格的 方法链接帖子【1】。
好了
感谢阅读。
请在我的 Github 上查看笔记本的源代码。
如果你对机器学习的实用方面感兴趣,请继续关注。
最后,这里有两篇你可能感兴趣的相关文章
参考
- [1]Tom Augspurger 的方法链接【https://tomaugspurger.github.io/method-chaining.html T2
- [2] 来自 DataSchool.io 的熊猫未来
使用 Pandas 管道函数提高代码可读性
熊猫最佳实践的直观指南pipe()
在数据处理中,经常需要编写一个函数对某一行或某一列进行运算(如统计计算、拆分或代入值)以获得新的数据。
而不是写作
# f(), g(), and h() are user-defined function
# df is a Pandas DataFramef(g(h(df), arg1=a), arg2=b, arg3=c)
我们可以写作
(df.pipe(h)
.pipe(g, arg1=a)
.pipe(f, arg2=b, arg3=c)
)
熊猫从 0.16.2 版本开始引入pipe()
。pipe()
在方法链中启用用户定义的方法。
方法链接是一种编程风格,它依次调用多个方法调用,每个调用在同一个对象上执行一个动作并返回它。
它消除了在每个中间步骤命名变量的认知负担。 Fluent 接口,一种创建面向对象 API 的方法依赖于方法级联(又名方法链)。这类似于 Unix 系统中的管道。
方法链接大大增加了代码的可读性。让我们深入教程,看看它如何提高我们的代码可读性。
数据集准备
在本教程中,我们将处理来自 Kaggle 的泰坦尼克号数据集。这是一个非常著名的数据集,通常是学生学习数据科学的第一步。让我们导入一些库并加载数据来开始。
import pandas as pd
import sys
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline
%config InlineBackend.figure_format = 'svg'**def load_data():
return pd.read_csv('data/train.csv')**df = load_data()
df.head()
我们创建了一个函数load_data()
来将 train.csv 文件加载到 pandas DataFrame 中。
泰坦尼克号数据预览
数据字典来自 Kaggle
让我们从检查缺失值开始。我们可以使用 seaborn 创建一个简单的热图来查看哪里缺少值
sns.heatmap(**df_train_raw.isnull()**,
yticklabels=False,
cbar=False,
cmap='viridis')
缺失值的 seaborn 热图输出
年龄船舱有缺失值。 年龄 缺失的比例很可能小到足以用某种形式的插补进行合理替换。看着 舱 栏,好像缺了很多值。 着手 缺失的比例很小。
任务
假设我们被要求完成以下任务
- 将 名 拆分为名和名
- 对于 性别 ,用 M 替换值公*,用 F 替换值*母**
- 用某种形式的插补替换缺失的 年龄
- 将年龄转换为年龄范围组:≤12 岁、青少年(≤18 岁)、成人(≤60 岁)和老年人(> 60 岁)。
让我们继续使用pipe()
一步一步地完成它们,
1.将姓名分为名和姓
让我们创建一个函数split_name()
,它接受一个数据帧作为输入并返回一个数据帧。
*def split_name(x_df):
def split_name_series(string):
firstName, secondName=string.split(', ')
return pd.Series(
(firstName, secondName),
index='firstName secondName'.split()
) # Select the Name column and apply a function
**res=x_df['Name'].apply(split_name_series)**
x_df[res.columns]=res
return x_df*
x_df['Name']
选择 名称 列(在熊猫中称为一个系列),熊猫[apply()](/introduction-to-pandas-apply-applymap-and-map-5d3e044e93ff)
用于对系列的值应用split_name_series()
函数。
运行以下代码
*res=(
load_data()
**.pipe(split_name)**
)res.head()*
我们应该得到如下输出:
split_name()
的结果
2.对于 性别 ,用 M 替代值男用女*用 F*
让我们创建一个函数substitute_sex()
,它接受一个数据帧作为输入并返回一个数据帧。
*def substitute_sex(x_df):
mapping={'male':'M','female':'F'}
x_df['Sex']=df['Sex'].map(mapping)
return x_df*
x_df['Sex']
选择性别列,然后选择熊猫[map()](/introduction-to-pandas-apply-applymap-and-map-5d3e044e93ff)
,用于将序列中的每个值替换为另一个值。
运行以下代码
*res=(
load_data()
.pipe(split_name)
**.pipe(substitute_sex)**
)res.head()*
我们应该得到如下输出:
substitute_sex()
的结果
3.用某种形式的插补替换缺失的 年龄
我们想用某种形式的插补来代替缺失的年龄。一种方法是填写所有乘客的平均年龄。然而,我们可以更聪明地处理这个问题,按乘客级别检查平均年龄。例如:
*sns.boxplot(x='Pclass',
y='Age',
data=df,
palette='winter')*
我们可以看到,在较高的阶层中,较富裕的乘客往往年龄较大,这是有道理的。我们将根据年龄的 Pclass 使用这些平均年龄值进行估算。
*pclass_age_map = {
**1: 37,
2: 29,
3: 24,**
}def replace_age_na(x_df, fill_map):
**cond=x_df['Age'].isna()
res=x_df.loc[cond,'Pclass'].map(fill_map)
x_df.loc[cond,'Age']=res** return x_df*
x_df['Age'].isna()
选择 年龄 列,检测缺失值。然后,x_df.loc[cond, 'Pclass']
用于有条件地访问 Pclass 值,并调用熊猫[map()](/introduction-to-pandas-apply-applymap-and-map-5d3e044e93ff)
将每个值替换为另一个值。最后,x_df.loc[cond, 'Age']=res
有条件地用res
替换所有缺失的年龄值。
运行以下代码
*res=(
load_data()
.pipe(split_name)
.pipe(substitute_sex)
**.pipe(replace_age_na, pclass_age_map)**
)
res.head()*
应根据年龄的 Pclass 替换所有缺失的年龄。让我们通过运行res
上的热图来检查一下。
*sns.heatmap(**res.isnull()**,
yticklabels=False,
cbar=False,
cmap='viridis')*
太好了,成功了!
4.将年龄转换为年龄范围组:≤12 岁、青少年(≤18 岁)、成人(≤60 岁)和老年人(> 60 岁)
让我们创建一个函数create_age_group()
,它接受一个数据帧作为输入并返回一个数据帧。
*def create_age_group(x_df):
bins=[0, 13, 19, 61, sys.maxsize]
labels=['<12', 'Teen', 'Adult', 'Older']
**ageGroup=pd.cut(x_df['Age'], bins=bins, labels=labels)**
x_df['ageGroup']=ageGroup
return x_df*
pd.cut()
用于将年龄转换为年龄范围组。
运行以下代码
*res=(
load_data()
.pipe(split_name)
.pipe(substitute_sex)
.pipe(replace_age_na, pclass_age_map)
**.pipe(create_age_group)**
)res.head()*
我们应该得到如下输出:
create_age_group()
的结果
好了
感谢阅读。
请在我的 Github 笔记本上查看源代码。
如果你对机器学习的实用方面感兴趣,请继续关注。
最后,这里有两篇你可能感兴趣的相关文章
利用词性分析电影评论
自然语言处理变得简单
惊讶于 POS 在数据分析中的强大功能。
马库斯·斯皮斯克在 Unsplash 上的照片
当我们谈论自然语言处理(NLP)时,很容易想到它在机器学习中的应用,特别是情感分析。像“这篇评论是正面的还是负面的?”,或者“这条推文到底讽刺不讽刺?”非常常见,所以我们的注意力通常集中在实际方面,比如使用哪种算法,或者如何在令牌中转换单词,等等。但是,当涉及到纯数据分析时,如何使用 NLP 来获得关于我们的数据的见解呢?
顺便说一句,如果你是 NLP 世界的初学者,你可能会喜欢在这里查看这个主题的简介。
在本文中,我们将使用最流行的 NLP 库之一 spacy 来执行文本分析并生成一些见解。包含所有代码和注释的笔记本在这里等着你。
什么是词性?
spacy 最强大的工具之一是它能够使用复杂的统计模型预测一个词在给定上下文中的功能。
词性指的是根据一个词的句法功能对其进行分类,比如它是动词还是名词等等。我们可以看看下面的例子:
来源:https://spacy.io/usage/linguistic-features
POS 标签及其描述的完整列表可在文档中找到。
关于数据
数据来自 IMDB 电影评论数据集,可在 kaggle 上获得。让我们检查第一行:
来源:作者
我们这里有关于评论的标签情绪:
- 1 =正面评价;
- 0 =负面评价。
然后我们有评论本身的文本。简单吧?
创建 NLP 模型
为了创建 nlp 对象,我们导入 SpaCy English 模型。简而言之,nlp 对象是 spacy 工作的核心,因为它执行繁重的任务,如将单词转换成记号、词条等。
我们还将导入英语停用词和一个带标点符号的列表。停用词是对我们的分析没有意义的常见词,如“the”和“an”。我们将使用这些对象来过滤我们想要保留的单词。
来源:作者
仅从评论中选择形容词
当人们写评论时,他们通常用词来描述他们如何喜欢或不喜欢某事,这些词被称为 形容词。 因此,使用几行代码,我们将创建一个循环,它将:
- 迭代每个评论,使用 spacy 删除停用词和标点符号,然后预测并保存形容词到列表中;
来源:作者
然后,我们需要将这个列表转换成一个新列,返回到我们的数据框:
来源:作者
最后,使用一些 python 基础知识,我们可以选择正面和负面评论中最常用的形容词:
来源:作者
而且,当我们谈论数据分析时,为什么不在一些漂亮的图中展示我们的发现呢?
来源:作者
使用词类过滤器,我们发现“好”是正面评论中第一个最常见的形容词,但令人惊讶的是,它是负面评论中第二个最常见的形容词。这是一个有趣的发现,可以进一步研究,如根据情绪使用二元模型来发现“好”之前和之后使用的常用词,以及它为其他分析打开了大门,这些分析可以提供有价值的信息来构建报告和仪表板。
本文的目标是向您展示如何将 POS 用于数据分析,展示我们在学习 NLP 时所学概念的实际应用。我希望你喜欢,请在评论中让我知道你可能会发现的 POS 的其他用途。感谢阅读!
利用 PIL 图像进行图像变换
在 Python 中轻松调整图像大小和连接图像
照片由 Hitesh Choudhary 在 Unsplash 上拍摄
我最近在做一个项目,涉及操纵图像,以便我们可以在其上运行无监督的机器学习算法。作为一个 Python 爱好者,我决定使用 Python 和它的库来为我操作,而不是走 Photoshop 路线。
在这篇文章中,我强调了我如何使用PIL
中的Image
子模块来创建我的项目所需的图像。
收集图像
我为这篇文章拍摄了两张图片,都来自 Unsplash。我特别使用了不同尺寸的图像,这样我们就可以了解不同尺寸的图像是如何相互作用的。
图片由 Ivars Utināns 提供
图片来自塞尔吉奥·索萨
请注意,第一幅图像较宽,而第二幅图像高度较长。把它们结合起来我们会很开心。
图片 1:https://unsplash.com/photos/DujBHPRqAKk
图片 2:https://unsplash.com/photos/JSye5r1eAVY
我通常使用 unsplash.com T21,因为它有大量各种各样的高质量图像,可以免费获取。显然,不要忽略对艺术家的适当肯定。是好的伦理!!
安装库
我在这里使用的是 Python 3.6,但是 Python 3.5 和更高版本的所有版本都应该可以正常工作。
pip install Pillow
pip install matplotlib
Pillow
是包含我们的子模块Image
的 PIL 包。我还决定安装matplotlib
,因为它让在 Jupyter 笔记本上显示图像变得更加容易,不管图像的大小如何。例如,当我试图使用Image
直接显示图像时,它打开了一个新窗口,图像全部放大。
如果你想在 Jupyter 笔记本中这样做,使用pip install jupyter
安装它,启动 Jupyter 服务器并创建一个新的笔记本。
加载库
我们现在将加载已安装的库。我们需要来自PIL
的Image
子模块和来自matplotlib
的pyplot
子模块,我们将以名称plt
加载它们。你可以给它起任何名字,也可以什么都不用,用plt
只是惯例。
from PIL import Image
import matplotlib.pyplot as plt
加载图像
使用Image.open()
,我们可以简单地将图像加载到笔记本中(或者 Python 文件,如果你正在使用的话)。我们将在变量img1
中保存第一幅图像,在变量img2
中保存第二幅图像。
img1 = Image.open("image.jpg")
img2 = Image.open("image2.jpg")
要查看第一幅图像,我们只需使用以下命令:
plt.imshow(img1)
imshow()
方法显示 Jupyter 笔记本中的图像,如下图所示:
plt.imshow 的结果(img1)
我们可以重复同样的操作来查看第二个图像,只需替换上面命令中变量的名称。
调整图像大小
使用Image
调整图像大小非常非常简单。我们只是使用resize()
方法,并为最终调整后的图像定义一个元组(宽度,高度)。在下面的代码中,我将图像 1 的大小调整为 1200x800,然后打印新旧图像的大小,最后显示结果图像。
img1_resized = img1.resize((1200, 800))
print("Original image size: {}".format(img1.size))
print("Resized image size: {}".format(img1_resized.size))
plt.imshow(img1_resized)## Output
# Original image size: (5464, 3640)
# Resized image size: (1200, 800)
已调整大小的图像
查看水平轴和垂直轴,您会看到新图像的大小现在调整为 1200x800。
水平连接
在我的项目中,我必须水平地一个接一个地组合几幅图像。这个想法是让图像具有相同的高度,但宽度增加。一旦我开始寻找可能的解决方案,我就能够编写出有效的代码。然而,与我项目中的正方形图像不同,我很好奇是否可以组合不同大小的图像并生成可用的图像。经过仔细的实验,我找到了一个适用于不同尺寸图像的解决方案。
result_img = Image.new('RGB', (img1.width + img2.width, min(img1.height, img2.height)))
result_img.paste(img1, (0, 0))
result_img.paste(img2, (img1.width, 0))
plt.imshow(result_img)
我首先使用Image.new()
创建一个新图像。第一个参数声明我正在创建一个 RGB 图像,第二个参数是最终图像的宽度和高度。显而易见,当两张图片并排放置时,它们的宽度会简单地相加。然而,由于两者的高度不同,使用min()
方法会将组合图像缩小到适当的高度,因此没有空白。
然后我粘贴从(0,0)
开始的第一张图片和到(img1.width, 0)
结束的第二张图片。生成的图像如下:
水平组合两幅图像
如果我们用max()
方法替换min()
方法,我们将在原本没有像素的地方得到黑色。在上面的代码中,如果我们使用max()
,得到的图像应该是:
水平组合两幅图像(使用 max())
垂直连接
就像水平拼接一样,我们也可以垂直组合图像。为此,总高度将是两个图像的高度之和,而宽度将是两个图像中的最小值。
result_img = Image.new('RGB', (min(img1.width, img2.width), img1.height + img2.height))
result_img.paste(img1, (0, 0))
result_img.paste(img2, (0, img1.height))
plt.imshow(result_img)
垂直组合图像
这里,我们将在(0,0)
再次粘贴图像 1,但是第二个图像将被放置在(0, img1.height)
。同样,我们在这里使用了min()
方法来代替max()
方法。
保存图像
最后,一旦所有的转换完成,我们可以保存图像。如果我们将图像设为result_img
,命令将会是:
result_img.save("new_image.jpg")
这里,新图像将以名称new_image.jpg
保存。
本文到此为止。如果你有建议或问题,请在评论中告诉我。
使用预训练的深度卷积神经网络对新冠肺炎 CT 扫描进行二值分类
作者:劳纳克·索德,斯蒂芬·汉弗莱斯博士。
马萨诸塞州阿克顿-博克斯伯勒地区高中高年级学生;国家犹太健康定量成像实验室高中实习生
放射学助理教授;科罗拉多州丹佛市国家犹太健康定量成像实验室主任。
介绍
起源于中国武汉的高毒性新冠肺炎病毒导致了全球性的疫情,影响了许多人的生活。截至本文撰写之时,全球已有超过 1160 万人被诊断感染该病毒,超过 53.9 万人死亡。许多人群正在研究不同的方法来诊断新冠肺炎:RT-PCR(逆转录聚合酶链式反应)测试,抗体测试和 CT 扫描。虽然 RT-PCR 是 CDC 推荐的新冠肺炎诊断方法,但测试可能需要两天才能完成。此外,抗体测试只有在患者对病毒产生免疫力后才有用。使用 CT 扫描进行诊断是有希望的,尽管研究表明它对于单一的诊断测试是不可靠的。
然而,最近的研究已经使用人工智能来诊断新冠肺炎感染的肺部图像,以增强放射科医生的分析。在 Trehan⁴和 Markevych⁵撰写的文章中,他们使用 Tensorflow 库从头构建的卷积神经网络分别对 x 射线和 CT 图像进行分类。然而,在这篇文章中,我将使用迁移学习,使用 VGG 和雷斯内特等最先进的模型来分类新冠肺炎阳性和阴性肺部 CT 扫描。此外,我将与 PyTorch 合作。
项目工作流程
数据
我使用了来自大 Challenge⁶新冠肺炎 CT 的开源数据集,这是一组超过 750 个 PNG 的肺部 CT 图像,其中大约一半是新冠肺炎阳性的。
让我们来看一些示例图片。
数据集中的示例图像
你也许能分辨出右边的图像是新冠肺炎阳性的。为什么?因为左叶浑浊的白色区域是磨玻璃样阴影(GGO)的一个例子,这是在肺部 CT 中识别新冠肺炎的关键特征之一。正如你在右边的图片上看到的,没有这种毛玻璃外观的痕迹。注意包含毛玻璃特征的肺部 CT 图像并不总是新冠肺炎阳性;还有其他疾病,如感染性疾病、间质性肺病和急性肺泡疾病,在肺部 CT scans⁷.中也显示 GGO 但是,在这种情况下,这不应该是一个问题,因为这是一个二元分类问题。此外,并不是所有的图像都这么容易识别;例如,让我们看看下面的图像。
你怎么想呢?结果,它是新冠肺炎阳性的,因为两个肺叶都有 GGO。这不是一个更难的例子吗?让我们看看卷积神经网络是否可以更好地对这些图像进行分类。
数据预处理
在开始训练模型之前,我们首先要完成一些预处理步骤。
- 读入图像并给它们贴上标签
- 调整图像大小
- 将数据分成训练集、验证集和测试集
- 将图像转换为 PyTorch 张量
- 添加图像增强
那我们开始吧。注意,本文中的所有代码都可以在我的 GitHub 新冠肺炎分类库中找到。
目录结构:
丙:。/图像-已处理-新建
|————CT _ COVID
|————CT _ non vid
我们首先导入预处理所需的库。然后,对于每个目录中的每个图像,我们读入具有 RGB 通道的图像,将图像的大小调整为(224,224)并将图像及其相关联的标签附加到列表 training_data。我们必须调整图像的大小,因为我们稍后将使用的迁移学习模型只接受这种形状的图像。然后我们标记图像,1 代表 COVID 阳性,0 代表 COVID 阴性。注意:tqdm 库用于创建一个进度条。
现在,我们必须将数据分为训练集、验证集和测试集,并将它们转换为 PyTorch 张量。
让我们把这段代码分成几个部分。我创建了一个分割大小,它是整个数据的 10%,因此训练集是数据的 80%(大约 600 张图像),测试和验证集各占总数据的 10%(大约 75 张图像)。然后,对于每个集合,我使用列表理解将原始数据分解为图像(X)和标签(y)。然后,我将每个数组转换为 PyTorch 张量,并将其重新整形为格式(图像数量、通道数量、图像宽度、图像高度)。这种形状格式是 PyTorch 模型接受张量数据的方式。最后,我对图像的像素强度进行归一化。PNG 图像的像素强度在 0 到 255 之间;然而,在训练过程之前,它们需要在 0 和 1 之间被标准化。
在 PyTorch 中,数据加载器用于创建成批的训练图像,并对图像应用变换。因此,我们必须将我们的代码包装到一个数据集类中,我们可以将该数据集类与任何相关的转换一起提供给 DataLoader 对象。
init 方法本质上与上面的代码相同,都被格式化以适合数据集类。len 和 getitem 函数必须被覆盖,以指定如何访问我们的图像。我使用“数据”参数来指定集合是训练、验证还是测试。你可以在 PyTorch 文档中读到更多关于 Dataset 类的内容。
最后,我们必须创建数据加载器对象以及可以应用于图像的转换。
我使用了 Monai ,一个基于 PyTorch 构建的医学成像库,用于图像转换。LoadPNG()、AddChannel()和 ToTensor()转换对于将原始 PNG 图像转换为机器可学习的张量至关重要。对于训练集,我们还使用 ScaleIntensity()来确保图像强度是归一化的;RandRotate()、RandFlip()和 RandZoom()用于几何变换。RandGaussianNoise(),顾名思义,就是给图像添加噪点。这些变换模拟真实世界的 CT 图像,因为它们有时可以以不同的方式变形,并且图像中可能存在噪声。总的来说,这些图像增强技术将使模型更健壮,并可推广到低质量的图像。对于验证和测试集,我只添加了前三个转换,因为我不想修改测试集。
接下来,我实例化了 Dataset 类,并将其传递给 DataLoader 对象。我最初将批处理大小设置为 32,但是我们将在本文后面处理这个值并查看结果。我们可以查看数据集中的一些图像,看看这些图像进入模型后是什么样子。
与预处理前的原始图像相比,图像略有改变。
太好了!我们完成了预处理步骤,并准备使用迁移学习来开发我们的模型。
模型开发
我用迁移学习进行模型开发。我测试了三种不同深度的不同型号的性能:VGG-16⁹、VGG-19⁹和⁰. resnet-34 这三个模型都在 ImageNet16 数据集(1400 万个图像数据集,包含 1000 个不同的类)上进行训练,获得了最先进的精度。
第一步是决定是否要在 GPU 上训练。我有一个 Nvidia GeForce GTX 本地 GPU,所以我打算将该模型分配给 GPU。
注意:如果您的本地设备上没有 CUDA,设置 GPU 可能是一个非常复杂的过程。我强烈推荐你阅读这篇文章,了解一个非常简单的设置过程。它为 Tensorflow 设置了一个 CUDA 环境,但它与 PyTorch 的工作方式相同。
然后,我必须“冻结”模型中的所有权重。如果你想了解 PyTorch 中迁移学习的更多信息,请参考这篇文章。这意味着我使权重不可训练,所以它们不会通过梯度下降来更新。因此,我遍历模型参数并将 requires_grad 设置为 false。
以下是代码:
最后,我创建了一系列可训练层来替换模型中的最终分类层。最初的模型有 1000 个类,但是这是一个二进制分类问题,所以我需要以两个类的输出结束。
我使用了 torch.nn 库中的顺序模型。这个模型的好处是它允许输入一个字典。所以,我可以给每一层标上一个名字,以便跟踪不同的层。我选择使用 4 层,以允许从数据中学习足够的可训练参数。当然,这是一个可以调整到任何程度的超参数。此外,每层中神经元的数量是另一个可变的超参数。通常,你可能会看到 2 的幂被用来表示神经元的数量,所以我坚持使用它。最后,你可能会注意到,我在每一层之间使用了 dropout 正则化。这防止了模型过度拟合训练数据,这将导致验证和测试数据的精确度降低。
转移学习工作流
如前所述,我比较了三种最先进的模型。冻结权重和替换最终分类层的过程对于所有三个模型都是相似的,所以我在这里不打算展示。
现在我们的模型已经准备好进行训练了。
培养
与 Tensorflow 和 Keras 库不同,我们必须用 PyTorch 编写自己的训练循环。但是,这实际上是一个直观的过程,所以我们开始吧。
我将定义一个名为 train 的方法,它接受模型、训练加载器、验证加载器、优化器、损失函数、时期数以及潜在的学习率调度器的参数。该函数将训练模型并返回四个列表:训练准确度、训练损失、验证准确度和验证损失的列表。
该方法是这样开始的:
然后,我们循环遍历历元数(模型通过数据的次数),并初始化变量来跟踪损失和准确性度量(这在本文的后面会很重要)。
然后,我们遍历训练数据加载器中的图像和目标类,找到模型的预测,将模型的预测与实际情况进行比较(计算损失函数),并相应地更新权重。
我还写了一个更小的循环来计算正确预测的数量和总预测,这样我们就可以计算精度了。
接下来,我们将模型设置为验证模式,并计算验证损失和准确性。该过程类似于训练步骤,除了模型不应该在该步骤中学习。这意味着我们不会更新该数据的权重。我创建了一个名为 validation 的辅助函数来计算验证损失和准确性。我没有在这里展示,但是你可以在我的 GitHub 上看到完整的代码。
我们在每个时期之后打印出损失和准确性度量,并将这些度量附加到列表中。您可能想知道为什么我将所有的度量存储在一个列表中。当我们查看损耗和精度曲线时,这将派上用场。
现在我们可以使用该方法来训练我们的模型。
对于这次试验,我使用了二元交叉熵损失和 Adam 优化,因为这些是二元分类任务的标准。我还训练了 30 个时期的模型,因为以前的尝试表明,损失在这个点附近稳定下来。
这是 30 个时期后的输出:
结果
我对 VGG16、VGG19 和 ResNet-34 进行了不同数量的历元、批量大小和学习率调度程序的培训。这些是每个预训练网络收到的最佳模型。
讨论
在我们深入分析模型的性能之前,让我们更仔细地看看循环学习率调度。根据 Smith(最初的循环学习率论文的作者)的说法,这种类型的学习率调度背后的思想是允许学习率在一定范围内循环变化,而不是系统地增加或减少它。
循环学习率可视化
我使用循环学习率调度,因为它比我尝试的其他调度程序更容易调优,并且显示出更好的结果。
好吧,回到模型分析。
VGG16 模型在 30 个时期后具有最高的验证和测试准确性,而 VGG19 模型具有最高的训练准确性。ResNet-34 型号在所有设备中表现最差。VGG16 模型是唯一没有过度拟合的模型,这可能是因为该模型较浅,因此无法拟合如此复杂的函数。因此,较浅的网络通常表现更好,因为数据集非常小。ResNet 通常表现不佳,因为它比其他网络更深,因此可能需要更长的训练时间或更大的批量。
我在训练各种模型时注意到的一件事是,验证/测试的准确性有时高于训练的准确性。这很可能是由于数据量小和验证分割大小。图片总数大约是 750 张,我使用了 0.1 的验证分割大小,这意味着只有大约 75 张图片需要验证。因此,如果在某个时期内有更多的图像被正确分类,验证精度的提高将超过训练精度的提高。这是这个项目的一个限制,因为没有足够的数据来真正比较验证准确性和训练准确性。然而,我们仍然可以区分模型性能,因为验证准确性可以在模型之间进行比较,因为验证集中的图像数量保持不变。
虽然结果显示 VGG16 模型表现最佳,但测试数据集非常小(只有 74 张图像),因此如果不进行额外的分析,还不足以说 VGG19 模型更好。因此,我们将在评估部分查看进一步的分析。
测试数据评估
还记得我说过存储准确性和损失指标很重要吗?现在,我们可以绘制这些值与时期数的关系,并想象它们如何随着训练过程的进行而变化。
VGG16 损耗和精度图
VGG19 损耗和精度图
ResNet-34 损失和精度图
您可能会在图中注意到一个趋势,即验证图比训练图噪声大得多。这是因为验证集只有 74 幅图像,而训练集大约有 550 幅图像。因此,验证集中的几个错误可能会导致损失和准确性比训练集中的几个错误差得多。但总的来说,每种型号的损耗都随着时间的推移而减少。此外,如果您查看所有三个模型的训练曲线,ResNet-34 和 VGG16 看起来似乎都趋于平缓,而 VGG16 模型似乎将继续改善。在未来的工作中,我计划为更多的纪元训练该模型,以观察 VGG16 是否会继续改进。
我们可以继续基于接收器操作特征(ROC)曲线来评估我们的模型,ROC 曲线是假阳性率对真阳性率的曲线图。
VGG16(左)、VGG19(中)和 ResNet-34(右)ROC 曲线
ROC 曲线表明该模型能够很好地区分这两个类别。曲线下面积(AUC)值接近 1 表明很少有假阳性和假阴性。直线的 AUC 为 0.5,表示随机猜测的对照二元分类器。显然,VGG16 是 COVID 阳性和 COVID 阴性 CT 扫描之间的最佳区分器。正如我们对测试准确性的预期,VGG19 模型的 AUC 得分第二好,ResNet 最差。
可分性的另一个评估是混淆矩阵。二元分类器的混淆矩阵在一个易于阅读的矩阵中显示真阳性、真阴性、假阳性和假阴性的数量。
VGG16(左)、VGG19(中)和 ResNet-34(右)混淆矩阵
同样,VGG16 模型具有最好的结果,很少有错误标记的图像。当测试时,它没有假阴性,这真的很重要,因为如果新冠肺炎被错误地诊断为阴性,那么对患者的生存来说可能是一个严重的问题。其他两个模型有一些假阴性,甚至比 VGG16 模型有更多的假阳性。
为了增强我们对假阳性和假阴性的分析,我们可以将其中一些图像可视化。本质上,我们需要创建一个函数来遍历测试集,并将错误标记的数据组织到适当的类别中。这将允许我们看到分类器正在犯什么样的错误。
下面是这个方法的代码。
该函数接受所使用的模型、测试数据、损失函数以及在显示假阳性和假阴性之间的选择。我们首先创建两个列表来分别保存误报和漏报。接下来,正如我们在训练循环中所做的那样,我们遍历测试数据并对数据运行模型。然后,我们编写“if”和“else if”语句来测试分类是假阳性还是假阴性,并将错误标记的图像附加到适当的列表中。最后,根据该函数是否用于显示假阴性和假阳性,我们使用 Matplotlib 中的 figure 和 subplot 函数在一个网格中显示错误标记的数据。
以下是一些贴错标签的图片的例子。
假阴性的例子
误报的例子
正如您在假阴性显示中看到的,很难判断图像是否为 COVID 阳性。同样,假阳性似乎也有一些异常;然而,这种特殊的异常可能不是由于新冠肺炎。为了修复这些错误分类,我们可能会添加更多类似于假阳性和假阴性的图像,以使模型对这些图像更具普遍性。查看错误分类是改进模型开发的重要工具。
我们可以对模型进行的最后一个测试是霍斯默-莱梅休(HL)拟合优度测试。HL 测试用于模型校准:它将数据的多个子组中的观察值(预测值)与预期值(真实值)进行比较,以确定模型与数据的拟合程度。
这是 HL 测试的方程式。
这可能看起来很复杂,但实际上很简单。该测试遍历每个观察值和期望值,取其差值的平方,然后除以期望值。第一个求和符号是对创建的所有不同子组求和,第二个求和符号是对每个观察值和期望值求和。如果您熟悉统计学,您可能会注意到 HL 检验与卡方检验非常相似。
HL 测试的输出是介于 0 和 1 之间的卡方值和 p 值。较低的卡方值表示观察值和期望值之间的相关性较高。在我们的例子中,较低的值意味着我们的预测更准确地符合实际情况。此外,小于 0.05 的 p 值表明模型拟合不佳。远远大于 0.05 的 p 值表明有足够的证据表明模型校准良好,并准确地拟合了数据。想要更深入的了解这个测试背后的统计数据,可以参考这篇文章。
现在我们已经了解了测试工作的基本原理,让我们看看如何在代码中实现它。不幸的是,Python 没有提供实现 HL 测试的便捷方法,所以我使用了 R。
首先,我导入了 ResourceSelection 库,它有一个实现 Hosmer-Lemeshow 测试的方法。然后,我创建了两个向量,用于地面真实分类和二进制输出预测。我从我创建的测试方法中获得这些值,以获得测试精度(同样,你可以在我的 GitHub 上找到这个)。最后,我对这两组数据运行了 hoslem.test 函数。请注意,参数“g”表示我将数据分成的子组的数量。
下面是这段代码的输出。
我们可以用它们的二进制输出概率为我的另外两个模型写同样的代码。我在下表中组织了所有三个模型的结果。
VGG16 分类器具有最低的卡方值和最接近 1 的 p 值,这意味着它是数据的最佳拟合模型。因此,VGG19 是第二好的校准模型,最不适合的模型是 ResNet-34。尽管 ResNet-34 在 HL 测试中表现最差,但这并不意味着该模型校准不佳。事实上,高 p 值表明该模型很好地拟合了数据,只是没有其他两个模型拟合得好。这是 HL 测试相对于其他指标的优势。它允许我们判断模型中的拟合缺失是否具有统计学意义。我们知道,高 p 值和低卡方值表明所有三个模型都相对符合数据。
限制
尽管我们能够在训练我们的各种模型时得到好的结果,但是我们确实有一些需要注意的限制。首先,我们使用 PNG 图像,而不是医学成像中使用的标准 DICOM 格式。由于新冠肺炎疫情是最近的,DICOM 格式的原始 CT 扫描很难找到,获得使用患者数据的许可就更难了。使用 DICOM 图像的好处是它们比 PNG 图像更标准化,质量更高。我们使用的数据集包含来自在线文章、网站和博客的图像,因此质量不如原始 DICOM 图像。
我们工作的另一个限制是数据集中图像的数量。正如我之前提到的,拥有更少的数据可能会导致我们的验证准确性略有偏差。这就是为什么我们看到有时验证精度高于训练精度。
最后一个限制是,在我们的模型中使用之前,我们必须对图像进行下采样。我们使用的最新模型需要(224,224,3)的输入图像,而医学图像通常具有更高的分辨率,例如(512,512)和(1024,1024)。这种信息的丢失会导致关键的细节被遗漏,并阻碍我们模型的准确性。解决这个问题的一个方法是创建我们自己的模型,该模型接收更高分辨率的图像。这样,我们就不必对图像进行下采样。
结论
在本文中,我们看到了如何使用 Dataset 类和 Dataloader 对象对 CT 扫描进行分类预处理。然后,我们使用迁移学习在 CT 图像上微调 VGG16、VGG19 和 ResNet-34 预训练模型。然后,我们根据 ROC 曲线、混淆矩阵和 Hosmer-Lemeshow 拟合优度测试进一步评估每个模型。最后,我们讨论了项目中的一些限制,这些限制可以在未来的工作中得到改进。例如,未来的工作可能涉及到使用 DICOM 图像,如果它们是开源的。
现在我们已经在 ROC 曲线、混淆矩阵和 Hosmer-Lemeshow 测试上评估了所有三个模型,我们可以说 VGG16 模型在这组新冠肺炎 CT 扫描上表现最好。
- 世卫组织。冠状病毒疾病(新冠肺炎)。2020.https://www . who . int/docs/default-source/corona virus/situation-reports/2020 06 11-新冠肺炎-sitrep-143.pdf?sfvrsn=2adbe568_4
- 在荷兰,CT 与 RT-PCR 诊断新冠肺炎相关:一项前瞻性研究。2020.https://www . medrxiv . org/content/10.1101/2020 . 04 . 22 . 20070441 v1 . full . pdf
- 梅等人工智能实现新冠肺炎患者的快速诊断。2020.https://www.nature.com/articles/s41591-020-0931-3
- 崔汉,达克什。使用深度学习检测新冠肺炎。2020.https://towards data science . com/detecting-新冠肺炎-使用-深度学习-262956b6f981
- 马克维奇,伊霍尔。卷积神经网络能通过肺部 CT 扫描诊断新冠肺炎吗?。2020.https://towards data science . com/can-a-卷积-神经网络-诊断-来自肺部的新冠肺炎-CT-扫描-4294e29b72b
- 新冠肺炎 CT 大挑战。2020.https://covid-CT . grand-challenge . org/CT-新冠肺炎诊断/
- Knipe 等人,毛玻璃乳浊化。2019.https://radio paedia . org/articles/磨砂玻璃-乳浊化-3?lang=us
- 哈里森.金斯利。“介绍和预处理—使用卷积神经网络识别狗与猫 p . 1”*YouTube,*send ex 上传,2019 年 2 月 22 日,https://www.youtube.com/watch?v=gT4F3HGYXf4。
- 用于大规模图像识别的非常深的卷积网络。2014.https://arxiv.org/abs/1409.1556
- 何等。图像识别的深度残差学习。2015.https://arxiv.org/abs/1512.03385
- 史密斯。训练神经网络的循环学习率。2017.https://arxiv.org/pdf/1506.01186.pdf
- 罗斯布鲁克。Keras 和深度学习的循环学习率。2019.https://www . pyimagesearch . com/2019/07/29/cyclic-learning-rates-with-keras-and-deep-learning/
使用 Pylint 编写干净的 Python 代码
因为代码质量很重要!
Pylint:启动您 Python 代码!
代码翻译器基本上是一个检查你的代码并给出反馈的程序。linter 可以告诉你程序中的问题,以及解决问题的方法。您可以随时运行它,以确保您的代码符合标准质量。
Linters 查看代码的各个方面并检测 lints:
- 逻辑 Lint:讲述代码错误、危险的代码模式
- 统计 Lint:查看格式问题
有许多 Python linters,如 Flake8、Pylint、Pylama 等。在本文中,我将讨论 Pylint,因为它处理逻辑和统计 lint。
安装 Pylint
如果您想从终端安装 Pylint,这是一个单行命令。
pip install pylint
我已经在我的代码编辑器 PyCharm 中安装了 Pylint。为此,你可以进入首选项>插件并搜索“Pylint”插件。从窗口安装并下载它,然后重新启动 IDE。
然后您可以为插件设置 Exec 路径,这样每当用户需要检查代码时,Pycharm 就可以执行 Pylint。为此,请转到设置/首选项,单击其他设置,然后单击 Pylint。在那里输入 PATH 环境变量,就可以开始了。
现在,你完成了!现在,每次运行代码时,您都可以使用 Pylint 来寻找重构、约定和其他小警告信号。
对于我的代码,你可以看到我有许多显示约定问题。在左下角,您可能会看到 4 个符号(红色、黄色、蓝色、红色),这些符号告诉用户代码中的“错误”、“警告”、“显示约定”和“重构”问题。
我有许多蓝色的,这意味着我的代码中有许多显示约定问题。您可以改进代码,然后再次打开 linter 来查看您的代码质量。
给航站楼的人
您可以简单地使用下面的命令来检查代码。
pylint example.py
Pylint 是一个令人惊叹的 linter,您的代码库中必须有它。因为代码质量很重要!
关于反馈和讨论,请通过我的 Linkedin 找到我!
用 Python 和蒙特卡罗来预测我的学生贷款还款
管理个人财务的数据科学方法
我开始对我的学生贷款报表上的数字没有意义感到沮丧,并对为什么你不应该在有能力的情况下尽快还清贷款发表了猜测性的意见。我想衡量我还清全部贷款的可能性,并练习我的数据科学技能,所以我用 Python 编写的蒙特卡洛模拟了我的收入。在这里,我给出了一个调查结果的走查和评估。
我的 GitHub 上有完整的代码。
如果时间不够,请跳到方法和结果!
介绍
在英国,学生可以向英国学生资助局申请大学贷款来支付学费和生活费,该机构提供由学生贷款公司担保的贷款。一旦你以毕业生的身份进入就业市场,你就开始按月偿还贷款,作为你税收减免的一部分。重要的是,如果你在 30 年内没有还清贷款和利息,你的贷款就会被取消。它们也很可观——在许多情况下,毕业一年后大约有 4.2 万人。
毕业生毕业后从学生贷款公司收到的声明摘要示例。(图片由作者提供)。
基本原理
- 我发现有关还款的文件晦涩难懂。这很令人困惑,网上和我的圈子里没有人能给我明确的答案。
- 围绕还款的相互矛盾的观点—“它来自我的税,所以我甚至没有注意到它”和“大多数学生不会还清它,它会被取消,所以提前支付额外的钱可能是浪费你的钱” —但这概括了“大多数学生”,它跨越了科目、所上大学和行业之间的收入(因此摊销)的巨大差异。
- 我想避开噪音,捕捉利息增长和每月纳税额之间的动态变化,量化我自己还清的概率。
问题
1.考虑到我可能的工资增长,我有多大可能在 30 年的付款窗口内还清我的学生贷款?
2。如果你设法存钱,或遇到一笔钱(如遗产),你应该以自愿付款的形式偿还部分或全部贷款吗?
事实
- 自 2012 年以来,贷款的学生都是“计划 2”贷款,他们被分成几期,你在整个学位期间都会收到。
- 截至本文发表之日,当毕业生月薪超过 2214 英镑或年薪超过 26568 英镑(税前)时,他们才开始偿还贷款。
- 当你的雇主给你发工资时(在英国,通常是每月的最后一个工作日),贷款还款会从你的工资中扣除——如果你的收入超过门槛,这些还款应在毕业后的 4 月份到期。
- 你从第一笔贷款的那一天开始计息(即在你进入大学后不久)。
- 利息每天都在增加。
- 计划 2 贷款被注销 30 年后的 4 月,你是第一次到期偿还,如果你还在偿还。
学生贷款不同于其他贷款
- 你偿还学生贷款的金额是按照你收入高于某个阈值的百分比来计算的,因此它更像是一种税,而不是定期的贷款偿还。
- 利率是可变的,而不是固定的——它们是按照通货膨胀的 RPI(零售价格指数)衡量标准加上 3%计算的,因此近年来利率一直高达 6.6%。
- 每个学年(9 月 1 日)的利率都会发生变化,使用截至上一年 3 月的财年的 RPI 通货膨胀率(即 2019 年 3 月的 RPI 用于 2020 年 9 月 1 日设定的利率)。
方法
- 在 Python 中构建一个模拟器,通过 30 年的贷款支付,贷款同时每天产生利息。
- 使用蒙特卡洛模拟许多不同的工资轨迹(更多信息见下文)。
导入 Python 库
导入相关库
构建函数来计算每月从工资中扣除的学生财务费用
找出我在读大学期间收到的每期贷款的利率
- 使用一个简单的 web scraper(beautiful souplibrary)从政府网站获取过去学年的利率,并将它们存储在一个查找字典中。
- 构建一个函数,它可以取我学位期间的任何一天,并从字典中找到在该时间点支付的分期付款的利率。
网络刮刀,以建立一个利率和附带的检索功能查找字典。
毕业后计算欠款
为了计算我大学毕业后欠了多少钱,我模拟了整个大学期间每天的利息,加上来自英国学生资助局的任何新分期付款,使用的是熊猫的付款数据框架(从我的纸质对账单数字化而来)。
毕业后计算利息和贷款总额的函数。
寿命模拟
使用上述函数,可以计算就业开始时学生金融贷款的欠款额。毕业后的四月,你开始以每月纳税的方式偿还贷款。在这个模拟中,贷款的利息每天都在增长,在每月的最后一个工作日(b 月末)你的还款从你的工资中扣除,就像现实中一样。
函数来模拟整个职业生涯的终身收入和贷款的价值,因为你每个月支付它。
进入蒙特卡洛
蒙特卡罗方法是一类依靠重复随机采样来获得数值结果的算法——根据大数定律,可以通过取独立样本的样本均值来逼近随机变量的期望值。用简单的英语来说:“虽然我们不知道正确的答案或策略是什么,但我们可以模拟许多尝试,平均结果将接近正确的答案”。蒙特卡罗可用于许多带有概率元素或“风险”的问题,如赌博,或金融中的股票市场和投资组合表现。这里有一个很好的例子。
模型—假设&考虑事项
由于我在整个工作生涯中支付的学生贷款金额(通过每月付款)是基于我在 t 时刻的收入,并且我不知道我会赚多少,我可以使用蒙特卡洛模拟我的职业收入。在模拟中:
- 起薪(29,000 英镑)是毕业生起薪的中位数,基于毕业生就业计划——由学生雇主协会报告。
- 我把贷款的 30 年支付期划分为职业生涯的不同工资段。
- 我选择了 6 个独立的乐队,每个乐队 5 年长(但这可以是不同的)。
- 每个工资级别代表时间 t 时的工资增长——该增长来自以平均值为中心的高斯分布,该平均值是我在时间 t-1 时总工资的百分比。
- 随着模拟在我的职业生涯中的深入,这个百分比会降低,也就是说,大部分工资增长发生在最初几年(高百分比增长),然后会放缓(低百分比增长)。如下表(0,1) = 37.49%加薪。
- 我按照这些薪带模拟职业生涯 10 万次。
蒙特卡洛模拟中使用的加薪数据框架。100,000 次 6 个薪带的重复——每列代表一个终生薪资轨迹。
- 贷款利率定为 5.5% — RPI 为 2.5%加 3% ( 政府法)。为了说明这一点,我使用了英国历史 RPI 值的 5 年移动平均值,这是我从国家统计局(ONS) 下载的。虽然预测的 RPI 估计值可用,但当前的新冠肺炎危机及其经济影响可能会影响这些预测,因此我采用了 COVID19 之前的值,该值近年来一直保持在 2.5%左右。
具有 5 年滚动平均值的英国历史 RPI,用于通知模拟的利率。
- 我计算了 30 年支付期后所有工资轨迹的平均贷款价值和累计还款总额(终身供款)。
翻译:
作为一名毕业生,大部分工资增长发生在最初几年(高增长%),然后趋于平稳(低增长%)——见财政研究所令人难以置信的彻底的新报告 本科学位对终生收入的影响 。
5 年内加薪 x%是不确定的——它可能略低于或高于 x%,或者你可能会有一系列或多或少的加薪(这些加薪的总和体现在 5 年的跳跃中)。蒙特卡洛通过从每个级别的正态分布中提取许多工资值(k = 100,000)来帮助缓解这一问题。这些平均值为 x%,但可以小于或大于 x%。我模拟了所有这些轨迹和我贷款的月还款额,并计算出在多少次模拟中我还清了全部贷款。
结果
50 贷款价值轨迹(上图)根据其对应的蒙特卡洛模拟工资轨迹。注意:只有 50 个模拟在这里运行,用于可解释的可视化。实际结果基于 1e5 模拟。
正如你在上面的图中看到的,只有一小部分贷款是通过每月扣款的方式还清的。这些贷款可以被识别为穿过零线(用红色标记)的轨迹,并且对应于最大的工资。多年来最低的工资导致贷款价值持续增长。
对于 100,000 次模拟,30 年付款期后:
- 平均贷款价值为: 60,600.74
- 只有在 3.54% 的工资轨迹中,贷款才完全还清,即越过了图中的红色零线(3,543/100,000)
- 平均已付贷款总额为:99691.01
评估—你应该做什么?!
免责声明:我不是财务顾问——这里的任何想法都纯粹是基于一系列假设的模拟假设。这些只是我对潜在有效策略的想法,作为概念性练习的一部分。如果你对自己的个人财务状况不确定,可以向有资质的理财顾问寻求建议。
注意事项
当然,这个模拟包含几个警告:
- 工资级别:我假设 6 x 5 年的工资级别——不能保证毕业生的收入会遵循这种格式。在英国,没有关于特定领域的常规工资增长的足够数据,因为工资增长和晋升受许多变量的影响,包括学术资格(例如,理学学士与理学硕士和博士)、领域、公司和个人本身的表现。虽然有关于每个学科毕业生平均收入的数据,但这些数据并没有涵盖拥有特定学位的毕业生可以选择的许多路线/领域。在与我网络中的高级角色交谈后,我确定,对于 STEM 毕业生来说,平均而言,大约 5 年的加薪频率和所选的百分比接近现实。
- 利率:该模拟假设最近的 RPI 值在未来几年保持不变,但是由于 RPI 的变化,贷款的实际利率每年都会发生变化,这将影响贷款的复利速度。
- 起薪:我使用的是基于毕业生计划的平均起薪,但这并不能代表所有毕业生的工资——这影响了还款。
- 其他薪酬:模拟仅考虑基本工资,不考虑奖金、公司股份或其他形式的薪酬。
那么这里的结论是什么呢?关于偿还学生贷款,你应该采取什么策略?
图片来源:DaveRamsey.com
首先,应该强调的是,债务偿还是算术和心理学的结合,因为作为复杂的生物,我们关于债务的情感福利受到多种因素的影响。如果你有多笔债务要偿还,有不同的债务合并策略,例如滚雪球法或雪崩法。
其次,从纯粹的数学意义上来说,支付学生贷款最低金额的最佳策略是在整个 30 年的还款期内保持低于还款门槛的工资水平,从而免费获得学位,因为你从未为此支付一分钱。然而,这只是在孤立于其他因素考虑贷款的情况下,显然不能反映一个典型毕业生的真实生活抱负。考虑到这一点,我考虑的策略是假定职业发展是渐进的,包括偿还你的贷款。
孤立贷款
实际上,在孤立的情况下,是的,你应该尝试用你遇到的任何额外的钱来偿还,因为结果显示,平均而言,在你的工作生涯中,你将支付超过两倍的贷款(99691.01/42000 = 2.37)。
至关重要的是,在这个问题中影响分期偿还的主要因素是利息和每月还款之间的动态变化。如果某个月的贷款利息是 20 英镑,你需要在月底支付比 20 英镑更多的还款来减少本金——这是基本的数学原理。
关键因素是你的工资增长速度,因为复合增长的力量。你的还款需要扣除利息,然后开始减少本金。但是,如果你的工资意味着你不能在早期设法克服利息,即使你的工资在以后的生活中增长到一个可观的数额,贷款将增长到一个需要不成比例的更高的工资来征服以后的月利息的数额,由于复合增长的非线性,这将更大。因此,要想通过每月纳税成功还清贷款,工资必须在最初几年增长足够快,这样还款才能很快超过利息,债务就不会“允许”增长。另一种选择是,如果你的工资太低,无法实现这一点,你可以每月自愿还款,以弥补差额。
现实——金钱的时间价值
当然,在现实中,你的贷款偿还不是孤立的其他因素。这是个多维度的问题!归根结底,你还可以将任何额外的钱投资于其他长期项目,因为金钱的时间价值有**和**机会成本。金钱的时间价值是这样一个概念,即同样数量的金钱由于其潜在的收入和通货膨胀,现在比以后更有价值。
图片来源:中等
如果你遇到一笔钱,使你有能力偿还贷款(无论是从储蓄账户,遗产等。),你可以投资它,让它成长,利用复合增长,而不只是成为它的受害者。
事后分析
我做了一个事后分析来展示这一点。假设你没有还清贷款,而是在一项投资中采用了长期持有策略,其平均回报高于你通过每月工资扣除支付的贷款金额——要么一次性支付相当于贷款价值的金额(42,000 英镑),要么——因为这种情况对典型的毕业生来说非常罕见——初始投资 1000 英镑,每月储蓄 200 英镑。**
两种投资方案的月平均累积还贷额为 1000 MC 模拟工资。
3 种情况:
- 你遇到 4.2 万,马上还清贷款。每月还款(蓝线)将花费你大约。你一生中有 10 万英镑,而你一直在逃避。10 万—4.2 万= 5.8 万“利润”。
- 你遇到 42,000 英镑,以 5.0%的回报率(通胀后)进行投资,让它继续增长 30 年(橙色线)。30 年后,这大约值 187,000 英镑。18.7 万— 10 万= 8.7 万利润。
- 你投资 1000 英镑,用你每月存下来的钱(例如 200 英镑)来补充每月的投资(绿线)。30 年后,这大约值 17 万英镑。17 万–10 万= 7 万利润。
正如这些结果所显示的,即使模拟毕业生没有一大笔可用的钱,而是用更少的钱来投资他们每月的储蓄,他们仍然可以比完全还清贷款获得更大的利润。在大多数情况下,毕业生不太可能遇到这种一次付清的情况,试图在支付生活费用后存钱,然后支付学生贷款可能会让你永远追逐自己的尾巴,因为贷款利息同时复合超过你可以储蓄的部分——你最好投资它。
机会成本
这种机会成本适用于其他投资,如购买一栋房子并利用其升值,甚至适用于对自己的投资,如课程、研讨会和会议,这些可以发展你的技能并扩大你的人脉。虽然这很难用金钱来衡量,但它将产生更多的机会,并可能带来更大的利润。
结论
这不是一个非常明确的结论——有很多“如果”:
- 如果贷款的利息低得多,很明显,只需通过每月扣款来支付,而不用担心额外的投资,因为你不太可能多付。
- 如果你遇到一笔接近你学生债务价值的钱或者如果你每个月都存钱,投资可能会更有利可图,因为偿还贷款会产生巨大的机会成本。作为一名年轻的毕业生,你处于有利地位——你最初几年投入的时间/精力/金钱会影响你的职业生涯,而未来几年的延迟投资会大大降低你以后的投资回报率。
- 如果你有钱,但不投资,那么你最好用这笔钱偿还贷款,否则你可以通过每月还款支付几倍,加上这些流动资金每年都会因通货膨胀而贬值。
这些结果无疑会在不久的将来影响我的个人理财决策。
同样令人欣慰的是,上面提到的报告发现,即使算上税收和学生贷款偿还,英国毕业生平均仍比获得学位多至少 10 万英镑,因为他们获得了额外的净实得工资。
最后的想法
这个项目确实让我明白了“不要全额还清贷款,因为贷款在之后的会被取消”的想法是错误的,因为它缺乏详细说明——它完全取决于工资,因为这决定了你是否会为你的学位支付过低或过高的费用。如果终身工资足够低,那么你可以通过每月还款来支付学位费用,在这种情况下,你不应该支付额外的费用或担心投资。然而,这些模拟表明,如果你最终收入在平均(最密集)区域(见结果中的工资阶梯图),那么你应该投资任何额外的钱,并支付最低还贷额。这是因为你以后需要投资来平衡你学位的超额支付。当然,这些结论是假设性的,是从预测利率和模拟工资中得出的,可能会有所不同。
除了让我对学生贷款系统有了更多的了解之外,这次练习也突出了一些重要的东西。我们没有足够的公开可用和可消费的数据来跟踪不同领域或职业的毕业生一生的收入。提到的报告分析了来自特定群体的毕业生和非毕业生的纵向数据,并于最近发表——像这样的研究需要提供给未来的本科生并易于理解。英国的大学本科课程现在每年的费用高达 9250 英镑,正如我们所看到的,你可能要支付数倍于此的费用。我们需要特定大学的特定课程毕业生的收入数据,这样候选人就可以根据他们将承担的巨额贷款,自己判断他们的课程和大学的投资回报率,而不是简单地在开放日被大学弄得眼花缭乱。
希望这篇文章已经很有见地了!下一个项目…
数据科学项目的一些有用链接:
**** [## 开启 2020 年数据科学学习之路的顶级资源
一步一步的指南,从初学者到中级,50+资源,使 2020 年成为你有意义的数据年…
medium.com](https://medium.com/omdena/top-resources-to-kick-off-your-2020-data-science-learning-path-5630470a801b) [## 教程:使用 BeautifulSoup 的 Python Web 抓取
在执行数据科学任务时,通常希望使用在互联网上找到的数据。你通常可以…
www.dataquest.io](https://www.dataquest.io/blog/web-scraping-tutorial-python/) [## Python 编程教程
欢迎使用 python 进行蒙特卡罗模拟实验。在我们开始之前,我们应该建立一个蒙特卡洛…
pythonprogramming.net](https://pythonprogramming.net/monte-carlo-simulator-python/)****
使用 Python 和 Robinhood 构建多头交易机器人
我过去一个月(一月到二月)的实际收获。
这是另一个我正在尝试自动化的交易策略。
所以对于那些不知道的人,我真的在努力了解被动收入的想法。在过去的三个月左右,我一直在大量研究交易。随着我越来越深入这个领域,越来越理解其中的动态,我想看看我是否能把这个领域的知识翻译成 Python。因此,让我们从理解什么是长时间通话开始。
什么是长途电话?
看涨期权是期权交易中的一种合约形式。它给了你以某一价格买入股票的“选择权”(执行价格 A)。从看涨期权中赚钱的方法是,如果你相信某只股票的价格会上涨。此外,如果你相信一只股票会持续上涨,那么它就有无限的盈利潜力。
艺术作品小敏
我们需要什么
对于那些不了解我的人,我已经写了两篇文章,这两篇文章已经在第一步帮助了我们。所以我们需要使用 Python 连接到 Robinhood。创建将为我们订购选项的代码。在所有的初始设置之后,实际的长期通话策略非常简单。
长通话的设置
- 以执行价格 A 买入看涨期权
- 我们希望股价等于或高于股价 A
编写代码
所以让我们从最后开始。要执行期权订单,我们需要从[robin_stocks](https://robin-stocks.readthedocs.io/en/latest/index.html)
开始使用:
robin_stocks.orders.order_buy_option_limit(*price*, *symbol*, *quantity*, *expirationDate*, *strike*, *optionType='both'*, *timeInForce='gfd'*)
我们只需要期权合约的价格。我们将要使用的股票的符号。我们想要购买的合同数量。上述合同的到期日。股票的执行价格。期权类型设置为看涨期权。
对于这个例子,让我们使用特斯拉购买我们的订单。我通常通过 Robinhood 应用程序购买一个期权,其执行价格与股价相当。所以我们来抓一下特斯拉目前的股价。
current_price = r.stocks.get_latest_price(‘TSLA’)[0]
一旦我们有了这个价格,我们就可以抓住高于这个价格的看涨期权,选择最接近这个价格的期权。但在此之前,我们还需要考虑产品的有效期。我喜欢购买 30-45 天后的长期看涨期权。我已经创建了代码,将检查今天的日期,并找到 30-45 的日期。然后我选择最远的日期。一旦我们有了成交价格和截止日期,我们就可以把它插回到r.options.find_options_for_stock_by_expiration_and_strike(symbol, expirationDate, strike_a, optionType=’call’, info=None).
中,现在它将搜索 30-45 天内最长的看涨期权,并为我们提供一个订单价格。对于订单价格,我们可以从最高卖价到最低卖价中选择一个数字。我只是倾向于选择low_fill_rate_buy_price
它通常会立即执行我的订单。但是你可以根据自己的交易偏好随意摆弄这些数字。
我们现在可以使用robin_stocks.orders.order_buy_option_limit(*price*, *symbol*, *quantity*, *expirationDate*, *strike*, *optionType='call'*, *timeInForce='gfd'*)
来执行我们的订单了
我们可以填写所有变量,订单现在已经发送过来了。这基本上是一个长期看涨期权,我知道一些投资者喜欢,但低于执行价格,但对我来说溢价太贵了。一切取决于你的交易策略。
我其他一些涉及 Python 和 Robinhood 的文章:
[## 使用 Python 和 Robinhood 创建一个简单的低买高卖交易机器人
所以我最近一直在折腾 Robinhood,一直在努力理解股票。我不是财务顾问或…
towardsdatascience.com](/using-python-and-robinhood-to-create-a-simple-buy-low-sell-high-trading-bot-13f94fe93960) [## 使用 Python 和 Robinhood 构建一个钢铁秃鹰期权交易机器人
所以我最近发现了期权交易的潜在收益。
towardsdatascience.com](/using-python-and-robinhood-to-build-an-iron-condor-options-trading-bot-4a16e29649b0)
我也有家教和职业指导。
如果你们有任何问题、意见或担忧,不要忘记在 LinkedIn 上与我联系!
注来自《走向数据科学》的编辑: 虽然我们允许独立作者根据我们的 规则和指导方针 发表文章,但我们不认可每个作者的贡献。你不应该在没有寻求专业建议的情况下依赖一个作者的作品。详见我们的 读者术语 。
使用 Python 和 Robinhood 构建一个钢铁秃鹰期权交易机器人
让·维拉在 Unsplash 上拍摄的照片
所以我最近发现了期权交易的潜在收益。我的思维非常技术性,注意到交易策略只不过是条件语句。所以我决定创建一个简单的铁鹰交易策略。
根据 OptionAlpha 的说法,铁鹰期权交易策略是与期权一起使用的利润最高、风险最低的交易策略。我将使用 Python 和 Jupyter Notebook 在 Robinhood 下这些期权订单。
让我们首先试着理解什么是铁鹰战略。对于那些熟悉期权的人来说,它们基本上是四种期权合约。有铁秃鹰,可以要求所有的呼吁,所有的 put 或基本的铁秃鹰。在这个例子中,我们将使用基础。要求同时做多卖出、做空卖出、做空买入和做多买入。执行这一策略的最佳时机是,当我们知道我们订购的股票在特定的时间框架内上下波动最小时。
图片来自 Options Bro—https://www . Options Bro . com/iron-condor-option-strategy-example/
我认识的大多数投资者都喜欢在到期后的 30 到 45 天内操作这个游戏。这就是时间衰减的好处。他们还在指数期权而不是个股上玩这种游戏,因为指数没有那么不稳定。但是我们可以在其他时间更深入地研究逻辑,让我们专注于创建可以工作的代码。
我们需要什么
对于那些不了解我的人,我已经写了两篇文章,这两篇文章已经在第一步帮助了我们。所以我们需要使用 Python 连接到 Robinhood。创建将为我们订购选项的代码。在所有的初始设置之后,实际的铁秃鹰战略非常简单。
铁鹰的设定
- 我们希望股票处于执行价 A(低于股价)和 B(高于股价)之间
- 买入低于执行价的看跌期权
- 以执行价格卖出看跌期权
- 卖出看涨期权执行价格 B
- 买入高于执行价 B 的看涨期权
- 所有这些合同都将有相同的到期日
编写代码
首先,让我们找一只股票来玩。我继续做了一些研究,并将在我们的代码中使用 Fitbit。Fitbit 在 11 月份左右有所上涨,但我相信它未来将横向移动。使我们可以很容易地看到我们可以订购哪些选项。我们只需要输入股票代码。
它现在将输出如下所示的数据帧:
因此,我们希望从这里开始关注的主要栏目是expiration_date
,让我们从创建代码开始,它将找到从现在起 30 到 45 天的到期日期。然后,我们将使用这些日期来消除现有的期权订单。我们现在将有一个列表,将是我们的铁秃鹰完美的可用选项。
接下来的部分,我获取当前的股票价格,并把它附加到一个列表中,这个列表是我从执行价格列中得到的。然后,我对这些值进行排序,以便使用指数值来选择每个订单所需的执行价格。然后,在到期日期中,我将选择从今天起 30–45 天内最晚的一天。
接下来,我只需下 4 个不同的期权订单,并填入所有必要的信息。在所有这些之后,我们已经成功地创建了执行 iron condor 订单的 Python 代码!
我其他一些涉及 Python 和 Robinhood 的文章:
[## 使用 Python 和 Robinhood 创建一个简单的低买高卖交易机器人
所以我最近一直在折腾 Robinhood,一直在努力理解股票。我不是财务顾问或…
towardsdatascience.com](/using-python-and-robinhood-to-create-a-simple-buy-low-sell-high-trading-bot-13f94fe93960)
我在这里也有家教和职业指导。
如果你们有任何问题、评论或顾虑,请不要忘记通过 LinkedIn与我联系!
来自《走向数据科学》编辑的提示: 虽然我们允许独立作者根据我们的 规则和指导方针 发表文章,但我们并不认可每个作者的贡献。你不应该在没有寻求专业建议的情况下依赖一个作者的作品。详见我们的 读者术语 。
使用 Python 和 Selenium 从街道地址获取坐标
澳洲墨尔本市(www.pxhere.com)
这是一篇分步介绍如何使用 Python 和 Selenium 从基于街道地址的谷歌地图中抓取坐标数据**(纬度和经度值)的文章。**
在这个例子中,我将使用一个包含所有澳大利亚慈善机构和非营利组织街道地址的官方数据集。最后,我将继续使用lyum,绘制墨尔本市的所有慈善机构和非营利机构的地图,以展示如何使用新获得的坐标数据。
要继续学习,您可以从我的 GitHub 库下载 Jupyter 笔记本。
软件包和设置:
Selenium 主要用于自动化 web 应用程序测试,但也可以用于从网站上抓取数据或在浏览器上执行重复性任务(www.rawpixel.com)
我们将需要 硒 python 包来执行数据抓取**。如果还没有,可以使用pip:**pip install selenium**
安装。**
我们还需要一个网络驱动来与浏览器互动,所以你必须到这里下载到你的机器上(确保它与你当前的 Chrome 版本兼容)。****
现在让我们开始导入:
**from selenium import webdriver**
让我们也得到 tqdm ,一个必不可少的 progressbar python 包。估计你的代码的 web 抓取部分将花费多少时间是非常有用的(我使用 tqdm_notebook 是因为我在 Jupyter 笔记本上工作) :
**from tqdm import tqdm_notebook as tqdmn**
除此之外,我们还需要 熊猫 来读取和操作数据集:
**import pandas as pd**
最后,我们将得到来绘制地图上的坐标数据(可以使用pip install folium
来安装):
****import folium****
我将在一个 Jupyter 笔记本上工作(你可以在这里下载)。如果您在 python IDE 中工作,所有这些步骤仍然适用。
数据集:ACNC 慈善和非营利注册
澳大利亚慈善和非营利委员会(ACNC)是澳大利亚慈善机构的国家监管机构
在涵盖过去 10 年的第 10 版 CAF 世界捐赠指数中,澳大利亚在全球排名第四。通过访问 ACNC 的网站,你会很快注意到他们让研究人员可以很容易地获取所有关于澳大利亚慈善机构和非营利组织的数据。
我们将使用的数据集可以在这里下载(以及一个解释变量的有用的用户注释文档):
- ACNC 慈善登记数据集【Excel 文件 XLSX|7.7 Mb|截至 2020 年 1 月 23 日 74155 行 60 列**
该数据集展示了澳大利亚慈善机构和非营利机构的许多有趣方面:唯一标识符、法定名称、地址(用于通信)、注册日期、规模、目的、受益人等(阅读用户注释了解更多信息)。
这里我们主要感兴趣的是街道地址特征,它跨越了几列:地址 _ 行 _1 ,地址 _ 行 _2 ,地址 _ 行 _3 ,城镇 _ 城市,州,邮政编码和国家。此外,为了简单起见,我们将只调查墨尔本市的慈善机构和非营利组织。**
数据清理和准备:
自由峰的 rawpixel.com/宏矢量
首先,让我们来看看之前从 ACNC 网站下载的关于熊猫的数据集:
请确保提供 Excel 文件在您的计算机上的路径。参数keep_default_na
被设置为False
,所以当某些值丢失时,我们将得到空值而不是NaN
。稍后当我们将所有街道地址变量合并成一个变量时,这将非常有用。
acnc.shape 给出了 74155 行和 60 列
现在,让我们创建一个新的数据框架mel
,在通过变量Town_City
过滤后作为acnc
的副本,以便只选择墨尔本市的慈善机构和非营利组织:
**mel = acnc[acnc.Town_City.str.contains('melbourne', case=False)][['ABN', 'Charity_Legal_Name', 'Address_Line_1', 'Address_Line_2', 'Address_Line_3', 'Town_City', 'State', 'Postcode', 'Country', 'Date_Organisation_Established', 'Charity_Size']].copy()**
我在这里做了两件事:我通过Town_City
变量过滤了acnc
数据帧,然后我从最初的 60 列中只选择了 11 列有用的列。copy()
确保我们制作了经过过滤的acnc
数据帧的正确副本。
我在这里没有使用acnc[acnc.Town_City == 'Melbourne']
,因为我怀疑它可能以不同的方式编写。为了确保这是必要的:
**mel.Town_City.value_counts()**
正如我们在上面看到的,这个专栏包含了不同的方式来表明一个慈善机构确实位于墨尔本,其中一些使用了郊区,甚至是城市中非常特殊的地方,比如墨尔本大学。通过使用acnc.Town_City.str.contains('melbourne', case=False)
,我们确保了上述所有慈善机构都被计算在内(否则我们只能得到 1779 个正确标记的慈善机构)。
让我们看看新的mel
数据框架是什么样的:
**mel.head()**
mel.shape 赋予 2722 行和 11 列
现在,让我们添加一个包含完整地址的新列Full_Address
:
**mel['Full_Address'] = mel['Address_Line_1'].str.cat( mel[['Address_Line_2', 'Address_Line_3', 'Town_City']], sep=' ')**
str.cat()
在这里有效,因为所有这些列都是 object 或 string 类型的。
下面是mel
中第一个完整地址的例子:
**mel.Full_Address.iloc[0]Output:'G Se 11 431 St Kilda Rd Melbourne'**
还有一点:这些完整的地址中,有些只包含邮政信箱号码(称为邮政信箱或邮政信箱)。这些地址对我们来说完全没有用,因为它们不涉及一个已存在的地方。这里有一个例子:
**mel[mel.Full_Address.str.contains('po box', case=False)].Full_Address.iloc[0]Output:'GPO Box 2307 Melbourne VIC 3001 AUSTRALIA'**
在继续之前,我们需要删除这些记录(或行):
**mel = mel[~mel.Full_Address.str.contains('po box', case=False)].copy()**
最后一件事:一些地址包含字符/
,它可以破坏任何 URL。我们需要用空格替换任何斜杠:
**mel.Full_Address = mel.Full_Address.str.replace('/', ' ')**
探索谷歌地图:
rawpixel.com
在任何网络抓取工作之前,探索你想要从中提取数据的网站是很重要的。在我们的例子中,它是谷歌地图。
首先,让我们研究一下使用 Google Maps 内部的搜索栏搜索完整地址是如何影响结果页面的 URL 的。为此,我将使用虚构的地址Grinch house mount crumpit whoville
,因为我希望谷歌地图不返回任何结果:
正如你在上面看到的,我们得到了www.google.com/maps/search/
,后面是我们搜索的地址。换句话说,如果我们想在谷歌地图中搜索一个地址 XYZ,我们所要做的就是使用网址www.google.com/maps/search/XYZ
,而不必与搜索栏本身进行交互。
这里的想法是在mel
中生成一个新列,我们将www.google.com/maps/search/
与数据帧mel,
中的每一个Full_Address
组合在一起,然后让 Selenium 遍历它们,一个接一个地访问 URL。
让我们创建新的Url
列:
**mel['Url'] = ['https://www.google.com/maps/search/' + i for i in mel['Full_Address'] ]**
现在我们有了一个包含所有要抓取的 URL 的列,让我们看看地址G Se 11 431 St Kilda Rd Melbourne
例如:
[www.google.com/maps/search/G 东南 11 区,墨尔本第一基尔达路 431 号](http://www.google.com/maps/search/G Se 11 431 St Kilda Rd Melbourne)
上面的链接给了我们:
以上地址对应的是慈善机构澳大利亚护士纪念中心。让我们在谷歌地图上按名字搜索一下:
我们得到完全相同的点,但在 URL 中没有相同的坐标。这是因为 URL 中的坐标与地图的居中方式相关联,而不是与标记相关联(如果放大或缩小,坐标会发生变化)。这就是为什么我们要直接从页面本身的源代码中提取坐标。
要查看源代码,请右键单击页面内的空白区域(地图外部),然后选择“查看页面源代码”(在 Mac 中为 CTRL+U 或 Command+U)。现在在源页面中搜索 -37.8 或 144.9 :
你会在源代码中的很多地方找到我们正在寻找的坐标。但是如果它们包含在一个我们可以瞄准的 HTML 标签中,那么它们对我们来说是最有用的。幸运的是,这里有一个元标记我们可以利用:
现在,让我们注意它是一个带有属性content
的meta
标签,属性content
包含我们想要提取的 URL,属性itemprop
的值image
可以用来识别和定位这个特定的meta
标签。
现在我们所要做的就是使用 Selenium 访问mel.Url
中的每个 URL,并定位这个meta
标签,以提取其属性content
的值。
使用硒:
rawpixel.com—网络仓鼠
下面是我们将用来从 Google Maps 中提取包含坐标的 URL 的代码:
- 第 1 行:我们创建了一个名为
Url_With_Coordinates
的空列表,稍后我们将用(你猜对了)我们想要提取的 URL 填充它; - 第 3 行到第 5 行 : prefs 在没有 javascript 和图片的情况下运行 Webdriver。这样,代码加载网页的时间会少得多。显然,如果您想要提取的内容依赖于 javascript,这不是一个好的选择。移除
'images':2, 'javascript':2
,网页将正常加载图片和 javascript - 确保指定你把 chromedriver.exe 文件放在你机器的什么地方。在我的例子中,为了简单起见,我把它放在 c 盘中。注意,路径中的反斜杠
\
需要加倍\\
才能识别路径; - 第 9 行:这个 for 循环在
mel.Url
系列上迭代。包装我们的 iterable 的tqdmn()
在执行完单元后添加了一个进度条。其参数leave=False
确保操作完成后,杆消失; - 第 10 行:对于
mel.Url
中的每个 URL,网络驱动打开那个 URL(对于第一个 URL,你会看到一个 chrome 窗口打开,然后你会注意到它从一个 URL 到另一个 URL,直到mel.Url
结束); - 第 11 行:首先,我们使用
driver.find_element_by_css_selector
搜索我们的meta
标签,并且我们通过meta[itemprop=image]
识别标签。之后,我们使用.get_attribute('content')
提取属性content
的值。然后使用append()
将该操作的结果(包含坐标的 URL)添加到Url_With_Coordinates
列表中。 - 第 13 行:我们在脚本完成后关闭 webdriver (chrome 窗口)(这是一个很好的做法)。
下面是运行中的脚本和 tqdm 进度条(或 tqdmn,因为我使用的是 tqdm_notebook 子模块) :
tqdm 估计整个操作大约需要 32 分钟(大约每秒 1 次迭代或 URL)
NB 1: 下次运行笔记本的时候,你就不用再重新运行一遍网页抓取代码了,因为我们已经把结果保存在一个名为Url_With_Coordinates.csv
的 CSV 文件里了。让我们改为读取该文件:
**import csvwith open('Url_With_Coordinates.csv', 'r') as f:
reader = csv.reader(f, delimiter=',')
for i in reader:
Url_With_Coordinates = i
break**
在你的测试中,你不会想让 for 循环遍历数千个地址,到头来却抛出一个错误。在正确执行脚本之前,您需要先测试几个值。在我们的例子中,测试代码将是这样的,只检查mel.Url
的前 10 个值:
**for url in tqdmn(mel.Url[:10], leave=False):
driver.get(url)
......**
现在让我们看看Url_With_Coordinates
列表是什么样子的:
2009 年前 5 个网址
让我们将这个列表作为一列添加到我们的mel
数据框架中:
**mel['Url_With_Coordinates'] = Url_With_Coordinates**
但是我们如何从这些 URL 中提取坐标呢?下面是如何使用 Python 的split()
方法实现这一目的的直观解释:
该代码翻译成如下代码(该代码不会运行,因为没有定义url
,它只是为了展示上面的解决方案是如何工作的):
**url.split('?center=')[1].split('&zoom=')[0].split('%2C')Output:[-37.8386737, 144.97706]**
现在使用上面的代码,我们将向我们的mel
数据框架添加两个新列:lat
表示纬度,long
表示经度:
**mel['lat'] = [ url.split('?center=')[1].split('&zoom=')[0].split('%2C')[**0**] for url in mel['Url_With_Coordinates'] ]mel['long'] = [url.split('?center=')[1].split('&zoom=')[0].split('%2C')[**1**] for url in mel['Url_With_Coordinates'] ]**
最有可能的是,上面的代码给出了一个错误list index out of range
:
这个错误意味着对于列Url_With_Coordinates
中的一些 URL,split()
方法没有按预期工作。也许一些 URL 没有我们用于split()
方法的关键字。让我们寻找缺少&zoom=
的 URL,例如:
**mel[~mel.Url_With_Coordinates.str.contains('&zoom=')]**
正如我们在这里看到的,我们有 5 个提取的 URL 以//www.gstatic.com/images ...
开头的实例(因此我们得到了错误) :
**list(mel[~mel.Url_With_Coordinates.str.contains('&zoom=')].Url_With_Coordinates)Output:['//www.gstatic.com/images/branding/product/1x/maps_round_512dp.png',
'//www.gstatic.com/images/branding/product/1x/maps_round_512dp.png',
'//www.gstatic.com/images/branding/product/1x/maps_round_512dp.png',
'//www.gstatic.com/images/branding/product/1x/maps_round_512dp.png',
'//www.gstatic.com/images/branding/product/1x/maps_round_512dp.png']**
为了简单起见,因为 5 不是一个大数字,我们将从mel
中删除这些实例:
**mel = mel[mel.Url_With_Coordinates.str.contains('&zoom=')].copy()**
现在,让我们重新运行将两列lat
和long
添加到我们的mel
数据帧的代码:
**mel['lat'] = [ url.split('?center=')[1].split('&zoom=')[0].split('%2C')[0] for url in mel['Url_With_Coordinates'] ]mel['long'] = [url.split('?center=')[1].split('&zoom=')[0].split('%2C')[1] for url in mel['Url_With_Coordinates'] ]**
成功了!下面是我们的mel
数据框架的样子,其中每个慈善机构或非营利组织都获得纬度和经度值(这里的一些列被屏蔽了) :
**mel.head()**
让我们来绘制这些坐标,看看它们能有多精确。
使用叶子进行坐标映射:
在此下载该地图的 HTML 文件
颜色编码(慈善规模取决于年收入) :
- 红色:大型慈善机构(100 万澳元以上);
- 紫色:中等慈善机构(25 万澳元至 100 万澳元之间);
- 橙色:小型慈善机构(25 万澳元以下);
- 灰色:无数据。
下面是我们将用来映射这些坐标的代码:
我不会详细介绍如何使用 Folium,但我会在这里澄清一些要点:
- 我在这张地图上使用了
CartoDB positron
,因为它对彩色标记提供了非常低的对比度(这使得它们更加明显)。使用默认图块OpenStreetMap
很难看到标记; - 我使用
folium.CustomIcon
的参数icon_size=(..,..)
根据慈善机构的规模改变了标记的大小。这背后的原因是为了防止居住在同一栋楼里的慈善机构互相掩盖。因为先画大的标记,然后在上面画小的标记,这样即使重叠的慈善机构也能被区分出来; - 我使用自定义标记(托管在 imgur 上),因为默认标记会大大降低导航速度,因为我们在地图上有大约 2000 个标记。对于自定义标记,您可以提供要使用的图像的 URL,或者您计算机上图像文件的路径。
- 如果你点击一个标记,它会给你慈善机构的名称和地址,所以你可以验证定位是否正确;
- 在上面的代码中,我可以只使用
mel_map
而不是mel_map.save('mel_map.html')
后跟IFrame(src='mel_map.html', width='100%', height=500)
,但是当标记的数量很大时,最好将地图保存为 HTML 文件,然后使用IFrame()
打开它(否则您将得到一个空白地图)。
这种方法有多准确可靠?
我们将使用已经有街道地址和坐标的华盛顿州 DC 的企业数据集来测试我们的方法——图片(CC0)pxhere.com
这是一个非常合理的问题。显然,从街道地址中获取坐标的最佳方式是从一个像 Google Maps 或 Bing 这样的知名 API 中获取,但是这些选项可能要花钱。
这种方法的准确性很大程度上受所提供的街道地址的精确度和正确性的影响。例如,在我们上面的例子中,你会注意到一个标记被扔在了印度洋的中央。经审查,地址65 Macarae Road Melbourne
实际上应该是65 Mcrae Road Melbourne
,因此出错。
为了从经验上测试我们的方法,我们将使用一个数据集,该数据集包含华盛顿州 DC 数千家企业的街道地址和坐标。我们将随机抽取 500 个商业地址,然后使用我们的方法从中生成坐标。之后,我们会将它们与数据集中列出的实际坐标进行比较。我们将在测试中使用的数据集可以从这里下载:
- 最近 30 天的基本营业执照:opendata.dc.gov 数据集, 6418 记录(截至 26/01/2020),2.64 Mb
我们将用来测试我们方法的数据集
结果如下(测试详情可在 Jupyter 笔记本中获得) :
红色:数据集中的实际坐标—橙色:使用 Selenium 和 Google Maps 生成的坐标(此处下载 HTML 文件形式的地图)
正如我们在上面看到的,在华盛顿州 DC 随机抽样的 500 家企业中,生成的坐标非常接近实际坐标。我将橙色标记做得比红色标记小,这样当它们完全重叠时就都可见了(这就是为什么缩小时橙色标记看起来有红色边框)。