この記事は、私たちが Open Data Foundation のために Fantom と UNICEF のある期間に受け取った助成金の申請データを分析したものです。私たちは、いくつかのアルゴリズムを使用して助成金の有効性を自動的に識別できることを発見しました。
この文章では、助成金申請の背後にある隠れたパターンを探ることを主に試みます。具体的には、データ収集 / マイニング、クラスタリング、半教師あり学習などの基本的および高度な技術を使用して、助成金申請の適格性を自動的に判断しようとします。それでは、始めましょう!
データ前処理#
フォーマット#
Open Data Foundation (ODF) は、私たちが探求するための 2 つの助成金申請を提供しました:Fantom の助成金申請と UNICEF の助成金申請。2 つの申請は異なるフィールドを持っているため(例:Fantom の助成金申請にはprevious_funding
フィールドがありますが、UNICEF の助成金申請にはありません)、まずこれら 2 つの申請を同じフォーマットに整形します。具体的には、後の分析のためにtitle
、description
、website
、github_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 情報を取得した後、ウェブサイトが以下のいずれかであるかどうかを確認します:
- すでに期限切れ。
- 90 日以内に期限切れになる。
- 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 の有名なプロジェクトが、ポジティブデータとして手動で収集されました。データセットは再現のために以下のように提供されています:
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