排名和积分可以成为极大的激励来源(图由 DALLE-3 创建)
我热爱 Kaggle,并相信它在传播数据科学和机器学习方面的贡献是无价的。作为一个游戏迷和游戏化爱好者,我欣赏 Kaggle 的排名和积分系统如何通过健康、建设性的竞争鼓励参与者改进他们的模型。Kaggle 已经如此受欢迎,以至于许多讲师已经将 Kaggle 纳入他们首选的教学机器学习工具中。
然而,作为一名教授一些机器学习课程的商学院讲师,我发现将 Kaggle 作为评估学生最终机器学习项目的工具略显繁琐。首先,跟踪学生的提交过程既繁琐又需要手动操作,而且我的学生(请注意,他们中的大多数人对数据科学和编程都是初学者),常常会感到沮丧,因为他们看到自己的努力在 Kaggle 排名中处于底部。话虽如此,我认为承认 Kaggle 并非作为教学工具而设计,这解释了它在这一领域的局限性是很重要的。
我一直梦想着创建一个针对我的学生的 Kaggle 迷你版。这个平台将使我能够复制 Kaggle 的竞技成功,并作为各种主题的模板,包括数学编程和组合优化。最初,我因为需要使用典型的 Python 网络开发框架(如 Django 或 Flask)来构建这样一个平台而感到气馁。
幸运的是,我最近发现了 Streamlit 及其与 Google Sheets 的交互能力。在这篇文章中,我将向您展示如何使用 Python、Streamlit 和 Google Sheets 构建一个类似 Kaggle 的 Web 应用,以游戏化您的课程。这个应用将允许学生使用个人账户登录,通过 CSV 文件上传提交解决方案,根据各种机器学习指标评分解决方案,并动态排名提交。最好的是,我将解释您如何免费部署这个应用。
你准备好进行一些动手学习了么?让我们先看看我们最终应用的结果…
图 1. 部署并正在使用的应用(图由作者创建)
请注意,这篇文章可能比较长。我的目标是提供尽可能多的细节,因为我相信这对许多可能不像典型数据科学专家那样精通 Python 的教师和教授可能是有益的。如果您已经是 Python 专家,您可能更喜欢跳过这篇文章,直接跳转到下面的项目 GitHub 仓库:
GitHub – ceche1212/project_medium_kaggle_app: 存储类似 Kaggle 应用程序代码的仓库,可以…
在我与我学生共同实施的原项目中,该应用程序包含三个不同的机器学习部分:一个用于回归问题,一个用于二进制分类问题,还有一个用于时间序列预测问题。为了简化,本教程将仅关注其中一个:使用著名的UC Irvine 机器学习存储库中的 Pima 糖尿病数据集进行二进制分类问题。此数据集也可以从Kaggle下载。
[文章索引:]
-
Streamlit 和 Google Sheets
-
应用程序设计
-
应用程序实现和部署
-
[1] – 设置项目环境
-
[2] – 设置 Google Sheets 数据库
-
[3] — 数据隐私和安全
-
[4] – 建立 Google Sheets 连接
-
[5] – 库、状态会话变量和应用程序配置
-
[6] – 登录模块
-
[7] – 提交结果模块
-
[8] – 动态排名模块
-
[9] – 提交日志模块
-
[10] – 应用程序部署
-
实际结果
-
结论
-
参考文献
[Streamlit 和 Google Sheets]
自 2023 年1.28版本以来,Streamlit 允许用户通过其st.connection方法连接到 Google Sheets。此方法使用户能够使用 Google Sheets 作为数据库执行 CRUD(创建、读取、更新和删除)操作。我从 Sven | Coding Is Fun 制作的 YouTube 视频中了解到这些功能。如果您想观看,我将在下面留下链接。
我知道你在想什么,但在你感到害怕之前,请听我说。我明白 Excel(Google 表格)不是一个数据库,并且我同意你的看法。我也有关于处理那些将其用作数据库的公司的噩梦。然而,对于我们想要创建的应用,这已经足够了。它允许我们完成所有需要的事情,它可以在网上使用,它是私密的(因为我们和该应用是唯一可以访问它的 – 除非有人足够熟练能够黑掉我的 Google 账户),而且最好的是,它是免费的。我确实认识到这是一个需要改进的领域,所以我正在探索用 Supabase 的连接替换 Google Sheets 的可能性。
图 2. 除非……Excel 不是一个数据库(由作者创建)
在直接进入实现之前,值得检查应用必须具备的不同模块,以及我们将遵循的实现策略。
应用设计
我们将要创建的应用需要几个过程。首先,我们想要确保只有我们的学生可以访问它,因此我们需要一个登录系统。一旦用户登录,他们需要一个模块来提交他们的结果。这需要一个上传 .csv 文件的过程,验证文件是否符合预期的行数和请求的列数,然后计算一个评估指标,比较模型预测与实际测试数据。在此之后,学生应该能够看到课堂排名以及他们的结果与同龄人的比较。最后,学生应该能够看到他们所有提交的记录以及他们的队友的提交,因为这是一个团队项目。图 3 展示了我们应用的总体用户流程图。
图 3. 应用程序的用户流程图(由作者创建)
应用实现
对于这个应用,我们将使用 Visual Studio Code。我强烈建议在你的机器上创建一个新的项目文件夹,并从该文件夹打开 Visual Studio Code。在我的情况下,我决定将文件夹命名为 project_app_medium。
设置项目环境
我强烈建议为每个 Streamlit 应用创建一个虚拟环境,以避免与其他 Python 项目发生依赖冲突。在创建并激活虚拟环境后,我们需要安装以下库。
pandas == 1.5.3
numpy == 1.26.4
matplotlib
streamlit
streamlit_option_menu
streamlit-extras
st-gsheets-connection
scikit-learn
要安装库,创建一个新的文本文件,并将其命名为 requirements.txt。在这个空白文本文件中,复制上面列出的库并保存;我们需要这个文件来部署我们的应用。然后,在终端中输入以下命令。
pip install -r requirements.txt
这个命令将安装 requirements.txt 文件中列出的所有库。关于我们使用的库,我们将从所有数据科学项目的“Mirepoix”经典库开始:numpy、pandas 和 matplotlib。我们还需要 streamlit,因为这是我们框架的基础库。为了扩展 Streamlit 的功能,我们将导入一些社区开发的扩展,如 streamlit_option_menu,它允许我们创建简单的侧边栏菜单,以及 streamlit-extras,它包含许多可定制的功能。此外,我们将使用 st-gsheets-connection 来帮助我们连接到 Google Sheets。请注意,除了上述库之外,我们还将使用 hashlib 来确保数据安全和保护。我们将在定义数据库细节时进一步讨论这一点。
在整个教程中,将使用以下文件夹结构,包含以下元素:
-
.streamlit:这个文件夹将放置通用的 Streamlit 设置。在这个文件夹内,我们将存储 secrets.toml 文件,该文件将包含建立连接所需的 Google Sheets API 凭证。
-
app.py:主要的 Streamlit 脚本。
-
.gitignore:如其名所示,当进行项目提交时,Git 会忽略的文件。
-
logo.png (可选):这是一个包含您公司标志的图像,将在应用侧边栏菜单的顶部显示。这完全是可选的,在我的情况下,我展示了我的公司 SAVILA GAMES 的标志。
-
requirements.txt:运行应用的 Python 依赖项。
-
README.md:项目描述。
project_app_medium/
│
├── .streamlit/ # General streamlit configuration
│ └── secrets.toml # credentials for google sheets connection
├── app.py # App code
├── .gitignore
├── logo.png
├── requirements.txt # Python dependencies
└── README.md
设置 Google Sheets 数据库
现在环境已经设置好了,我们需要创建 Google Sheets 数据库的结构。打开您的 Google Sheets 应用并创建一个新文件;我给我的命名为 project_database。然后,在文件中创建四个标签页。首先,“users” 标签页将包含所有用户登录凭证以及用户组配置。我们将使用这个标签页的信息来创建应用的用户登录模块。这个标签页应该具有以下列结构:
| email | name | last name | password | group |
|------------------------|---------|-----------|-----------|----------|
| [[email protected]](/cdn-cgi/l/email-protection) | John | Doe | Pass1234 | G1 |
| [[email protected]](/cdn-cgi/l/email-protection) | Jane | Smith | SecurePwd | G2 |
| [[email protected]](/cdn-cgi/l/email-protection) | Alex | Jones | MyPass789 | G1 |
| [[email protected]](/cdn-cgi/l/email-protection) | Emma | Brown | Emma12345 | G3 |
下一个标签页是 “log” 标签页。这个标签页将存储用户提交的历史信息。它也将用于应用中排名和历史提交模块的逻辑。这个标签页应该具有以下列结构:
| user | group | time | score |
|-----------------------|-----------|------------------|-------|
| john | G1 | 2024-06-17 10:00 | 0.85 |
| jane | G2 | 2024-06-17 11:00 | 0.72 |
| alex | G1 | 2024-06-17 12:00 | 0.90 |
| emma | G3 | 2024-06-17 13:00 | 0.65 |
下一个标签页将是 “test_data” 标签页。这个标签页将包含用于评估学生提交输出质量的真实测试 y 数据。对于这个教程,我们将分割 Pima 数据集并选择最后 78 行作为测试数据集。这个标签页将只有一个包含二进制结果数据的列,具有以下列结构:
| y |
|------------|
| 0 |
| 1 |
| 1 |
| 0 |
我们将创建的最后一个标签是“配置”标签。这个标签将包含项目的可定制参数,例如截止日期和每天每队允许尝试的次数(以 Kaggle 为例,每天每队允许尝试五次)。这个标签将使我们能够动态更改项目特征,以便它能够轻松适应不同学期。"配置"标签应该只有一行,以下列结构:
| deadline | max_per_day |
|-----------------------|-----------------|
| 2024-07-01 23:59 | 5 |
您可以通过点击下面的链接下载并查看本教程项目中使用的谷歌表格示例:
数据隐私和安全
这是一个应该在我们控制之下的小项目,并且只对我们的学生开放。然而,我们必须注意数据安全,特别是关于我们学生隐私信息的安全。想象一下,如果因为某种原因,数据库泄露并落入骗子或恶意实体手中,那么我们学生的姓名、电子邮件和密码可能会使他们处于危险之中。因此,我们必须确保,即使发生这种情况,数据对这些恶意代理来说仍然没有意义。
你可能会想知道我们是否可以以最低的成本实现这样的系统。这就是哈希和hashlib库发挥作用的地方。
哈希是将输入数据转换为固定大小的字符字符串的过程,通常是一个哈希码,使用数学算法。它确保数据完整性,便于快速数据检索,并安全存储敏感信息。有哪些可用的哈希算法?幸运的是,Python 自带hashlib,它包括几个哈希算法:
-
MD5 (
md5) -
SHA-1 (
sha1) -
SHA-224 (
sha224) -
SHA-256 (
sha256) -
SHA-384 (
sha384) -
SHA-512 (
sha512) -
SHA-3 系列(
sha3_224、sha3_256、sha3_384、sha3_512) -
BLAKE2 系列(
blake2b、blake2s)
在我们的教程中,我们将使用hashlib中可用的哈希算法之一,即 SHA-256,将我们学生的电子邮件和密码转换为哈希码。这些哈希码将被存储在数据库中。这样,即使数据库泄露,他们的数据仍然受到保护。哈希的优点在于,通过暴力破解将哈希码反向转换为原始信息几乎是不可能的。例如,当上一节中的电子邮件使用 SHA-256 进行哈希处理时,它们变得安全且无法阅读。
# original emails
['[[email protected]](/cdn-cgi/l/email-protection)',
'[[email protected]](/cdn-cgi/l/email-protection)',
'[[email protected]](/cdn-cgi/l/email-protection)',
'[[email protected]](/cdn-cgi/l/email-protection)']
# Hashed emails
['836f82db99121b3481011f16b49dfa5fbc714a0d1b1b9f784a1ebbbf5b39577f',
'f2d1f1c853fd1f4be1eb5060eaae93066c877d069473795e31db5e70c4880859',
'134318bc6349ad35d7e6b95123898eecdd437ad9b0c49cc4bdd66a811afc6909',
'd41d9b2f5671358bc6faf79b7435b4a9805a72d012f06d4804815328f39aed1e']
真的很不容易解读,你不这么认为吗?下面,你可以找到一个函数,给定一个数据框和一个列,它会返回指定列的哈希项。这样你可以对你的数据库信息进行哈希处理,然后将这些值存储在在线谷歌表格数据库中。
import hashlib
def hashit(df,column):
return_list = []
for data in df[column].tolist():
hash_object = hashlib.sha256()
hash_object.update(data.encode())
return_list.append(hash_object.hexdigest())
return return_list
你可能会想,“但是 Luis,如果他们有用户名和密码,他们可以登录并冒充学生之一。”然而,这种情况已经得到了解决。学生们将使用我们提供的电子邮件和密码登录应用程序,但他们将在登录屏幕上输入他们的真实电子邮件和密码。然后应用程序将获取他们的登录凭证,使用 SHA-256 对其进行散列,并将散列输出与数据库进行验证。
因此,如果数据库泄露,有人试图使用这些信息登录,这是不会成功的,因为他们的输入将被再次散列,并且不会与数据库中存储的散列匹配。以下是一个示例代码和输出。
password = "password"
hash_object.update(password.encode())
hash_password = hash_object.hexdigest()
print(hash_password)
打印输出:
"5377a16433598554e4a73a61195dbddea9d9956a22df04c3127c698b0dcdee48"
现在如果我们重新散列已经散列的密码,如下面的代码所示。
hash_object.update(hash_password.encode())
double_hash_password = hash_object.hexdigest()
print(double_hash_password)
我们得到以下结果:
"dfd4bb46c954f3802c7c2385b1a6b625b3cf0b4ce6adf59d3eec711c293994bb"
你可以轻松地验证这两个密码肯定不匹配。所以你看,对先前散列的密码进行散列会产生完全不同的结果。
建立 Google Sheets 连接
建立连接所需的所有说明都可以在st-gsheets-connection软件包的GitHub 仓库中找到。让我们一起遵循说明:
- 前往Google 开发者控制台并创建一个新的项目。在 Google Cloud 图标旁边,你会找到一个下拉菜单。点击它,然后点击“创建新项目”。根据你的意愿命名你的项目;在我的情况下,我将其命名为
project_app_medium。
图 4. Google 开发者控制台创建项目(由作者创建)
- 现在,选择项目后,我们需要激活两个不同的 API:Google Drive 和 Google Sheets。在页面顶部的搜索栏中输入“Google Drive”,选择 API,然后点击“启用”。对 Google Sheets 重复相同的步骤。
图 5. Google 开发者控制台启用 API(由作者创建)
- 启用项目 API 后,我们现在需要创建一个具有访问权限的技术用户。点击“凭证”,然后点击“创建凭证”,并选择“服务帐户”选项。为技术用户分配一个名称;在我的情况下,我将其命名为"
medium-project-google-sheets"。将“编辑”角色分配给技术用户,最后点击“完成”。
图 6. Google 开发者控制台创建技术用户(由作者创建)
- 在创建了技术用户后,我们现在需要为该用户生成凭证。点击您刚刚创建的用户,然后点击“密钥”,然后点击“添加密钥”,选择“创建新密钥”。选择 JSON 选项,然后点击“完成”。这将自动下载一个包含所有从我们的应用使用 Google Sheets 所需凭证的 JSON 文件。
图 7. Google 开发者控制台创建技术用户 JSON 凭证(图片由作者创建)
- 最后一步是将我们刚刚下载的凭证存储到
secrets.toml文件中。如果您还没有这样做,请在项目文件夹内创建一个名为.streamlit的新文件夹。在这个文件夹内,创建一个名为secrets.toml的新文件。使用您选择的文本编辑器(如 VS Code)打开文件,并将以下信息粘贴到其中。
# .streamlit/secrets.toml
[connections.gsheets]
spreadsheet = "<spreadsheet-name-or-url>"
worksheet = "<worksheet-gid-or-folder-id>" # worksheet GID is used when using Public Spreadsheet URL, when usign service_account it will be picked as folder_id
type = "" # leave empty when using Public Spreadsheet URL, when using service_account -> type = "service_account"
project_id = ""
private_key_id = ""
private_key = ""
client_email = ""
client_id = ""
auth_uri = ""
token_uri = ""
auth_provider_x509_cert_url = ""
client_x509_cert_url = ""
- 将
secrets.toml文件中的每个元素替换为从 Google 下载的 JSON 凭证文件中的数据。对于“spreadsheet”字段,复制我们为项目创建的 Google Sheets 数据库的 URL。接下来,复制 JSON 文件中的“client_email”数据,然后转到 Google Sheets 数据库。在电子表格中,点击“共享”,然后将“client_email”粘贴到文本输入字段中,确保选择“编辑者”权限,然后点击“发送”。
在完成所有这些准备工作后,我们现在可以开始编写应用代码了。
库、状态会话变量和应用配置
现在我们将进行导入应用所需的库,并创建应用将使用的会话状态变量。大多数会话状态变量将与登录模块相关联。在 Streamlit 中,会话状态变量用于在会话的不同交互之间存储信息。它们有助于在应用的重运行之间维护状态,例如用户输入或选择。最初,这些变量将被设置为空字符串,并在应用运行时更新。对于我们的特定应用,我们将创建用户名(使用学生电子邮件作为用户名)的状态变量、密码以及用户所属的组。我们还将使用 Streamlit 的st.set_page_config()方法设置页面标题和页面图标(favicon)。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import datetime
from pathlib import Path
import streamlit as st
from streamlit_option_menu import option_menu
from streamlit_extras.add_vertical_space import add_vertical_space
from streamlit_extras.stylable_container import stylable_container
from streamlit_gsheets import GSheetsConnection
from sklearn import metrics
import hashlib
if 'user_name' not in st.session_state:
st.session_state['user_name'] = ''
if 'student_name' not in st.session_state:
st.session_state['student_name'] = ''
if 'password' not in st.session_state:
st.session_state['password'] = ''
if 'group' not in st.session_state:
st.session_state['group'] = ''
st.set_page_config(
page_title='Medium Project',
page_icon='📈 '
)
为了测试一切是否正常工作,您可以从目录的根目录运行应用,在终端中执行以下命令。这将在本地的 localhost 上运行应用。
streamlit run app.py
您应该看到一个名为“Medium Project”的空页面,带有我们选择的 favicon📈,如图 8 所示。
图 8. 空的 Streamlit 页面(图片由作者创建)
登录模块
现在会话状态变量已创建,我们可以继续编写我们应用程序的第一个模块:登录模块。此模块将包含图 9 中展示的逻辑。
图 9. 登录模块流程图(由作者创建)
首先,我们将与我们的 Google Sheets 数据库建立连接。接下来,我们将使用st.sidebar方法和option_menu方法创建侧边栏导航菜单,使我们能够轻松地为我们的应用程序创建不同的页面。一旦设置好,我们将建立登录模块的逻辑。
如果用户未登录,他们将无法访问下一个模块。在登录模块中,我们将要求用户名(电子邮件)和密码。这些凭证将与数据库进行验证。如果匹配,用户的凭证将被存储在会话状态变量中,用户将收到一条确认成功登录的消息。如果不匹配,用户将收到一条警告消息,说明登录失败。我们还将从数据库中存储项目配置数据(项目截止日期和每天最大提交次数)。上述逻辑将通过以下代码实现。
login = None
log_df_n_cols = 4
login_df_ncols = 5
config_df_ncols = 2
conn = st.connection("gsheets", type=GSheetsConnection)
users_existing_data = conn.read(worksheet="users", usecols=list(range(login_df_ncols)), ttl=1)
users_existing_data = users_existing_data.dropna(how="all")
users_existing_data.index = users_existing_data['email']
gs_user_db = users_existing_data.T.to_dict()
configuration_parameters = conn.read(worksheet="configuration", usecols=list(range(config_df_ncols)), ttl=1)
deadline = configuration_parameters['deadline']
deadline = pd.to_datetime(deadline)
max_per_day = int(configuration_parameters['max_per_day'])
today = pd.Timestamp.today()
difference = deadline - today
days = difference.dt.days.iloc[0]
seconds = difference.dt.seconds.iloc[0]
hours = seconds // 3600
minutes = (seconds % 3600) // 60
# emojis are obtained from bootstrap icons https://icons.getbootstrap.com/
with st.sidebar:
st.image("./savila_games_logo.png")
selected = option_menu(
menu_title='MEDIUM APP',
options= ['Login','Rankings','My group Submissions','Submit Results'],
icons=['bi bi-person-fill-lock', '123','bi bi-database','bi bi-graph-up-arrow'], menu_icon="cast"
)
add_vertical_space(1)
if selected == 'Login':
user_name = st.text_input('User email', placeholder = '[[email protected]](/cdn-cgi/l/email-protection)')
user_password = st.text_input('Password', placeholder = '12345678',type="password")
login = st.button("Login", type="primary")
add_vertical_space(1)
if selected == 'Login':
st.header('Welcome to your Final group project Challenge')
st.divider()
st.subheader(f" Time remaining: {days} days, {hours} hours and {minutes} minutes")
st.divider()
st.write('Please proceed to login using the left side panel')
st.write('⚠️ Please note that access to subsequent sections is restricted to logged-in users.')
st.divider()
st.subheader('Rules:')
with st.expander('Rules',expanded=True):
st.markdown("- This app lets you submit your project calculations.")
st.markdown("- Everyone on your team can submit their solutions")
st.markdown(f"- The site let each team submit {max_per_day} submissions per day max ")
st.markdown("- For each one of the parts whichever team comes up with the best result for a given part gets the top grade for that part.")
st.markdown("- In case there is a tie. The team that submitted first the solution will get the higher grade.")
st.subheader('How to use the app:')
with st.expander('How to use the app',expanded=True):
st.markdown("- Check out the **Ranking** 🥇 tab to see where your team stands for each one of the parts.")
st.markdown("- Click on **My Group Submissions** 🗃 ️ to see the history of all the solutions that your team has submitted.")
st.markdown("- Hit **Submit Results** 📊 to drop in a new solution.")
st.markdown("- Every time you submit, the app checks if it's all good and pops out some feedback in case you had a bug.")
st.markdown("- Keep trying new things and submitting - there's no limit")
add_vertical_space(1)
if login:
hash_object_user_name = hashlib.sha256() #hashing
hash_object_user_name.update(user_name.encode())
hashed_username = hash_object_user_name.hexdigest()
if hashed_username not in gs_user_db.keys():
with st.sidebar:
st.error('Username not registered')
else:
hash_object_password = hashlib.sha256() #hashing
real_password = gs_user_db[hashed_username]['password']
hash_object_password.update(user_password.encode())
hashed_user_password = hash_object_password.hexdigest()
if hashed_user_password != real_password:
with st.sidebar:
st.error('Sorry wrong password')
else:
user_first_name = gs_user_db[hashed_username]['name']
group = gs_user_db[hashed_username]['group']
st.session_state['user_name'] = user_name
st.session_state['student_name'] = user_first_name
st.session_state['password'] = real_password
st.session_state['group'] = group
with st.sidebar:
st.success(f'{user_first_name} from group ({group}) succesfully log-in', icon="✅")
with st.sidebar:
if st.session_state['user_name'] != '':
st.write(f"User: {st.session_state['user_name']} ")
st.write(f"Group: {st.session_state['group']} ")
logout = st.button('Logout')
if logout:
st.session_state['user_name'] = ''
st.session_state['password'] = ''
st.session_state['group'] = ''
st.session_state['student_name'] = ''
st.rerun()
else:
st.write(f"User: Not logged in ")
提交结果模块
现在,登录模块已经就绪,我们可以继续创建提交模块。在这个模块中,学生将能够上传包含他们模型预测输出的 CSV 文件。此模块将包含图 10 中展示的逻辑。
图 10. 提交结果模块流程图(由作者创建)
我们只允许用户在项目截止日期未过且他们的团队未超过每天最大提交次数的情况下访问此模块(为了避免垃圾邮件和过度拟合测试数据集的风险)。如果任一条件不满足,学生将收到一条警告消息说明问题。如果一切正常,学生可以通过上传 CSV 文件来提交他们的结果。如果文件的形状符合要求(行数等于测试数据集大小,且文件包含名为“predictions”的列),提交将被接受。如果形状不匹配,学生将收到一条包含问题详细信息的警告消息。
如果一切匹配,模块将计算模型评估指标。在这个特定案例中,我们使用的是准确率,但如果你更喜欢 F1 分数,代码也很容易修改。完成此操作后,模块将把学生的提交存储在我们的 Google Sheets 数据库的“log”标签页中。上述逻辑将通过以下代码实现。
if selected == 'Submit Results':
st.markdown("""
<style>
div[data-testid="stMetric"] {
background-color: #EEEEEE;
border: 2px solid #CCCCCC;
padding: 5% 5% 5% 10%;
border-radius: 5px;
overflow-wrap: break-word;
}
</style>
"""
, unsafe_allow_html=True)
st.header('Submit Predictions')
st.subheader("Machine Learning Classification")
st.divider()
st.subheader(f" Time remaining: {days} days, {hours} hours and {minutes} minutes")
st.divider()
if st.session_state['user_name'] == '':
st.warning('Please log in to be able to submit your project solution')
else:
group_log_df = conn.read(worksheet="log", usecols=list(range(log_df_n_cols)), ttl=1).dropna(how="all")
group_log_df = group_log_df[group_log_df['group'] == st.session_state['group']]
group_log_df['time'] = pd.to_datetime(group_log_df['time'])
test_data = conn.read(worksheet="test_data", usecols=list(range(1)), ttl=30).dropna(how="all")
test_data_y = test_data['y']
n_test = len(test_data)
current_date = pd.Timestamp.today()
submissions_count = group_log_df[(group_log_df['time'].dt.date == current_date.date())].shape[0]
time_diff = deadline - current_date
time_diff = time_diff.dt.total_seconds().iloc[0]
if time_diff <= 0:
st.warning('Sorry you cannot longer submit anymore, the project deadline has passed')
else:
if submissions_count >= max_per_day:
st.warning(f'Sorry your team has already submitted {submissions_count} times today, and this exceeds the maximun amount of submissions per day')
else:
user_file = st.file_uploader("Upload your predictions file",type=['csv'])
st.caption(f"Your file must have {n_test} rows and at least one column named 'predictions' with your model predictions")
if user_file is not None:
submit_pred = st.button('submit',type="primary",key="submit_pred")
if submit_pred:
pred_df = pd.read_csv(user_file)
if 'predictions' not in pred_df.columns.to_list():
st.error('Sorry there is no "predictions" column in your file', icon="🚨 ")
elif len(pred_df) != n_test:
st.error(f'Sorry the number of rows of your file ({len(pred_df)}) does not match the expected lenght of ({n_test})', icon="🚨 ")
else:
with st.spinner('Uploading solution to database'):
user_predictions = pred_df['predictions']
timestamp = datetime.datetime.now()
timestamp = timestamp.strftime("%d/%m/%Y, %H:%M:%S")
st.write(f'Submitted on: {timestamp}')
ACC = metrics.accuracy_score(test_data_y,user_predictions)
F1 = metrics.f1_score(test_data_y,user_predictions)
cm = pd.DataFrame(metrics.confusion_matrix(test_data_y,user_predictions),
columns = ["T Pred","F Pred"],index=["T Real","F Real"])
columns_part_2 = st.columns(3)
with columns_part_2[0]:
st.metric("ACCURACY",f"{100*ACC:.1f} %")
with columns_part_2[1]:
st.metric("F1-Score",f'{F1:.3f}')
with columns_part_2[2]:
st.dataframe(cm,use_container_width=True)
solution_dict = dict()
solution_dict['user'] = st.session_state['student_name']
solution_dict['group'] = st.session_state['group']
solution_dict['time'] = timestamp
solution_dict['score'] = ACC
logs_df_2 = conn.read(worksheet="log", usecols=list(range(log_df_n_cols)), ttl=1).dropna(how="all")
solution_2 = pd.DataFrame([solution_dict])
updated_log_2 = pd.concat([logs_df_2,solution_2],ignore_index=True)
conn.update(worksheet="log",data = updated_log_2)
st.success(f'Your solution was uploaded on: {timestamp}',icon="✅")
st.balloons()
动态排名模块
我们的应用程序已经允许已登录的用户提交他们的解决方案。现在,我们需要通过添加动态排名来创建应用程序的娱乐化 🎮 方面,以显示学生的解决方案与其他团队提交的解决方案的比较。该模块的逻辑简单明了,不需要图表。本质上,我们需要从我们的 Google Sheets 数据库的“日志”标签中收集所有数据,找到每个团队的最佳分数,并在表格中展示这些分数,最佳分数的团队位于顶部位置,依此类推。在出现两个或更多团队分数相同的情况下,应用程序将给予首先提交解决方案的团队更高的排名。上述逻辑将通过以下代码实现。
if selected == "Rankings":
st.header('Rankings')
if st.session_state['user_name'] == '':
st.warning('Please log in to be able to see The rankings')
else:
st.write('The table below shows the rankings for the project')
rank_df = conn.read(worksheet="log", usecols=list(range(log_df_n_cols)), ttl=1).dropna(how="all")
GROUPS = list(rank_df['group'].unique())
default_time = pd.to_datetime('01/01/1901, 00:00:00')
st.header("Part 4: Machine Learning Classification")
st.divider()
ranking_list_2 = []
for gr in GROUPS:
mini_df_2 = rank_df[rank_df['group'] == gr]
if len(mini_df_2) == 0:
row = {'group':gr,'Accuracy':0,'time':default_time}
ranking_list_2.append(row)
continue
else:
best_idx_2 = np.argmax(mini_df_2['score'])
best_value_2 = mini_df_2.iat[best_idx_2,-1]
best_time_2 = pd.to_datetime(mini_df_2.iat[best_idx_2,2])
row = {'group':gr,'Accuracy':best_value_2,'time':best_time_2}
ranking_list_2.append(row)
ranking_df_2 = pd.DataFrame(ranking_list_2).sort_values(by = ['Accuracy','time'],ascending=[False, True])
ranking_df_2 = ranking_df_2.reset_index(drop=True)
ranking_df_2.iat[0,0] = ranking_df_2.iat[0,0] + " 🥇"
ranking_df_2.iat[1,0] = ranking_df_2.iat[1,0] + " 🥈"
ranking_df_2.iat[2,0] = ranking_df_2.iat[2,0] + " 🥉"
st.dataframe(ranking_df_2,use_container_width=True,hide_index=True)
提交日志模块
我们将要实现的最后一个模块是提交日志模块。此模块将允许每个学生访问他们在整个项目期间提交的所有提交的历史记录。该模块的逻辑简单明了,不需要图表。我们需要从我们的 Google Sheets 数据库的“日志”标签中收集所有数据,为当前用户的组过滤它,并以表格格式展示信息。上述逻辑将通过以下代码实现。
if selected == 'My group Submissions':
st.header('My Group Submissions')
if st.session_state['user_name'] == '':
st.warning('Please log in to be able to see your submission history')
else:
st.write(f'The table below shows you the submission history of your group: **{st.session_state["group"]}**')
group_log_df = conn.read(worksheet="log", usecols=list(range(log_df_n_cols)), ttl=1).dropna(how="all")
group_log_df = group_log_df[group_log_df['group'] == st.session_state['group']]
group_log_df = group_log_df[['user','time','score']]
st.subheader('Submissions History:')
st.dataframe(group_log_df,use_container_width=True,hide_index=True)
在编写完最后一个模块的代码后,应用程序就完成了。整个代码可以在以下链接中找到。
project_medium_kaggle_app/app.py at main · ceche1212/project_medium_kaggle_app
应用程序部署
到目前为止,我们的应用程序在本地执行中运行顺利。然而,构建它的主要目的是与您的学生分享它,并用于他们的最终项目。这就是部署变得至关重要的地方。对于这个项目,我选择使用 Streamlit 社区云来部署应用程序。它是免费的,并且易于使用。该服务使用 GitHub 来部署应用程序,唯一的缺点是 GitHub 仓库必须是公开的。因此,不要上传任何敏感信息,例如我们从 Google 下载的技术用户凭证。这些信息将直接在 Streamlit 社区云服务中管理,所以请放心。如果您想探索其他部署选项,我鼓励您查看以下由 Damian Boh 撰写的优秀文章。
部署我们的应用程序,请按照以下步骤操作:
-
创建一个新的公共 GitHub 仓库。在创建仓库时可以直接创建
README.md文件。 -
上传或提交以下文件,但请记住,我重复一遍,不要上传
secrets.toml文件:
-
app.py -
requirements.txt -
logo.png(可选,如果您想用您的大学标志或公司标志自定义应用程序)
您的仓库应该看起来像图 11 所示。
图 11。部署仓库的示例快照。请注意,仓库中没有 secrets.toml 文件。请不要将您的私人凭据与公众分享(图像由作者创建)
-
前往 Streamlit 社区云网站
streamlit.io/cloud并登录。如果您还没有账户,请创建一个。 -
登录后,点击位于网站右上角的“创建应用程序”按钮,然后选择从 GitHub 获取应用程序代码的选项。
-
输入包含您的应用程序文件的仓库名称,选择主分支,并输入 Python 文件名(在我们的例子中,是
app.py)。您还可以自定义应用程序的 URL 名称;我选择了medium-kaggle-like-app。表单应该看起来像图 12 所示。
图 12。在 Streamlit 社区云服务中创建新应用程序(图像由作者创建)
-
点击“部署”按钮。这可能需要几分钟。
-
应用程序部署后,您将立即收到 Streamlit 的错误,如图 13 所示。这是完全正常的,因为部署的应用程序无法访问技术用户凭据,因为我们从未将
secrets.toml文件上传到 GitHub。但别担心,我们将在下一步修复这个错误。
图 13。Streamlit 部署错误(图像由作者创建)
- 在网站的右下角,有一个名为“管理应用程序”的菜单。点击它,然后打开三明治菜单,选择“设置”,然后选择“密钥”,并将
secrets.toml文件的内容复制到那里。应用程序将自动重启,应该可以正常工作。
图 14。修复 Streamlit 部署错误(图像由作者创建)
您可以通过点击下面的链接来检查最终部署的应用程序。使用用户名:[[email protected]](/cdn-cgi/l/email-protection) 和密码:pass1234 登录。在这个项目的 GitHub 仓库中,我包含了一个 .csv 文件,您可以用它作为您的结果提交。
真实生活的实施结果
使用本教程的框架,我将我教授给硕士研究生的 Python 金融课程最终项目进行了游戏化。坦白说,我对这个项目的期望并不高。我希望每个团队在项目期间至少与平台互动两次,因此,到项目截止日期时,7 个团队和 3 个项目部分共有 50-60 次互动将被视为成功。
但我的学生给了我一位教师可能收到的最好的礼物。一个月后,该应用收到了超过 690 份提交,几乎是原始预期的 12 倍。对我来说,这些参与度水平是前所未有的。每个小组在每个项目部分平均提交了 30 多份,相当于每个部分每天大约提交一份。将每个部分和每个团队的第一次提交与最佳提交进行比较,平均提高了 21%,有些团队的提交提高了 60%以上。如果我用典型的项目版本而不是游戏化版本实施,这种程度的改进可能不会实现。这证明了游戏化的力量。现在,你有一个可以轻松应用于你课堂的应用程序,而且一切都是免费的。太棒了,你不这么认为吗?
如果你想了解更多关于游戏化的信息,请确保阅读我之前发表的文章,在那里我讨论了其背后的逻辑以及它如何有助于培养参与度和学习。
结论
本文已向您展示了使用 Streamlit 创建一个与 Google Sheets 集成的 CRUD 应用程序的全过程,该应用程序可用于为学生游戏化机器学习项目。我还向您展示了如何使用 Streamlit Community Cloud 服务部署您的应用程序。请注意,此代码非常灵活,不仅限于机器学习项目。我还将其应用于我的一个项目调度课程,并取得了优异的成绩。
在下面的文章中,Bruno Scalia C. F. Leite 使用 Streamlit 部署了一个物流应用程序。如果您想了解如何使用 Streamlit 创建运筹学应用程序,我建议您通过以下链接查看他的文章。
我真诚地希望您觉得这篇文章既有用又有趣。如果是这样,我很乐意听听您的想法!请随意留下评论或用掌声👏 表达您的赞赏。如果您想了解我的最新文章,请考虑在 Medium 上关注我。您的支持和反馈是我继续探索和分享的动力。感谢您花时间阅读,请期待我下一篇文章中的更多见解!
参考文献
-
使用 Python & Streamlit 创建 Google Sheets 数据输入表 | 快速简单教程:
www.youtube.com/watch?v=_G5f7og_Dpo&t=66s -
我如何将我的学生小组项目转变为成功:使用游戏化技术提升学习:
medium.com/@luisfernandopa1212/transforming-group-projects-enhancing-learning-with-gamification-techniques-81e2ba2e02ff -
使用 Streamlit 设计运筹学解决方案:一个用户友好的路由应用程序:
medium.com/towards-data-science/designing-operations-research-solutions-a-user-friendly-routing-application-with-streamlit-17212553861d -
在线部署 Streamlit Web 应用程序的 3 种简单方法:
towardsdatascience.com/3-easy-ways-to-deploy-your-streamlit-web-app-online-7c88bb1024b1 -
Penard, Wouter; van Werkhoven, Tim. “关于安全哈希算法家族”。staff.science.uu.nl。2016–03–30。
-
联邦登记公告 02–21599,宣布批准 FIPS 出版物 180–2。
4790

被折叠的 条评论
为什么被折叠?



