ふらふら Diary (仮)

興味のあることを適当に書いていく感じです

統計検定2級を受けて

先月、統計検定2級を受験し、合格したので記録も兼ねて勉強法などを書いておこうと思う。
勉強時間は30時間ほどで事前に勉強しないと受からない試験であった。

参考書籍

過去問

だいぶ前に受けようと思って購入していた本。これを3周解いた。1回目はとりあえず解いて、分からなかった箇所を後述する参考書で補って理解していった。約2割ほどは解説を読んでも難しいので、このあたりは諦めて、確実に得点できるように理解していった。
統計学入門
統計検定2級の範囲を超えてしまっているところもあるが解説がとても分かりやすい。過去問の解説だけでは理解が深められないときにその箇所を読むと理解が深められることが多かった。2級の範囲を超えていても理解しておくとよいところもたくさんあるので手元においておくのがおすすめ。
入門統計解析
上記の統計学入門では回帰分析の解説が少し物足りない。また、分散分析については記載がない。この2つの項目の理解を深めるためにこちらの入門統計解析を読んだ。統計学入門より易しいのでもしかしたら、試験対策であればこちらだけで十分かもしれない。統計学入門よりも全体的に丁寧に書いてあり、理解がしやすかった。

勉強法としては過去問を解く→間違えた箇所を統計学入門または入門統計解析を読むことにより、理解を深める。これを3回繰り返した。最初に解いたときは50%いくかいかないかだったが、解くことで点数を上げていき、合格圏内に入っていった。

試験を受けて

CBT方式での受験で、メモ用紙を渡され、計算などをし、PCで解答する流れ。
慣れない環境ということもあり、過去に解いていたよりも難しく感じた。正直、問題を解いていても合っているという確信が持てるものが少なかったため、不合格だろうなと思っていた。
試験時間が終了し、アンケートに答えると結果が表示される。「合格」と記載されていた。一安心、勉強した甲斐があったなと思った。

おわりに

最近は何でも調べることができ、何かを覚える・身につけるという経験が減っていたように思う。統計検定2級の勉強を経て、改めて学んだこともあったし、覚えないといけないということで何となく理解していたつもりがなくなったように思う。さまざまな手法を使いこなしていくには、こういった経験もとても大事なんだろうなと思った。

Python ファルダ内のファイルを一括処理

はじめに

一つのファイルに対して行う処理のコードは書けたけど、この処理をある特定のファルダ内のファイルすべてに対して行いたいというときに自分がよく使う方法をまとめた。

フォルダ構成

temp
├── dir
└── image0.png
└── image1.png
└── image2.png
├── dir2

一括処理

今回は簡単な画像処理を用いた例を書く。

import cv2
import pathlib

input_dir = './dir'
# 保存するファルダ
output_dir = './dir2'

# フォルダ内に保存されているファイル一覧
image_list = list(pathlib.Path(input_dir).glob('**/*.png'))

for i in range(len(image_list)):
    # 画像一枚をグレースケールで読み込み
    img = cv2.imread(str(image_list[i]), cv2.IMREAD_GRAYSCALE)
    # (100,100)にリサイズ
    img_resize = cv2.resize(img, (100,100))
    # 保存先
    output_path = output_dir + '/' + image_list[i].name
    # 画像保存 
    cv2.imwrite(output_path, img_resize)

以下でinput_dir内の「.png」が使用されるファイルパスのみ取得。

image_list = list(pathlib.Path(input_dir).glob('**/*.png'))

保存するファイル名は読み込んだファイル名と同一にしたいのでimage_list[i].nameとする。拡張子なしのものが欲しいときはimage_list[i].stemとする。

image_list[0] 

dir/image0.png

image_list[0].name 

image0.png

image_list[0].stem 

image0

おわりに

pathlibを用いてフォルダ内のファイルを一括処理する方法を記載した。pathlibは慣れればとても使いやすいものだと思う。

G検定2020#2を受けて

2020年7月4日(土)にG検定を受験しました。合格しました。

勉強時間は7時間弱。勉強しなくても知っていたことが多かったし、知らなくても試験中に調べることができるからという理由でほとんど勉強はしなかった。
それでも、一応まとめておこうと思う。

使用した参考書

推薦図書とされる1冊。合格した人がやっておくといいよと言っている本。
個人的には上記2冊ぐらいは読んでおいた方が良いのでは思う。受験する1年ほど前に読んでいた本。普通に興味があって読んだ。試験対策で読んだわけではなかったけど、3度のブームと冬の時代を繰り返したこととか上記2冊で出題された内容が書かれていて、読んでいてよかったと思った。実際、この本を読んでいたから試験対策をしなくてもよかったぐらい参考になった。

あとは、機械学習の基本的なところを理解していればいいかなと思う。

私の場合は問題集にある総仕上げ問題を何も見ずに解いて8割ほど合っていた(これで2時間ほど使った)ので、残り(5時間ほど)は電車の中で公式テキストと問題集を読んでいた感じだった。
正直いうと公式テキストを完璧に覚えても試験に合格するかは分からない。むしろ公式テキストに載っていない問題が多すぎて"過学習"しているのではと思ってしまうほど。試験に合格するためのだけの勉強をしても意味がないということをG検定を通して教えてくれているのかもしれない。
あと法律の問題がたくさん出題された。これも公式テキストには載っていなくて驚いた。文脈で分かってしまうものもあればよく分からなくて飛ばしてしまう問題もあった。逆に、公式テキストや問題集には人名がたくさん載っていたけど、本番では一人も出題されなくて残念だった。

おわりに

あくまでもG検定なので技術的なところはそんなに詳しく知らなくてもいいし、AIの歴史とかが出題されるので、試験をして何をみたいのだろうという感じ。
E資格は受験するまでが大変そうなので今のところ受験するつもりはない。
G検定を取得したところで特に何かすごいということもないと思うけど、取りました!とは言えるので興味があれば勉強するのもありかな。幸い、現時点では不合格者よりも合格者の方が多くて合格する確率も高いので。
まあでも公式テキストを完璧にすれば合格できるような試験にはして欲しいなと思う。

irisでロジスティック回帰

はじめに

ロジスティック回帰は、教師あり学習における分類を行うアルゴリズムである。scikit-learnライブラリでロジスティック回帰をする方法についてirisデータセットを使ってまとめてみた。

二値分類

ロジスティック回帰は、二値分類のアルゴリズムである。教師あり学習には以下の二つがある。

  • 回帰: 数値を予測する
  • 分類: カテゴリを予測する
アルゴリズム

基本的な考え方は線形回帰と同様で、データ \mathbf{x}に対して重みベクトル \mathbf{w}を掛けてバイアス w_0を加えた \mathbf{w}^T\mathbf{x}+w_0を計算する。ロジスティック回帰では確率を計算するため出力の範囲を0以上1以下に制限する必要がある。そのため、シグモイド関数を用いることで0から1の間の数値を返している。シグモイド関数は以下のようになる。
 \displaystyle \sigma (z) = \frac{1}{1+e^{-z}}
シグモイド関数を用いることで0から1の間の数値を返している。シグモイド関数をグラフに書くと次のような形状になる。

import math

# シグモイド関数
def sigmoid(x):
    y = 1 / (1 + math.e**(-x))
    return y

プロットしてみる。

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(-10, 10, 100)
y = sigmoid(x)
plt.plot(x, y)
plt.show()

f:id:jetarinA:20200802102455p:plain
このシグモイド関数を用いて、データ  \mathbf{x} が与えられたときに、そのラベルが  y である確率  p p = \sigma (\mathbf{w}^T \mathbf{x} + w_0) で計算する。二値分類であれば  pが0.5未満のとき0、0.5以上のとき1というように分類を行う。
学習では、ロジスティック損失を誤差関数として用い、これを最小化する。最小化では勾配降下法を用いる。

具体例

irisのデータセットでロジスティック回帰をしてみる。

import pandas as pd
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split

# irisデータセットの読み込み
data = load_iris()
X = pd.DataFrame(data.data, columns=data.feature_names)
y = pd.DataFrame(data.target, columns=['Species'])
df = pd.concat([X, y], axis=1)
df.head()
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) Species
0 5.1 3.5 1.4 0.2 0
1 4.9 3.0 1.4 0.2 0
2 4.7 3.2 1.3 0.2 0
0 4.6 3.1 1.5 0.2 0
0 5.0 3.6 1.4 0.2 0

irisのデータセットにはあやめの sepal length(がくの長さ)、sepal width(がくの幅)、petal length(花弁の長さ)、petal width(花弁の幅)に加え、それぞれの品種(setosa、versicolor、virginica)の情報がある。
今回は品種 setosa、versicolor のpatal length、petal widthのデータを使用してロジスティック回帰をしてみる。

# 品種 setosa、versicolorを抽出
df2 = df[(df['Species']==0) | (df['Species']==1)]
# 説明変数
X = df2.iloc[:, [2, 3]]
# 目的変数
y = df2.iloc[:, 4]
# 学習データと検証データを分割
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
model = LogisticRegression()
# 学習
model.fit(X_train, y_train)

切片と傾きを確認する。

# 切片
print(model.intercept_)
[-7.3018626]
# 傾き
print(model.coef_)
[[2.41092643, 1.01214654]]

ここで、決定境界をプロットしてみる。決定境界とは、分類結果が切り替わる境目のことで、ロジスティック回帰の場合、決定境界は確率を計算した結果がちょうど50%になる箇所を指す。
まず、以下の通り定義する。
 x_1: petal length (cm)
 x_2: petal width (cm)
バイアス:  w_0
重みベクトル \mathbf{w} = \left(\begin{array}{c}w_1 \\w_2 \end{array} \right)
データ \mathbf{x} = \left(\begin{array}{c}x_1 \\x_2 \end{array} \right)

データ \mathbf{x} が与えられたとき、そのラベルが  y である確率  p
 \displaystyle \sigma (z) = \frac {1}{1+e^{-z}}であるから
 p = \sigma(\mathbf{w}^T \mathbf{x} + w_0)
確率0.5のとき決定境界が求まるので
 \displaystyle \begin{eqnarray} \frac{1}{2} &=& \sigma (\mathbf{w}^T \mathbf{x} + w_0) \\ 
&=& \frac{1}{1 + e^{-(\mathbf{w}^T \mathbf{x} + w_0)}} \\
e^{-\mathbf{w}^T \mathbf{x} + w_0} &=& 1 \\
\mathbf{w}^T \mathbf{x} + w_0 &=& 0 \\
\left( \begin{array}{cc}w_1 & w_2 \end{array} \right) \left( \begin{array}{c} x_1 \\ x_2 \end{array} \right) + w_0 &=& 0 \\
w_1 x_1 + w_2 x_2 + w_0 &=& 0 \\
x_2 &=& -\frac{1}{w_2} (w_1 x_1 + w_0)
\end{eqnarray}
となり、これが平面の場合の決定境界となる。
Pythonでプロットしてみる。

w_0 = model.intercept_
w_1 = model.coef_[0, 0]
w_2 = model.coef_[0, 1]

x1 = np.linspace(0, 6, 30)
x2 = (-w_1 * x1 - w_0) / w_2
# プロット
plt.plot(x1, x2, color='gray')
plt.scatter(X.iloc[:, 0][y==0], X.iloc[:, 1][y==0], color='lightskyblue', label=data.target_names[0])
plt.scatter(X.iloc[:, 0][y==1], X.iloc[:, 1][y==1], color='sandybrown', label=data.target_names[1])
plt.ylim(-0.25, 2)
plt.xlabel(X.columns[0])
plt.ylabel(X.columns[1])
plt.legend()
plt.show()

f:id:jetarinA:20200807101705p:plain
今回はもともときれいに分かれていたので、決定境界もいい感じに引けていた。

特徴量の解釈について

ロジスティック回帰では各特徴量の係数を見ることができる。目的変数としてsetosaを0、versicolorを1として用いているとき、各特徴量は以下のようになる。

petal length (cm) petal width (cm)
2.41092643 1.01214654

先ほどのグラフの軸petal length、petal widthの重みは正の値だったため、どちらも大きくなるとversicolorである割合が大きくなることが分かる。
各特徴量の係数の符号を見ることで、正の影響を与えているのか負の影響を与えているのかが解釈できる。

おわりに

scikit-learnライブラリでロジスティック回帰をする方法についてirisデータセットを使って記述した。ロジスティック損失なども記述していけたらと思う。

多項式回帰に正則化を利用

はじめに

正則化とは過学習を防ぐための手法の一つであり、モデルの汎化性能の向上に役立つ。過学習は学習データに対してうまく予測できているが、未知のデータ(検証データ)に対しては、うまく汎化できないことを意味する。

過学習していく様子

まずは過学習のイメージを掴んでおく。今回は、前回の記事で示したモデルに、正規分布に従う乱数を加えて生成したデータを使用する。

# 前回の復習
import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.linear_model import LinearRegression

data = load_iris()
df = pd.DataFrame(data.data, columns=data.feature_names)
X = df.iloc[:, 0][data.target==0].values[:, np.newaxis]
y = df.iloc[:, 1][data.target==0].values
model = LinearRegression()
model.fit(X, y)

データの生成から行う。

# 上記のモデルに乱数を加える
# 学習データ
X_train = 10 * np.random.rand(20, 1)
y_train = model.predict(X_train) + np.random.randn(20)
# 検証データ
X_test = 10 * np.random.rand(12, 1)
y_test = model.predict(X_test) + np.random.randn(12)

プロットしてみる。

import matplotlib.pyplot as plt
plt.scatter(X_train, y_train, color='black', label='train')
plt.scatter(X_test, y_test, color='gray', label='test')
plt.legend()
plt.show()

f:id:jetarinA:20200515165147p:plain
このデータを多項式回帰でモデル化することを考える。線形回帰を1次式、2次式…と次数を増やしていくにつれてどのように変化していくかを調べる。同時に平均2乗誤差も見てみる(これを最小化するように学習しているため)。

from sklearn.preprocessing import PolynomialFeatures
from sklearn.metrics import mean_squared_error

y_quad_fit, train_error, test_error = [], [], []
# 次数を 1 から 6 まで増やす
for n in range(6):
    quadratic = PolynomialFeatures(degree=n+1)
    pr = LinearRegression()
    X_quad = quadratic.fit_transform(X_train)
    pr.fit(X_quad, y_train)
    X_fit = np.linspace(X_train.min(), X_train.max(), 10)[:, np.newaxis]
    y_quad_fit.append(pr.predict(quadratic.fit_transform(X_fit)))
    # 学習誤差
    train_error.append(mean_squared_error(pr.predict(X_quad), y_train))
    # 検証誤差
    test_error.append(mean_squared_error(pr.predict(quadratic.fit_transform(X_test)), y_test))

各次数 d での学習結果を可視化したものは以下に示す。

plt.scatter(X, y)
plt.plot(X_fit, y_quad_fit[0], color='firebrick', label='d=1')
plt.plot(X_fit, y_quad_fit[1], color='darkorange', label='d=2')
plt.plot(X_fit, y_quad_fit[2], color='gold', label='d=3')
plt.plot(X_fit, y_quad_fit[3], color='mediumseagreen', label='d=4')
plt.plot(X_fit, y_quad_fit[4], color='skyblue', label='d=5')
plt.plot(X_fit, y_quad_fit[5], color='mediumpurple', label='d=6')
plt.legend()
plt.show()

f:id:jetarinA:20200515165159p:plain
また、次数ごとの平均2乗誤差は以下の通り。学習誤差は学習データにおける平均2乗誤差、検証誤差は検証データにおける平均2乗誤差を表している。

次数 学習誤差 検証誤差
1 1.270780 0.931902
2 1.164353 0.433551
3 1.159767 0.503716
4 0.786159 7.871051
5 0.779189 11.882549
6 0.679223 92.420817

次数を大きくすると複雑な曲線になり、学習誤差がだんだんと小さくなる。学習誤差はd=6のときに最も小さくなるが、検証誤差は学習誤差に比べかなり大きくなっている。このモデルは、過学習しているため汎化性能の低いモデルとなっている。

正則化を利用

次に、多項式回帰に正則化を利用した結果を示す。今回はRidge回帰を使用した。

from sklearn.linear_model import Ridge

y_ridge_fit, train_ridge_error, test_ridge_error = [], [], []
# 次数を 1 から 6 まで増やす
for n in range(6):
    quadratic = PolynomialFeatures(degree=n+1)
    X_ridge = quadratic.fit_transform(X_train)
    ridge = Ridge(alpha=0.5)
    ridge.fit(X_ridge, y_train)
    X_fit = np.linspace(X_train.min(), X_train.max(), 10)[:, np.newaxis]
    y_ridge_fit.append(ridge.predict(quadratic.fit_transform(X_fit)))
    # 学習誤差
    train_ridge_error.append(mean_squared_error(ridge.predict(X_ridge), y_train))
    # 検証誤差
    test_ridge_error.append(mean_squared_error(ridge.predict(quadratic.fit_transform(X_test)), y_test))

各次数 d での学習結果を可視化したものは以下に示す。

plt.scatter(X_train, y_train, color='black', label='train')
plt.scatter(X_test, y_test, color='gray', label='test')
plt.plot(X_fit, y_ridge_fit[0], color='firebrick', label='d=1')
plt.plot(X_fit, y_ridge_fit[1], color='darkorange', label='d=2')
plt.plot(X_fit, y_ridge_fit[2], color='gold', label='d=3')
plt.plot(X_fit, y_ridge_fit[3], color='mediumseagreen', label='d=4')
plt.plot(X_fit, y_ridge_fit[4], color='skyblue', label='d=5')
plt.plot(X_fit, y_ridge_fit[5], color='mediumpurple', label='d=6')
plt.legend()
plt.show()

f:id:jetarinA:20200515165206p:plain
また、次数ごとの平均2乗誤差は以下の通り。

次数 学習誤差 検証誤差
1 1.270831 0.944950
2 1.164395 0.433568
3 1.160380 0.478892
4 0.848420 4.590636
5 0.793668 16.438639
6 0.784268 29.653928

正則化によりモデルの複雑さが抑えられ、誤差も次数が増えたときの検証誤差が抑えられており、過学習を防ぐことができていることが確認できる。
そうはいっても今回はデータが良くなかった可能性もあるが、全体的にあまりいい結果は出なかった。

アルゴリズム

簡単のため、2次式の線形回帰に正則化を利用した場合を考える。
 \displaystyle R(w) = \sum^n_{i=1} \{y_i-(w_0+w_1x_i+w_2x_i^2)\}^2+\alpha(w_1^2+w_2^2)
右辺の第1項は線形回帰の損失関数、第2項は罰則項と呼ばれるもので、学習パラメータの2乗和の形をとる。また、 \alpha (\geq0) は、正則化の強さをコントロールするパラメータである。
ここで、Ridge回帰の損失関数  R(w) を最小化することを考える。右辺第2項は学習パラメータの2乗和になっているため、学習パラメータの絶対値が大きいと、損失関数全体が大きくなる。 \alpha により学習パラメータを抑えることが可能となっている。
適切な  \alpha は検証誤差を見ながら調整するのが一般的である。

Ridge回帰とLasso回帰

Ridge回帰は、誤差関数の罰則項が学習パラメータの2乗和であるが、この罰則項を別の形にすることで、異なる性質の正則化を行うことができる。代表的な正則化手法として罰則項が学習パラメータの絶対値の和となっているLasso回帰がある。Lasso回帰の誤差関数は次のようになる。
 \displaystyle R(w) = \sum^n_{i=1}\{y_i-(w_0+w_1x_i+w_2x_i^2)\}+\alpha(|w_1|+|w_2|)
Lasso回帰は学習パラメータが0になりやすいという性質がある。この性質により特徴量選択を行うこともでき、モデルを解釈しやすくなるという利点もある。

おわりに

scikit-learnライブラリで多項式回帰に正則化を利用する方法についてまとめた。正則化を利用することでモデルの複雑さを緩和し、モデルの汎化性能の向上に役立つことを示した。正則化手法については、代表的なものとしてRidge回帰及びLasso回帰について簡単にまとめたが、今後は学習パラメータが計算されるときのイメージなどをまとめていきたい。

irisで線形回帰

はじめに

線形回帰は予測を行うアルゴリズムである。scikit-learnライブラリで線形回帰をする方法についてirisデータセットを使ってまとめてみた。

線形回帰

「ある説明変数が大きくなるにつれて、目的変数も大きく(または小さく)なっていく」関係性をモデル化する手法。
以下の表のように、x が大きくなると y x の定数倍大きくなる状態をいう。

  • 説明変数:  x
  • 目的変数:  y
 x  y
1 2
2 4
3 6
4 8
アルゴリズム

 y = w_0 + w_1 x で表す。この  w_0 w_1 を求めることで、 x が決まれば  y が決まる状態になる。
平均二乗誤差
適切な  w_0 w_1 を求めるために平均二乗誤差を利用することで定量的に判断をすることができる。
平均二乗誤差とは、 n 個のデータが存在するとき、以下の式で表す。
 \dfrac{\Sigma^{n}_{i=1}\{y_i - (w_0 + w_1x_i) \}^2 }{n}
目的変数  y_i と直線  w_0 + w_1x_i の差を 2 乗し、その平均を計算したものとなる。
線形回帰はさまざまな直線のうち、平均二乗誤差が最小となる  w_0 w_1を求める。
分からなかった点としては、最小二乗法だと  \Sigma^n_{i=1}\{y_i - (w_0 + w_1x_i)\}^2 から求めるけど、なぜ平均二乗誤差の最小化と記述されているのかというところ。

具体例

irisのデータセットで線形回帰をしてみる。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.linear_model import LinearRegression

# irisデータセットの読み込み
data = load_iris()
df = pd.DataFrame(data.data, columns=data.feature_names)
df.head()
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm)
0 5.1 3.5 1.4 0.2
1 4.9 3.0 1.4 0.2
2 4.7 3.2 1.3 0.2
3 4.6 3.1 1.5 0.2
4 5.0 3.6 1.4 0.2

irisのデータセットにはあやめの sepal length(がくの長さ)、sepal width(がくの幅)、petal length(花弁の長さ)、petal width(花弁の幅)に加え、それぞれの品種(setosa、versicolor、virginica)の情報がある。
今回は品種 setosa のsepal length、sepal widthのデータを使用して線形回帰をしてみる。

# setosa の sepal length と sepal width の散布図をプロット
plt.scatter(df.iloc[:, 0][data.target == 0], df.iloc[:, 1][data.target == 0])
plt.xlabel(df.columns[0])
plt.ylabel(df.columns[1])
plt.show()

f:id:jetarinA:20200506143603p:plain
setosaのsepal lengthとsepal widthの散布図
何となく線形になっていることが分かる。

model = LinearRegression()

# 説明変数
X = df.iloc[:, 0][data.target==0].values.reshape(-1, 1)
# 目的変数
y = df.iloc[:, 1][data.target==0].values

# 学習
model.fit(X, y)
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=False)

切片と傾きを確認する。

# 切片
print(model.intercept_)
-0.5694326730396493
# 傾き
print(model.coef_)
[0.7985283]

回帰直線を引き確認する。

plt.scatter(X, y)
plt.plot(X, model.predict(X), color='red')
plt.xlabel(df.columns[0])
plt.ylabel(df.columns[1])
plt.show()

f:id:jetarinA:20200506145624p:plain
結果
ある程度当てはまっていること思われる。

おわりに

scikit-learnライブラリで線形回帰をする方法についてirisデータセットを使って記述した。決定係数や線形回帰がうまく当てはまらない例、重回帰については今後まとめていきたい。

Pythonでカレンダー表示

カレンダーを表示

>>> import calendar
>>> my_calendar = calendar.TextCalendar(firstweekday=6)
>>> my_calendar_string = my_calendar.formatmonth(2018, 11)
>>> print(my_calendar_string)

   November 2018
Su Mo Tu We Th Fr Sa
             1  2  3
 4  5  6  7  8  9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30

これで普通にカレンダー表示できたけど、GUIツールでは表示できなかった。
なので別の方法を検討。調べていくうちに、日をリストにすればいいのではと思った。

>>> my_calendar.monthdayscalendar(2018, 11)

[[0, 0, 0, 0, 1, 2, 3],
 [4, 5, 6, 7, 8, 9, 10],
 [11, 12, 13, 14, 15, 16, 17],
 [18, 19, 20, 21, 22, 23, 24],
 [25, 26, 27, 28, 29, 30, 0]]

日付が入らないところは0になるみたい。
日にちを一つずつ扱っていけばいい感じに表示できそう。