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

ファントムとユニセフの助成金申請に関する探索的データ分析

この記事は、私たちが Open Data Foundation のために Fantom と UNICEF のある期間に受け取った助成金の申請データを分析したものです。私たちは、いくつかのアルゴリズムを使用して助成金の有効性を自動的に識別できることを発見しました。

この文章では、助成金申請の背後にある隠れたパターンを探ることを主に試みます。具体的には、データ収集 / マイニング、クラスタリング、半教師あり学習などの基本的および高度な技術を使用して、助成金申請の適格性を自動的に判断しようとします。それでは、始めましょう!

データ前処理#

フォーマット#

Open Data Foundation (ODF) は、私たちが探求するための 2 つの助成金申請を提供しました:Fantom の助成金申請と UNICEF の助成金申請。2 つの申請は異なるフィールドを持っているため(例:Fantom の助成金申請にはprevious_fundingフィールドがありますが、UNICEF の助成金申請にはありません)、まずこれら 2 つの申請を同じフォーマットに整形します。具体的には、後の分析のためにtitledescriptionwebsitegithub_user、およびproject_githubfieldsのみが必要です。

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

関連性検出#

助成金申請を行う際、タイトルと説明は、レビュアーがプロジェクトとその潜在的な価値を理解するために重要です。したがって、タイトルと説明は理解しやすく、プロジェクトに関する十分な情報を提供する必要があります。この小節では、提供されたタイトルと説明の関連性をどのように検出するかを示します。これは、機械学習と事前学習された大規模な自然言語処理(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)

次に、タイトルと説明の関連性を判断するために別のモデルを使用します。助成金申請におけるプロジェクト説明の質を評価するタスクは、通常の対話における応答の質を評価するタスクとして見ることができるため、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):
    """
    指定されたURLのwhois情報を照会する

    :param url: ウェブサイトの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 チェック#

個人については、彼(彼女)の過去 1 年間の貢献を確認します。この指標は、オープンソースコミュニティにおける彼(彼女)の活動を反映しています。

from bs4 import BeautifulSoup
import requests

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

def get_github_user_contributions(usernames):
    """
    GitHubユーザーの過去1年間の公開貢献を取得します。

    :param usernames: GitHubユーザー名の文字列またはシーケンス。
    """
    contributions = {'users': [], 'total': 0}

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

    for username in usernames:
        # ユーザー名が'https://'で始まるURLの場合、ユーザー名を抽出します。
        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)

組織については、過去 1 年間に組織のすべての公開リポジトリのコミットの合計数を確認します。この指標は、オープンソースコミュニティにおける組織の活動を反映しています。

import datetime
from github import Github

github = Github()

def get_github_org_contributions(orgs):
    """
    GitHub組織の過去1年間の公開貢献を取得します。

    :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 つの次元を持っており、3 次元の世界に住んでいる私たちにとって分析が難しいです。したがって、クラスタリングの前に、データセットの次元を減らしましょう。まず、MinMaxScaler()を使用してデータセットを正規化します:

from sklearn.preprocessing import MinMaxScaler

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

次に、可能なパターンを探るために、現在の最先端の手法の 1 つである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()

さまざまな助成金申請の特徴

図からわかるように、助成金は異なるグループに分かれています。それでは、DBSCANアルゴリズムを使用してクラスタリングを行いましょう。ここでは、DBSCANが外れ値を検出できる密度ベースのクラスタリングアルゴリズムであり、ハイパーパラメータkを指定する必要がないため、K-MEANSの代わりにDBSCANアルゴリズムを使用します。

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()

クラスタリングされた助成金申請

図に示されているように、各助成金にはそのクラスタを示すラベルが適切に割り当てられています。注意すべきは、-1ラベルはDBSCANアルゴリズムが助成金を外れ値と見なしていることを意味します。明らかに、クラスタリングの結果は、処理されたデータセットの有効性を検証しており、私たちが発見するのを待っている隠れたパターンがいくつかあります。

「適格」の定義#

異なる申請間の関係がわかったので(クラスタリングが示すように)、クラスタリング内のグループにラベルを割り当てて、申請がどのグループに属するかを計算することで分類できるようにしたいと考えています。実際、私たちの分類器が申請が適格かどうかを教えてくれれば十分ですので、ラベルは助成金が適格かどうかを示すブール値のようにシンプルであればよいのです。これを達成するために、まず適格とは何かを定義する必要があります。したがって、このセクションでは、手動で小さな適格プロジェクトのセットを収集します(おそらく 10 プロジェクト)、その後、半教師あり学習を使用してデータセット内の助成金申請を自動的に分類する方法を学習します。

Uniswap、AAVE、Curve、Gnosis Safe などの 10 の有名なプロジェクトが、ポジティブデータとして手動で収集されました。データセットは再現のために以下のように提供されています:

positive_applications.csv

Fantom および UNICEF データセットと同じ方法でポジティブデータセットを前処理した後、すべてのデータサンプルの可視化は以下の図に示されています。ここで、ラベル 0 はラベル付けされていないデータを、ラベル 1 はポジティブデータを表します。

ポジティブデータとネガティブデータ

ご覧のとおり、ポジティブデータは他のグループと比較して明らかにいくつかのグループに近いことが示されており、これらは機械学習モデルがどのような申請が適格であるべきか、またはそうでないべきかを理解するのに役立てることができます。

ポジティブデータとラベルなしデータからの学習(PU 学習)#

このセクションでは、いくつかのポジティブデータを使用してラベルなしデータを分類するためのシンプルな分類器を訓練します。実際、ラベルなしデータと少数のポジティブデータから学習するために提案された多くのアルゴリズムがあります。しかし、次元削減後にかなり良いクラスタリングが得られたため、ラベル伝播と多数決を使用して独自のシンプルな分類器を作成できます。具体的には、まず各ポジティブサンプルと各クラスタの中心点との 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 活動と非公式 / 外部ウェブサイトを持っていることが示されており、助成金レビュアーはそれらにもっと注意を払う必要があるかもしれません。

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
読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。