使用 FCNN 感受野进行物体检测
从 PyTorch 中预先训练好的图像分类器中创建一个物体检测器
目标检测的任务是检测和空间识别(使用边界框等)。)图像中的各种对象,而图像分类告诉图像是否包含某些对象,而不知道它们的确切位置。
在这篇文章中,我描述了一种在 PyTorch 中从预训练的图像分类模型创建对象检测模型的方法。
尽管物体检测和图像分类是计算机视觉的两个不同类别,它们的训练方法也完全不同,但我们将在这篇文章中看到,它们的功能有一些重叠,我们可以利用它们来达到我们的目的。
这篇文章中提出的想法和实现是使用 CNN 感受域、反向传播和全卷积网络的概念构建的。
如果你对上面的一些术语不确定,我推荐你去看看下面的LearnOpenCV.com的博客,其中T21 很好地介绍了这些概念。
事实上,即使您已经非常熟悉这些术语,我仍然强烈建议您浏览一下上面的帖子,因为我所展示的实现扩展了上面的引用,并且为了简洁起见,我没有捕捉已经有了更好解释的细节。
虽然你可能会发现使用这种方法的结果不太匹配流行的对象检测器的性能,如 YOLO ,但是,这个练习的目的是描述一种方法,其中预训练的图像分类器可以用来创建对象检测器,而无需对带注释的边界框进行任何显式训练。
设定期望后,让我们开始吧。
一点背景
在这个练习中,我使用了 PyTorch 中提供的经过预训练的 Resnet-18 模型的稍微修改的版本。
具体来说,使用的模型是 Resnet-18 的变体,其中模型的最终(也是唯一的)全连接(或线性)层被 2D 卷积层取代,从而将模型转换为全卷积神经网络(FCNN)。
全卷积 Resnet18 模型架构如下所示:
FCNN Resnet-18(图片由作者使用 Netron 获得)
作为前面提到的博客中提出的概念的快速回顾,FCNN 将任意大小的图像作为输入,并将包含模型对图像中检测到的对象的预测的激活/响应图作为输出。
使用反向传播算法,我们计算该响应图的感受野,并找出被检测物体在原始图像中的位置。
正如你现在所知道的,神经网络中特征图(或层)中像素的感受野代表影响其值的来自先前特征图的所有像素。这是一个有用的调试工具,可以了解网络在给出某个预测时在输入图像中实际看到了什么。
下图说明了这一概念
在以前的帖子中,我们只关注具有最大得分/概率的结果类别(我们毕竟只是想要一个图像分类器),这次我们将更深入地提取所有模型的预测,并计算每个相应的感受域,这将为我们提供边界框。
我们开始吧
我们的程序开始于我们获得 FCNN Resnet-18 模型在输入图像上预测的反应图之后,使用的是我在本文开头提到的这篇博客中捕获的步骤。我不会在这篇文章中重复这些步骤,因为这将是重复的努力,而且没有必要让这篇文章变得更长。
您应该记得,FCNN 的响应图形状为[1 x 1000 x n x m],其中 n 和 m 取决于输入图像和网络本身的宽度和高度。1000 对应于为 Resnet18 定型的 ImageNet 数据库的 1000 个类。
使用该响应图上的最大运算,我们从模型中获得顶部[n×m]预测或得分图,其可以被解释为在通过大小为 224×224(原始网络的输入图像大小)的滑动窗口获得的图像上的[n×m]个位置上执行的推断。
对于得分图中的每一个预测,我们都有概率(由 Softmax 层给出)和类别(来自 ImageNet)。
以下是形状[3 x 8]的分数图示例,其中突出显示了顶部预测:
得分图示例(图片由作者提供)
我们现在将检查这些[n×m]预测中的每一个,如果它的概率高于预定的阈值,则使用反向传播来计算它的感受野。
在这些感受野的每一个上,我们将应用 OpenCV 图像阈值和轮廓操作来获得边界框。
整个管道总结如下图所示:
FCNN 物体探测管道(图片由作者提供)
下面是实现的一个片段。从这里下载完整代码。
以下是我们取得的成果示例:
样品结果(图片由 Sidekix 媒体在 Unsplash 上拍摄)
处理重叠边界框
需要注意的重要一点是,网络可能会多次检测到同一个对象(概率各不相同)。这导致重叠边界框的问题,即使在标准对象检测算法中这也是典型的。
为了解决这个问题,我们使用了非极大值抑制方法。我用过 imutils 包提供的函数object _ detection . non _ max _ suppression()。然而,可以使用实现相同目的的任何其他方法。
感受野计算选项
现在我想谈谈一个有趣的设计选择。
对于任何检测到的对象类别,其相应的感受野可以使用以下任一方法计算:
-最大激活像素
-净预测
不用说,根据我们上面的选择,最终的边界框会有很大的不同。
同样,相应的细节很好解释本博客。
但是,我将对它们进行简要总结,并对这两种方法产生的结果进行比较。
选项-1: 为类别的最大激活像素计算的感受野,仅查看响应图中的一个像素,并提供原始图像中最大化该像素的区域的边界框。
在某些图像(例如低分辨率图像)上,该选项很好地隔离了类别的每个单独实例,如下图所示:
适当的分割(图片来源:Emmer &公司)
但是,在某些图像(例如高分辨率图像)中,这可能会返回一个边界框,该边界框仅包围对象的一部分,而不是整个对象,例如,参见下图:
不完全检测(由安德烈斯·达利蒙提在 Unsplash 上拍摄)
选项-2: 为净预测计算的感受野考虑了类别的相应响应图中的每个[n×m]像素,并提供了原始图像中使该类别的净预测最大化的区域的边界框。
和以前一样,这种方法的结果随着所用图像的不同而不同。
对于某些图像,它返回整齐地包围整个对象的边界框,如下图所示:
正确检测边界框(照片由 Andrés Dallimonti 在 Unsplash 上拍摄)
但是,在某些图像上,获得的边界框可能会将类别的几个实例封闭在一起,如下所示:
分割不当(图片来源:Emmer & Co. )
结论是什么?
如您所见,对于两个选项中哪一个会产生更好的结果,并没有明确的结论。
我建议读者使用他们自己的图像数据集进行进一步的实验,并获得更广泛的想法,了解什么对他们有效,什么对他们无效。
正如我最初提到的,这篇文章的目的不是介绍一种鲁棒的对象检测方法,而是探索如何使用一个特定的模型来达到另一个目的。
在机器学习中,模型的创建者发现他们的创造即使在从未被训练过的任务中也表现良好,这并不罕见。
带着这样的想法,我想结束这个博客。
我希望你喜欢它,我很高兴听到你可能遇到的任何进一步的想法和发现。
从这里下载完整的代码。
使用 leav 生成带有自定义工具提示的 Choropleth 地图(Python)
用数据标签在地图上显示香港选民和人口数据
由 cyda 拍摄
Folium 是在交互式地图上可视化数据的一个非常好的软件包。在这个演示中,使用了香港选民统计和人口数据。数据经过预处理后存储在 Consolidated Data.xlsx 中。随意摆弄数据集=)
可以直接从我的 Github repo 中克隆文件。
包裹
薄层
功能
folium
建立在 Python 生态系统的数据优势和leaflet.js
库的映射优势之上。用 Python 处理你的数据,然后通过folium
在活页地图上可视化。
示范
- 用不同的图块生成简单的地图
- 准备定制的数据标签并在悬停时显示在地图上
数据
Consolidated Data.xlsx
照片由 cyda 拍摄
任务 1:用不同的图块生成简单的地图
hk_geo = r’hkg_adm1.geojson’
voter_proportion = data[['Eng_name','Proportion']]map1 = folium.Map([22.38, 114.15], tiles='cartodbpositron', zoom_start=10)tiles = ['stamenwatercolor', 'cartodbpositron', 'openstreetmap', 'stamenterrain']for tile in tiles:
folium.TileLayer(tile).add_to(map1)
choropleth = folium.Choropleth(
geo_data = hk_geo,
name = 'choropleth',
data = voter_proportion,
columns = ['Eng_name', 'Proportion'],
key_on = 'feature.properties.name_1',
fill_color = 'YlGn',
fill_opacity = 0.7,
line_opacity = 0.2,
legend_name = '2019 年選民登記比例', # Voter Proportion (%) in 2019
highlight = True
).add_to(map1)folium.LayerControl().add_to(map1)# Display Region Label
choropleth.geojson.add_child(
folium.features.GeoJsonTooltip(['name_1'], labels=False)
)map1
逐步解释:
hk_geo = r’hkg_adm1.geojson’
要在地图上绘制数据,需要一个地图 json 文件来定位位置。本演示中使用的 json 文件是从这个站点下载的。
这定义了底图的初始设置。
tiles = ['stamenwatercolor', 'cartodbpositron', 'openstreetmap', 'stamenterrain']for tile in tiles:
folium.TileLayer(tile).add_to(map1)
Folium 支持在同一个地图中显示不同的地图框,下面是一些常见的地图框。您可以在每张图片的标题中找到瓷砖类型。
folium.LayerControl().add_to(map1)
此代码将图层选择按钮(显示在右上角)添加到地图,如果此代码丢失,地图将显示最后添加的图块,并且图块样式不能更改。
tiles = ’ cartodbpositron ',照片由 cyda 拍摄
tiles=“雄蕊水彩”,照片由 cyda 拍摄
tiles='openstreetmap ',照片由 cyda 拍摄
tiles='stamenterrain ',照片由 cyda 拍摄
choropleth = folium.Choropleth(
geo_data = hk_geo,
name = 'choropleth',
data = voter_proportion,
columns = ['Eng_name', 'Proportion'],
key_on = 'feature.properties.name_1',
fill_color = 'YlGn',
fill_opacity = 0.7,
line_opacity = 0.2,
legend_name = '2019 年選民登記比例', # Voter Proportion (%) in 2019
highlight = True
).add_to(map1)
叶。Choropleth 根据数据“比例”将彩色层添加到地图中。“Eng_name”是要与 geojson 文件的位置进行映射的数据的关键字。请确保 json 文件中位置名称的拼写与数据中的关键字相同。
照片由 cyda 拍摄
任务 2:准备定制的数据标签并在悬停时显示在地图上
要向地图添加自定义数据标签,使用follow . features . geojsontooltip。通过使用此代码,带标签的值将被追加到 geojson 文件中。
照片由 cyda
首先,您需要研究 json 文件的结构,并将工具提示数据添加到“属性”中。上面的脚本还允许您找出要在leav 的 key_on 中使用的映射键。Choropleth 。
key_on='feature.properties.name_1',
在工具提示中,我想用中文显示地区名称和数据值。运行代码后,您将看到附加的工具提示数据。
# prepare the customised text
tooltip_text = []
for idx in range(len(data2)):
tooltip_text.append(data2[‘Chi_name’][idx]+’ ‘+
str(int(round(data2[‘18–35 (Proportion in 2019)’][idx]*100)))+’%’)
tooltip_text# Append a tooltip column with customised text
for idx in range(len(tooltip_text)):
map_data['features'][idx]['properties']['tooltip1'] = tooltip_text[idx]
预期产出:
照片由 cyda
hk_geo = map_data
voter_proportion = data2[['Eng_name','18-35 (Proportion in 2019)']]map2 = folium.Map([22.38, 114.15], tiles='cartodbpositron', zoom_start=10)choropleth = folium.Choropleth(
geo_data=hk_geo,
name='選民比例',
data=voter_proportion,
columns=['Eng_name','18-35 (Proportion in 2019)'],
key_on='feature.properties.name_1',
fill_color='YlOrBr', # ‘BuGn’, ‘BuPu’, ‘GnBu’, ‘OrRd’, ‘PuBu’, ‘PuBuGn’, ‘PuRd’, ‘RdPu’, ‘YlGn’, ‘YlGnBu’, ‘YlOrBr’, and ‘YlOrRd’.
fill_opacity=0.7,
line_opacity=0.2,
legend_name='2019 年 18-35 歲選民 vs 18-35 歲人口比例',
highlight=True
).add_to(map2)folium.LayerControl().add_to(map2)
choropleth.geojson.add_child(
folium.features.GeoJsonTooltip(['tooltip1'], labels=False)
)map2.save('map2.html')
IFrame('map2.html', width=700, height=450)
绘图脚本类似于任务 1 中显示的脚本。在leav . features . geojsontooltip中,请选择您存储工具提示数据的变量名称。在我的例子中,它是“tooltip1”。 labels = False 隐藏变量名(即‘tool tip 1’)不与数据标签一起显示。
cyda 拍摄的照片
cyda 拍摄的照片
也有一些替代方法将数据标签添加到绘图中,但我发现这种方法更方便。如果你知道添加标签的其他方法,请随意评论。希望你喜欢我的文章=)
要了解更多关于使用 leav 的信息,请前往官方 Github 资源库。
如果你觉得我的文章有用,请在我的 linkedIn 页面上为我的技能背书,鼓励我写更多的文章。
使用免费 API 从公共 IP 地址获取地理位置信息
为娱乐而创作的项目
这篇文章演示了我使用免费 API 创建的一个项目。最初,目的是理解和学习更多关于 API 调用的知识。我在一家从事地理空间信息的公司实习。我在那里工作时得到了灵感。我将尝试简单地解释我使用的代码和方法,并从潜在的商业角度进行解释。同样,这是免费的,不需要额外的验证。我们开始吧!
这个项目的商业视角是确定人们访问网站的区域,基本上是受众细分。例如,一个电子商务网站想知道纽约有多少人在浏览他们的网站。因此,假设我是那家电子商务公司的老板,我在网上销售游戏设备,我想知道大概有多少人从城市或世界各地访问我的网站。我只有消费者的公开 IP 地址。现在,我想知道我的消费者来自哪个州和国家。这个 API 将从我的消费者的公共 IP 地址给我经纬度。从那里,我可以使用 geopy (名字)来获得地址,使用派生纬度和经度。很简单,对吧。既然思考和识别资源的工作已经完成,让我们开始学习一些 python 代码吧!
等一下,我没有所有消费者的公共 IP 地址,我只是在想象拥有一个游戏小工具公司。不用担心,Python 可以支持我的想象力,我们看看如何。
*import* random*import* socket*import* structsocket.inet_ntoa(struct.pack('>I', random.randint(1, 0xffffffff)))
如果您在一个 Jupiter 笔记本中运行上面的行,您将看到下面的输出。
好的,所有这些都很好,但是一次查找一个用户将会很耗时并且很困难,让我们制作一个自动化的 python 脚本,它将生成 100 个像这样的随机 IP 地址,查找它们的位置并将结果保存在一个 CSV 文件中。我在开发这个项目时使用了 VScode,但为了简单和易用,我将在最后提供一个包含源代码的 colab 笔记本。另外,我也会在最后给出 GitHub 的链接。
为此,我需要导入以下库。请求,json 是 API 调用所需要的,global_land_mask 用于验证纬度和经度(后面会讨论)。
import requestsimport pandas as pdimport numpy as npimport jsonimport randomimport socketimport structfrom geopy.geocoders import Nominatimimport osfrom global_land_mask import globe
如果您在导入库时遇到任何错误,只需在终端中使用以下命令。
pip install <library> #(pip install global-land-mask)
or
conda install <library>
下面是生成 100 个随机 IP 地址的函数。
def getting_ip_address():
"""This function returns a list of random IP address"""
new, explored=[],[]
i=0
while i<100:
ip = socket.inet_ntoa(struct.pack('>I', random.randint(1, 0xffffffff)))
if ip in explored:
continue
else:
new.append(ip)
i+=1
new = pd.DataFrame(new, columns=['ip'])
return new
我还创建了一个存储生成的 IP 地址的列表,该列表稍后将用于检查该 IP 地址之前是否已经创建过。我已经把最终的名单转换成了熊猫的数据框架,因为和熊猫一起工作我会更舒服。现在,下一步将是识别和获取他们的纬度和经度信息。
def getting_ip(row):
"""This function calls the api and return the response"""
url = f"[https://freegeoip.app/json/{row](https://freegeoip.app/json/{row)}" # getting records from getting ip address
headers = {
'accept': "application/json",
'content-type': "application/json"
}
response = requests.request("GET", url, headers=headers)
respond = json.loads(response.text)
return respond
这个函数获取每个 IP 地址,并向 API 发送请求,以获取经度和纬度信息以及其他信息。下面给出了一个位置的输出示例。
输出是 JSON 格式的,保存为 pandas 数据报中的一列。之后,从该列中可以推导出纬度、经度和时区等信息,如下所示:
def get_information():
"""This function calls both api and add information to the pandas dataframe column"""
new = getting_ip_address()
new['info'] = new['ip'].apply(lambda row: getting_ip(row))
new['time_zone'] = new['info'].apply(lambda row: row['time_zone'])
new['latitude'] = new['info'].apply(lambda row: row['latitude'])
new['longitude'] = new['info'].apply(lambda row: row['longitude'])
new['on_land'] = new.apply(lambda row: globe.is_land(row['latitude'],row['longitude']),axis=1)
new = new[new['latitude']!=0]
new = new[new['on_land']==True]
new['address'] = new.apply(lambda row: getting_city_nominatim(row),axis=1)
return new
这是主要的信息提取函数,它调用两个 API 并处理它们的信息。首先,记住上面显示的生成 100 个随机 IP 地址的函数,调用该函数并返回一个包含 IP 地址的数据帧。之后,使用 lambda 函数为每个 IP 地址调用 API,并将结果存储在名为“info”的列中。之后,从该列中为每个 IP 地址导出时区、纬度和经度。现在,它涉及到之前提到的 global_land_mask 库。我想检查一下纬度和经度是属于陆地还是海洋。这个 globe.is_land(latitude,longitude)为保存在另一个名为 on_land 的列中的每一行返回布尔值 True 或 False,稍后在 dataframe 上进行一个简单的过滤,以删除任何纬度是否正好为 0 以及纬度和经度是否在海洋中。然后调用 geopy Nominatim API 来获得更深的地址。调用 geopy API 的函数如下所示:
def getting_city_nominatim(row):
"""This function calls the geopy api and return the json address output"""
try:
lat = row['latitude']
lon = row['longitude']
geolocator = Nominatim(user_agent="my-application")
location = geolocator.reverse(f"""{lat,lon}""")
address = location.raw['address']
return address
except:
print('timeout')
该函数使用从早期 API 调用中获得的纬度和经度来调用 geopy Nominatim。在“192.168.10.111”返回纬度和经度之前,相同的 IP 地址显示为一个示例,下图显示了一个使用该纬度和经度的示例 nominatim 调用。
现在,我已经准备好了所有的构建模块,我需要将结果保存在 CSV 中。其功能如下所示:
def append_to_existing_df(new):
"""This function appends the new ip addresses to the dataframe"""
if os.path.isfile(f'{os.path.abspath("")}\location_of_ip_address.csv'):
new.to_csv('location_of_ip_address.csv', mode='a', header=False,index=False)
else:
new.to_csv('location_of_ip_address.csv', mode='w', header=True,\
columns=['ip','info','time_zone','latitude','longitude','address'],index=False)def deleting_duplicate_entries():
"""This function makes sure there are no duplicate ip addresses saved in the csv file"""
df = pd.read_csv('location_of_ip_address.csv')
df.sort_values('ip',inplace=True)
df.drop_duplicates(subset='ip',keep='first',inplace=True)
df.to_csv('location_of_ip_address.csv',index=False)
上面的第一个函数查找是否存在 CSV 文件,如果该文件存在,则将结果追加到现有文件中,如果该文件不存在于该目录中,则创建一个新的 CSV 文件。os.path.abspath(" ")返回 python 文件所在的当前目录的路径。和 os.path.isfile()检查文件是否存在。在保存它们并多次运行程序后,我注意到几个 IP 地址包含相同的内容,因为最初的重复检查只针对 100 个随机生成的 IP 地址。但是现在,我已经运行该程序多次,同一个 IP 地址在多个地方被发现。为了消除这种情况,后来添加了一个 add on 函数,该函数读取 pandas 数据帧中的整个 CSV 并删除重复值,然后将文件保存回来。
现在,我们已经准备好了所有的函数,下面的代码块显示了调用之前创建的所有函数的主函数。
def main():
"""main function"""
new = get_information()
append_to_existing_df(new)
deleting_duplicate_entries()if __name__ == '__main__':
main()
运行后,这会创建一个 CSV,其结果如下所示:
这是我创建的项目。我只是概述了我是如何想出这个主意,创造材料,以及自动化一个过程的。我没有深究太多的技术细节。我打算为此单独写一篇文章。这可以进一步改进和修改,例如,进一步自动化该过程,以便它在每天的特定时间运行。这里显示的源代码在 colab 中给出。另外,在 GitHub 中也有。
非常感谢您阅读这篇文章。这是我第一次为媒体写作,我会努力提高我的写作水平,并经常发表一些项目和想法。
利用 GBIF 数据和 GeoPandas 绘制生物多样性趋势图
基于 Python 的生物多样性数据热图
我在的上一篇文章中介绍了 GBIF 以及如何使用 Python 代码通过 API 访问这一优秀的生物多样性数据源,在这篇文章中,我将展示几种不同的方法来绘制之前下载的生物多样性数据。这将是一篇代码量很大的文章,主要使用 GeoPandas,并以复制我在论文中使用的图形而告终。然而,我使用了 R 和 ArcGIS 的组合来创建该图,现在我只使用 Python!希望这将是一个关于 GeoPandas 制图的有用教程,geo pandas 是处理地理空间数据的一个很好的工具!
如果您对不同地区之间的生物多样性变化感兴趣,地图是可视化空间差异的一种很好的方式。在进化生物学中,知道不同的生物多样性“热点”在哪里,也就是相对高生物多样性的区域,通常是很有趣的。这些热点可以代表高物种形成率的区域,或者如果你正在观察一个单一的生物群体,它们可能代表它们的起源区域。良好的生物多样性可视化对生物学家非常有用!
创建高质量的地图是复杂的,当我作为一名研究生第一次处理这个问题时,我非常依赖于预先存在的软件和图书馆中非常有用的 GIS 帮助台。现在我有了更多的编程技能,用 Python 重做这个问题真的很有趣。在 Python 中,有一些很好的处理地理空间数据的包。如果你正在处理栅格数据(基于像素的数据),一定要看看栅格。不过在这个问题中,我发现使用包 GeoPandas 更容易,它是基于非常有用的 Pandas DataFrame 构建的,但是使用 shapely 库合并了一个几何特性。就像 Pandas 中有数据框和系列一样,GeoPandas 中有地理数据框和地理系列,允许您将其他要素与地理数据相关联。几乎任何可以用熊猫做的事情都可以用地质公园来做。
绘制生物多样性地图的一种方法是通过一些现有的地理划界,如国家。这些被称为 choropleth 地图,有一些不同的软件包可以制作它们。这在 GeoPandas 中非常容易,它基于 matplotlib 进行绘图。这是我们首先要做的。
通过全国范围的物种计数绘制地图(Choropleth 地图)
从物种数据、原产国和地理坐标的数据框架开始,获得每个国家的物种数量是一个简单的聚合函数。我建议在你的数据集中添加一列,使用三个字母的 ISO A3 代码来代表每个国家,这样在以后加入数据框时事情会简单得多。你可以查看我的 GitHub(这篇文章底部的链接)来了解细节。
除了物种数据框之外,您还需要一个地理数据框作为底图进行绘制。我使用了来自 NaturalEarth 的中等分辨率的乡村形状文件。在下面的代码中,我展示了如何合并两个数据帧,然后绘制 choropleth 图(图 1)。
import pandas as pd
import matplotlib.pyplot as plt
import geopandas as gpd*#Read back in csv file*
df = pd.read_csv("../data/Ayenia_cleaned_dataframe.csv")*#Create a new dataframe with just number of species per country and #the country code*
df_plot = pd.DataFrame(df.groupby('country')['species'].nunique().sort_values(ascending=False))*#Load a country map*
countries = gpd.read_file("../data/ne_50m_admin_0_countries/ne_50m_admin_0_countries.shp", encoding = 'UTF-8')*#Only keep the relevant columns* countries = countries[['NAME', 'ISO_A3', 'CONTINENT', 'geometry']]*#Restrict countries to only North and South America*
countries = countries[(countries['CONTINENT'] == 'North America') | (countries['CONTINENT'] == 'South America')]*#Join with the species count dataframe*
countries = countries.merge(df_plot,
how = 'outer',
left_on = 'ISO_A3',
right_on = 'code')*#Drop the ISO code column we merged on*
countries.drop('code', axis = 1, inplace = True)#*Fill species count with 0 for countries that weren't in the species #count dataframe*
countries['species'].fillna(value = 0, inplace = True)#*Drop any remaining NaN (one small island country wasn't in the #basemap file, not a big deal here)*
countries.dropna(inplace = True)#*Now plot the choropleth with GeoPandas and matplotlib*fig, ax = plt.subplots(1, 1, figsize= (10, 10))countries.plot(column='species',
ax = ax,
legend = True,
cmap = 'YlOrRd',
legend_kwds={'label': "Number of Species",
'shrink': 0.5}) ax.set_facecolor("lightblue")
plt.xlim((-130, -30))
plt.ylim((-60, 50))
plt.xlabel('Longitude')
plt.ylabel('Latitude')
plt.title(r"Number of $Ayenia$ Species by Country",
fontdict = {'fontsize': 'x-large'})
plt.tight_layout()
图一。每个国家的叶蝉物种数量分布图。
虽然 choropleth 显示了物种多样性在 Ayenia 的位置,但它还不够具体。其中一些国家非常大(如巴西、美国),因此了解这些物种在这些国家的什么地方会更有意思。此外,通过给整个国家着色,地图可能会产生误导。例如,在南美洲的阿根廷, Ayenia 只限于该国北部地区(零下 30 度以上),而在美国, Ayenia 只出现在该国最南部地区。我想更真实地展示这些植物是在哪里发现的。
通过网格单元绘制生物多样性图
对于下一张地图,我想计算地图上每 1 个方格中可以找到的物种数量。这将绘制一张更详细的地图,显示艾叶尼亚的生物多样性。为了做到这一点,我必须完成以下步骤:
- 使用 GeoPandas 将物种数据框中的坐标更改为 shapely 对象
- 在我们感兴趣的区域内创建 1 格网像元的地理数据框架
- 计算每个网格单元内有多少物种
- 绘制网格
下面是我为每个步骤编写的代码:
#*1\. Import species data into a GeoDataFrame*
species_gdf = gpd.GeoDataFrame(df,
geometry=gpd.points_from_xy(df['decimalLongitude'],
df['decimalLatitude']),
crs = "EPSG:4326")*#2\. Create GeoDataFrame with 1 degree grid cells
#Make list of 1 degree grid cells with shapely polygons* from shapely.geometry import Polygonlong_range = list(range(-130, -29))
lat_range = list(range(-60, 51))poly_list = []for x in long_range:
for y in lat_range:
new_poly = Polygon([(x, y),
(x + 0.99999, y),
(x + 0.99999, y + 0.99999),
(x, y + 0.99999)])
poly_list.append(new_poly)#*Make GeoDataFrame from list of polygons, making sure that the #coordinate reference system aligns with your data points DF*
grid_df_1d = gpd.GeoDataFrame(geometry = poly_list,
crs = species_gdf.crs)#*Add a column of cell numbers for visualization purposes*
grid_df_1d['cell_no'] = list(range(0, len(grid_df_1d)))
此时,我们有了一个包含感兴趣范围内非重叠 1 格网单元的地理数据框架(图 2)。您可以轻松地修改此代码,使其具有更小或更大的网格单元大小(比如 0.5 或 10),并根据您的需要覆盖更大或更小范围的地球部分。我建议让它尽可能的小,因为这下一步不是很有效率,在我的笔记本电脑上运行需要 30 多分钟。
图二。研究区域内 1 格网单元的范围。为了说明的目的,示出了单元号。
#*3\. Calculate the number of species in each grid cell* #*Make a dictionary to store a list of each grid cell's species*grid_species = {}for x in list(range(0, len(grid_df_1d))):
grid_species[f'{x}'] = []#*For each species in the species dataframe, determine whether or not #it's within the bounds of the polygons in the grid dataframe. If it #is, add that species to the list in the grid_species dict (This step is very slow and inefficient! Does anyone know of a better way??)*for ind_s, val_s in species_gdf.iterrows():
for ind_g, val_g in grid_df_1d.iterrows():
if val_s['geometry'].within(val_g['geometry']):
grid_species[f"{val_g['cell_no']}"].append(val_s['species'])#*Now count the number of unique species in each list and save in a #new dictionary and then add data to the grid dataframe*import numpy as np
grid_counts = {}for k, v in grid_species.items():
grid_counts[k] = len(np.unique(v))grid_df_1d['species_count'] = np.zeros(len(grid_df_1d))
for k, v in grid_counts.items():
grid_df_1d.loc[int(k) ,'species_count'] = v#*Drop cells that don't have any species in them*
grid_df_1d_nozero = grid_df_1d.drop(grid_df_1d[grid_df_1d['species_count'] == 0].index, axis = 0)#*4\. Plot the grid*fig, ax = plt.subplots(1, 1, figsize = (10, 10))
countries.plot(ax = ax, color = 'whitesmoke')
grid_df_1d_nozero.plot(column = 'species_count',
ax = ax,
legend = True,
cmap = 'YlOrRd',
legend_kwds={'label': "Number of Species",
'shrink': 0.5})
ax.set_facecolor('lightblue')
plt.xlabel('Longitude')
plt.ylabel('Latitude')
plt.title(r"Biodiversity of $Ayenia$",
fontdict = {'fontsize': 'x-large'})
plt.xlim((-120, -30))
plt.ylim((-40, 40))
plt.tight_layout()
图 3。横跨北美和南美的每 1 个网格单元中的叶虫种类数。
现在,我们有了一个更好的表现,可以看出艾叶尼亚在哪里生长,在哪里不生长,哪里有更多的物种,哪里有更少的物种。在图 3 中,你可以看到墨西哥,尤其是墨西哥南部,是 T2 艾尼亚多样性的热点。在巴西南部以及巴拉圭、玻利维亚和阿根廷北部附近地区,还有几个热点。这些地区都有相似的栖息地,包括季节性干燥的热带森林或稀树草原。在巴西和阿根廷的中部有几个网格单元,它们的物种数量非常高,但这些可能不是真实的,因为我们数据中的一些标本具有模糊的位置数据,并且在该国中部给出了 GIS 坐标。以后我得用手移除它们。
我希望这些代码对学习如何绘制生物多样性地图有用。如果任何人有兴趣将其应用于他们自己的有机体群体,并需要帮助来运行它,我很乐意帮助你!
这是我在 Jupyter 笔记本上的 GitHub 页面的链接。
使用 GCP 人工智能平台笔记本作为可复制的数据科学环境
使用预配置的云托管虚拟机解决 Python 和 Jupyter 笔记本电脑的再现性问题。
照片由 Michael Dziedzic 在 Unsplash 上拍摄
在本文中,我们将介绍如何设置一个云计算实例,以便在有或没有 Jupyter Notebook 的情况下运行 Python。然后,我们展示如何将该实例连接到 Github,以实现流畅的云工作流。
我们利用云计算实例来获得灵活的 Python 和 Jupyter 环境,同时保持企业数据科学平台的可再现性。
这些人工智能平台笔记本电脑配置了许多数据科学和分析包,包括 NumPy、Pandas、Scikit-learn 和 TensorFlow。通常,我们不鼓励使用臃肿的虚拟机。然而,我们的分析机器上的包膨胀不是一个大问题,因为我们只保存结果(模型、数据、报告)供以后使用。只需要这个结果和运行我们的模型所需的几个包就可以让我们忽略 VM 上的大量包。
例如,在这篇中型文章中,我们将 NLP 模式推送到云中,而不必担心依赖性。
[## 使用 Docker、GCP 云运行和 Flask 部署 Scikit-Learn NLP 模型
构建一个服务于自然语言处理模型的应用程序,将其容器化并部署的简要指南。
towardsdatascience.com](/deploy-a-scikit-learn-nlp-model-with-docker-gcp-cloud-run-and-flask-ba958733997a)
请注意,AI 平台笔记本已经安装了 GCP 服务的所有客户端包,并且已经过认证,可以轻松访问同一个 GCP 项目中的任何内容。此外,这个平台不仅为我们提供了对 Jupyter 笔记本的访问,还提供了 Python 控制台和 CLI,我们可以在其中运行 BASH 命令。
获得 GCP 账户
谷歌的人工智能平台笔记本电脑为数据科学家和机器学习开发人员提供了一个 JupyterLab 和 Python 环境,用于实验、开发和将模型部署到生产中。用户可以创建运行 JupyterLab 的实例,这些实例预装了常见的软件包。
在我们可以建立一个人工智能平台笔记本之前,我们必须建立一个帐户和账单,不要担心新用户会获得 300 美元的免费积分!
访问 GCP 人工智能平台,点击“转到控制台”
请务必单击下面的“启用 API”来访问笔记本。
启用 API
一旦我们有帐单设置,我们可以开始一个项目。
启动您的第一个 GCP 人工智能平台笔记本实例
现在我们需要选择运行虚拟机的硬件。如果您要进行测试,请确保设置尽可能便宜的机器!
启用 API 后,弹出选项将变为如下所示,单击“转到实例页面”开始。
单击转到实例页
“实例”页面可能会让您在其他时间选择“启用 API”,请务必这样做。然后点击“新建实例”按钮,选择“Python 2 和 3”
笔记本实例
这将打开一个选项菜单,您可以在其中输入您想要使用的区域。请注意,不同的地区可能有不同的定价。一旦您选择了一个区域,您将需要单击“定制”并选择具有最少 RAM 的机器以获得最低的成本。在我们的示例中,它是具有 3.75GB 内存的“n1-standard-1”虚拟机。
这个实例只会在运行时产生费用,并且可以随时轻松暂停!如果需要,您可以在实例暂停时使用下面的下拉菜单更换硬件。
选择低成本的机器
现在我们可以使用 SSH 将我们的 VM 连接到 GitHub,这样我们就可以轻松地将push
和pull
连接到我们的存储库。
设置 SSH
请注意,每个实例只需这样做一次。
用 ssh 连接到 GitHub
- 通过运行
ssh-keygen
生成一个 ssh 密钥,并通过将它们留空并按回车键来接受默认值。该命令在~/.ssh/id_rsa
生成文件,您需要将这些文件输入 GitHub。
ssh-keygen
2.将您的公钥复制到剪贴板。一种方法是通过运行cat ~/.ssh/id_rsa.pub
将公钥文本返回到您的控制台,显示其内容,然后用鼠标和键盘进行复制。
用猫拿到我们的钥匙
3.前往 github.com 并登录。
4.点击右上角的个人资料图片,然后点击“设置”
5.在左侧,点按“SSH 和 GPG 密钥”
6.在右上角,单击“新建 SSH 密钥”
7.标题随便你怎么定。“标题”是您的选择,但它将帮助您识别该授权授权的计算机。将复制的密钥粘贴到“密钥”栏,然后按“添加 SSH 密钥”
8.回到您的计算机,运行eval
ssh-agent -s
来启动您的 ssh 验证代理。
步骤 8 和 9 添加我们的 ssh-key
9.运行ssh-add
添加您的私钥,以便代理可以验证公钥。
10.设置您的 git 配置,以便 GitHub 通过运行git config --global user.email you@email.com
和git config --global user.name username
知道您是谁,其中电子邮件和用户名是您的 GitHub 帐户附带的。
现在,您可以git clone
任何您有权访问的存储库到虚拟机上,对代码进行更改,并将它们推回存储库!
结论
我们已经讨论了如何设置一个云计算实例来运行 Python、BASH 和 Jupyter 笔记本,以及如何将该实例连接到 Github 来实现一个简单而安全的云工作流。
这个工作流程非常棒,因为它是如此的可复制!像这样使用虚拟机的团队将会遇到更少的“它在我的机器上工作”的错误。使用 ssh 连接云虚拟机和我们的远程存储库提供了一个安全的连接来保护您的数据。此外,如果您想在昂贵的硬件上运行代码,您不必购买硬件!相反,运行您需要的东西并暂停您的实例以节省成本。
我们希望这份指南对你有所帮助,并且你的编码技能和我们一样高!
使用遗传算法建立交易策略
展示遗传算法找到创造性解决方案的能力
来自《走向数据科学》编辑的提示: 虽然我们允许独立作者根据我们的 规则和指导方针 发表文章,但我们并不认可每个作者的贡献。你不应该在没有寻求专业建议的情况下依赖一个作者的作品。详见我们的 读者术语 。
遗传算法经常被忽略,作为另一种未能有效收敛的无监督学习算法。这是部分正确的,因为遗传算法不使用偏导数,因此训练算法不太直接。然而,遗传算法允许使用传统的基于梯度的优化来制定不可能的解决方案。
为什么会这样呢?如果基于梯度下降的模型从局部最小值开始,它将被永久卡住,因为每一侧上的点的梯度会更高。然而,由于遗传算法更具随机性,它最终会找到解决方案。
此外,神经网络需要具有明确定义的损失函数的标记数据。遗传算法只需要数据,可以巧妙地设计一个定制的损失函数(通常称为遗传算法的适应度函数)来优化某些特征。这种灵活性使得遗传算法从其余的无监督算法中脱颖而出。
遗传算法是如何工作的?
遗传算法是对该问题的一种强力攻击形式。唯一的区别是使用进化的概念来加速这个过程。遗传算法是这样工作的:
- 生成一个代理。它包含一组与神经网络兼容的权重。
- 计算代理的适应值
- 重复多次,直到你有一个代理“人口”
- 按适合度对人群进行排序,适合度最好的代理位于列表顶部。
- 一起“繁殖”顶级特工。
- 重复进行,直到达到满意的损失值。
通过改变适应度函数和输入数据,遗传算法可以应用于几乎每个问题!
当不应使用遗传算法时:
对于回归和分类等基本任务,使用基于梯度的优化器训练的深度神经网络总是比遗传算法收敛得更快。
遗传算法并不擅长寻找模式,而是有着广阔的视野,去寻找尚未被考虑的解决方案。
将遗传算法应用于股票交易:
股票交易是复杂的,因为不同来源的噪音冲淡了数据背后的真实模式。使用遗传算法就像走捷径一样;忽略了模式识别和复杂分析。它只是测试不同的策略,并找到交易证券的最佳策略。如果遗传算法不能找到一个好的解决方案,这只能是一个(或两个!)的这两个原因:
- 遗传算法没有被训练足够长的时间
遗传算法是一种蛮力算法,需要很长时间来缩小结果范围。这是一个很大的障碍,因为计算能力必须非常高才能克服这个问题
2.损失函数有问题
这个就常见多了。评估网络的损失函数并没有封装损失函数的目标。
代码:
**免责声明:**此代码改编自此处的代码,使其兼容优化创建交易策略的神经网络。
步骤 1|先决条件:
import random
import numpy as npdef sigmoid(x):
return 1/(1+np.exp(-x))
这些是项目最基本的依赖项。为了使学习过程更好,您可以为您合适的数据集添加更多的激活函数。
第 2 步|代理:
class Agent:
def __init__(self,network):
class neural_network:
def __init__(self,network):
self.weights = []
self.activations = []
for layer in network:
if layer[0] != None:
input_size = layer[0]
else:
input_size = network[network.index(layer)-1][1]
output_size = layer[1]
activation = layer[2]
self.weights.append(np.random.randn(input_size,output_size))
self.activations.append(activation)
def propagate(self,data):
input_data = data
for i in range(len(self.weights)):
z = np.dot(input_data,self.weights[i])
a = self.activations[i](z)
input_data = a
yhat = a
return yhat
self.neural_network = neural_network(network)
self.fitness = 0
def __str__(self):
return 'Loss: ' + str(self.fitness[0])
每个代理都有自己的一组权重,这些权重是根据神经网络设置的架构随机生成的。论证网络是一个列表,看起来像这样:
network = [[5,10,sigmoid],[None,1,sigmoid]]
列表中的每个术语都描述了神经网络中的一层。每个嵌套列表中的第一项是该层的输入神经元的数量,第二项是输出,第三项是要应用于该层的激活函数。
步骤 3|生成代理:
def generate_agents(population, network):
return [Agent(network) for _ in range(population)]
这用于生成代理,如运行遗传算法的方法的步骤 1 中所详述的。
第 4 步|计算健康度:
def fitness(agents,X):
neutral_range = 0
for agent in agents:
profit = 0
qty = 10
yhat = agent.neural_network.propagate(X[1:])
for i in range(len(yhat)):
if 0.5 + neutral_range > yhat[i] and yhat[i] > 0.5 -neutral_range:
yhat[i] = None
elif 1-yhat[i] > yhat[i]-0:
yhat[i] = 0
elif 1-yhat[i] < yhat[i]-0:
yhat[i] = 1
correct_trades = []
for i in range(1,len(X)):
if X[i][3] > X[i-1][3]:
correct_trades.append(1)
elif X[i][3] < X[i-1][3]:
correct_trades.append(0)
for i in range(len(yhat)):
if yhat[i]:
if correct_trades[i] == yhat[i]:
profit += abs(X[i+1][3] - X[i][3])*qty
elif correct_trades[i] != yhat[i]:
profit -= abs(X[i+1][3] - X[i][3])*qty
agent.fitness = profit
return agents
这个适应度函数是一个交易模拟,它使用过去的数据来计算模型的利润。每个代理通过输出一个 sigmoid 值生成一个交易列表。然后将这些值四舍五入到最接近的值。如果该值为 0,模拟将卖出股票。如果该值为 1,模拟将买入股票。
然后,它通过比较最佳交易和实际交易来计算利润。
第 5 步|代理选择:
def selection(agents):
agents = sorted(agents, key=lambda agent: agent.fitness, reverse=False)
print('\n'.join(map(str, agents)))
agents = agents[:int(0.2 * len(agents))]
return agents
这段代码本质上是根据代理的适应值对它们进行排序,从最低值到最高值。记住,适应值越低越好。然后,它将代理列表缩短为仅前 20%的代理。
第六步|代理交叉:
def unflatten(flattened,shapes):
newarray = []
index = 0
for shape in shapes:
size = np.product(shape)
newarray.append(flattened[index : index + size].reshape(shape))
index += size
return newarray
def crossover(agents,network,pop_size):
offspring = []
for _ in range((pop_size - len(agents)) // 2):
parent1 = random.choice(agents)
parent2 = random.choice(agents)
child1 = Agent(network)
child2 = Agent(network)
shapes = [a.shape for a in parent1.neural_network.weights]
genes1 = np.concatenate([a.flatten() for a in parent1.neural_network.weights])
genes2 = np.concatenate([a.flatten() for a in parent2.neural_network.weights])
split = random.randint(0,len(genes1)-1)child1_genes = np.array(genes1[0:split].tolist() + genes2[split:].tolist())
child2_genes = np.array(genes1[0:split].tolist() + genes2[split:].tolist())
child1.neural_network.weights = unflatten(child1_genes,shapes)
child2.neural_network.weights = unflatten(child2_genes,shapes)
offspring.append(child1)
offspring.append(child2)
agents.extend(offspring)
return agents
这是程序中最复杂的部分。在交叉过程中,从选定的代理中随机选择两个父代理。权重的嵌套列表被简化为一个长列表。分裂点是随机选取的。这决定了子对象将由每个父对象的多少权重组成。然后重新格式化该子代的权重,并将该子代添加到代理列表中。
第七步|突变:
def mutation(agents):
for agent in agents:
if random.uniform(0.0, 1.0) <= 0.1:
weights = agent.neural_network.weights
shapes = [a.shape for a in weights]flattened = np.concatenate([a.flatten() for a in weights])
randint = random.randint(0,len(flattened)-1)
flattened[randint] = np.random.randn()newarray = []
indeweights = 0
for shape in shapes:
size = np.product(shape)
newarray.append(flattened[indeweights : indeweights + size].reshape(shape))
indeweights += size
agent.neural_network.weights = newarray
return agents
遗传算法在技术上是“盲目的”,因为它们无法洞察由损失函数绘制的表面看起来是什么样子。这意味着遗传算法很容易卡在某个点上。突变允许轻微的、随机的、不频繁的变化来搅动锅,使系统脱离局部最小值。
第八步|执行程序:
def split_sequences(sequences, n_steps):
X, y = list(), list()
for i in range(len(sequences)):
end_ix = i + n_steps
if end_ix > len(sequences)-1:
break
seq_x, seq_y = sequences[i:end_ix, :], sequences[end_ix, :]
X.append(seq_x)
y.append(seq_y)
return np.array(X), np.array(y)
np.random.seed(0)
X,y = split_sequences(np.random.randn(1000,1),5)
X = np.reshape(X,(X.shape[0],X.shape[1]))
network = [[5,10,sigmoid],[None,1,sigmoid]]
ga = genetic_algorithm
agent = ga.execute(100,100,10000,X,network)
为了执行这个计划,我们需要数据。出于某种原因,SSL 证书不允许我从 yfinance 或 alphavantage 获取财务数据。我还不能运行这个程序,所以我只是使用随机 numpy 数组作为数据。
如果你想使用你自己的财务数据,这个概念就是用一组收盘价数据把它分成 n 个大小的块。
为了再现性,这个程序将设置自己的种子,使随机性固定。这是为了减少随机性,允许调整参数,如人口规模,时代和神经网络架构。这里更有趣的参数是输入网络的每个数据块的长度。调整这个参数可以找到程序运行的最佳时间。
结论:
正如我在自己的许多其他文章中所说的,这仅仅是用这个概念可以做什么的框架。你可以做无数的事情来改进我的程序。以下是其中的几个例子:
- 记录过去代理的生成
通过记录过去代理的生成,它可以防止程序重新生成以前已经生成的代理。
2.将该程序链接到一个纸交易账户
羊驼是使用纸面交易组合的明显选择。它可以快速进行交易,便于分析程序的性能。
3.创建更多贸易类型
这是三个中最难的。添加不同的交易,如止损和买入卖出交易,允许更微妙和分层的交易策略,将不同的交易类型相互叠加,以实现利润最大化。
我的链接:
如果你想看更多我的内容,点击这个 链接 。
使用遗传算法来安排时间表
遗传算法如何应用于解决时间表问题
遗传算法是一种受自然选择过程启发的进化算法。我在之前的文章 遗传算法介绍中已经介绍过遗传算法——包括示例代码 。如果你还没看过,就去看看吧!
遗传算法是一种受查尔斯·达尔文的自然进化理论启发的搜索启发式算法。这个…
towardsdatascience.com](/introduction-to-genetic-algorithms-including-example-code-e396e98d8bf3)
最近我注意到,我留下的一个关于使用遗传算法安排时间表的回复对很多人真的很有用。因此,我想把这个回复扩展成一篇文章,这样对读者会更有用。让我们开始吧。
什么是遗传算法?
引用我以前的文章,
遗传算法是一种受查尔斯·达尔文的自然进化理论启发的启发式搜索。这种算法反映了自然选择的过程,即选择最适合的个体进行繁殖,以产生下一代的后代。
如果你想了解更多关于遗传算法的知识,你可以阅读我的文章 遗传算法简介—包括示例代码 在这里我用示例解释了每个阶段。
总而言之,遗传算法的 5 个主要阶段是:
- 原始群体
- 适应度函数
- 选择
- 交叉
- 变化
下图显示了一般遗传算法的工作流程图。
通用遗传算法如何工作的流程图(图片由作者提供)
制定时间表
在时间表中,我们必须为我们计划的活动分配时间,并有序地协调资源,以便我们可以在不违反任何约束的情况下获得预期的结果。例如,学校时间表将协调学生、教师、教室、科目和时间段。一个考试时间表将协调学生、监考人员、科目、考场和时间段。公共交通时刻表将协调交通方式(公共汽车、火车等)。)、路线、到达时间和离开时间,以便乘客计划行程。
遗传算法可以应用的一个非常流行的场景是在安排时间表的过程中。
示例场景
假设你正试图为一所大学的不同学生群体制定一份每周课程表。我们必须安排课程,制定时间表,这样课程之间就不会有冲突。在这里,我们的任务是寻找最佳的时间表安排。
创造染色体
一个个体由一组被称为基因的参数(变量)来表征。基因连成一串形成染色体(解)。我们将我们的解决方案建模为染色体。
在我们的示例场景中,对于学习不同模块的学生,染色体将是不同的 讲座 。考虑到我们必须协调学生小组、模块、演讲厅、一周中的日期和时间。你可以把一节课描述成,
**<Module, Student Group, Day, Location, Lecture Time>**
您可以将讲座作为二进制模式编码到染色体中。
您可以为每个实体中的每个值指定二进制值。您可以随意更改编码模式。下面给出了一个你可以对讲座进行编码的例子。
获取模块列表并分配二进制值。
Data Mining - 0000,
Machine Learning - 0001,
Biology - 0010,
...
获取学生组列表,并给出二进制值。
STG0 - 00000,
STG1 - 00001,
STG2 - 00010,
STG3 - 00011,...
类似地,你可以在讲座中为每个实体提出编码方案。
下面给出了一个讲座的示例编码。
**<Data Mining, STG3, Monday, Hall D, 8.00AM>**Data Mining - 0000
STG3 - 00011
Monday - 000
Hall D - 1010
8.00AM - 1000Chromosome - **00000001100010101000**
单个的位被称为基因。这条染色体有 20 个基因。
创建初始群体
不同的学生群体在一周内上不同的课。因此,你必须想出不同的类组合,并创建初始种群。你可以决定人口数量(班级数量)。
**<Data Mining, STG3, Monday, Hall A, 8.00AM>
<Machine Learning, STG2, Tuesday, Hall B, 8.00AM>
<Computational Biology, STG8, Tuesday, Hall A, 10.00AM>
...**
如前所述,你必须将这些类别编码到染色体中。
提出一个评估函数
例如,您可以将评估函数公式化为学生组 的 班级冲突数的倒数。冲突的数量越少,讲座就越合适。你可以选择一个合适的评价函数。
现在,您可以执行交叉和变异操作,以最大化每个讲座的适应值。
结束
当群体达到最大适应值时,您可以终止该过程。在我们的例子中,这是讲座冲突最少的时候。
最后的想法
我在这篇文章中描述的方法是非常基本的,这样你就可以大致了解如何对时间表问题建模。如果你想阅读更多,你可以阅读许多的研究文章,其中提出了新的和改进的方法来使用遗传算法解决时间表问题。我看到的一些文章列举如下。
- 使用采用引导变异的遗传算法的时间表调度
- 带模糊时间窗的高中课表编排的改进遗传算法
- 利用遗传算法通过新的染色体表示解决时间表调度问题
- 遗传算法分析利用图着色法求解大学课程表问题
- 求解柔性作业车间调度问题的改进遗传算法
- 一种基于递归遗传算法的新型教育课程表解决方案
遗传算法并不是我们解决课程表问题的唯一方法。还有许多进化算法,如基于种群的搜索算法蚂蚁算法和蜜蜂算法。
希望你对现实世界的问题有了一个基本的概念,我们可以用遗传算法来解决它。请在回复部分告诉我你的想法。
不要忘记看看这个令人敬畏的遗传算法的实现,在【https://github.com/memento/GeneticAlgorithm】由 mem ento 制作的每一代基因库的可视化。
重构和改进来自 Vijini Mallawaarachchi 女士(@Vini2)的一个要旨我还把@liangyihuai 的编辑带进了…
github.com](https://github.com/memento/GeneticAlgorithm)
感谢您的阅读!
干杯!
使用遗传算法训练神经网络
许多人使用遗传算法作为无监督算法,来优化特定环境中的代理,但是没有意识到将神经网络应用到代理中的可能性。
什么是遗传算法?
遗传算法是一种学习算法,它使用的思想是,交叉两个好的神经网络的权重,会产生更好的神经网络。
遗传算法如此有效的原因是因为没有直接的优化算法,允许可能有极其不同的结果。此外,他们通常会提出非常有趣的解决方案,这些方案通常会对问题提供有价值的见解。
它们是如何工作的?
生成一组随机权重。这是第一个代理的神经网络。在代理上执行一组测试。代理会收到基于测试的分数。重复几次,创建一个群体。选择人口中前 10%的人进行杂交。从前 10%中选择两个随机亲本,并且它们的权重是交叉的。每次交叉发生时,都有很小的变异机会:这是一个不在双亲权重中的随机值。
随着代理慢慢适应环境,这个过程会慢慢优化代理的性能。
优点和缺点:
优势:
- 计算不密集
没有线性代数计算要做。唯一需要的机器学习计算是通过神经网络的正向传递。因此,与深度神经网络相比,系统要求非常广泛。
- 适合的
人们可以适应和插入许多不同的测试和方法来操纵遗传算法的灵活性。人们可以在遗传算法中创建一个 GAN,方法是让代理传播生成器网络,测试作为鉴别器。这是一个至关重要的好处,它使我相信遗传算法的使用在未来将会更加广泛。
- 可理解的
对于正常的神经网络来说,算法的学习模式充其量也是神秘的。对于遗传算法来说,很容易理解为什么会发生一些事情:例如,当遗传算法处于井字游戏环境中时,某些可识别的策略会慢慢发展。这是一个很大的好处,因为机器学习的使用是利用技术来帮助我们获得对重要问题的洞察力。
缺点:
- 需要很长一段时间
不幸的交叉和突变可能会对程序的准确性产生负面影响,因此使程序收敛更慢或达到某个损失阈值。
代码:
现在你已经对遗传算法及其优势和局限性有了相当全面的了解,我现在可以向你展示这个程序了:
import random
import numpy as np
这只是这个程序的两个依赖项。这是因为实现的神经网络基础设施是我自己创建的简单版本。要实现更复杂的网络,可以导入 keras 或 tensorflow。
class genetic_algorithm:
def execute(pop_size,generations,threshold,X,y,network):
class Agent:
def __init__(self,network):
这是“genetic_algorithm”类的创建,它包含了与遗传算法以及它应该如何工作有关的所有函数。主函数是 execute 函数,它将 pop_size、generations、threshold、X,y、network 作为参数。pop_size 是生成种群的大小,generations 是历元的术语,threshold 是您满意的损失值。x 和 y 用于标记数据的遗传算法的应用。对于没有数据或数据未标记的问题,可以删除 X 和 y 的所有实例。网络是神经网络的网络结构。
class neural_network:
def __init__(self,network):
self.weights = []
self.activations = []
for layer in network:
if layer[0] != None:
input_size = layer[0]
else:
input_size = network[network.index(layer)-1][1]
output_size = layer[1]
activation = layer[2]
self.weights.append(np.random.randn(input_size,output_size))
self.activations.append(activation)
def propagate(self,data):
input_data = data
for i in range(len(self.weights)):
z = np.dot(input_data,self.weights[i])
a = self.activations[i](z)
input_data = a
yhat = a
return yhat
self.neural_network = neural_network(network)
self.fitness = 0
该脚本描述了每个代理的神经网络的权重初始化和网络传播。
def generate_agents(population, network):
return [Agent(network) for _ in range(population)]
该函数创建将被测试的第一个代理群体。
def fitness(agents,X,y):
for agent in agents:
yhat = agent.neural_network.propagate(X)
cost = (yhat - y)**2
agent.fitness = sum(cost)
return agents
因为我使用的例子利用了标记数据。适应度函数仅仅是计算预测的 MSE 或成本函数。
def selection(agents):
agents = sorted(agents, key=lambda agent: agent.fitness, reverse=False)
print('\n'.join(map(str, agents)))
agents = agents[:int(0.2 * len(agents))]
return agents
这个函数模仿了进化论中的选择理论:最优秀的生存下来,而其他的则任其自生自灭。在这种情况下,他们的数据会被遗忘,不会被再次使用。
def unflatten(flattened,shapes):
newarray = []
index = 0
for shape in shapes:
size = np.product(shape)
newarray.append(flattened[index : index + size].reshape(shape))
index += size
return newarray
为了执行交叉和变异功能,权重需要展平和取消展平为原始形状。
def crossover(agents,network,pop_size):
offspring = []
for _ in range((pop_size - len(agents)) // 2):
parent1 = random.choice(agents)
parent2 = random.choice(agents)
child1 = Agent(network)
child2 = Agent(network)
shapes = [a.shape for a in parent1.neural_network.weights]
genes1 = np.concatenate([a.flatten() for a in parent1.neural_network.weights])
genes2 = np.concatenate([a.flatten() for a in parent2.neural_network.weights])
split = random.ragendint(0,len(genes1)-1)child1_genes = np.asrray(genes1[0:split].tolist() + genes2[split:].tolist())
child2_genes = np.array(genes1[0:split].tolist() + genes2[split:].tolist())
child1.neural_network.weights = unflatten(child1_genes,shapes)
child2.neural_network.weights = unflatten(child2_genes,shapes)
offspring.append(child1)
offspring.append(child2)
agents.extend(offspring)
return agents
交叉函数是程序中最复杂的函数之一。它产生两个新的“子”代理,它们的权重被替换为两个随机产生的父代理的交叉。这是创建权重的过程:
- 拉平父母的权重
- 生成两个分割点
- 使用分割点作为索引来设置两个子代理的权重
这就是智能体交叉的全过程。
def mutation(agents):
for agent in agents:
if random.uniform(0.0, 1.0) <= 0.1:
weights = agent.neural_network.weights
shapes = [a.shape for a in weights]flattened = np.concatenate([a.flatten() for a in weights])
randint = random.randint(0,len(flattened)-1)
flattened[randint] = np.random.randn()newarray = [a ]
indeweights = 0
for shape in shapes:
size = np.product(shape)
newarray.append(flattened[indeweights : indeweights + size].reshape(shape))
indeweights += size
agent.neural_network.weights = newarray
return agents
这就是变异函数。展平与交叉功能相同。不是分割点,而是选择一个随机点,用一个随机值替换。
for i in range(generations):
print('Generation',str(i),':')
agents = generate_agents(pop_size,network)
agents = fitness(agents,X,y)
agents = selection(agents)
agents = crossover(agents,network,pop_size)
agents = mutation(agents)
agents = fitness(agents,X,y)
if any(agent.fitness < threshold for agent in agents):
print('Threshold met at generation '+str(i)+' !')
if i % 100:
clear_output()
return agents[0]
这是 execute 函数的最后一部分,它执行所有已定义的函数。
X = np.array([[0, 0, 1], [1, 1, 1], [1, 0, 1], [0, 1, 1]])
y = np.array([[0, 1, 1, 0]]).T
network = [[3,10,sigmoid],[None,1,sigmoid]]
ga = genetic_algorithm
agent = ga.execute(100,5000,0.1,X,y,network)
weights = agent.neural_network.weights
agent.fitness
agent.neural_network.propagate(X)
这执行整个遗传算法。对于网络变量,每个嵌套列表保存输入神经元数目、输出神经元数目和激活函数。执行函数返回最佳代理。
我的链接:
如果你想看更多我的内容,点击这个 链接 。
使用 GeoPandas 和 Folium 过度设计犹太法律中的一个问题
借助计算地理学解决一个古老问题的尝试
今年是犹太人的普林节,庆祝挫败了古波斯针对犹太人的种族灭绝阴谋。根据你所在的地方,普林节是在 3 月 10 日,或者“蜀山节”是在 3 月 11 日。
这意味着这个古老的节日增加了时间和空间的维度。正是在这样的时候,我喜欢我的职业,数据科学,像过去的拉比和圣贤一样质疑一切。
哈尔布拉查高等犹太法学院的拉比埃利泽·梅拉梅德解释说,某些地方之所以会在一天后庆祝这个节日,是因为一些城市有城墙,而另一些没有。如果一座城市在约书亚·本·嫩时期建有城墙,那么这座城市会庆祝书珊节(后来的日子),否则这座城市会庆祝常规的普林节。
从表面上看,这似乎是一个相对容易的城市之间的划分,但仔细观察,许多问题出现了。
- 我们怎么知道约书亚·本·嫩时期存在的有城墙的城市就是今天以同样的名字命名的城市呢?Lod 就是这种情况。
- 这座在约书亚·本·嫩时代被城墙包围的城市已经失去了它的犹太社区和历史记录。
- 一个历史上存在于约书亚本嫩时期的城市,历史上有城墙,但不知道它是否在奇迹发生时有城墙。希伯伦就属于这一类
唯一一个仍然只庆祝舒山节的城市实际上是耶路撒冷。然而,我们的问题仍然没有完全回答,因为城市的边界在过去的 2000 年里呈指数增长。还有其他城市被认为可能是庆祝蜀山普林节的地方,所以在我们的实验中,我们也将解决它们。
今天的耶路撒冷被认为是约书亚本嫩时代的同一实体吗?
要回答这个问题,我们可以从《塔木德经》中找到一些具体的城市划分规范。“拉比耶霍舒亚·本·利瓦伊说:一座城市,和所有毗邻它的定居点,以及所有能与它一起被看见的定居点,即,能从有城墙的城市被看见的,都被认为是像这座城市一样的”(Megila 3,2)。
但是什么是相邻的呢?仅仅是密集的连片建筑吗?拉比·梅拉梅德澄清道邻里之间少于 141 肘(67.8 米)的差距足以将他们视为一个邻里。
所以现在我们有了一个关键变量,来过度设计犹太律法的问题。
有许多方法可以用数据来解决这个问题,但我认为最简单的方法是使用 OpenStreetMap 下载以色列的所有空间建筑数据,并将建筑的边界扩展 67.8 米,统一重叠部分,并找到包含耶路撒冷古代部分的多边形。
让我们开始吧:
导入必要的包后,我创建了一个可能的蜀山普林节城市(以及耶路撒冷)的字典,然后将它转换成笛卡尔参考系中的地理数据框架。我通过简单地使用谷歌地图来找到疑似城墙城市的可能历史中心地理坐标。
**import** **geopandas** **as** **gpd**
**import** **folium**
**import** **pandas** **as** **pd**
**from** **folium.plugins** **import** LocateControl
**from** **shapely.geometry** **import** Point
**from** **shapely.ops** **import** cascaded_union
walled_cities_dict = {"Tiberias":[32.775011, 35.545584],"Hebron":[31.524723, 35.110460],"Nablus":[32.213117, 35.284881],
"Jaffa":[32.053705, 34.750625],"Lod":[31.955268, 34.896203],"Gaza":[31.504135, 34.464426],
"Zefat":[32.966292, 35.494927],"Acre":[32.920831, 35.069042],"Haifa":[32.800833, 35.019167],
"Beit Shean":[32.499474, 35.498450],"Jericho":[31.870487, 35.440522],
"Beer Sheva":[31.237182, 34.793101],"Ramla":[31.924759, 34.872939],"Tyre":[33.274876, 35.193785],
"Sidon":[33.562539, 35.369619],"Damascus":[33.511125, 36.301019],"Izmir":[38.418095, 27.139753],
"Baghdad":[33.350506, 44.401307],"Jerusalem":[31.777055, 35.234268]}
walled_cities_gdf = gpd.GeoDataFrame()
walled_cities_gdf['City'] = list(walled_cities_dict.keys())
walled_cities_gdf['geometry'] = list(walled_cities_dict.values())
walled_cities_gdf['geometry'] = walled_cities_gdf['geometry'].apply(**lambda** x: Point(x[1],x[0]))
walled_cities_gdf.crs = {'init':'epsg:4326'}
walled_cities_gdf = walled_cities_gdf.to_crs(epsg=3857)
我在以色列这里下载了建筑足迹的 shapefile:
我们在质心中旋转多边形(以使其更容易计算)并将多边形缓冲 67.8 米。
buildings = gpd.read_file('/home/arieh/Downloads/israel-and-palestine-latest-free.shp/gis_osm_buildings_a_free_1.shp')
buildings = buildings.to_crs(epsg=3857)
buildings_cent = buildings.copy()
buildings_cent['geometry'] = buildings_cent['geometry'].centroid
buildings_cent['geometry'] = buildings_cent['geometry'].buffer(67.8)
然后,我们将其全部转换为一个多面,删除重叠面之间的边界,然后将该多面转换回地理数据框。这样做是为了方便我们在它和城墙城市地理数据框架之间执行空间连接。
all_union = cascaded_union(buildings_cent['geometry'])
all_union_polys = []
**for** poly **in** all_union:
all_union_polys.append(poly)
max_buffers_gdf = gpd.GeoDataFrame()
max_buffers_gdf['geometry'] = all_union_polys
max_buffers_gdf['id'] = range(len(all_union_polys))
max_buffers_gdf.crs = buildings_cent.crs
shushan_extents = gpd.sjoin(walled_cities_gdf,max_buffers_gdf)
max_buffers_gdf_subset = max_buffers_gdf[max_buffers_gdf['id'].isin(shushan_extents['index_right'])]
max_buffers_gdf_subset = max_buffers_gdf_subset.to_crs({'init':'epsg:4326'})
Shushan_Zones = pd.merge(max_buffers_gdf_subset,shushan_extents[['id','City']],on = 'id')
现在,我们只剩下可能庆祝蜀山普林节的最大范围。
然后,我们可以使用 leav 将我们计算的蜀山普林节区域转换成交互式网络地图。
x1,y1,x2,y2 = Shushan_Zones['geometry'].total_bounds
m = folium.Map(tiles='openstreetmap')
LocateControl().add_to(m)
m.fit_bounds([[y1, x1], [y2, x2]])
folium.GeoJson(Shushan_Zones,name='Shushan Zones').add_to(m)
folium.LayerControl().add_to(m)
m.save('local_files/Shushan_Zone_Tool.html')
互动网络应用位于这里
来自网络应用的一些截图显示了蜀山区的高级地图:
蜀山区高级地图
一张放大的照片显示了耶路撒冷的细节:
放大到耶路撒冷
嗯,那还行…但是杰里科和希伯伦呢?
希布伦
杰里科
这里没有太多的成功,因为有几个建筑足迹的工作。
所以我们有了它,就像物理肘杆如何帮助过去的圣人提出犹太律法的解决方案,所以我们也可以利用今天技术时代的工具和逻辑,并以可扩展和计算的方式应用它们!
这个项目的一个更好的版本将使用对象检测来描绘建筑物,并以光栅聚焦的方式创建区域。我想我们会把改造工作留到明年在耶路撒冷(zone)进行!
使用 GeoPandas 进行空间可视化
将您的数据带入现实世界
维尔莫索夫在 Freepik 上的照片
最近,我在做一个项目,试图建立一个模型来预测华盛顿金县——西雅图周围的地区——的房价。在查看了这些特征之后,我想要一种基于位置来确定房屋价值的方法。
数据集包括纬度和经度,很容易通过谷歌搜索它们来查看房屋、它们的街区、它们离水的距离等。但是对于超过 17000 次的观察,这是一个愚蠢的任务。我必须找到更简单的方法。
我以前只用过一次地理信息系统(GIS ),但不是用 Python。所以我做了我最擅长的事情:我谷歌了一下,然后遇到了这个叫做 GeoPandas 的神奇包。我将让 GeoPandas 团队总结他们所做的工作,因为他们可以说得比我好得多。
GeoPandas 是一个开源项目,旨在简化 python 中地理空间数据的处理。GeoPandas 扩展了 Pandas 使用的数据类型,允许对几何类型进行空间操作。几何运算由 shapely 完成。GeoPandas 进一步依赖 fiona 进行文件访问,依赖 descartes 和 matplotlib 进行绘图。—来自 GeoPandas 网站的描述(2020)
这让我大吃一惊,我想要的真的只是最基本的功能。我将向您展示如何运行这段代码,并做我所做的事情——在地图上绘制精确的点。
除了基本的 pandas 和 *matplotlib,你还需要几个包和一些文件。*它们包括:
- geo pandas——让这一切成为可能的包
- 造型优美的——用于处理和分析平面几何对象的软件包
- 笛卡尔 —用 Matplotlib 更好地集成了形状优美的几何对象。不是每次都需要它,但我导入它只是为了安全
- 任何。shp 文件——这将是情节的背景。我的将有国王县,但你应该可以从任何城市的数据部门找到一个。不要从中删除任何文件。压缩文件。总会有东西坏掉。
关于 shapefiles 的更多信息可以在这里找到,但是总而言之,这些不是普通的图像。它们是一种矢量数据存储格式,包含与位置相关的信息—坐标和其他信息。
首先,我导入了我需要的基本包,然后是新包:
点和多边形特征帮助我将我的数据与我制作的地图相匹配。
接下来,我加载我的数据。这是基本的熊猫,但对于那些新的,在报价的一切是我不得不访问住房记录文件的名称。
随着所有包的导入和数据的准备,我想看看我将要绘制的地图。我是通过找一个金县政府网站做的形状文件做到的。他们已经完成了土地勘测和编目的所有艰苦工作——不使用他们免费提供的服务是不礼貌的。加载到形状文件中很容易,与用熊猫加载到 csv 文件中相当。
如果你想看看数据,你可以打开它。King County shape 文件只是一个与他们的学区、几何坐标和面积相匹配的位置数据框架。但是最好的部分是当我们绘制它的时候,是的,我们必须绘制它。这不是一个你可以随便调用的图像——它将内置坐标,因此我们的数据可以像五年级(x,y)图表上的一个点一样放置。
使用下面的代码(注意我是如何编辑它的,就像我编辑图形一样):
我的输出如下所示:
作者提供的图片
在我们开始添加我们的住房数据之前,我们应该充分利用形状文件。我们来看看文件。
如您所见,该县被划分为几个学区,每个学区都有一个用作边界的形状。我们现在将尝试绘制形状文件,并使用提供的数据对区域进行注释,如下所示:
列表——左边、右边、中间——是在地区名称的位置上反复试验的结果。一些重叠或需要被操纵,以便它们不会偏离它们的实际区域太远。
为了清晰起见,我将颜色贴图改为 gist_earth 。接下来,我使用 NAME 系列中的条目遍历每一行,并将标题放在多边形中明确的一个点上。我根据我之前列的名单排列了这些名字。这是我们的输出:
金县的学区。作者提供的图片
每个区域代表金县的一个学区。这与我找到的关于该县二十个学区的数据相符。我从来没有真正想过一个县的大小和形状,所以我谷歌了一下,只是为了确定一下。
来源:谷歌地图
看起来谷歌地图上的图像是我拼图的完美洞口。从这里开始,只需要格式化我的数据以适应形状文件。我通过初始化我的坐标系统并使用我房子的纬度和经度创建适用的点来做到这一点。
如果你要查看几何体中的一个条目,你只会得到它们是形状优美的物体。它们需要应用于我们的原始数据帧。下面,你可以看到我做了一个全新的数据框,内置了坐标系统,旧的数据框,以及由房屋的纬度和经度的交叉点创建的点的添加。
这是我们绘制房屋图之前的最后一步。现在,我们把它们放在一起。
在上面的代码中,步骤包括:
- 调用对象进行绘图。
- 绘制金县形状文件。
- 绘制包括几何点在内的数据。
这包括制作标记、选择特征以及为图例添加标签。 - 添加图例、标题和轴标签。
对每幅图都执行了这些步骤。
我们的产出:
这是一个伟大的产品,但我们的目标是从这个可视化学习一些东西。虽然这提供了一些信息,如远离该县东部的异常值,但它没有提供太多其他信息。我们必须使用参数。让我们试着按价格分割数据。这些是标价低于 75 万美元的房子。
价格低于 75 万美元的房子。作者提供的图片
现在我们用图表表示大于或等于 750,000 美元的房屋。
价格在 75 万美元以上的房子。作者提供的图片
无论是位置还是数量都有很大的区别。但这不是目的,我们还可以将它们一层一层地堆叠起来。我们将在便宜的基础上做昂贵的,因为它更稀缺。
并排比较。作者提供的图片
这幅地图画的图画很有趣。金县有太多的房子低于我们设定的标准。大多数价格较低的房子比价格较高的房子更靠近内陆。
如果放大,更贵的房子点缀在水边。他们也更集中在西雅图市中心。有几个物理异常值,但趋势是明确的。
总的来说,可视化已经完成了它的工作。我们已经根据地图上的房子做了一些决定。价格较高的房屋聚集在市中心周围,散布在普吉特湾周围。他们在数据中也是少数,这可能有助于预测房价。价格较低的房子数量更多,位置也各不相同。这将有助于进一步的 EDA。
如果你想联系更多关于这个技术的讨论,你可以在 LinkedIn 上找到我。如果你想看看代码,看看我的 Github 。
来源
使用 GitHub 动作加速数据科学项目中的 CI/CD
随着关于云计算的最新进展,实现工具变得更加必要,这些工具同时是可扩展的,并且确保执行的可再现性。考虑到这一需求,一些工具应运而生,如 Docker ,它们允许创建应用程序的“配方”,确保同一应用程序的不同版本平等运行。
与虚拟机(VM)不同,虚拟机(VM)通过虚拟机管理程序提供基础架构并模拟处理器和内存,Docker 在整个容器中共享这些资源,使开发人员可以更少地关注基础架构,而更多地关注应用程序的开发。尽管如此,项目和应用程序的容器化减轻了“它在我的机器上运行”的表述,因为它试图确保独立于开发者选择的平台,Docker 容器总是以相同的方式执行。
鉴于容器化的好处不仅限于应用程序的开发,还可以用于其他领域,许多数据科学家开始使用 Docker 来容器化他们的分析、模型训练、仪表板和 API,这不仅可以使项目的交付更容易(因为它减少了出现错误的可能性),还可以确保一旦发现结果,就可以再次获得。
CI/CD —持续集成和持续部署
尽管数据科学家生成的许多见解和机器学习模型是有价值的,但当项目被困在不能被其他人使用的个人机器中时,它们无法为它们所插入的业务增加价值。因此,为了确保任何修改都被识别,并且其结果被扩展到其他团队,有持续集成和持续部署(CI/CD)的过程,这允许在项目的初始版本中测试和部署过程的自动化。
许多人可能熟悉 CI/CD 的概念,但是,用于此流程的许多工具都是付费的(例如 Jenkins 、 CircleCI 和 TravisCI ),因此仅限于以下人员使用:
- a)愿意支付这些工具的价格;
- 或者 b)在已经开发了 CI/CD 循环的公司工作。
GitHub 和 GitHub 操作
GitHub 是一个知名的代码版本平台,拥有超过 4000 万用户和超过 1 亿个存储库,是一个巨大的开源代码来源,可供世界各地成千上万的人使用。
GitHub 于 2019 年创建了 GitHub Actions 工具,旨在支持开源项目的创建,并允许其用户抽象涉及 CI/CD 的流程。它允许用户定义的工作流的自动化,以帮助集成测试、拉请求的验证和许多其他特性。此外,用户使用的动作数量与日俱增,因为许多公司都在寻求开发工具来帮助用户社区。许多这些行动已经允许集成许多流行的工具,如 Docker,AWS CloudFormation,Terraform,以及许多其他可以在这里找到的工具。
尽管 GitHub Actions 只对非私有存储库免费使用,但在考虑使用任何 GitHub 企业工具之前,在私有项目中可以利用不同级别的使用。这打开了一扇门,使得许多开发开源项目的人可以测试他们的工具,并以更加自动化和可扩展的方式传播他们的发现。
Docker 登录& Docker 构建和推送
与 GitHub 操作一起使用的工具之一是存储库中的登录操作,它允许存储 Docker 映像(如 AWS 的 Docker Hub 、ECR、GCP 的GCR),以及构建这些映像,而无需占用用户的机器。考虑到这些,在 CI 工作流文件中声明了两个操作,可以在这些链接中找到: docker/login-action 和docker/build-push-action。
将数据科学融入 CI/CD 流程
数据科学领域充满了不同的框架、依赖项和不同的语言,可以根据数据科学家的需求和能力来使用,但其中一个共同的事实是,它们都有可能被容器化过程封装,这有助于确保项目的可重复性。
考虑到这一点,我用来部署 GitHub Actions 自动化工具的例子涉及到使用 R 的 Shiny 库开发一个 web 应用程序。然而,同样的工作流实现可以用于部署使用 Python 的 FastAPI 开发的 API,即,或任何其他可以封装在 Docker 容器中的框架。
项目可以在这里找到:paeselhz/ghActionsDockerShiny。我不会详细介绍应用程序的开发,因为我使用的例子相对简单,没有详细的开发。本文的重点是项目的容器化,以及构建图像并将其存储在 Docker Hub 中的工作流自动化,以供进一步下载。
创建 Dockerfile 文件
对于那些熟悉 Docker 文件及其语法的人来说,它的执行与使用 Docker 在本地开发、构建和运行的项目中的预期是一样的。在其中,我们声明了将用于进一步安装库和依赖项的基本映像,以及项目的配置、文件复制和其他通常可以添加到 Dockerfile 中的步骤。
FROM rocker/shiny:4.0.0RUN apt-get update \
&& apt-get install -y \
libxml2-dev \
libglpk-dev \
&& install2.r \
--error \
dplyr \
shiny \
purrr \
highcharter \
shinyWidgets \
shinycssloaders \
devtools \
xml2 \
igraph \
readr
RUN R -e "devtools::install_github('wilsonfreitas/rbcb')"COPY . /srv/shiny-serverRUN chmod -R 777 /srv/shiny-server
这个脚本位于项目根目录中,负责收集已经安装了 Shiny 及其依赖项的映像,并安装将由 r。
创建工作流文件
为了让 GitHub Actions 知道工作流自动化需要采取哪些步骤,有必要在项目中创建一个文件,该文件位于。github/workflows/main.yml,文件语法与任何 YAML 文件相同,易于编码。如果用户不想在本地完成这个过程并提交更改,GitHub 本身有一个在线代码编辑器来创建工作流。
在这个文件中声明了一些步骤,例如工作流的名称、将用于部署工作流执行的触发器以及它将负责执行的作业。文件的名称和触发器部分是高度可定制的,用户可以以多种方式对其进行更改,此外,在作业部分,需要几个步骤来完成作业:登录 Docker Hub,配置 BuildX(将用于构建映像的工具),配置 QEMU(将允许多平台构建的工具),将构建的映像部署到 Docker Hub,注销并清理机器以确保没有进程仍在运行。
# Setting up a Workflow to work with Github Actionsname: ci# Controls to when trigger the GH Action
# Below are configurations to the following triggers:
# - commits at master branch
# - tag commits at the project
# - scheduled to run at 01:00GMT
# The user can also configure triggers at pull requests
# as well as remove branches from triggering GH Actions
on:
push:
branches: [ master ]
tags: [ '*.*.*' ]
schedule:
- cron: '0 1 * * *'# Below there is the job configuration to build the image
# and push it to a DockerHub repository
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v2
-
name: Prepare
id: prep
run: |
DOCKER_IMAGE=<USER_NAME>/<REPOSITORY_NAME>
VERSION=noop
if [ "${{ github.event_name }}" = "schedule" ]; then
VERSION=nightly
elif [[ $GITHUB_REF == refs/tags/* ]]; then
VERSION=${GITHUB_REF#refs/tags/}
elif [[ $GITHUB_REF == refs/heads/* ]]; then
VERSION=$(echo ${GITHUB_REF#refs/heads/} | sed -r 's#/+#-#g')
if [ "${{ github.event.repository.default_branch }}" = "$VERSION" ]; then
VERSION=edge
fi
fi
TAGS="${DOCKER_IMAGE}:${VERSION}"
if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
MINOR=${VERSION%.*}
MAJOR=${MINOR%.*}
TAGS="$TAGS,${DOCKER_IMAGE}:${MINOR},${DOCKER_IMAGE}:${MAJOR},${DOCKER_IMAGE}:latest"
elif [ "${{ github.event_name }}" = "push" ]; then
TAGS="$TAGS,${DOCKER_IMAGE}:sha-${GITHUB_SHA::8}"
fi
echo ::set-output name=version::${VERSION}
echo ::set-output name=tags::${TAGS}
echo ::set-output name=created::$(date -u +'%Y-%m-%dT%H:%M:%SZ')
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
platforms: linux/amd64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.prep.outputs.tags }}
labels: |
org.opencontainers.image.title=${{ github.event.repository.name }}
org.opencontainers.image.description=${{ github.event.repository.description }}
org.opencontainers.image.url=${{ github.event.repository.html_url }}
org.opencontainers.image.source=${{ github.event.repository.clone_url }}
org.opencontainers.image.version=${{ steps.prep.outputs.version }}
org.opencontainers.image.created=${{ steps.prep.outputs.created }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.licenses=${{ github.event.repository.license.spdx_id }}
工作流代码几乎没有外部依赖性,假定 Docker 图像名称及其标签的创建在该代码中,然而,它需要一对秘密来登录 Docker Hub,在这种情况下,需要 Docker 使用的用户名和一个令牌来登录 Docker Hub ( 可以在这里生成)。有了用户名和令牌,用户只需进入他们的存储库,在“设置”选项卡中,在“机密”子页中添加令牌,如下图所示:
通过这些步骤,项目应该能够使用 GitHub 动作来执行,从而允许构建、测试和部署过程的自动化。
在本文使用的示例中,最终的映像可以在 Docker Hub 这里找到,并通过运行以下命令进行本地测试:
` docker run-p 3838:3838 lhzpaese/GH actions _ docker _ shiny:latest ’
来源:
本文中使用的许多项目和工具的参考都是以超链接的形式随文本一起添加的。然而,我想离开 Docker 网上研讨会,它是这个项目的灵感来源,由 metcalfc 主持,他在会上介绍了用于集成 GitHub Actions 和 Docker 的工具。
感谢您的关注,如有任何问题或建议,请随时通过https://www.linkedin.com/in/lhzpaese/和 paeselhz 联系我。
使用 Github 页面创建全局 API
这篇文章是关于创建一个全球托管的静态 API 并将其用于你的网站的 API。
所以,我意外地发现,当我们访问任何一个 GitHub Pages 站点时,我们可以返回 JSON 响应,而不是标准的 HTML。所以,我想与其创建一个静态网站,为什么不做一些开发的乐趣;)并为我需要的数据创建一个 REST API。
例如,如果您在终端上键入以下内容
**curl https://gauravghati.github.io/apis/home.json**
给出以下结果:
{
"name": "Gaurav Ghati",
"phone": "+91 9067365762",
"mail": "gauravghati225@gmail.com",
"bio": "MY BIO",
"fun": "FUN STATEMENT",
"github": "https://github.com/gauravghati/",
"twitter": "https://twitter.com/GauravGhati/",
"linkedin": "http://linkedin.com/in/gauravghati/",
"aboutme": {
"para1": "This is Paragraph 1",
"para2": "This is Paragraph 2"
},
"volunteer": {
"tfi": {
"heading": "Teach For India",
"description": "description of TFI"
},
"pisb": {
"heading": "PICT IEEE Student Branch",
"description": "description of PISB"
}
}
}
我在我的网站上使用了这个 JSON API,现在每次当网站加载时,它都会请求 Github pages 链接,从那个 URL 获取数据,并在网站上显示获取的数据!
现在,每当我想编辑我网站上的任何数据时,我只需更改 JSON 文件并将它们推送到我的 Github 页面 repo ,这些更改就会自动显示在主网站上。
可以查看网站的代码,这里。在姜戈。
现在,如何创建自己的 API?
通过使用 Github,我们可以在那里托管我们的 JSON 文件数据,因为它是一个静态站点,唯一的限制是它只支持 GET 请求。
设置 Github 页面
在 Github 上创建一个新的存储库,存储库名称为"Github _ username . Github . io"之后,您的新 GitHub 页面库就准备好了。该存储库的 URL 将是
https://github _ username . github . io/
准备 JSON 文件
克隆上述存储库,或者如果您已经在本地保存了 JSON 文件,运行该文件夹中的 git init 并添加上述远程存储库,然后提交并推送您的。json 文件。将文件推送到那里后,您可以在点击 URL 时访问 JSON 响应:
https://github _ 用户名. github.io/json_file.json
通过 JSON API 访问数据
我们点击 URL 后得到的数据是 JSON 对象的形式。我们可以在呈现 HTML 页面时直接将它发送到前端。
当你的后端在 python 中,即在 Django 或 Flask 中,你可以访问 JSON 文件的数据,如下所示:
import urllib.request, jsongithub_link = "https://github_username.github.io/json_file.json"with urllib.request.urlopen(github_link) as url:
json_data = json.loads(url.read().decode())print(json_data)
这段代码将输出所有 JSON 数据。
如果您想要使用 JQuery 获取 JavaScript 中的数据,您可以访问 JSON 文件的数据,如下所示:
let github_link = ‘https://github_username.github.io/jsonfile.json';$.getJSON(github_link, function(data) {
//data is the JSON string
console.log(data);
});
这就是你如何使用 Github 页面来创建全局 API。
使用 Google BigQuery 分析 EBI 小分子数据库的数据
分析欧洲生物信息学研究所 CheMBL 数据集
曼努埃尔·盖辛格在的照片
CheMBL 是一个生物活性分子的药物发现数据库。它汇集了化学、生物活性和基因组学信息,目标是帮助将基因组信息转化为有效的新药。在这篇文章中,我们将对 CheMBL 数据库中的数据样本进行探索性的数据分析。
要访问这些数据,您需要创建一个 Google Cloud 帐户,安装 Google BigQuery 并生成认证凭证。操作说明可以在这里找到。
我们开始吧!
首先,让我们导入必要的 google 身份验证和 BigQuery 包:
from google.oauth2 import service_account
from google.cloud import bigquery
接下来,我们需要导入熊猫并将列设置定义为“None”。我们这样做是为了显示所有的列,因为 pandas 默认情况下会截断它们。
import pandas as pd
pd.set_option("display.max_columns", None)
接下来,我们定义密钥路径、客户端凭证和客户端对象:
key_path = "key_path/key.json"credentials = service_account.Credentials.from_service_account_file(
key_path,
scopes=["[https://www.googleapis.com/auth/cloud-platform](https://www.googleapis.com/auth/cloud-platform)"],
)client = bigquery.Client(
credentials=credentials,
project=credentials.project_id,
)
接下来,我们定义数据集引用,其中我们传入数据库的名称“ebi_chembl”和项目的名称“patents-public-data”:
dataset_ref = client.dataset("ebi_chembl", project="patents-public-data")
我们可以看看有空的桌子。共有 229 张桌子,但我们将查看前 10 张桌子的名称:
columns = [x.table_id for x in client.list_tables(dataset_ref)][:10]
print(columns)
让我们看看列表中的第一个元素“action_type”:
table_ref = dataset_ref.table('action_type')
table = client.get_table(table_ref)
我们可以将结果存储在数据帧中,并打印前五行:
df = client.list_rows(table).to_dataframe()
print(df.head())
我们可以从 python 中的 collections 模块导入 counter 方法来分析动作类型的分布:
from collections import Counter
print(Counter(df['action_type'].values))
让我们看看接下来的十张表,看看是否有更详细的信息:
columns = [x.table_id for x in client.list_tables(dataset_ref)][10:20]
print(columns)
让我们看一下“化验分类”表:
table_ref = dataset_ref.table('assay_classification')
table = client.get_table(table_ref)df = client.list_rows(table).to_dataframe()
print(df.head())
让我们用计数器来看看“l1”最常见的 3 个值:
from collections import Counter
print(Counter(df['l1'].values).most_common(3))
对于“l2”:
from collections import Counter
print(Counter(df['l2'].values).most_common(3))
我们也可以将这些输出可视化。让我们定义一个函数,它将类别字符串作为输入,并显示三个最常见值的条形图:
def plot_most_common(category):
bar_plot = dict(Counter(df[category].values).most_common(3))
plt.bar(*zip(*bar_plot.items()))
plt.show()
现在让我们用“l1”调用函数:
plot_most_common('l1')
和“l2”:
plot_most_common('l2')
在这篇文章中,我们讨论了如何使用 Google BigQuery 访问欧洲生物信息学研究所 CheMBL 数据库。该数据库包含化学,生物活性和基因组信息,可以帮助小分子药物的发现。我们对 action_type 和 assay_classification 表进行了一些基本分析,并生成了一些可视化结果。这篇文章的代码可以在 GitHub 上找到。感谢您的阅读!
使用谷歌位置历史分析健身房访问
Sebastian Hietsch 在 Unsplash 上拍摄的照片
除了搜索目的地之外,谷歌地图还有其他用途吗
我 2019 年的新年决心之一是更经常地去健身房。作为一个分析数据集的狂热爱好者,我决定利用我一直在收集的位置历史,调查一下去年我在这项工作中的一致性和成效。对于那些不知道的人来说,谷歌记录了你访问过的所有地方(当然,前提是你给予许可!).自从 2013 年我拥有第一部 Android 手机以来,我已经为谷歌分配了存储我的位置历史的权限,因此检索 JSON 中的数据并不困难。使用 Python 和 Matplotlib 来分析数据,我决定看看我去年在遵守决心方面有多成功。
起初,这些数据对我来说没有多大意义。但是一个小小的研究让我了解了错综复杂的数据。这些数据包含了我自 2013 年以来去过的所有地方的纬度、经度和日期。此外,它还包括几个描述“精度”、“速度”、“高度”、“航向”、“垂直精度”的栏目,这些在当前的上下文中对我没有用。因此,第一项任务是把数据整理成我能理解的东西。
来自 JSON 的原始数据帧
除了将“timestampMs”转换为相应的日期和时间,我还删除了“精度”、“速度”、“高度”、“航向”和“垂直精度”列,以创建一个经过处理的数据帧。第二个挑战是瞄准我的健身房的坐标来过滤数据帧。我不得不承认这是分析中最具挑战性的部分之一。纬度和经度是地理坐标系统的一部分,用来确定地球上任何地方的位置。
在谷歌地图上看到的健身房位置
我的健身房由北纬 50° 06 ’ 53.5 "和东经 8° 41 ’ 12.9 "描述。但是,使用它们,我无法从我的位置历史记录中筛选出与我的健身房相对应的数据点。为了找到一个大概的经纬度范围进行过滤,我把目光对准了谷歌为我 12 月 6 日的锻炼记录的坐标。
为我在 2019 年 12 月 6 日的锻炼处理数据帧
使用与我的健身房相对应的坐标范围,以及相应的日期和时间信息,我终于可以开始了。使用一些数据预处理技巧,为了方便起见,我添加了一些列,这将使分析变得稍微容易一些。使用“日期”列,我将日期和月份信息添加到数据框中。我还添加了我在坐标对上花费的时间,以分钟为单位。
在研究数据以获得进一步的见解之前,我必须解决某些障碍:
- 并非所有数据都是 100%准确的,因为收集的数据取决于几个因素,包括但不限于持续的互联网连接和手机 GPS 定位。当然没有办法克服这个丢失数据的问题。
- 数据点有可能会受到附近地区的影响。例如,我在体育馆下面的地铁站等火车也可能被错误地解释为我在体育馆度过的时间。为了解决这个问题,我简单地排除了坐标对持续时间少于 15 分钟的数据点。
现在我已经获得了我需要的必要信息,即日期和时间,我可以更深入地研究这些数据了。
使用熊猫图书馆的 unique(),我确定在去年的 365 天中,我去了健身房 119 次。更深入地说,我决定调查每次锻炼需要多长时间,并研究一天中的时间、一周中的日子和一个月中的趋势。
18:00 似乎是我最喜欢的锻炼时间
那么我在健身房锻炼的平均时间是多少呢?一天中的大部分时间,我要么在大学,要么在语言学校,要么在工作,这可能解释了为什么我锻炼的高峰时间会在 17:00 或 18:00 左右。在假期里,也有几天我会稍微早一点去健身房。还有一个异常点,我 00:00 在体育馆。
一周中的哪一天我更有可能去健身房?星期二!。令人惊讶的是,我最自由的日子是我很少去健身房的日子。然而,作为辩护,大多数周末也是我计划旅行的日子。然而,事后看来,人们可以得出这样的结论:随着时间的推移,我的动力会逐渐减弱。
2019 年每周每天的健身房访问量
查看我每月的访问量也发现了一些有趣的事实。十月是访问量最大的月份。
2019 年每月健身房访问量
然而,当它们被绘制成每次访问所花费的平均时间时,表格完全翻转过来,其中七月名列榜首。七月是夏天来临的时候,宜人的天气加上即将到来的暑假增加了在健身房锻炼的动力。虽然我在 10 月份去健身房的次数增加了,但我忙于应付工作和学校,没有时间在健身房呆很长时间(也降低了温度!).由于考试带来的压力,二月和三月不见了。
2019 年每月在健身房花费的平均时间
健身房的订阅费是每月 15€,去年 119 次,每次大约花费我 2€。不管有没有商业意识,收益远远超过了成本,所以对我来说,这是值得的。
从 2018 年的 20 天到 2019 年的 119 天,我认为去年是一个很大的进步。我后悔没有通过一个应用程序来跟踪我的成长,这可以给我一个关于我的习惯如何影响我的整体健康的整体信息。然而,我已经开始采取措施来整合它们,并希望能够在 2020 年给你一个更好的画面。感谢您的阅读。如果你有任何问题,你一定可以联系我。
大规模使用谷歌趋势
https://trends.google.com/trends/explore?geo=US&q =古驰米兰,时尚
当你的问题对他们面向公众的网站来说太大时,如何使用谷歌趋势
谷歌趋势是公众对一个话题的兴趣的有力衡量标准,换句话说,就是它的炒作;然而,它的设置方式使得它很难在最简单的应用程序之外使用。在这篇文章中,我们深入研究了 Google Trends 到底在测量什么,并探索了当你的问题超过了面向公众工具的五个主题限制时如何使用它。
谷歌趋势到底衡量什么?
Google Trends 给你一个在一段时间内给定搜索词的搜索量的标准化测量。来自他们的常见问题:
每个数据点除以它所代表的地理和时间范围的总搜索次数,以比较相对受欢迎程度。否则,搜索量最大的地方总是排名最高。
然后,根据一个主题在所有主题的所有搜索中所占的比例,将结果数字在 0 到 100 的范围内进行缩放。— 谷歌趋势常见问题解答
也就是说,对于你搜索中的每一个词,谷歌会找到你的词在每个地区和时间段的搜索量相对于该地区和时间段所有搜索量的比例。它将所有这些指标合并成一个单一的受欢迎程度指标,然后对你的主题进行分级,因此最大的指标被设置为 100。
简而言之:谷歌趋势并不能准确地告诉你你的主题出现了多少次搜索,但它确实给了你一个很好的代理。
是什么让谷歌趋势变得棘手?
尽管 Google Trends 可以方便地快速把握互联网的脉搏,但服务本身的结构使大规模应用变得困难,原因有二:
1.五趋势极限
目前,面向公众的谷歌趋势网站不允许超过五个词的查询。如果你想探索任何真实世界的问题,这显然是有限制的。例如,在民主党初选的早期,你不可能轻易地比较所有主要候选人的受欢迎程度。
2.相对度量
对这个限制的明显反应是,“好吧,我就用多重查询”。但是正如我们前面提到的,所有的搜索都被缩放到你的查询中最高容量的主题,所以如果两个不同的查询没有相同的最大主题,它们是不可比较的。
怎样才能一次看五个以上的趋势?
最后一句话是关键:
如果他们没有相同的最大话题
只要两个查询共享相同的最受欢迎的主题,它们将以相同的方式缩放,因此趋势将是可比较的。接下来,如果你想在谷歌趋势中比较五个以上的主题,你只需要在每个搜索中包含一个控制主题。
例如,在最近的一个项目中,我和我的团队想要比较网飞和亚马逊内容的受欢迎程度。我们浏览了两个平台的最佳内容列表,并找出了每个标题相对于单词“法国”的谷歌趋势指标。
我们到底是如何决定使用“法兰西”这个词的?
这有点像菜谱:你想要比你感兴趣的所有条款都更稳定、更安全的东西。
利用领域知识,我知道哪些图书会有特别高的搜索量,所以我寻找一个比我们最受欢迎的图书更受欢迎的词,但不要太受欢迎以至于我会丢失关于不太受欢迎的图书的任何真实信息。例如,“比特币”的搜索量如此之高,以至于许多标题被归一化为零(见我这里的意思)。
我可以通过编程来实现吗?
是的。
对于流媒体项目,我使用了 Python 库 pytrends ,这是 Google Trends 的一个非官方 API。下面您可以找到一个快速片段来帮助您开始:
import pytrends kw_list = ##list of topics I wanted to search
trends = dict()for i in kw_list:
##build out query
pytrends.build_payload([i,'France'], cat=0,timeframe='today 5-y')
##save trend to dictionary
trends[i] = pytrends.interest_over_time()[i]
您应该注意到,如果您尝试对多个查询执行此操作,您将会相对较快地达到 Google 的 DoS(拒绝服务)限制,因此您会希望在每个查询之间暂停循环几秒钟。
总而言之:
- 谷歌趋势很好地代表了公众利益
- 要使多个查询具有可比性,请包含一个在每次搜索中最大的控制项
- 使用 pytrends 来自动化该过程
使用这种方法,您现在可以回答一些大问题,如:与竞争对手的产品相比,我的产品有多受欢迎,最近的广告活动对不同产品类别的兴趣有没有不同的影响…
总之:五个字的限制不再是你的了。
在 R 中使用梯度推进机器进行分类
机器学习中的分类方法
背景
分析目的:
了解推动学生成功的因素,以便开放大学能够分配资源来提高学生的成功
数据集描述:
开放大学学习分析数据集是一个公开可用的数据集,包含七个选定课程(模块)的课程、学生及其与 VLE 互动的数据。
由于所有数据表的唯一标识符是学生 ID、模块和课程描述,因此数据在这一级进行聚合。分析中使用了以下变量:
变量名称变量类型变量名称变量类型id _ 学生唯一标识符/主键 id 段分类代码 _ 模块分类段分类代码 _ 演示分类前一次尝试的次数数字学习者分类学分分类区域分类残疾分类最高教育分类最终结果数字总和加权分数数字平均模块长度数字平均提交持续时间数字平均比例内容访问数字平均日期注册数字修整评估类型分类
方法学
数据转换
- 基于唯一标识符(学生 ID、课程和代码描述)组合数据集
- 在唯一标识符级别聚合变量
- 将名义变量类型从字符更新为因子
预测值:
- 代码模块
- 代码呈现(更改为 1 = B 和 2 = J),
- 性别、地区、最高教育程度、IMD 级别、年龄级别、先前尝试次数、学习学分、
- 平均提交持续时间(平均为单个代码演示可以有多个提交持续时间不同的评估),
- 平均模块长度,
- 被访问内容的平均比例(基于“ou”内容和资源/测验/词汇表的总点击数除以每个代码描述的总点击数),
- 平均注册日期和
- 修剪评估类型(因为一个代码描述可以有多个评估和多个相同类型的评估。确定每门课程的评估类型和描述是否能推动学生取得成功是很重要的)
排除变量:学号和加权总分
排除原因:学号——识别信息,加权分数总和——与最终结果相关
定义“成功”目标变量
- 响应变量:成功(“通过”或“区别”=“是”、“失败”或“撤回”=“否”
不使用“最终结果”作为成功因素的原因:数据集中有不成比例的“通过”记录,因此降低了预测“失败”、“撤回”和“区别”的模型准确性。因此,响应变量是二进制的。
- 检查将输入模型的每个变量的空值、缺失值和异常值
为什么我们要检查丢失的数据?—如果大部分数据缺失/为空,样本的代表性不足以提供准确的结果。当缺失/空数据由于有效原因而缺失时,情况并非如此。
数据设置
将数据分为名义预测值和二元预测值的训练集和测试集,以测试模型的准确性
为什么我们要将数据分为训练集和测试集—模型是在训练集上训练的。在测试集上测试模型的准确性,以确定模型在预测它没有“看到”的数据的成功与否方面的能力。
数据建模
- **分析方法:**梯度推进机(GBM)
- 也可以使用其他方法,但模型精度足够好——分布式随机森林(DRF)和广义线性模型(GLMNET)
- 使用混淆矩阵(即正确预测的比例)和曲线下面积(与随机机会相比,我们做得有多好)检查模型的准确性。
输出
- 最佳预测值(按可变重要性分数)
数据 ETL
所有数据探索、转换和最终数据集的加载都在 Alteryx 中完成,以避免编写代码并测试其基本数据转换步骤的功能,这些步骤通常在 R 中执行,如 joins、mutate 和 group by。
首先,我通过 ID 评估(主键)连接了三个数据集— assessments.csv、studentAssessments.csv 和 courses.csv。
接下来,我设计了两个特性:加权分数和提交持续时间。日期通常没有意义,除非它们被转换成有用的特征。课程描述只有两个类别,为了便于参考,我将其转换为 1 和 2。
下一步是添加学生与虚拟学习平台(VLE)互动的数据。为了便于分析,进行了一些功能工程,例如创建一个名为 activity_type_sum 的新变量,将活动类型中的类别数量减少为两大类——内容访问和浏览。这样做的原因是粒度类别只会产生更多的特征,并减少每个类别的观察数量。点击次数由活动类型特征相加。还计算了与浏览和内容访问相关的活动占总活动的比例。这是创建与另一个要素相关并按总活动比例缩放的要素的好方法,从而确保所有学生都按其活动类型以相似的比例表示。
使用 student_id、code_module 和 code_presentation 作为主键将块 1 连接到块 2。结果输出如下所示。
上面的输出(块 3)使用 student_id、code_module 和 code_presentation 将学生注册数据连接起来,以显示 data_registered 字段。
date_unregistered 字段被忽略,因为它有许多缺失值。此外,未注册字段单元格为空的学生将撤回作为其最终结果的值。这个变量是我们的目标/响应变量。因此, date_unregistered 字段似乎是 final_result 的代理度量,因此从我们的分析中排除该变量是有意义的。
如上图,对于给定的 id_student、code_module、code_presentation,重复 module_presentation length、proportion_content、date_registration。由于我们希望拥有唯一的记录,我们可以将数据汇总如下:
- 使用总和总结加权分数
- 平均提交持续时间
- 平均模块呈现(您也可以使用其他聚合,如最小值、最大值和中值)
- 比例 _ 内容 _ 访问的平均值
- 平均日期 _ 注册
数据现在处于学生标识、代码模块、代码演示和评估类型级别;但是,目标变量—最终结果—处于学生标识、代码模块和代码演示级别。因此,需要进一步汇总这些数据。
先看学生信息。这里的唯一记录是 id_student、code_module、code_presentation。
因此,我们需要后退一步,总结出一个 student_id、code_module 和 code_presentation 来代表个人进行的所有评估。我们仍将使用之前的汇总公式。
通过这样做,我们有 8 种独特的评估类型,学生可以针对给定的代码模块和代码演示进行评估。评估类型不重复(仅微调),因此如果学生参加了 3 次 TMA,则不会反映出来,如下所示。
可以创建一个变量来计算每种评估类型的评估数量,但它会包含许多缺失值,因为并非所有评估都具有所有三种评估类型。
现在,我们准备好加入学生信息数据,输出如下所示。
现在,我们有 18 列。
我们被告知,如果在二月和十月进行演示,演示可能会有所不同。我们将假设每年没有不同(即 2013 年 b 与 2014 年 b 相同)。因此,我们将把 code_presentation 作为二进制变量重新编码为 1 代表 B,2 代表 J。
最终输出如下所示。
终于到了一些数据探索的时候了。
探索性数据分析
分类变量可以用条形图表示,其中 y 轴是给定类别出现的频率。例如,在下面的图表中,我们可以看到最常使用的代码模块是 FFF ,其次是 *BBB。*有七个唯一的代码模块,没有缺失值。
对于连续变量,可以使用五点汇总法,对于分类变量,可以使用模式进行数字汇总,如下所示。
从下面的总结中我们可以看出,更常见的学生是苏格兰男性学生,他们没有残疾,Imd_band 在 20%到 40%之间,最终结果是典型的及格。
现在,我们可以开始对数据集建模了。
机器学习模型
我们被要求帮助开放大学更好地理解学生的成功。我们将假设学生的成功是通过最终结果来衡量的,其中通过和优秀是“成功”的指标,撤回和失败是“不成功”的指标。
对于独立变量,我们将使用之前表格中的所有变量,除了 weighted_score。这是因为加权分数决定了给定学生的最终成绩。因此,它与最终结果高度相关(多重共线性),因此将被排除。
学生证是识别信息,因此不会被用作预测因素。
*GBM(梯度增强模型)*被用作选择的模型。这种类型的模型创建了一系列弱学习器(浅树),其中每个新树都试图提高前一个树的错误率。最终的树是具有最低错误率的树。这是一种集成机器学习方法,因为创建了几棵树来提供最终结果。然而,不像在 randomForest 中,这些树是串行创建的,而不是并行创建的。此外,这些树不是独立的,而是依赖于前一个树的错误率,而后面的三个树将更努力地改善对更困难情况的预测。这是由一个称为学习率的参数控制的。
该模型运行了 500 轮(500 棵树),树的最小和最大深度为 4。通常,拥有非常深的树并不好,因为这会导致算法试图解释数据集中的每个观察值的过度拟合,因为它增加了树的深度,导致叶子包含非常少量的符合给定规则的观察值。
我们可以从上面的输出中看到,该模型的 RMSE(均方根误差)值为 0.55,这是相当高的。它特别不擅长预测区分和失败,这可能是由于数据集的不平衡,从我们的探索性数据分析中我们知道通过是最常见的最终结果。
为了解决这一不平衡问题,目标变量被重新定义为“成功”(优秀和及格)和“失败”(不及格和退出)。组合类别来处理不平衡数据集是很常见的。其他方法是欠采样(即减少最频繁类的实例数量)或过采样(即为非频繁类创建人工观察)。
模型重新运行,输出如下。在这里,我们可以看到平均每类误差显著下降。曲线下面积(AUC)是另一个准确性指标,它告诉您模型在正确分类病例方面有多好(即最大化真实阳性率(TPR))。AUC 越高,模型越精确。因为 AUC 是在 0 和 1 之间测量的,所以 0.87 的 AUC 是相当好的。
分类问题中常用的另一个度量是 F1 分数,它是精确度和召回率的调和平均值。这两个指标都旨在最大化 TPR,同时最小化假阴性率(召回率)或假阳性率(精确度)。真正的肯定是成功或失败被正确分类。一个假阴性是成功被贴上失败的标签。一个假阳性是当一个失败被贴上成功的标签。F1 分数要高,精度和召回率都要高。
混淆矩阵表明总错误率为 17.11%,这主要是由模型在成功分类方面的良好程度决定的。该模型不太擅长对故障进行分类,错误率为 39.07%。同样,这可能是由于“遍数”过多地代表了数据。因此,应谨慎对待结果,并使用更平衡的数据集重新运行模型。
现在,让我们通过查看变量重要性列表来看看成功或失败的主要预测因素。
- 前 3 个变量是代码模块、trimmed_assessment_type 和平均提交持续时间。
- 预测给定学生是否会取得成功的最后 3 个变量是残疾状况、平均注册日期和性别。
- 注意:由于代码模块和代码表示是唯一标识符的一部分,它们应该被排除在分析之外。然而,由于一些课程在二月和十月的介绍可能不同,两个变量都保留在模型中。排除这些变量可能会提高准确性或使其他变量变得更“重要”。
现在,让我们来看看最重要的预测信息,以便更好地理解这个模型。下面的堆积条形图显示了课程模块和 final_result 的记录比例。我们可以推断,与其他课程相比,学生更有可能成功完成 AAA、EEE 和 GGG 课程。
- 从上表中,我们可以看到,如果考试是给定课程和演示文稿的唯一评估,那么成功率是 100%。
- 如果只有计算机标记的评估构成了课程的组成部分,那么不及格率和退学率就非常高。调查为什么将 CMA 作为演示评估的一部分会导致成功率下降是很有趣的。
上面的直方图显示了成功和失败的平均提交持续时间。
似乎当学生成功时,他们更有可能在评估提交日期后的 10 天(+/-)内提交作业。
包扎
机器学习被用来快速识别学生成功的主要因素。
模型改进的建议包括:
- 使用平衡数据集
- 包括数据集中资源分配的替代措施
- 按课程类型统计评估数量,并作为
功能进行演示 - 删除相互关联的分类变量(即使用卡方独立性检验)
希望您现在对利用 GBM 解决分类问题、分类问题的缺陷(即不平衡数据集)以及各种准确性指标的使用有了更好的理解。
我的 git 资源库中提供了所有 R 代码的参考:https://github.com/shedoesdatascience/openlearning
基于结构化文档的图卷积神经网络信息抽取
将结构化文档转换为图形的代码,以及对 GCN 实现的全面调查,以对其中的文本实体进行分类
虽然像 CNN 这样的深度学习解决方案可以有效地捕获欧几里得空间中的数据模式,但越来越多的应用程序以图形的形式表示数据,缺乏网格状的规律性。由于图形可能是不规则的,它们可能具有可变大小的无序节点,并且每个节点可能具有不同数量的邻居,导致诸如卷积之类的数学运算难以应用于图形域。
这种非欧几里德数据的一些例子包括:
- 蛋白质-蛋白质相互作用数据其中分子之间的相互作用被建模为图形
- 引文网络其中科学论文是节点,引文是单向或双向边
- 社交网络,网络上的人是节点,他们的关系是边
本文特别讨论了在发票和账单等结构化文档中使用图形卷积神经网络(GCNs ),通过学习文本实体之间的位置关系来自动提取有意义的信息。讨论了多种方法以及将文档转换为图形的方法,因为文档本身没有类似图形的布局。
什么是图?
图表表示为:
在哪里,
现在让我们,
表示一个节点,
表示从 v_i 指向 v_j 的一条边。那么,邻接矩阵 A 是一个 n x n 矩阵,
其中 n 是图中节点的数量。
此外,图可以具有节点属性,
其中 X 是节点特征矩阵,
代表节点 v 的特征向量。
以下是一些没有自循环的无向图及其对应的邻接矩阵的图示:
礼貌:mathworld.wolfram.com
这些节点中的每一个还可以具有由特征矩阵表示的节点属性或特征向量:
四个节点中的每一个都具有三维特征向量的示例
在社交网络的真实世界示例中,节点属性可以是关于人(节点)的人口统计信息,并且他们的关系可以是边。请务必阅读这篇文章,深入了解图表。
如何将结构化文档转换成图形?
当涉及标题和表格等实体时,发票和账单等结构化文档有一定的出现顺序。例如,在大多数发票中,表格位于页眉和页脚之间。此外,总金额通常位于表格的右下角,这是我们希望提取的发票的一个重要实体。这种重复出现的结构信息以及文本属性可以帮助图形神经网络学习邻域表示,并作为结果执行节点分类。
但是我们如何以图形格式获得这样的文档呢?
答案是 OCR 加上几何算法。
光学字符识别:
在这一步,我们使用商业上可用的 OCR 引擎,如 Amazon Textract 或 Google Vision API 来生成所谓的文档对象图。
地图中的每个“对象”都是一个文本实体——一个单词或一个句子。在本文的上下文中,我们将对象映射称为具有五列的 Pandas 数据帧——xmin、ymin、xmax、ymax、object。
(xmin,ymin)和(xmax,ymax)给出了矩形边界框的坐标,而 Object 列给出了该框内的实际文本。
下面是 GitHub 的一个要点,给出了文档图像的路径和 Google Vision API 实例的端点 URL。
生成“对象图”的 OCR 脚本
对象映射到图形:
一旦我们生成了对象图,我们就利用基于可见性的几何算法将对象彼此连接起来,从而形成一个图,其中对象作为节点,连接作为边。
几何算法:基于可见性连接对象
*Input*:
Object Map*Output*:
Graph Dictionary: {source_node: [dest_node_1, dest_node_2, …], ..}
Text List: Text for each node
第一步:从左上角的物体开始。对于地图中的每个对象,迭代所有其他对象。
步骤 2:对于每个物体,向右看。
第三步:如果另一个物体直接可见,建立连接。第四步:对于每个物体,直接看下面。
第五步:如果另一个物体直接可见,建立联系。
输出是字典的形式,因此我们可以使用网络分析的 Python 库 networkx 来构建一个图。此外,我们返回文本列表,以便使用每个节点的文本作为其特征向量。
下面是 Python 实现。
接下来,我们使用 networkx 为文档创建一个邻接矩阵 A 以及一个特征矩阵 X ,该矩阵是通过在字符级别对每个文本对象进行计数矢量化而生成的。
下面是同样的代码。
最终结果,我们希望文档看起来像这样:
将发票绘制成图表
用于信息抽取的文档图上的卷积
一旦我们有了图表形式的文档,下一步就是将这些数据提供给 GCN。在这里,我们可以从许多 GCN 实现中进行选择,其中最著名的如下所述:
GraphSAGE —大型图上的归纳表征学习:
论文: arXiv
网站:snap.stanford.edu
代码: GitHub 插图:
归纳表征学习。
图卷积神经网络半监督分类: 论文: arXiv
网站:tkipf . GitHub . io
代码: GitHub 插图:
具有一阶滤波器的多层 GCN。
以下是特别适用于从发票中提取信息的 GCNs 的实现:
结构化文档中的表格理解: 论文: arXiv
描述:
本文中 GCN 的实现并不严格遵循图卷积的数学定义,而是对于每个节点,将图中连通节点的特征向量简单地与所考虑节点的特征向量串接起来。因此,为每个节点形成新的聚集特征向量,然后简单地通过完全连接的层馈送该向量。
图形神经网络在发票单据中的表格检测: 纸张: ICDAR 幻灯片:priba . github . io
*代码:*虽然代码不是作者提供的,但这里是我的入门代码同样。 插图:
模型架构概述,P. Riba 等人。
实验
所有这些方法都给出了令人鼓舞的结果。例如,下面是一个样本发票的半监督分类方法的输出:
半监督分类的输出
由于半监督学习的范例规定,在学习表示时,标记的和未标记的节点都出现在图中,所以在作为图的单个发票上观察到该输出。
总共有四个类——信息、联系人、表项和其他(未显示)。最初为每个类标记了一组种子节点。然后,GCN 能够从这些初始种子节点中学习未标记节点的表示。
可以通过使用标准 NLP 技术首先识别种子节点,然后将图馈送到网络来实现端到端解决方案。这里最值得注意的特性是能够以非常低的延迟为以前看不见的文档生成节点表示。
结论
图卷积神经网络被证明在新的应用中越来越有用,其中数据采用连接主义结构。此外,在结构化文档的情况下,具有空间意义的数据可以适用于图形结构,然后与 gcn 一起使用。
半监督学习可以在静态图上动态使用,以生成节点的表示,而不需要大的训练集。超越非欧几里德数据的普通 CNN 为应用研究的新领域打开了令人兴奋的机会。
这里是截至 2019 年对图神经网络的全面调查,供进一步阅读。
达瓦尔·波特达尔是一名数据科学爱好者,拥有人工智能研究的经验。他毕业于 Udacity 的数据分析师和机器学习工程师纳米学位,目前在孟买的一家分析初创公司工作。我越来越擅长处理杂乱的数据,并将其转化为有价值的东西。在 LinkedIn 上与我联系,并查看我的 GitHub 以获得更多酷项目。