Tak's Notebook

Kaggle, Machine Learning, Engineering

SQL と Pandas の対応表

https://qiita.com/takaiyuk/items/5232442eaeb01299b265

2018-11-10T22:55:36+09:00

2020-01-08T09:34:07+09:00

トピック

SQL のクエリと、Pandas のメソッドの対応表を作成する。

SQL 勉強中のため、備忘録代わりに箇条書き(殴り書き)で書いていく。

Udemy のこちらのコースで勉強していました。

DBやテーブル自体の更新・操作に関するものはこちらにまとめている。(SQL のクエリだけを書き散らかしているだけ)

順序

記述順序

  1. select
  2. from
  3. join系(+on)
  4. where
  5. group by
  6. having
  7. order by
  8. limit

実行順序(※)

  1. from
  2. join系(+on)
  3. where
  4. group by
  5. select
  6. having
  7. order by
  8. limit

(※)追記

@nora1962jp さんからご指摘をいただきましたので、コメント内容を追記します。

実行順序
from
join系(+on)
where

SQLについてなら実行順序はonとwhereの順序はonが先とは一概に言えないです。
外部結合などが絡まない

from a join b
on a.id = b.id
where a.col1 = 1

のような場合、a.col1にインデックスが存在し選択性が有効ならwhereの判定結果をもとに結合処理を行うrdbmsは少なくありません。

基本操作

列選択

select COL1, COL2 
from TABLE;
py
df.loc[:, ["COL1", "COL2"]]

列名変更

select 
    COL1 as COL1_renamed,
    COL2 as COL2_renamed 
from TABLE;
py
df.rename(columns={"COL1": "COL1_renamed", "COL2": "COL2_renamed"})

列追加

select 
    COL1 as COL1_renamed, 
    COL2 as COL2_renamed,
    COL2 * 2 as NEW_COL
from TABLE;
py
df["NEW_COL"] = df["COL2"] * 2

条件付き行抽出

select COL1, COL2 
from TABLE 
where COL1 = 'hoge'
    and COL2 > 100;
py
df[(df["COL1"]=="hoge") & (df["COL2"] > 100)]

代表的な演算子

  • 一致
select * 
from TABLE 
where COL1 = 'hoge';
py
df[df["COL1"] == "hoge"]
  • 不一致
select * 
from TABLE 
where COL1 != 'hoge';
-- select * from TABLE where COL1 <> 'hoge'; という書き方もできる
py
df[df["COL1"] != "hoge"]
  • 含まれない
select * 
from TABLE 
where COL2 not in (1,2,3);
py
df[~(df["COL1"].isin(1,2,3))]
  • NULL
select * 
from TABLE 
where COL2 is null;
py
df[df["COL2"].isnull()]
  • 範囲
select * 
from TABLE 
where COL2 between 1000 and 2000;
-- select * from TABLE where COL2 >= 1000 and COL2 <= 2000; という書き方もできる
py
df[(df["COL2"] >= 1000) & (df["COL2"] <= 2000)]

パターンマッチング

ワイルドカード文字を利用

追記
@satorimon さんのご指摘により、Python のコードを埋めることができました。

  • 0文字以上の任意の文字列: '%'
select * 
from TABLE 
where NAME_COL like '中%';
-- 「中林」とか「中田」とかが抽出
py
df[df["NAME_COL"].str.startswith('中')]  # 指定した文字で始まる
select * 
from TABLE 
where NAME_COL like '%中%';
-- 「中林」とか「竹中」とかが抽出
py
df[df["NAME_COL"].str.contains('[ぁ-んァ-ン一-龥]中[ぁ-んァ-ン一-龥]', regex=True)]
# [ぁ-んァ-ン一-龥]: 任意のひらがな・カタカナ・漢字
# cf.) http://fujiringo.sakura.ne.jp/hayabusa/blog/code/2011/07/post-1.html
select * 
from TABLE 
where NAME_COL like '%子';
-- 「翔子」とか「美智子」とか
py
df[df["NAME_COL"].str.endswith('子')]  # 指定した文字で終わる
  • 任意の1文字: '_'
select * 
from TABLE 
where NAME_COL like '__子';
-- アンダースコア(_)が2つ
-- 「あや子」とか「美智子」とか
py
df[df["NAME_COL"].str.contains('..子', regex=True)]  # ○○子: 正規表現で"."は任意の一文字を表す

取得件数を制限

select * 
from TABLE 
limit 10;
-- 最初の10行を取得
py
df.head(10)
select * 
from TABLE 
limit 0, 10;
-- 0行目の次から、10行を取得
py
df.iloc[0:10, :]
select * 
from TABLE 
limit 10, 10;
-- 10行目の次から、10行を取得
py
df.iloc[10:20, :]

データの集約

  • expr: expression(式)...引数

合計値

select sum(COL1) 
from TABLE;
py
data["COL1"].sum()

平均値

select avg(COL1) 
from TABLE;
py
data["COL1"].mean()
  • 最小値

省略

  • 最大値

省略

集約関数における null の扱い

  • 集約関数では、null は基本的に無視される
  • 値に null が含まれないように DB 設計を構造したほうがトラブルが減る
    • null を許可する場合、0 と null の違いや、null と空文字の違いを意識する必要がある
    • 例えば、null の代わりに下記は使えないのか?検討する
      • 数値: 0
      • 文字列: ''(空文字)

カウント

select count(COL1) 
from TABLE
py
len(df)

ユニークなカウント

select count(distinct COL1) 
from TABLE
py
len(df["COL1"].unique())

グループ化

select COL1, count(*) 
from TABLE 
group by COL1;
py
df.groupby("COL1").count()

集約結果をさらに絞り込む

  • having: テーブルのデータを集約した結果に対して、条件式を適用する場合に利用する。
select COL1, count(*) 
from TABLE 
where ... 
group by COL1 
having count(*) > 10;
py
df_grouped = df.groupby("COL1").count()
df_grouped[df_grouped["COL2"] > 10]

並び替え

降順

select * 
from TABLE 
order by COL desc;
py
df["COL"].sort_values(ascending=False)

昇順

select * 
from TABLE 
order by COL asc;
-- order by はデフォルトでは昇順なので、昇順の場合 asc は省略可
-- ただし、取得するデータの並び順が重要な場合は明示的に示しておいたほうが無難
py
df.sort_values("COL")

複数条件

select * 
from TABLE 
order by COL1 desc, COL2 asc;
py
df.sort_values("COL1", ascending=False).sort_values("COL2")
# 両方とも同じ順ならば、df.sort_values(["COL1", "COL2"]) で可

関数と演算子

  • 四則演算, 絶対値, 四捨五入

省略

select COL1, round(COL1 * 1.08, 0) 
from products;
-- round(COL1 * 1.08, 0) を返り値のデータ型は???
  • null を含んだ計算結果は null を返す

省略

文字列の演算

select concat(COL1, '_', COL2) 
from TABLE;
py
df["NEW_COL"] = [f"{COL1}_{COL2}" for COL1, COL2 in zip(df["COL1"], df["COL2"])]

日付と時刻の演算

  • 現在の日付: current_date()
  • 現在の時刻: current_time()
  • 現在の日付時刻: current_timestamp()
  • N日後の日付: d + interval N day
  • N日前の日付: d - interval N day
  • X時間後の時刻: t - interval X hour
  • X時間前の時刻: t - interval X hour
  • extract: 日付や時刻の特定の部分(年や月)までを取り出す
  • date_format(TIMESTAMP, '%Y%M'): タイムスタンプから年月を取り出す
select * 
from TABLE 
where extract(year_month from COL) = 201811;
-- 単体の場合は、select * from TABLE where extract(year from COL) = 2018; などと書く
py
df[COL] = pd.to_datetime(df[COL])
df["COL_year"] = [ymd.year for ymd in df[COL]]
df[df["COL_year"] == 2018]

結合

  • テーブルの正規化: テーブルを分けて情報の重複をなくす作業
    • メリット: 管理と容量の面で嬉しい
  • キーの種類
    • 主キー(Primary Key, PK): 1つの行を特定できる列のこと
    • 外部キー(Foreign Key, FK): 他のテーブルとの関連づけに使う列のこと
  • リレーションシップの種類
    • 一対多: ex.) ユーザー - 注文
    • 多対多: ex.) 商品 - 商品カテゴリ
    • 一対一: ex.) ユーザー - 電話番号

内部結合

  • お互いに一致している行が結合テーブルの対象となる。
select T1.COL1, T1.COL2, T2.COL3
from TABLE1 as T1
inner join TABLE2 as T2
on T1.COL4 = T2.COL5;
py
df1.merge(df2, how="inner", left_on="lkey1", right_on="rkey1")
  • 内部結合 + 絞り込み
select T1.COL1, T1.COL2, T2.COL3
from TABLE1 as T1
inner join TABLE2 as T2 on T1.COL4 = T2.COL5
where T1.COL2 = 'hoge';

外部結合

  • 片方のテーブルの情報がすべて出力される、テーブルの結合
  • 片方のテーブルをベースに、お互いに一致している行は結合、一致していない行は null として結合テーブルの対象となる
select T1.COL1, T1.COL2, T2.COL3
from TABLE1 as T1
left outer join TABLE2 as T2  -- left join とも書かれる
on T1.COL4 = T2.COL5;
py
df1.merge(df2, how='left', left_on="lkey1", right_on="rkey1")

集合演算子

集合演算子 union は、テーブルの足し算を行う

  • テーブル1とテーブル2で列数を合わせる必要がある。
  • 同じ位置にあるカラムのデータ型は一致している必要がある。
  • order by は全体の最後に一度しか用いることができない。
select COL1, COL2
from TABLE
union  -- 重複削除がデフォルト。残したい場合は、union all とする。
select COL1, COL2
from TABLE
py
pd.concat([ df1[["COL1", "COL2"]], df[["COL1", "COL2"]] ]).drop_duplicates()

ビュー

  • ビューとは

    • データを取り出すSELECT文だけを保存する
    • DBユーザーの利便性を高める道具(SQLの観点から見ると、テーブルと同じもの)
  • ビューのメリット

    • 必要なデータが複数のテーブルにまたがる場合などの複雑な集約を行いやすくなる
    • よく使う select 文はビューにして使い回すことができる
    • データを保存しないので、記憶装置の容量を節約できる
  • ビューのデメリット

    • パフォーマンスの低下を招く場合がある
  • ビューの作成

create view VIEW_NAME(COL1, COL2, ...)
as 
select * 
from TABLE ...;
  • ビューの呼び出し
select *
from VIEW_NAME;
  • ビューの削除
drop view VIEW_NAME;
  • ビューの制限事項
    • order by 句が使えない
    • 更新系(insert, delete, update)に制約がある
      • ビューとテーブルの更新は連動して行われるため、集約されたビューは更新不可。

サブクエリ

  • あるクエリの結果に基づいて、異なるクエリを行う仕組み。
  • where 句の中で使われることが多い(それ以外にも様々な場所で利用できる)
select *
from TABLE1
where COL1 演算子 (select COL2 from TABLE2 ...);
  • スカラ・サブクエリ
    • 1行1列だけの戻り値を返すサブクエリのこと

条件分岐

  • Python で言うところの if文
case
    when 条件式1 then 1
    [when 条件式2 then 2]
    [else 3]
    -- []の部分は省略可能
end  -- end は省略不可

RユーザーのためのPython対応表 [tidyr, ggplot2]

https://qiita.com/takaiyuk/items/0bf9b1db8b4707e3dae2

2018-07-17T19:22:46+09:00

2018-10-27T08:47:19+09:00

概要

Rユーザーが、Pythonを使う際に、

「Rのアレ、Pythonではどうやるんだっけ?」

というのをまとめてみた感じです。

Pythonユーザーで、「Pythonのアレ、Rでどうやるんだっけ?」って人にも役立つかもしれません。

(dplyr, stringrの対応はこちらも参考にしてみてください)

Rユーザー向け Pythonデータ処理入門

ライブラリ

  • tidyr => pandas
  • ggplot2 => seaborn

データはirisのデータセットを利用。(R標準のデータセット、列名を一部変更)

Sepal_Length Sepal_Width Petal_Length Petal_Width Species
5.1 3.5 1.4 0.2 setosa
4.9 3.0 1.4 0.2 setosa
4.7 3.2 1.3 0.2 setosa
4.6 3.1 1.5 0.2 setosa
5.0 3.6 1.4 0.2 setosa
5.4 3.9 1.7 0.4 setosa
... ... ... ... ...


tidyr

gather

DataFrameをWideからLongにする

R
iris %>% gather(key, value, -Species) %>% head()
py
pd.melt(iris, id_vars=['Species'], var_name='key', value_name='value').head()
    Species          key value
1  setosa Sepal_Length   5.1
2  setosa Sepal_Length   4.9
3  setosa Sepal_Length   4.7
4  setosa Sepal_Length   4.6
5  setosa Sepal_Length   5.0
6  setosa Sepal_Length   5.4

spread

DataFrameをLongからWideにする

R
iris %>% 
    rownames_to_column('id') %>%  # idとなる列がないと "Duplicate identifiers for rows" というエラーが生じる
    gather(key, value, -id, -Species) %>%
    spread(key, value) %>% 
    arrange(as.integer(id)) %>% 
    head()
py
iris_index = iris.reset_index()  # df.pivot()で列(columns)にする値と行(index)にする値がユニークでないと、 "ValueError: Index contains duplicate entries, cannot reshape" というエラーが生じる
iris_melt2 = pd.melt(iris_index, id_vars=['index', 'Species'], var_name='key', value_name='value')
iris_melt2.pivot(index='index', columns='key', values='value').head()
id Species Petal_Length Petal_Width Sepal_Length Sepal_Width
1  1  setosa          1.4         0.2          5.1         3.5
2  2  setosa          1.4         0.2          4.9         3.0
3  3  setosa          1.3         0.2          4.7         3.2
4  4  setosa          1.5         0.2          4.6         3.1
5  5  setosa          1.4         0.2          5.0         3.6
6  6  setosa          1.7         0.4          5.4         3.9


ggplot2

Histgram

R
iris %>% ggplot(aes(x=Sepal_Length)) + geom_histogram(fill="blue", alpha=0.5) + theme_bw()

r1.png

py
sns.distplot(iris.Sepal_Length, kde=False)  # kde=True で密度曲線

p1.png

Point Plot

R
iris %>% ggplot(aes(x=Petal_Width, y=Petal_Length, color=Species)) + geom_point()

r2.png

py
sns.jointplot(x="Sepal_Length", y="Petal_Length", data=iris)

p2.png

Bar Plot

R
iris %>% ggplot(aes(x=Species, y=Petal_Length, fill=Species)) + stat_summary(fun.y=mean, geom="bar", alpha=0.5) + theme_bw()

r3.png

py
sns.barplot(x="Species", y="Petal_Length", data=iris)  # 平均値

p3.png

Count Plot

R
iris %>% ggplot(aes(x=Species, fill=Species)) + geom_bar(stat="count", alpha=0.5)

r4.png

py
sns.countplot(x="Species", y="Petal_Length", data=iris)  # 個数

p4.png

Box Plot

R
iris %>% ggplot(aes(x=Species, y=Sepal_Length, fill=Species)) + geom_boxplot(alpha=0.5)

r5.png

py
sns.boxplot(x="Species", y="Sepal_Length", data=iris)

p5.png


参考

Rユーザー向け Pythonデータ処理入門

https://qiita.com/takaiyuk/items/4cb1708a3f886b3d2043

2017-10-19T20:56:30+09:00

2018-06-21T17:49:25+09:00

はじめに 

こんな人向け

・Rは分かるけど, Pythonは分からないという人向け.

背景

・R初心者(初稿投稿時: R歴7ヶ月).
・最近Pythonにも手を出してみるも, 単純なデータ処理すら書き方がRと異なるため難しい.
・RとPythonのデータ処理の対応表を見たい.
・いくつか参考サイトあるが, 自分が必要とするものが完全に揃っているわけではない.
 =>R vs Python:データ解析を比較
・じゃあ自分用メモを作っちゃおう.

参考にしたサイト




基本操作(base)

・irisデータセットを使用.
・ちょこちょこ余計な操作もしています.

行・列数を確認

R
dim(iris)
py
iris.shape

行数を確認

R
nrow(iris)
py
len(iris)

列数を確認

R
ncol(iris)
py
len(iris.columns)

データ型確認

R
class(iris)
R
class(iris$Sepal.Length)
py
type(iris)
py
type(iris.Species)

先頭のn行を確認

R
head(iris)
py
iris.head()

列名を確認

R
names(iris)
py
iris.columns

指定行の取り出し

R
iris[1,]
py
iris.iloc[0] # 行番号を使って行抽出

指定列の取り出し

R
head(iris[,1])
py
iris["Sepal.Length"].head()

行結合

R
iris2 = iris
dim(rbind(iris, iris2))
py
iris2 = iris
pd.concat([iris, iris2],axis=0).shape # axis=0は省略可能

列結合

R
iris2 = iris
head(cbind(iris, iris2))
py
iris2 = iris
pd.concat([iris, iris2],axis=1).head() # axis=1は省略不可

文字列型ー>数値型

R
c = "1"
class(as.numeric(c))
py
c = "1"
type(float(c))

数値型ー>文字列型

R
v = 1
class(as.character(v))
py
f = 1
type(str(f))

統計要約量

R
summary(iris)
py
iris.describe()
# 件数、平均、標準偏差、最小値、最大値、中央値、四分位数

欠損値を含む行を削除

R
iris.na <- iris %>% mutate(column.na=seq(1:nrow(iris))) #nrow()は行数を返す。seqは順に任意の数(デフォルト1)ずつ増減する数列を返す。
iris.na[1,6] <- NA # "<-" は代入記号
head(na.omit(iris.na))
py
series = pd.Series(range(150), index=new_iris.index) #index付きのシリーズ作成。indexは元のdfのindex(=行番号)
del series[0] # 要素の削除(del文, popメソッド, removeメソッド) https://www.pythonweb.jp/tutorial/list/index8.html
iris_na = iris
iris_na["column_na"] = series
iris_na.dropna(axis=0).head()


データ操作(dplyr)

rename

R
new_colnames <- stringr::str_replace(colnames(iris), "[.]", "_") # "."を"_"に変換する
new_iris <- iris
colnames(new_iris) <- new_colnames
# df %>% rename(new_colname = old_colname) とすることもできる。
names(new_iris)
py
rename_dict = {column: column.replace('.', '_') for column in iris.columns} # 列名の"."を"_"に書き換える
new_iris = iris.rename(columns=rename_dict)
new_iris.columns

filter

R
head(iris %>% filter(Sepal.Length > 6.4))
py
new_iris[(new_iris['Sepal_Length'] > 5) & (new_iris['Sepal_Width'] > 3)].head()
# データフレーム[Booleanの配列を入れる]
# 複数条件の場合は、or条件の "|" もしくは and条件の "&"を間に入れ、それぞれの条件を()で囲む

select

  • 列名で列選択
R
head(iris %>% select(Sepal.Length))
py
new_iris[["Sepal_Length", "Petal_Length"]].head() # 複数列選択([]で[列名,列名]を囲む)  
  • 列番号で列選択
R
head(iris %>% select(-1))
py
new_iris.drop('Species', axis=1).head(6) # 列削除

mutate

R
head(iris %>% mutate(new.column="new value"))
py
series = pd.Series("new_value", index=new_iris.index) # index付きのシリーズ作成。indexは元のdfのindex(=行番号)
new_iris["new_column"]=series
new_iris.head()

arrange

  • デフォルトでは昇順
R
head(iris %>% arrange(Sepal.Length))
py
new_iris.sort_values(by=['Sepal_Length', 'Sepal_Width']).head() # 複数の値で昇順でソート
  • desc()で降順にもできる
R
head(iris %>% arrange(desc(Sepal.Length)))
py
new_iris.sort_values(by='Sepal_Length', ascending=False).head() # 降順でソート

group_by

R
head(
knitr::kable(iris %>% 
       arrange(Sepal.Width) %>% 
       group_by(Species) %>% 
       mutate(mean.S.L.by.Species=mean(Sepal.Width)))
)
py
new_iris_grouped = new_iris.groupby("Species")  # SpeciesでGroup_byを行う。
new_iris_grouped[["Sepal_Length","Sepal_Width"]].mean()          
  # Group化されたされたオブジェクトに対してMeanを行う。
  # 必要であれば、Meanを行う変数を指定できる。

summarise

R
head(iris %>% summarise(max.sl=max(Sepal.Length), 
                        max.sw=max(Sepal.Width), 
                        min.pl=min(Petal.Length), 
                        mean.pw=mean(Petal.Width)))
py
# 同上
new_iris_grouped = new_iris.groupby("Species")  # SpeciesでGroup_byを行う。
new_iris_grouped[["Sepal_Length","Sepal_Width"]].mean()          
  # Group化されたされたオブジェクトに対してMeanを行う。
  # 必要であれば、Meanを行う変数を指定できる。

join

R
colnames_for_join <- stringr::str_replace(colnames(iris), "th", "th_for_join") # 列名の"*th"を"*th_for_join"に書き換える
iris_for_join <- iris 
colnames(iris_for_join) <- colnames_for_join
iris %>% left_join(iris_for_join, by="Species")
py
dict_for_join = {column: column.replace('th', 'th_for_join') for column in new_iris.columns}
iris_for_join = new_iris
iris_for_join = iris_for_join.rename(columns=dict_for_join)
pd.merge(new_iris, iris_for_join, how='left')


文字列処理(stringr)

データ読み込み

R
species <- iris$Species
species[1]
py
species = new_iris["Species"]
species[0]

str_length: 文字列の長さを返す.

R
str_length(species[1])
py
len(species[0])

str_sub: 文字列の一部抽出する.

R
str_sub(species[1], start = 1, end = 4)
R
str_sub(species[1], start = -4, end = -1)
py
species[0][0:2]
py
species[0][-2:]

str_detect: 特定のパターンを文字列が含むかどうか判別する.

R
str_detect(species[1], "eto")
py
"eto" in species[0]

str_subset: 特定のパターンを含む文字列を抽出する.

R
str_subset(species, "set")
py
for index in range(1,len(species)): 
    if ("set" in species[index]) ==True:
        print(species[index])

str_locate: 特定のパターンが文字列の何番目に位置するかを返す.

R
str_locate(species[1], "set")
py
species[0].find("t")

str_replace: 文字列内の特定のパターンを置換する.

R
str_replace(species[1], "sa", "sa_replaced")
py
species[0].replace("sa", "sa_replaced")

str_trim: 文字列の空白を除去する.

R
species_trim <- c(" species ")
str_trim(species_trim, side="both")
py
species_trim = (" species ")
species_trim.strip()
# lstripはstripと同等の処理を左端のみに適用
# rstripはstripと同等の処理を右端のみに適用


その他

readr::parse_number: 数字のみを抽出する.

R
x <- c("6e23")
parse_number(x)
py
import re
x = ("6e23")
re.match("\d", x).group()

Kaggle Data Science Bowl 2019 上位解法まとめ

f:id:takaishikawa42:20200126205045p:plain
https://www.kaggle.com/c/data-science-bowl-2019/

Data Science Bowl 2019

  • 子供向けの教育系ゲームアプリの行動ログから session_id の Assessment の正答率を0~3の4段階のどれになるかを予測するタスク
  • Public で使われるテストデータの評価される session_id のユニーク数が1000しかなく、CV と PublicLB が安定しにくかった
  • 評価指標もQWKと不安定になりやすいものだったので結果的に比較的大きなShakeが起きた


所感

上位陣に共通していたポイント

  • Validation, Feature Selection に注力しているチームが比較的多かった印象がある。
  • ランダム性やPublic/Privateの乖離のリスクから、ランダムに truncated した Validation データセットを結構な回数回して平均を取ることで安定したCVスコアを求めていた

異なる点

  • 閾値は OptimizeRounder を使うだけのチームがある一方、閾値の決定に工夫をこらしているチームがあった
  • 目的変数の決め方も比較的バリエーションが多かった印象がある
  • ログデータを系列として捉えてそれをうまくモデルに落とし込めたチームは少なかったが、一定数いた


1st Place Solution

リンク

https://www.kaggle.com/c/data-science-bowl-2019/discussion/127469

Summary

  • 5 fold の Lightgbm のシングルモデルを Seed Averaging
  • Private QWK: 0.568, Public QWK: 0.563
  • CV Weighted QWK: 0.591, CV Weighted RMSE: 1.009

Validation

初期にLBスコアは不安定でローカルCVと相関が低いことが分かったので、ローカルCVのみにフォーカスし、以下の2つのバリデーションを試した。

  1. GroupK CV

  2. installation_id ごとの 5fold の GroupKFold を5回回した。各回のGroupKFoldでSeedと列順を変えた。それでもQWKはローカルCVで不安定だったのでRMSEの重みに注意してQWKは無視した。

  3. Nested CV

  4. 上記のGroupKFoldのCVはうまくいったが、信頼度が低かったので学習データで Train-Test の分割をシミュレーションした。つまり、全履歴を持った1400ユーザーをランダムに選び学習データとし、ランダムに履歴を切り捨てた2200ユーザーをテストデータとした。これを50~100回繰り返して平均値をバリデーションスコアとした。

Feature

特徴量生成に特に時間を割いた。20000個の特徴量を作り、Null Importance で上位500個の特徴量を選択した。

  1. true attempt の割合・correct true の割合・correct feaedback の割合の統計量(mean/sum/last/std/max/slope)。同じAssessmentや似たGameに基づいた統計量の重要度が高かった。(各々のGameを似たAssessmentに紐付けた)
  2. 異なるTimespanで履歴データから特徴量を抽出した。(1) 全履歴データ (2) 直近5/12/48時間 (3) 直近のAssessmentから現在のAssessmentまで
  3. イベントの間隔の特徴量(次のイベントのタイムスタンプから現在のイベントのタイムスタンプ)。event_id/event_code で groupby したイベントの間隔の統計量(mean/last)。いくつかの特徴量が高い重要度を示した。
  4. ビデオスキップの発生頻度。Clipイベントの間隔、ホストによって提供されたClipの長さ。
  5. Event Data 特徴量。event_data × event_id/event_code の全ての数値特徴の統計量(mean/sum/last)。例)event_code2030_misses_mean

Feature Selection

  • 重複列の削除
  • Adversarial Validation で Adversarial AUC が0.5になるようにして、リークとコードエラーをなくした
  • Null Importance で上位500個の特徴量を選択

Model

  1. Data Augmentation: 全データで学習させた(学習データ + テストで利用可能なデータ)
  2. Loss: RMSEを学習に用いて、Validation のために重み付きの RMSE Loss を用いた
  3. Threshold: QWKの最適化に Opitmizer Rounder を用いた
  4. Ensemble: シンプルなBlendingを試した(0.8 * lightgbm + 0.2 * catboost)。CVスコアが改善しなかったのでFinal Submissionに選択しなかった。


2nd Place Solution

リンク

https://www.kaggle.com/c/data-science-bowl-2019/discussion/127388

Summary

  • Public 0.563 / Private 0.563

Validation

  • Validation セットには 1ユーザーあたり1サンプルをリサンプリングした
  • StratifiedGroupKFold, 5-fold

Feature

  • Word2Vec features of title series
    • 予測対象の Assessment までの title の系列を文書と見なして、Word2Vec で処理して得られたベクトルの統計量(mean/std/max/min)を計算した
  • Historical feature
    • 全体および World ごとに group by して (session, world, type, title, event_id, event_code) のカウントを historical data として取得した
    • (event_round, game_time, event_count) の count, mean, max を取得した
  • Decayed historical feature
    • 上記の hisotrical data に関して session 以外のものについて各セッションごとに半減するような時間減衰をかけたものを取得した
  • Density of historical feature
    • Density = (count) / (elapsed days from a first activated day)
  • Lagged Assessment
    • num_correct, num_incorrect, accuracy, accuracy_group の様々な統計量
    • 過去の Assessment との hours の差分
  • Meta Features
    • 「ある game_session がどれだけ後の assessment の結果に影響を与えたか」を表すために、"meta target features" を各 assessment title に対して生成した。 KFold の oof の平均値を用いた

f:id:takaishikawa42:20200126204806p:plain
https://www.kaggle.com/c/data-science-bowl-2019/discussion/127388

Feature Selection

  • 重複列の削除
  • 高い相関係数(0.99以上)を持つ列の削除
  • Null Importance で上位300個の特徴量を最終的に選択した

Model

  • Lightgbm, Catboost, NN の Random Seed Averaging (5 random seed)
  • Ensemble(0.5 * LGB + 0.2 * CB + 0.3 * NN)
  • CV の QWK を最適化する Threshold を設定した

Others


3rd Place Solution

リンク

https://www.kaggle.com/c/data-science-bowl-2019/discussion/127891

Summary

  • 特徴量生成よりもデータに適したNNモデルのアーキテクチャを見つけることに注力した。
  • Single TRANSFORMER を用いた

Validation

  • 言及なし

Feature

  • installation_id の系列をそのまま使うには長すぎるので game_session ごとの集計した。以下コード例。
df = train_df
event_code = pd.crosstab(df['game_session'], df['event_code'])
event_id = pd.crosstab(df['game_session'], df['event_id'])
...
agged_df = pd.concat([event_code, event_id, game_accuracy, max_round])
session_df = df.drop_duplicates('game_session', keep='last').reset_index(drop=True)
session_df = session_df.merge(agged_df, how='left', on='game_session')
  • LSTM や TRANSFORMER は単語(あるいは文章)の0列を入力データとして受けつけるので、game_session (あるいは installtion_id) の系列を入力データとして用いた

Feature Selection

  • 言及なし

Model

  • position-related な情報(position embedding)を用いると手元のCVスコアが落ちた
    • BERT, ALBERT, GPT2 のような poision embedding を用いたモデルは良くなかったので、poisiton embedding がない TRANSFORMER を用いた
  • モデルの構造は以下図の通り

f:id:takaishikawa42:20200129083338j:plain
https://www.kaggle.com/c/data-science-bowl-2019/discussion/127891

  • 重要なポイントは game_session からどのように embedding を作り出すかという点だった。
    • Categorical columns(event_code, title, word, etc.)は別々に embedding した。それから、embedding を concatenete することで categorical_vector を獲得した。つづく nn.linear 層は categorical_vector の次元削減のために用いられた。
    • また Continuous columns は linear 層を使って直接 embedding された。また、標準化のために np.log1p を用いた
self.categorical_proj = nn.Sequential(
    nn.Linear(cfg.emb_size*num_categorical_columns, cfg.hidden_size//2),
    nn.LayerNorm(cfg.hidden_size//2),
)
self.continuous_emb = nn.Sequential(                
    nn.Linear(num_continuous_columns, cfg.hidden_size//2),
    nn.LayerNorm(cfg.hidden_size//2),
)
  • ハイパーパラメータは以下の通り

    • optimizer: AdamW
    • schedular: WarmupLinearSchedule
    • learning_rate: 1e-04
    • dropout: 0.2
    • number of layers : 2
    • embedding_size: 100
    • hidden_size: 500
  • Loss Function は以下の通り

    • ディスカッションで言及したとおり、nun_incorrect の値の上限を2に制限することで、正答・誤答の数と accuracy_group の組み合わせが一意になる。これらを用いて accuracy_group, num_correct, num_incorrect を目的変数にしてモデルを学習させた予測結果から、accuracy_group を算出した。
# num_incorrect[num_incorrect > 2 ] = 2 # Constrained not to exceed 2.
new_accuracy_group = 3 * num_correct - num_incorrect

Others

  • Game の event_data から Assessment と同じように正答・誤答を求めて、事前学習ステップとして3エポックまで元の学習データに追加して学習させた
  • game_session 数が30以上ある installation_id の場合、最大50~60%までランダムに古いevent_dataを削除した


4th Place Solution

リンク

https://www.kaggle.com/c/data-science-bowl-2019/discussion/127210 https://www.kaggle.com/c/data-science-bowl-2019/discussion/127312 https://www.kaggle.com/sergeifironov/bowl-stabilize-coefs-cntrs-all5

Summary

  • 最終サブミットは、一つはNNのブレンドで、もう一つは3段階のStacking(Public/Privateが、前者は0.572/0.561, 後者は0.566/0.560)

Validation

  • テストデータは学習データと同じ処理でラベル付け可能なので学習データとして利用できる
  • データ量が少なかったので、高スコアを出すことよりもスコアを安定させることが重要だった。
    • 列の順序を変えるだけでスコアが悪化した場合、何か間違ったことをしていたと言えるだろう。
  • Discussion にも色々書いたが、コンペ終盤でPublic LBと相関をもたせることを諦めた

Feature

  • events の系列(title + event_code + correctflag + incorrectflag)に対して TfIdf を行った
    • その後 installation_id の hisotrical data を tokens の系列と見なして、それらに対して TfIdf を行った(?)
  • clip や別の title の中には Assessment の正答率の予測に対して大変重要なものがあった。順番はおそらくそれほど重要でなかったが、RNNアーキテクチャがユーザー履歴内でのそれらの重要性を十分扱うことができた

Model

  • Tfidf features + RNN on title sequence (last 64) with some additional features:
  • Tree base models (Lightgbm, Xgboost, Catboost)
  • Stack
    • 0 level: NN, Lightgbm, Catboost
    • 1 level: MLP, Lightgbm
    • 2 level: Ridge

f:id:takaishikawa42:20200126212242p:plain
https://www.kaggle.com/c/data-science-bowl-2019/discussion/127312

Others

  • うまくいかなかったこととして以下を挙げている
  • event_id, title, title+accuracy_group などの予測に対する(?) Transfomers, GPT-2, BERT
  • Grapgh NN
  • events の系列に対する Transformer


7th Place Solution

リンク

https://www.kaggle.com/c/data-science-bowl-2019/discussion/127213

Summary

  • Feature Engineering に注力した。最終的に51個の特徴量を用いた(151個から切り捨てた)
    • 最も効いた特徴量は各 Assessment ごとの分布だったが、これはどの人も使っていた
  • "Trick" としてはテストセットの Assessment を学習データとして用いたことである
    • データ量が少なかったので学習データ量を増やすことを模索した
  • Score
    • Truncated CV: 0.575
    • Private LB: 0.559
    • Public LB: 0.559

Validation

  • Truncate CV を見ていた
    • テストセットの構造を反映するために各 installation_id ごとにランダムに一つの Assessment を Validation として選択した

Feature

  • Francois Chollet の On the measure of intelligence にインスパイアされて、以下のような特徴量のグループを作った
    • 経験:どういうアクティビティに時間を使ったか
    • 正答率:どういう正答率で来たか
    • スキルの獲得過程:どれほど早く学習してきたか

Feature Selection

  • 150の特徴量まで減らす際、一つずつ個別に特徴量を落としたときにCVスコアを計算して削除した
  • 100の特徴量まで晴らす際、QWKスコアが0.0001以下の改善しか寄与しなかった特徴量をノイズと見なして削除した
  • その後、再度CVを計算してスコアを僅かにしか改善しない特徴量を削除した。

Model

  • 最終的なモデルはアンサンブルだった(0.3 LGB, 0.3 CATB, 0.4 NN)
  • 全モデルとも 20 Kfold で回した。NNは追加で3 seedsの平均を取った


8th Place Solution

リンク

https://www.kaggle.com/c/data-science-bowl-2019/discussion/127285 https://www.kaggle.com/zgzjnbzl/dsb-mlp-216-feature

Summary

  • 最終サブミットは8時間前に提出して6時間実行にかかったので、締切の2時間前に何とか提出できたので諦めなくてよかった

Validation

  • 5 GroupKFold

Feature

  • numericな特徴量に対してログ変換や、Stdを取ったりした
  • 欠損値は0埋めで、欠損フラグを新たな特徴量とした
  • 主な特徴量は以下
    • type, title, event, event_code の Count Encoding
    • title_accuracy, title_accuracy_lasttime
    • title duration の max/mean/std(title duration を1000でクリッピングした), ミスした title duration の mean/std
    • ミスしたtitleラウンド数の平均をラウンドの時間で割ったもの(accuracy vs. speed の情報を反映)
    • title の nunique
    • world ごとの title の nunique
    • session・eventの合計数
    • gameの試行数をgameの全体数で割った値

Feature Selection

  • Null Importance で1100程度の特徴量を216まで減らした
  • その216の特徴量を100までに減らした方がCVスコアが良かったが、Validation Lossもは大きくなり過学習を引き起こした。加えて PublicLB のスコアも良かったので最終サブミットは両方とも216特徴量を使ったものにした

Model

  • シンプルな3層のMLP(256-256-256 + BN, Dropout=0.3, LeakyReLU)
    • 異なるseed、微妙に異なるepochで全データを使って9つのモデルを学習した
    • OptimizerはAdam, Batchsizeは128, LRは0.0003(cyclic decay)
  • 9つのモデル各々で2つのターゲットの18個の結果の平均値を出して、それをOptimizerに適用した
    • ランダムに閾値の初期値を決めて25回繰り返してローカルCVが最良の閾値を選択した


9th Place Solution

リンク

https://www.kaggle.com/c/data-science-bowl-2019/discussion/127221 https://twitter.com/Trust_CV/status/1220537156692627462

Summary

  • コンペ終了9日前に800th->50th、前日に80th->23thに順位を上げてきた
  • 解法はシンプルで、特徴量生成(ほとんど集計値)・多様なモデルとスタッキング・ランダムサーチで閾値をチューニングをした

Validation

  • 言及なし

Feature

  • 多くのカーネルで用いられていた get_data 関数は特徴量を作ったり管理したりするのが難しかったので以下のようにして作った
train_gs_assess_dict = {}
for ins_id, user_sample in tqdm(train.groupby('installation_id')):
    assess_count = 0
    for gs, session in user_sample.groupby('game_session', sort=False):
        if session['type'].iloc[0] == 'Assessment':
            assess_count += 1
        train_gs_assess_dict[gs] = assess_count
train['assess_count'] = train['game_session'].map(train_gs_assess_dict)
  • これを用いて assessment 以前のユーザーアクティビティのサブセットのための集計系の特徴量を作った
  • 実行時間はかかるが、こちらの方が特徴量の実装と管理が容易である

Feature Selection

  • 言及なし

Model

  • first level は8つのモデルを用いた
    • LightGBM は type / target / eval metrics などを変えて7種類 + NN
    • accuracy group との相関係数・ケンドールの順位相関などを見た?
  • second level は accuracy group を目的変数として Ridge Regression を用いた

    • accuracy group と予測値の相関係数が最良のモデルの重みが0になってしまったが、結果的にこのスタッキングが効いた
  • 閾値の決定はこのコンペではかなり重要だった。最初は OptimizeRounder 関数を用いていて、これは初期値に大きく依存することが分かったので閾値をランダムサーチで決めることにした。このアプローチのおかげで800位ほど順位を上げた。

  • また adversial validation の結果 public と private のデータセットはとても似ていると考え、ランダムに100回選んだ truncated validation の QWK の平均値を最大化する閾値を選択した。


10th Place Solution

リンク

https://www.kaggle.com/c/data-science-bowl-2019/discussion/127332

Summary

  • 1年前のMalwareコンペで2位 -> 1500位までのデカいshake downの経験からバリデーション戦略やPublic/Private分析に努力してきた結果、初めて金メダルを獲得できた

Validation

  • Stratified Group KFold (10-fold)
  • バリデーションスコアは全て truncated validation で計算した
    • 各 installation_id ごとにランダムに一つの Assessment を Validation データセットにした
  • 各foldでは51個の truncated validaiton セットを使用した

    • 1つを early stopping に
    • 残りの50個を手元のCVスコアに(QWKの平均値を取った)
  • publicは1000個のinstallation_idしかなかったのでValidationとしては不適切な分布に鳴っていると考え、truncatedされた学習データの oof prediciton の平均値を1000回計算した。すると、パブリックデータセットは分布のレアケースである事がわかったので、Trust CV(LBを無視)して、CVでQWKが最良となる閾値を用いた。

  • 最終的な閾値は500のtruncated oof validationの平均値から OptimizedRounder を用いて [1.04, 1.76, 2.18] となった。(パブリックカーネルで行われていたような学習データの分布に合わせる閾値の決め方は分布が合ってないので過学習を招くと考えている)

f:id:takaishikawa42:20200129083436p:plain
https://www.kaggle.com/c/data-science-bowl-2019/discussion/127332

Feature

  • 3000~5000程度の特徴量を全部で作ったが、Magic Features はなかった(最終的には300個くらいだけ使った)
  • 良い特徴量は以下の通りだった
    • 標準化した Accuracy の特徴量
    • title ごとの特徴量
    • 関連した特徴量
      • event_code_4200_count, event_code_4700_count, last_accuracy, all_accuracy_mean

Feature Selection

  • truncated なバリデーションに対する特徴量の効果を評価するために、truncated な学習データの特徴重要度を用いた
  • 各foldで、50個の truncated なデータセットを作り、init_modelパラメータを用いて5回のイテレーションごとにデータセットを変えた結果から、なんとなく上位300個の特徴量を用いた(詳しくはpaoさん本人のツイート参考)

Model

  • Lightgbm を使った
    • feature fraction を0.8から1.0に変更したらCVが0.005改善した
    • title が target value に対して大きな効果があるので全てのツリーに assessment_title を使った方が良い結果になったのではないか
  • game_session ごとのモデル
    • Transformer とは別に、Lightgbmをgame_sesionごとに作った
    • 直接は使わなかったが、game ごとに良い特徴量を素早く見つけるのに役立った
  • 学習にテストデータセットを用いた
    • 改善に役立ったかどうか分からない

Others

  • うまくいかなかったこと
    • MLP Regressor
    • NN EventCode Transformer
    • Word2Vec Feature
    • 標準化した accuracy group をターゲットとする
    • titleごとの残差エラーの学習


14th Place Solution

リンク

https://www.kaggle.com/c/data-science-bowl-2019/discussion/127221 https://www.kaggle.com/khahuras/bowl-2201-a?scriptVersionId=27403894

Summary

  • Public/Private で金圏にとどまることが出来た3チームのうちの1チームであった
  • Public-Private の相関が謎で、それがコンペを面白くしていた
  • 4人チームだったのでモデルや特徴量の多様性を意識した(コンペ最終盤でチームマージするとソリューションの統合がとても大変)

Validation

  • テストデータで利用可能なものを学習データに加えて学習させた

Feature

  • パブリックカーネルの特徴量に加えて、以下のような特徴量を加えた
    • session における good action に対する全アクションの比率
    • ミスクリック/ミスドラッグ数に対する全アクション数(あるいはセッション時間)の比率
    • 前回のAssessmentからの特定のevent_codeの数
  • 各installation_idごとの予測対象と同じassessmentに関する統計量は効いた
    • ツリーモデルの収束を早め、Assesment titleの重要度を下げるのに約だった

Feature Selection

  • 言及なし

Model

  • 全AssessmentをTargetにしたモデル (Main Model) と、各Assessmentごとに別々のモデル (Separate Model) を作り結果を結合したものの2種類のモデルを作った
  • セパレートモデルをメインモデルに適切にブレンドするとスコアの上昇が確認され、test dataを学習データに利用する/しないのオプションと組み合わせることで、4つの異なる学習戦略を持つことが出来た

  • シンプルなOptimizerを利用してCVを最大化する閾値を見つけようとした。モデリングやアンサンブルに注力して、閾値探索には注意を払わなかった。

  • Assessmentの数を元に学習データに重みを与えて学習させた。CVは殆ど変わらなかったけどLBは上昇した。このアプローチには問題があり、0-prior-assessmentのサンプルサイズは学習データでは1/4、テストデータでは1/2だったので、それに対して単純に重みを与えることは難しかったので、結局えいやで思いを決めた。 [0-prior, 1-prior, 2-prior, 3-prior, 4-prior, and more-than-4-prior]: [1.65 , 1.09, 0.87, 0.77, 0.57, 0.47]

  • アンサンブルについて。メインモデルの結果とセパレートモデルの結果を結合する良い方法を見つけた。3つのシンプルな分類器を学習させた。すなわち、0/1, 1/2, 2/3 のクラスに分類する分類器を学習させ、図のような方法でmodel1とmodel2の2つの予測値をブレンディングした。(上リンク先参考)

  • スタッキングについて。スタッキングはCV, LBの両方に対して有効だった。結果的に、一つはブレンディングモデル、もう一つはスタッキングモデルを最終サブミットに採用した。スタッキングでは4つのstackerの平均値と、extra-tree regressorの2つのアプローチを試した。後者のほうがCVでも、結果的にPrivateLBでもより良い性能だったが選ばず、ブレンディングモデルとスタッキングモデルのブレンディングを選んだ。


15th Place Solution

リンク

https://www.kaggle.com/c/data-science-bowl-2019/discussion/127500 https://www.kaggle.com/hidehisaarai1213/dsb2019-nn-ovr-reduce-90-val-60-percentile https://www.kaggle.com/c/data-science-bowl-2019/discussion/125001#726894


16th Place Solution

リンク

https://www.kaggle.com/c/data-science-bowl-2019/discussion/127331


17th Place Solution

リンク

https://twitter.com/sakata_ryuji/status/1220151217650749441?s=20 https://twitter.com/sakata_ryuji/status/1220171672419885057?s=20 https://twitter.com/sakata_ryuji/status/1220241711542046722?s=20

2019年に買ってよかったもの

はじめに

今年買ってよかったものをまとめてみた。

と言っても新たに買ったものといえば ほとんどこれくらいだけど、備忘録も兼ねて。



2019年に買ってよかったもの

Yogibo

Yogibo Midi

これは良かった。Twitter である時期に Yogibo を買ってる人たちを見かけて良さそうと思って買ってみたら、正解だった。



家で大流行して、結局本体だけでなく yogibo roll と traybo が買い足された


気持ち的なQOLが急上昇した。


ワイヤレスノイズキャンセリングヘッドホン

SONY-WH-1000XM3

元々夏の旅行時にノイキャンのイヤホン or ヘッドホンが欲しいと思い買ったもの。Sony のこのシリーズのヘッドホンは以前から良いと聞いていて、実際に試着してみてヘッドホンの頭と耳が痛くなりそうな感じがないのを気に入って即購入した。イヤホンもいいけど、ワイヤレスなので十分通勤時にも使える。 その後、iPodAir2 が出たからと言って、後悔はしてない…‼︎



WiFi中継機

TP-Link WiFi 無線LAN 中継器 RE450

引越し先の部屋が親機が家の奥にあり、玄関近くまで届かないのが解消してよかった。


ディスプレイ

LG ゲーミング モニター ディスプレイ 27UL500-W

4K最高。サイバーマンデー最高。



スーツ

FABRIC TOKYO オーダースーツ

fabric-tokyo.com

ファブリック東京というセミオーダーメイド?のスーツを作るメーカーで実際に採寸してもらって作ったのが良かった。スッキリとした明るい店舗で、採寸だけ店舗に足を運べば、後のほとんどはネットで完結できるのがイマドキだった。 普段スーツを着ない分 新しくスーツを買う際にはちゃんと身体にフィットスーツを買いたいと思ったけど、正解だった。



包丁

藤次郎 プロ DPコバルト合金鋼割込 牛刀 180mm

買ったものではなくふるさと納税で届いたものだけど、これもよかった。やはり切れ味が良い気がする。それ以上に見た目が良い。


MA1

ライトMA-1 ペーパータッチ

alpha-usa.jp

軽くて薄く温かい。秋頃からずっと着ている。


(引っ越し)

買ったものじゃないけど、今年に入ってから引っ越して良かった。

家賃はびっくりするくらい跳ね上がったけど(以前が安すぎた)、通勤時間の短縮と通勤で消耗される度合いがグッと減った。

徒歩で会社行けるってほどではないけど、やっぱり近いは正義。


おわりに

おわりです。

Folium での Mapbox tile の使い方

これは何か?

  • folium で Mapbox をベースタイルに使いたい
  • 公式ドキュメントのチュートリアルどおりにやってもエラーが出る
  • API をマニュアルで設定してあげると表示される

folium のバージョン

import folium
print(f"folium version: {folium.__version__}")
folium version: 0.10.0

Mapbox の設定

API KEYを取得

アカウント登録すると API Key を取得可能。個人的な利用なら無料でできそう(詳しくは料金表を参照)

https://account.mapbox.com

MAPBOX_API_KEY = YOUR_API_KEY

Mapbox Attribution

ビルドインのタイルの場合は attribution が用意されているが、 folium でカスタムタイルを使用するときは適当な attribution を自分で付ける必要がある。

Mapbox の場合は以下を参照する。

https://docs.mapbox.com/help/how-mapbox-works/attribution/#static--print

MAPBOX_ATTR = "© <a href='https://www.mapbox.com/about/maps/'>Mapbox</a> © <a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a> <strong><a href='https://www.mapbox.com/map-feedback/' target='_blank'>Improve this map</a></strong>"

Mapbox を folium のタイルに利用する

こちらを参考に

https://python-visualization.github.io/folium/quickstart.html

OpenStreetMap

デフォルトのタイルは OpenStreetMap

DEFAULT_LOCATION = [35.6812362, 139.7671248]
DEFAULT_ZOOM_START = 10
m = folium.Map(location=DEFAULT_LOCATION, zoom_start=DEFAULT_ZOOM_START)
m

f:id:takaishikawa42:20191111072031p:plain
OpenStreetMap

Mapbox

記述されている通りに Mapbox をタイルに指定する

m = folium.Map(
    location=DEFAULT_LOCATION,
    zoom_start=DEFAULT_ZOOM_START,
    tiles="Mapbox",
    API_key=MAPBOX_API_KEY,
)
m

なぜだか以下のエラーが出る。API_key を渡しているはずだが...。

ValueError: You must pass an API key if using Cloudmade or non-default Mapbox tiles.

解決策

マニュアルでAPIを指定してあげる

以下の Issue を参考にして解決できた。

https://github.com/python-visualization/folium/issues/922

m = folium.Map(
    location=DEFAULT_LOCATION,
    zoom_start=DEFAULT_ZOOM_START,
    tiles=f"https://api.mapbox.com/v4/mapbox.streets/{{z}}/{{x}}/{{y}}.png?access_token={MAPBOX_API_KEY}",
    attr=MAPBOX_ATTR,
)
m

f:id:takaishikawa42:20191111072134p:plain
Mapbox

Mapbox API のバージョンが folium パッケージでは v3 が使われている一方、2015年以降に新規登録したユーザーは v4 しか使えないことに起因しているっぽい。そのようなことが書いてあった。

Issue で議論されていることや Mapbox API ドキュメントの内容を正確に理解できていないので根本的な解決法なのかは不明だけど、暫定的にはこの方法で対応できた。

Kaggle IEEEコンペ 上位解法まとめ

f:id:takaishikawa42:20191007061742p:plain

目次

IEEE コンペ

  • クレジットカード等の取引(Transaction)履歴のデータから、その取引が詐欺(Fraud)かどうかを検知するタスク
  • ホストによってあらかじめ特徴量エンジニアリングされた列が追加されているが、ほとんどすべての列が匿名化されている
  • コンペ序盤に匿名化された取引時間列から、学習データとテストデータの期間が半年ずつで時系列になっている

所感

上位陣に共通しているポイント

  • ユーザーIDを特定する
  • 学習データ中のユーザーIDとテストデータ中のユーザーIDの被り率は低いことを見つける
  • ユーザーIDはそのまま用いず、集計値に変換して用いる。ユーザーIDの特定に寄与するような特徴量も直接用いない
  • あるユーザーIDが一度 Fraud とされると、その後も Fraud とされるパターンを見つけて後処理を行う

異なる点

  • ユーザーIDの定義の仕方はチームによって異なっていたが、いずれももっともらしいユーザーIDを定義して特徴量生成に役立てていた。
  • コンペ終盤の伸び悩んでいた時期にユーザーIDを特定することでスコアを一気に押し伸ばすことができたというチームもあれば、序盤から特定していたチームもいた。
  • ユーザーIDの特定によって予測値を特定できることを利用して Psuedo-labeling を行うチームが多く見られた
  • モデルの Blending は Lightgbm を基本として、Catboost を組み合わせるパターンが多かったように思われる。

1st Place Solution

チーム

  • 2人チーム

リンク

IEEE-CIS Fraud Detection | Kaggle

IEEE-CIS Fraud Detection | Kaggle

要点

時間特徴は重要でない

  • 時系列データだが時間は重要ではなく、むしろ未知の顧客の Fraud 検知問題であると捉えるべきである。
    • プライベートテストデータ中の68.2%が学習データに含まれていない顧客であるため。

f:id:takaishikawa42:20191007062622p:plain
各青線はユニークな顧客IDを表す。x軸は経過日数を表す。縦の黒線は学習の終了日、パブリックテストの開始日、プライベートテストの開始日を表す。

Magic Feature

  • 一度顧客が Fraud 判定されれば、そのアカウント全体が Fraud とされる(ホストのコメント)。そのため Fraud な顧客を予測する必要がある。ホストは120日後には Fraud のラベルが外されると言っているように思われたが、学習データ中にそのようなデータは殆ど見られなかった。
  • 取引2回以上のクレジットカードの96.9%が Fraud=0 で、2.9%が Fraud=1 で、0.2%が両方ある。

Fraud な顧客

  • 顧客IDは(ユニークな顧客を特定するのは) card1, addr1, D1 の組み合わせで決まる。D1 は顧客(クレジットカード)が取引を始めてから経過した日を表すので、 Transaction_Day - D1 によってクレジットカードが始まった日にちが分かる。同様に、 Transaction_Day - D3 によって直近の取引日が分かる

f:id:takaishikawa42:20191007063147p:plain

過学習の防止

  • 過学習を防止するために顧客UIDは用いず、また顧客を特定するの役立つようなD, V, ID 列は用いなかった。(学習データ・テストデータの Adversial Validation で Lightgbm の特徴重要度を見ることで分かる)
  • プライベートテストデータの68.2%が学習データにないので顧客IDを特徴量に使えなかったが、代わりに df.groupby("uid")[CM_columns].agg(["mean"]) によって集計値を取り、 uid を用いなかった。

詳細

Final Model

  • Catboost, Lightgbm, Xgboost の3モデルを組み合わせたもの。
  • 選択した Submission は CB, XGB の予測値を LGB に加えたもの、もう一つは単純な平均値を取ったアンサンブルモデルである。両方の Submission とも、単一の Transaction しか持たない顧客に関しては全予測値の平均値に置き換える後処理を行って、LBスコアで0.001上昇した。

UIDの見つけ方

EDA

Feature Selection

  • 以下の手法を用いながら特徴選択した
    • forward feature selection (using single or groups of features)
    • recursive feature elimination (using single or groups of features)
    • permutation importance
    • adversarial validation
    • correlation analysis
    • time consistency
    • client consistency
    • train/test distribution analysis
  • time consistency とは一つの特徴量あるいは特徴量のグループを用いたシングルモデルで学習データの最初の一ヶ月を学習し、学習データの最後の一ヶ月を予測した。
    • これによって特徴量自体が時間に対して一貫性があるかどうかを評価できる。
    • 5%の特徴量がモデルの精度を下げた。これは現在において存在するが将来においては存在しないパターンが、いくつかの特徴量には存在することを意味する。

Validation Strategy

  • 様々な Validation 戦略を試した。
    • 最初の4ヶ月を学習、1ヶ月をスキップして、最終月を検証データとする
    • 最初の2ヶ月を学習、2ヶ月をスキップして、最後の2ヶ月を検証データとする
    • 最初の1ヶ月を学習、4ヶ月をスキップして、最終月を検証データとする
    • month を group として GroupKFold で CV を行った
    • また UID を使って既知・未知の顧客をどの程度分類できるモデルかどうかも分析した

2nd Place Solution

チーム

  • 5人チーム

リンク

IEEE-CIS Fraud Detection | Kaggle

要点

Data Cleaning

  • 生の特徴量と Frequency Encoding で学習データとテストデータの分布を見て、全ての特徴量をスクリーニングした。 card1 の例を以下に示す。(左が生データ、右がエンコーディング済みデータ。赤が学習データ、青がテストデータ。左のx軸は card1 、左上のy軸は頻度、左下のy軸はログ変換したターゲットの平均で0は学習の平均。右は、x軸は card1 の頻度をログ変換したもの)

f:id:takaishikawa42:20191007063218p:plain

  • card1 の値の多くはテストデータにのみ現れることが確認できるので、これをそのまま用いると Private LB で大幅にスコアを落とすことになる。そのため Frequency Encoding した値を用いた。

CV

month を使って以下のように fold を切った。(初月を0とする)

0 | 2 3 4 5 6
0 1 | 3 4 5 6
0 1 2 | 4 5 6
0 1 2 3 | 5 6

この方法で特徴量、ハイパーパラメータチューニング、特徴量選択を評価した。

Feature Engineering

  • 全ての特徴量に対して Frequency Encoding を行ったうえで、上記の CV 方法で Lightgbm を用いて Permutation Importance を行った。ただし、上記4モデルの全てでスコアが改善しなかった特徴量のみ保持することにした(Permutation Importance ではスコアが上昇する=意味のない特徴量のため)
  • uid は以下のようにして特定した
data['uid1']` =  (data.day - data.D1).astype(str) +'_' + \
            data.P_emaildomain.astype(str)
data['uid2'] =  (data.card1.astype(str) +'_' + \
            data.addr1.astype(str) +'_' + \
            (data.day - data.D1).astype(str) +'_' + \
            data.P_emaildomain.astype(str))
  • ユーザーID は過学習を起こしうるので、直接特徴量として用いなかった(しかし Late Submission で CB に uid を加えたところベストスコアを記録した)。代わりに、 uid を用いて集計値を取った。
    • uid1, uid2 それぞれで groupby して、 transactionDTtransactionAmt の直近値との差(shift で算出)・平均値・標準偏差・中央値を算出する
  • その他、 uid を用いて Target Encoding した特徴量のうちいくつかはスコアの改善に寄与した。

Final Blend

  • Lightgbm のほか Xgboost, Catboost を用いた
  • 後処理として、ある時点で Fraud とされたカードは将来の時点でも Fraud とされることに注目して( Lag 変数を注目することで気づいた?)、予測値を馬が帰することで PublicLB で0.001上昇させた。

6th Place Solution

チーム

  • 5人チーム

リンク

IEEE-CIS Fraud Detection | Kaggle

要点

User IDs

CV

  • 月ごとに time-split すると、時間を経過するごとにユーザーIDのカバー率が低下していくので、学習データ中に含まれているユーザーIDがテストデータ中にある場合と、そうでない場合に分けてモデルを作ることに注力した

Features

  • あまり時間を割かなかった。多くの Count Encoding や Aggregated Features を用いた。また、UID ベースの集計値を用いた。

Models

  • LGB, Catboost, NN を月ごとに分割したCVベースで学習させた。Catboost では ID列 をカテゴリカル変数として含んで学習させた。UID を用いて確実に Fraud だと分かるデータを用いて Psedo-labeling を行って Lightgbm を学習させた。

Blending

  • 全体的な CV スコアを最適化するより、UID が被っているか否かで別々に最適化させた。
  • 基本的には生の確率値の平均値を取った。
  • ランクベースのブレンディングも行った(詳細イマイチ理解できず)。

Post-processing

  • UID ごとに予測値の平均値をとって、その値で予測値を置き換えた。

Final submission

    1. Blending モデルのうちスコアの高いものをさらに rank-average で Blending した
    1. month の代わりに UID ごとにCVを分割したモデルを用いた

9th Place Solution

チーム

  • 2人チーム

リンク

IEEE-CIS Fraud Detection | Kaggle

要点

ユーザーID

  • ユーザーIDの特定
  • "2017-11-30" + TransactionDT - D1 がユーザーを特定するのに最も有効な特徴量だった。同様のことを他の D 特徴量にも行った
  • ユーザー特定を確認するために V95, V97, V96 あるいは V126, V128, V127 が有効だった。前者はそれぞれユーザーごとの前日、前週、前月の取引回数であり、後者はそれぞれユーザーごとの前日、前週、前月の TransactionAmt の累積和である。
  • ユーザーID情報を特徴量として用いることもできたが、後処理で Psuedo−Labelling に用いた

CV

  • CV設定は学習データセットの最初の3ヶ月を学習データに、最後の2ヶ月を検証データにした(間の1ヶ月は不使用)。Public LB と良く相関したが、Pribate LB では Shake down する形になった。

Model

  • モデルは Lightgbm, Catboost, Xgboost を使用した。

11th Place Solution

チーム

  • 5人チーム

リンク

IEEE-CIS Fraud Detection | Kaggle

要点

モデル

  • 解法は Stacking ベースのもの。20個のモデルを Stacking することから始めて、どんどんモデルを追加していった。追加したモデルは以下の通り
    • Public Kernel の大半
    • LGBMモデル: カテゴリカル変数と Numerical 変数をPCAしたデータを用いた
    • XGBモデル: カテゴリカル変数を様々な方法(Target, Catboost, Weighted of evidence?...)で Encoding したものと生の Numerical 変数で学習させた
    • Lasso: 平均値・中央値で欠損値補完した Numerical 変数を用いた
    • Ridge: 平均値・中央値で欠損値補完した Numerical 変数を用いた
    • LGBM/Lasso: Count Encoding した
    • LGBM/Lasso: Count Encoding + One-hot Encoding
    • Gaussian Naive Bayes: Count Encoding
    • Multinomial Naive Bayes: Count Encoding
    • Univariate LR: スピアマンの相関係数でターゲットとの相関が高い V 特徴量を選択して用いた
    • LGBM/XGB: カテゴリカル変数と、コルモゴロフ・ミルノフ検定で選択した いくつかの Numerical 変数
    • NFFM/FFM: 生のカテゴリカル変数とコレを使った Numerical 特徴量
    • LibFM: このコードを用いたカテゴリカル特徴量
    • Catboost: カテゴリカル変数と Numerical 変数をすべてカテゴリカル変数としてモデルに与えた
    • NN: 異なるアーキテクチャで生のデータを学習させた(カテゴリカルデータを Embedding したのと、異なる欠損値補完手法を適用した Numerical 変数)
    • LGBM/XGB: カテゴリカル変数の二次、三次、四次の組み合わせ特徴量を用いた
    • Extra-Trees Classifeir: Numerical 変数を PCA したデータを用いた
    • KNN: 少数の Numerical 変数を用いた
  • 最終的に900くらいのモデルができたので、LightGBM を classifier に用いて RFE(Recursive Feature Elimination)によって120くらいモデルを選択したところ、LB で 0.9552 まで上がった。
  • その他、bruto force で集計値を取って2000個の特徴量を用いた XGB や、month で GroupKFold したバリデーションベースの FE を行ったデータで学習させたりした。
  • ユニークID として card1 + card6 + addr1 + addr2 + date.day - D1 を使用して FE したモデルでは 0.9649 までスコアを伸ばした。
  • ベストモデルは190このモデルから5回 XGB でバギングしたモデルで、CV: 0.9644, LB: 0.9650 だった。

13th Place Solution

チーム

  • ソロ

リンク

IEEE-CIS Fraud Detection | Kaggle

要点

1. Validation Scheme

  • 最初はシャッフル無しの 5fold CV を用いた。
  • コンペ後半ではシンプルな time-split で学習データの後半10万件を検証データとした。
    • 5fold CV は時間がかかりすぎるため.
    • 5fold CV で作られる重要な特徴量はリークを引き起こしたためでもある。

2. Reconstructing user_id

  • isFraud=1 のデータを注意深く見ると、非常に似ているデータの多くのクラスターが存在していることに気づいた。Home Credit でも同じようなことを経験していたため、bruto force アプローチで D 特徴量とカード情報から様々な user_ids を作成した。

3. Feature Engineering

  • user_ids を用いて様々な特徴量生成を行った。生成された特徴量の多くの中でも、過去のデータに対する Seq2Dec と Target Encoding が CV と LB の両方でかなりの改善が見られた。
  • Seq2Dec は個別のトランザクションエンコードするのに良い手法だと考えている。というのも、順序情報の損失を防ぐことができるのと、様々な長さの系列を扱うことができるためである。さらに、典型的な特徴量も足した(count, share?, user_id の系列の個数, user_id が最初に出現してからの経過日数, user_id が直近で現れた日からの経過日数等)

f:id:takaishikawa42:20191007063639p:plain
Seq2Dec

4. Modeling and Blending

  • モデルは Lightgbm と Regularized Greedy Forest(メインは Lightgbm)
  • Seed Averaging と Seq2Dec / Target Encoding の際に異なる n_folds でバギングしたモデルによってモデルの多様性を増やした

5. Result

  • 13th / 6831 teams
  • Public LB: 0.965086, Private LB: 0.941338

15th Place Solution

チーム

  • ソロ

リンク

IEEE-CIS Fraud Detection | Kaggle

要点

CV

時系列データのため unshuffled KFold を用いて予測した。学習データの最後の20%をホールドアウトしておき、最初の80%のデータをすべて使ってモデルを作成した。このCV設定に沿って、特徴量生成・パラメータチューニング・後処理を行った

モデル

  • Lightgbm のみを用いた
    • Catboost も NN も良い精度が出なかったため。
  • Final submission は 4~5 個の Lightgbm モデルの重み付きブレンディングしたもの
  • 特徴量選択はいずれの手法もCVスコアを下げたので行わなかった。

User ID

  • 異なる特徴量の組み合わせに基づくいくつかの User ID 候補を作成した。それから学習データ内で isFraudshift(1) した後に Target の平均を計算することで その質を検証した。具体例は以下の通り。
train['ReferenceDate'] = train.DayNumber - train.D1

key_cols = ['TransactionAmt','ProductCD','card1','card2','card3', 'card4','card5','card6','addr1','addr2','ReferenceDate','P_emaildomain']

train['key'] = train[key_cols].apply(tuple,1)

train['previous_isfraud'] =  train.groupby(['key'])['isFraud'].shift(1)

print(train.groupby('previous_isfraud')['isFraud'].mean())
>previous_isfraud
>0.0    0.000528
>1.0    0.991961
  • 学習データで得られた同一ユーザーID情報をテストデータに用いる後処理をすることでスコアが大幅に改善した。

Feature Engineering

  • Userid ベースの多くの集計特徴量を作成した
  • 予測値の平均をユーザーIDの全てのトランザクションに適用した。これもスコアの大幅な改善に役立った(高精度に予測できていたユーザーIDにのみ適用した)。

Blending

  • LGBM の dart メソッドが良かった
  • Blending による予測値はテストデータにのみ出現する新しいユーザーIDにのみ適用した

詳細

EDA

  • TransactionAmt や カード情報でソートしてエクセル上でデータセットを分析した。そのときに シフトした isFraud とターゲット値の isFraud に高い相関があることが見て取れた(エクセルでデータを見ることはかなり有用だった)
  • D1, D3 列が意味することに気づいた。それから data['DayNumber'] - data['D1'] によってReferenceDate 特徴量を作成することができた

特徴量生成

  • 異なる特徴量の組み合わせによって Userid を作成することと、シフトした isFraud 情報を後処理で用いることから始めた
  • ユーザーIDベースの集計特徴量を作成した

CV

  • モデルが同一ユーザーを探して分類するだけになっていたので、テストデータにしか存在しない新しいユーザーを予測できなかった。実際に学習データを8:2に時系列で分割してユーザーごとにAUCを見ると、既存ユーザーは0.9777になる一方、未知ユーザーの場合は0.900にしかならない。
  • 結果的に unshuffled KFold からユーザーIDベースの GroupKFold に変更した
  • 既存ユーザーの予測値はそのままにして、未知ユーザーの予測に対してのみブレンディングした予測値を適用した。