Soptq

Soptq

Probably a full-stack, mainly focusing on Distributed System / Consensus / Privacy-preserving Tech etc. Decentralization is a trend, privacy must be protected.
twitter
github
bilibili

對Fantom和UNICEF資助申請的探索性數據分析

這一篇文章是我們為 Open Data Foundation 寫的對 Fantom 和 UNICEF 某一段時間收到的 Grant 的申請數據做數據分析。我們發現可以使用一些算法自動鑑別 Grant 的有效性。

在這篇文章中,我們將主要探索 Grant 申請背後的隱藏模式。具體來說,我們將嘗試使用基本和先進的技術,如數據收集 / 挖掘、聚類、半監督學習等,自動確定 Grant 申請的資格。讓我們開始吧!

數據預處理#

格式化#

Open Data Foundation (ODF) 提供了兩個 Grant 申請供我們探索:Fantom Grant 申請和 UNICEF Grant 申請。由於這兩個申請有不同的字段(例如,Fantom Grant 申請有一個 previous_funding 字段,而 UNICEF Grant 申請則沒有),我們首先將這兩個申請格式化為相同的格式。具體來說,我們只需要 titledescriptionwebsitegithub_userproject_githubfields 以進行後續分析。

fantom_grants = fantom_grants[["title", "description", "website", "github_user", "project_github"]]
unicef_grants = unicef_grants[["title", "description", "website", "github_user", "project_github"]]

相關性檢測#

在申請 Grant 時,標題和描述對於審核者理解項目及其潛在價值至關重要。因此,標題和描述必須足夠清晰以便理解,並提供足夠的項目信息。在這一小節中,我們將展示如何檢測提供的標題和描述的相關性,這可能用於使用機器學習和預訓練的大規模自然語言處理 (NLP) 模型過濾無意義或垃圾申請。
觀察到項目的描述可能非常長,這對後續分類不利,我們將首先使用摘要生成器將非常長的描述總結為相對較短的描述。在這裡,我們將使用 Facebook 訓練的 bart-large-cnn 模型。bart-large-cnn 基於 BART,這是一種基於變壓器的序列到序列模型的去噪自編碼器。bart-large-cnn 的實驗結果顯示,它在 cnn-news 數據集上達到了非常高的準確率。

from transformers import pipeline

# 如果描述長於 100 個單詞,則總結描述以過濾無意義的句子
    if len(description.split()) > 100:
        # 如果描述長於 512,則取前 512 個單詞
        if len(description.split()) > 512:
            description = ' '.join(description.split()[:512])
        description = summarizer(description, max_length=100, min_length=0, do_sample=False)

然後,我們將使用另一個模型來確定標題和描述之間的相關性。考慮到評估 Grant 申請中項目描述質量的任務可以視為評估基本對話中回應質量的任務,我們將使用 tinkoff-ai 訓練的 response-quality-classifier-large 模型。為了將我們的任務轉換為回應質量評估任務,我們需要使用項目標題和項目描述構建查詢:

[CLS]你的項目 {PROJECT_TITLE} 是什麼?
[RESPONSE_TOKEN]{PROJECT_DESCRIPTION}

因此,通過將上述查詢輸入模型,模型將根據問題確定 PROJECT_DESCRIPTION 的相關性。評估的代碼如下所示:

from transformers import AutoTokenizer, AutoModelForSequenceClassification

rel_tokenizer = AutoTokenizer.from_pretrained("tinkoff-ai/response-quality-classifier-large")
rel_model = AutoModelForSequenceClassification.from_pretrained("tinkoff-ai/response-quality-classifier-large")

query = f"""[CLS]你的項目 {title} 是什麼?
[RESPONSE_TOKEN]{description}"""
    inputs = rel_tokenizer(query, max_length=128, add_special_tokens=False, truncation=True, return_tensors='pt')
    with torch.inference_mode():
        logits = rel_model(**inputs).logits
        probas = torch.sigmoid(logits)[0].cpu().detach().numpy()
    relevance, _ = probas

在我們的實現中,相關性 < 0.1 的項目將被直接拒絕,無需進一步考慮。

網站檢查#

我們使用 WHOIS 檢查網站是否可連接,並查詢網站的信息。

import whois

def get_website_whois_info(urls):
    """
    查詢給定網址的 whois 信息

    :param url: 網站的網址
    :return: 網站的 whois 信息
    """
    results = []

    for url in urls:
        try:
            whois_data = whois.whois(url)
            results.append(whois_data)
        except whois.parser.PywhoisError:
            results.append(None)

    return results

在獲取提供網站的 whois 信息後,我們將檢查該網站是否:

  1. 已經過期。
  2. 會在 90 天內過期。
  3. 會在 1 年內過期。

此外,注意到一些項目使用外部鏈接作為其網站(例如 github.io、twitter.com、youtube.com、notion.so 等),我們使用一個簡單的分類器來通過模式匹配判斷提供的網站是否為外部網站。

Github 檢查#

對於個人,我們將檢查他(她)在過去一年的貢獻。這一指標反映了他(她)在開源社區的活動。

from bs4 import BeautifulSoup
import requests

GITHUB_URL = 'https://github.com/'

def get_github_user_contributions(usernames):
    """
    獲取 github 用戶過去一年的公共貢獻。

    :param usernames: 一個字符串或 github 用戶名的序列。
    """
    contributions = {'users': [], 'total': 0}

    if isinstance(usernames, str):
        usernames = [usernames]

    for username in usernames:
        # 如果用戶名是以 'https://' 開頭的網址,則提取用戶名。
        if username.startswith('https://') or username.startswith('http://'):
            username = username.split('/')[3]

        response = requests.get('{0}{1}'.format(GITHUB_URL, username))

        if not response.ok:
            contributions['users'].append({username: dict(total=0)})
            continue

        bs = BeautifulSoup(response.content, "html.parser")
        total = bs.find('div', {'class': 'js-yearly-contributions'}).findNext('h2')
        contributions['users'].append({username: dict(total=int(total.text.split()[0].replace(',', '')))})
        contributions['total'] += int(total.text.split()[0].replace(',', ''))

    return json.dumps(contributions, indent=4)

對於組織,我們將檢查該組織所有公共存儲庫在過去一年的提交總數。這一指標反映了該組織在開源社區的活動。

import datetime
from github import Github

github = Github()

def get_github_org_contributions(orgs):
    """
    獲取 github 組織過去一年的公共貢獻。

    :param orgs: 一個字符串或 github 組織的序列。
    """
    contributions = {'orgs': [], 'total': 0}

    if isinstance(orgs, str):
        orgs = [orgs]

    for org in orgs:
        all_repos = github.get_organization(org).get_repos()
        total_commits = 0
        for repo in all_repos:
            commits = repo.get_commits(since=datetime.datetime.now() - datetime.timedelta(days=365))
            total_commits += commits.totalCount
        contributions['orgs'].append({org: dict(total=total_commits)})
        contributions['total'] += total_commits

    return json.dumps(contributions, indent=4)

聚類#

在數據預處理後,剩下 7 個字段供後續分析:

		"github_user_contributions",
    "project_github_contributions",
    "website_expired",
    "website_expired_in_90_days",
    "website_expired_in_1_year",
    "external_url",
    "desc_relevance"

也就是說,我們的數據集目前有 7 個維度,這在三維世界中很難分析。因此,在聚類之前,讓我們減少數據集的維度。我們首先使用 MinMaxScaler() 對數據集進行標準化:

from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()
grants_scaled = scaler.fit_transform(grants)

然後,為了探索可能的模式,讓我們使用 T-SNE 來降低維度,因為它目前是最先進的技術之一。

import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE

tsne = TSNE(n_components=2, verbose=True)
grants_reduced = tsne.fit_transform(grants_scaled)

# 可視化
sns.scatterplot(x=grants_reduced[:, 0], y=grants_reduced[:, 1])
plt.show()

不同 Grant 申請的特徵

從圖中可以看出,Grant 被劃分為不同的組。接下來,我們使用 DBSCAN 算法進行聚類。這裡我們使用 DBSCAN 算法而不是 K-MEANS,因為 DBSCAN 是一種基於密度的聚類算法,可以檢測異常值,並且不需要指定超參數 k

import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.cluster import DBSCAN

# DBSCAN 聚類
db = DBSCAN(eps=1.0, min_samples=5).fit(grants_reduced)
labels = db.labels_

# 可視化
sns.scatterplot(x=grants_reduced[:, 0], y=grants_reduced[:, 1],
                hue=labels, palette=sns.color_palette("hls", len(set(labels))))
plt.show()

聚類的 Grant 申請

如圖所示,每個 Grant 都被正確地分配了一個標籤,表示其所屬的聚類。請注意,-1 標籤表示 DBSCAN 算法認為該 Grant 是異常值。顯然,聚類的結果驗證了我們處理過的數據集的有效性,並且有一些隱藏的模式等待我們去發現。

定義「合格」#

現在我們已經知道不同申請之間的關係(如聚類所示),我們希望為聚類中的組分配標籤,以便我們可以通過計算申請屬於哪個組來對其進行分類。事實上,如果我們的分類器能告訴我們申請是否合格,那麼標籤可以簡單地表示 Grant 是否合格。因此,為了實現這一目標,我們需要首先定義什麼是合格的。因此,在本節中,我們將手動收集一小組合格的項目(可能是 10 個項目),然後我們將使用半監督學習自動學習如何對我們的數據集中的 Grant 申請進行分類。

十個知名項目,包括 Uniswap、AAVE、Curve、Gnosis Safe 等,手動收集作為正數據。數據集提供如下以供重現:

positive_applications.csv

在使用與 Fantom 和 UNICEF 數據集完全相同的方式預處理正數據集後,所有數據樣本的可視化如圖所示。這裡,標籤 0 代表未標記數據,標籤 1 代表正數據。

正數據和負數據

如圖所示,正數據顯然比其他組更接近某些組,這表明它們可以用來幫助機器學習模型理解什麼樣的申請應該是合格的,什麼樣的申請不應該是合格的。

從正數據和未標記數據學習 (PU-Learning)#

在本節中,我們將訓練一個簡單的分類器,使用一些正數據對未標記數據進行分類。事實上,已經提出了許多算法來從大量未標記數據和少量正數據中學習。然而,由於我們在降維後已經有了一個相當不錯的聚類,我們實際上可以使用標籤傳播和多數投票來製作自己的簡單分類器。具體來說,我們首先計算每個正樣本與每個聚類的中心點之間的 L2 距離。然後,每個正樣本將對與其距離最小的聚類進行投票。最後,我們對投票結果應用 top-k 來檢索未標記數據的估計。實現如下:

# 計算每個聚類的中心
centers = {}
for label in set(labels):
    if label == -1:
        continue
    centers[label] = np.mean(X[labels == label], axis=0)

votes = {}
positives = X[:10]
for positive in positives:
    dists = {}
    for label, point in centers.items():
        dist = np.linalg.norm(positive - point)
        dists[label] = dist
    min_label = min(dists, key=dists.get)
    if min_label not in votes:
        votes[min_label] = 1
    else:
        votes[min_label] += 1

# top-k 投票
eligible_clusters = sorted(votes, key=votes.get, reverse=True)[:4]
new_labels = [int(label in eligible_clusters) for label in labels]

估計結果可視化如下圖,其中標籤 0 代表我們手動收集的正數據,標籤 1 代表從未標記數據中估計的負申請,標籤 2 代表從未標記數據中估計的正申請。

最終聚類結果

估計的負申請如下所示。顯然,項目 114、116 和 117 明顯是測試申請,我們的算法成功地將它們分類為負申請,驗證了我們提出的算法的有效性。此外,在手動檢查其他估計的負項目後,大多數項目都具有低 GitHub 活動和非官方 / 外部網站,這表明 Grant 審核者可能需要更加注意它們。

id                                       title  ... desc_relevance
2                             Just Brew It DAO  ...       0.762122
9                         The Sterling project  ...       0.640220
23           Validator Node Encouragement Fund  ...       0.668328
29                                       Mowse  ...       0.910632
30                           Crypto Policy DAO  ...       0.814382
31                               Racing Snails  ...       0.313622
48                   A Fantoman & Fantomonstre  ...       0.470715
49                                 Grey Market  ...       0.890565
57                              ALL IN FINANCE  ...       0.584226
58                               Planet Keeper  ...       0.714447
64                               Depeg Finance  ...       0.612105
69                               Fantom Nobles  ...       0.407439
74                               Fantom Italia  ...       0.701125
100                                  inDemniFi  ...       0.657283
101                      JPGs Against Humanity  ...       0.873490
111  Pixframe Studios - Transforming Education  ...       0.888190
114                     Daniele's Test Project  ...       0.060001
116                                        NaN  ...       0.000000
117                                       Test  ...       0.230208
載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。