- 投稿日:2022-02-24T21:10:28+09:00
コンピュータとオセロ対戦45 ~最適化関数~
前回 今回の目標 勾配降下法以外の最適化関数も実装していきます。 ここから本編 本編に入る前に、前回、実際にテストとして作成した足し算人工知能について追記。 あれから精度を上げるべくいろいろ試したところ、誤差0のネットワークが作れてしまいました。 test.java import optimizer.*; import network.*; import layer.*; import nodes.activationFunction.*; import costFunction.*; import matrix.*; public class test { public static void main(String[] str){ Network net = new Network( 2, new Input(4, AF.RELU), new Output(1, AF.LINER) ); GradientDescent GD = new GradientDescent( net, new MeanSquaredError() ); Matrix X = new Matrix(new double[10][2]); Matrix T = new Matrix(new double[10][1]); for (int i = 0; i < X.row; i++){ X.matrix[i][0] = i * 0.1; X.matrix[i][1] = i * 0.2; T.matrix[i][0] = X.matrix[i][0] + X.matrix[i][1]; } MeanSquaredError f = new MeanSquaredError(); Matrix Y = GD.forward(X); System.out.println(f.calcurate(Y, T)); for (int i = 0; i < 30; i++){ GD.back(X, Y, T); Y = GD.forward(X); System.out.println(f.calcurate(Y, T)); } System.out.println(Matrix.hstack(Y, T)); for (int i = 0; i < X.row; i++){ X.matrix[i][0] = i * 0.15; X.matrix[i][1] = i * 0.12; T.matrix[i][0] = X.matrix[i][0] + X.matrix[i][1]; } Y = GD.forward(X); System.out.println("score: "); System.out.println(f.calcurate(Y, T)); System.out.println(Matrix.hstack(Y, T)); } } 実行結果はこちら。 [[2.0757 ]] [[0.0124 ]] [[0.0010 ]] [[0.0001 ]] [[0.0000 ]] ~略(この間ずっと0)~ [[0.0000 ]] [[0.0037 0.0000 ] [0.3029 0.3000 ] [0.6021 0.6000 ] [0.9012 0.9000 ] [1.2004 1.2000 ] [1.4996 1.5000 ] [1.7988 1.8000 ] [2.0979 2.1000 ] [2.3971 2.4000 ] [2.6963 2.7000 ]] score: [[0.0010 ]] [[0.0037 0.0000 ] [0.2673 0.2700 ] [0.5309 0.5400 ] [0.7945 0.8100 ] [1.0581 1.0800 ] [1.3217 1.3500 ] [1.5853 1.6200 ] [1.8488 1.8900 ] [2.1124 2.1600 ] [2.3760 2.4300 ]] 層の数やノード数をべらぼうに増やしてみたりもしたのですが、誤差0.7や0.5から先へ入ってくれませんでした。そして結局、二層合計ノード数5で平均二乗誤差を採用したところかなり高い精度で足し算してくれました。 学習履歴を見ればわかる通り、かなり早い段階で誤差0に到達していますね。確認データでも非常に高い精度です。 以上、本筋と関係ないですが前回のオマケでした。 修正点 Matrixクラスに以下のメソッドを追加。 Matrix.java /** * Stack matrices vertical. * @param matrices Matrices to stack. * These should not have more than two rows. * @return New Matrix instance stacked. */ public static Matrix vstack(Matrix ... matrices){ Matrix rtn = new Matrix(new double[matrices.length][matrices[0].col]); for (int i = 0; i < rtn.row; i++){ for (int j = 0; j < rtn.col; j++){ rtn.matrix[i][j] = matrices[i].matrix[0][j]; } } return rtn; } /** * Split a matrix vertically. * @param in Matrix to be split. * @param num Number of split. * @return Array of Matrix instance. */ public static Matrix[] vsplit(Matrix in, int num){ Matrix[] rtn = new Matrix[num]; int size = in.row / num; // 過不足なく分けきれることを確認 if (size * num != in.row){ System.out.println("vsplit error"); System.exit(-1); } for (int i = 0; i < num; i++){ rtn[i] = new Matrix(new double[size][in.col]); for (int j = 0; j < size; j++){ for (int k = 0; k < in.col; k++){ rtn[i].matrix[j][k] = in.matrix[i*size+j][k]; } } } return rtn; } /** * Sort a matrix vertically. * @param in Matrix to be sort. * @param order Order of sort. * @return Matrix instance. */ public static Matrix vsort(Matrix in, int[] order){ Matrix rtn = new Matrix(new double[order.length][in.col]); // order.lengthがin.rowより大きくても問題ない。 // 下のvsortメソッドも同様。 // 詳しくは後述。 for (int i = 0; i < order.length; i++){ for (int j = 0; j < in.col; j++){ rtn.matrix[i][j] = in.matrix[order[i]][j]; } } return rtn; } /** * Sort a matrix vertically. * @param in Matrix to be sort. * @param order Order of sort. * @return Matrix instance. */ public static Matrix vsort(Matrix in, ArrayList<Integer> order){ Matrix rtn = new Matrix(new double[order.size()][in.col]); for (int i = 0; i < order.size(); i++){ for (int j = 0; j < in.col; j++){ rtn.matrix[i][j] = in.matrix[order.get(i)][j]; } } return rtn; } /** * Calcrate sum. * @return Result of sum. */ public double sum(){ double sum = 0.; for (int i = 0; i < this.row; i++){ for (int j = 0; j < this.col; j++){ sum += this.matrix[i][j]; } } return sum; } /** * Calcrate sum. * @param in matrix to investigate. * @return Result of sum. */ public static double sum(Matrix in){ double sum = 0.; for (int i = 0; i < in.row; i++){ for (int j = 0; j < in.col; j++){ sum += in.matrix[i][j]; } } return sum; } Optimizer.java 最適化関数たちの親クラス。 まず、GradientDescentクラスに置いていたcalA、calW、forwardメソッドを移動してきました。 GradientDescent.java /** * Calcurate output of a layer. * @param nodes Nodes in the layer. * @return Output of the layer. */ public Matrix calA(Node[] nodes){ Matrix rtn = new Matrix(new double[nodes[0].a.row][nodes.length]); for (int i = 0; i < rtn.row; i++){ for (int j = 0; j < rtn.col; j++){ rtn.matrix[i][j] = nodes[j].a.matrix[i][0]; } } return Matrix.appendCol(rtn, 1.0); } /** * Get a matrix of weights related to the output of a node. * @param nodes Nodes of next layer. * @param num Number of the node. * @return Matrix instance. */ public Matrix calW(Node[] nodes, int num){ Matrix rtn = new Matrix(new double[nodes.length][1]); for (int i = 0; i < nodes.length; i++){ rtn.matrix[i][0] = nodes[i].w.matrix[num][0]; } return rtn; } /** * Doing forward propagation. * @param in input matrix. * @return Matrix instance of output. */ public Matrix forward(Matrix in){ return this.net.forward(in); } ミニバッチ学習のため、以下のメソッドを追加。 学習用データを分割して返します。 Optimizer.java /** * Make data for mini batch learning. * @param x Input data. * @param t Answer. * @param batchSize Number of batch size. * @param rand Random instance. * @return Splited input data and answer. */ public Matrix[][] makeMiniBatch(Matrix x, Matrix t, int batchSize, Random rand){ // 分ける数 int rtnSize = (int)(x.row / batchSize) + 1; int num, i; ArrayList<Integer> order = new ArrayList<Integer>(rtnSize); ArrayList<Integer> check = new ArrayList<Integer>(rtnSize); for (i = 0; i < x.row; i++){ check.add(i); } // 並べ替えの番号 for (i = 0; i < x.row; i++){ num = rand.nextInt(x.row - order.size()); order.add(check.get(num)); check.remove(num); } // バッチサイズできれいに割れなかった場合、データは重複するが追加 for (; i < rtnSize*batchSize; i++){ order.add(rand.nextInt(x.row)); } // 並べ替えの番号通りに並べ替え Matrix x_ = Matrix.vsort(x, order); Matrix t_ = Matrix.vsort(t, order); // バッチサイズごとに分割 Matrix[][] rtn = {Matrix.vsplit(x_, rtnSize), Matrix.vsplit(t_, rtnSize)}; return rtn; } ここでは、総データ量がバッチサイズで割り切れなかった場合、データの重複を許し水増ししています。実際にどうしているかは分かりませんが、楽に実装できるのでこうしました。 実際に使ってみると、こんな感じ。 プログラムと実行結果を載せます。 test.java import optimizer.*; import network.*; import layer.*; import nodes.activationFunction.*; import costFunction.*; import matrix.*; import java.util.*; public class test { public static void main(String[] str){ Network net = new Network( 2, new Input(4, AF.RELU), // new Dense(10, AF.RELU), // new Dense(5, AF.RELU), new Output(1, AF.LINER) ); GradientDescent GD = new GradientDescent( net, new MeanSquaredError() ); Matrix X = new Matrix(new double[10][2]); Matrix T = new Matrix(new double[10][1]); for (int i = 0; i < X.row; i++){ X.matrix[i][0] = i * 0.1; X.matrix[i][1] = i * 0.2; T.matrix[i][0] = X.matrix[i][0] + X.matrix[i][1]; } // 入力データ(無加工) System.out.println(X); // 正解データ(無加工) System.out.println(T); System.out.println("----------------"); // さっき作ったメソッド Matrix a[][] = GD.makeMiniBatch(X, T, 3, new Random()); // 分割した入力データと正解データ出力 for (int i = 0; i < a[0].length; i++){ System.out.println(a[0][i]); System.out.println(a[1][i]); } } } [[0.0000 0.0000 ] [0.1000 0.2000 ] [0.2000 0.4000 ] [0.3000 0.6000 ] [0.4000 0.8000 ] [0.5000 1.0000 ] [0.6000 1.2000 ] [0.7000 1.4000 ] [0.8000 1.6000 ] [0.9000 1.8000 ]] [[0.0000 ] [0.3000 ] [0.6000 ] [0.9000 ] [1.2000 ] [1.5000 ] [1.8000 ] [2.1000 ] [2.4000 ] [2.7000 ]] ---------------- [[0.7000 1.4000 ] [0.0000 0.0000 ] [0.2000 0.4000 ]] [[2.1000 ] [0.0000 ] [0.6000 ]] [[0.4000 0.8000 ] [0.8000 1.6000 ] [0.9000 1.8000 ]] [[1.2000 ] [2.4000 ] [2.7000 ]] [[0.1000 0.2000 ] [0.6000 1.2000 ] [0.5000 1.0000 ]] [[0.3000 ] [1.8000 ] [1.5000 ]] [[0.3000 0.6000 ] [0.4000 0.8000 ] [0.7000 1.4000 ]] [[0.9000 ] [1.2000 ] [2.1000 ]] 入力データが分割出来ていて、足し算すると正解と同じになっていることが分かります。 GradientDescent 勾配降下法。前回作成したものです。 $$ w_{new} = w_{old} - \eta \frac{\partial E}{\partial w_{old}} $$ fitメソッド追加 kerasの真似をしてfitメソッドを追加しました。 GradientDescent.java /** * Run learning. * @param x Input layer. * @param t Answer. * @param nEpoch Number of epoch. * @return Output of this network. */ public Matrix fit(Matrix x, Matrix t, int nEpoch){ Matrix y = this.forward(x); for (int i = 0; i < nEpoch; i++){ System.out.printf("Epoch %d/%d\n", i+1, nEpoch); this.back(x, y, t); y = this.forward(x); System.out.printf("loss: %.4f\n", this.cFunc.calcurate(y, t).matrix[0][0]); } return y; } /** * Run learning. * @param x Input layer. * @param t Answer. * @param nEpoch Number of epoch. * @param valX Input layer for validation. * @param valT Answer for validation. * @return Output of this network. */ public Matrix fit(Matrix x, Matrix t, int nEpoch, Matrix valX, Matrix valT){ Matrix y = this.forward(x); Matrix valY; for (int i = 0; i < nEpoch; i++){ System.out.printf("Epoch %d/%d\n", i+1, nEpoch); this.back(x, y, t); valY = this.forward(valX); y = this.forward(x); System.out.printf( "loss: %.4f - valLoss: %.4f\n", this.cFunc.calcurate(y, t).matrix[0][0], this.cFunc.calcurate(valY, valT).matrix[0][0] ); } return y; } 以下のプログラムで実際に動かしてみます。 ネットワークの設定は最初のオマケと全く同じで、今度は掛け算をやってみます。 test.java import optimizer.*; import network.*; import layer.*; import nodes.activationFunction.*; import costFunction.*; import matrix.*; public class test { public static void main(String[] str){ Network net = new Network( 2, new Input(4, AF.RELU), new Output(1, AF.LINER) ); GradientDescent Opt = new GradientDescent( net, new MeanSquaredError() ); Matrix X = new Matrix(new double[10][2]); Matrix T = new Matrix(new double[10][1]); for (int i = 0; i < X.row; i++){ X.matrix[i][0] = i * 0.1; X.matrix[i][1] = i * 0.2; T.matrix[i][0] = X.matrix[i][0] * X.matrix[i][1]; } Matrix valX = new Matrix(new double[10][2]); Matrix valT = new Matrix(new double[10][1]); for (int i = 0; i < valX.row; i++){ valX.matrix[i][0] = i * 0.15; valX.matrix[i][1] = i * 0.1; valT.matrix[i][0] = valX.matrix[i][0] * valX.matrix[i][1]; } Opt.fit(X, T, 5, valX, valT); } } 実行結果。 Epoch 1/5 loss: 0.0408 - valLoss: 0.0374 Epoch 2/5 loss: 0.0302 - valLoss: 0.0299 Epoch 3/5 loss: 0.0307 - valLoss: 0.0304 Epoch 4/5 loss: 0.0308 - valLoss: 0.0304 Epoch 5/5 loss: 0.0308 - valLoss: 0.0304 足し算と比べると非常に低い精度となりました。 SGD.java 確率的勾配降下法。式自体は勾配降下法と全く同じですが、1エポック内で複数回パラメータを更新するという違いがあります。 $$ w_{new} = w_{old} - \eta \frac{\partial E}{\partial w_{old}} $$ 特筆すべきことも特にないので全文一気に載せます。 SGD.java package optimizer; import java.util.Random; import network.*; import costFunction.*; import matrix.*; import layer.*; import nodes.*; /** * Class for Stochastic Gradient Descent. */ public class SGD extends Optimizer{ Random rand; /** * Constructor for this class. */ public SGD(){ ; } /** * Constructor for this class. * @param net Network to which optimization is applied. * @param f Cost function in this net. */ public SGD(Network net, CostFunction f){ this.net = net; this.cFunc = f; rand = new Random(0); } /** * Constructor for this class. * @param net Network to which optimization is applied. * @param f Cost function in this net. * @param eta Learning rate. */ public SGD(Network net, CostFunction f, double eta){ this.net = net; this.cFunc = f; this.eta = eta; rand = new Random(0); } /** * Constructor for this class. * @param net Network to which optimization is applied. * @param f Cost function in this net. * @param seed Seed of random. */ public SGD(Network net, CostFunction f, int seed){ this.net = net; this.cFunc = f; rand = new Random(seed); } /** * Constructor for this class. * @param net Network to which optimization is applied. * @param f Cost function in this net. * @param eta Learning rate. * @param seed Seed of random. */ public SGD(Network net, CostFunction f, double eta, int seed){ this.net = net; this.cFunc = f; this.eta = eta; rand = new Random(seed); } /** * Run learning. * @param x Input layer. * @param t Answer. * @param nEpoch Number of epoch. * @param batchSize Size of batch. * @return Output of this network. */ public Matrix fit(Matrix x, Matrix t, int nEpoch, int batchSize){ Matrix[][] xt = this.makeMiniBatch(x, t, batchSize, rand); Matrix[] xs = xt[0]; Matrix[] ts = xt[1]; Matrix y = ts[0].clone(); int backNum = (int)(x.row / batchSize) + 1; for (int i = 0; i < nEpoch; i++){ System.out.printf("Epoch %d/%d\n", i+1, nEpoch); for (int j = 0; j < backNum; j++){ y = this.forward(xs[j]); this.back(xs[j], y, ts[j]); System.out.printf("\rloss: %.4f", this.cFunc.calcurate(y, t).matrix[0][0]); } System.out.println(); } return y; } /** * Run learning. * @param x Input layer. * @param t Answer. * @param nEpoch Number of epoch. * @param batchSize Size of batch. * @param valX Input layer for validation. * @param valT Answer for validation. * @return Output of this network. */ public Matrix fit(Matrix x, Matrix t, int nEpoch, int batchSize, Matrix valX, Matrix valT){ Matrix[][] xt = this.makeMiniBatch(x, t, batchSize, rand); Matrix[] xs = xt[0]; Matrix[] ts = xt[1]; Matrix[][] valxt = this.makeMiniBatch(valX, valT, batchSize, rand); Matrix[] valxs = valxt[0]; Matrix[] valts = valxt[1]; Matrix y = ts[0].clone(); Matrix valY; int backNum = (int)(x.row / batchSize) + 1; for (int i = 0; i < nEpoch; i++){ System.out.printf("Epoch %d/%d\n", i+1, nEpoch); for (int j = 0; j < backNum; j++){ valY = this.forward(valxs[j]); y = this.forward(xs[j]); this.back(xs[j], y, ts[j]); System.out.printf( "\rloss: %.4f - valLoss: %.4f", this.cFunc.calcurate(y, ts[j]).matrix[0][0], this.cFunc.calcurate(valY, valts[j]).matrix[0][0] ); } System.out.println(); } return y; } /** * Doing back propagation. * @param x Input layer. * @param y Result of forward propagation. * @param t Answer. */ public void back(Matrix x, Matrix y, Matrix t){ // last layer Layer nowLayer = this.net.layers[this.net.layers_num-1]; Layer preLayer = this.net.layers[this.net.layers_num-2]; for (int i = 0; i < nowLayer.nodes.length; i++){ Node nowNode = nowLayer.nodes[i]; Matrix cal; cal = this.cFunc.differential(nowNode.a, t.getCol(i)); cal = Matrix.dot(cal.T(), nowNode.aFunc.differential(nowNode.x)); nowNode.delta = cal.matrix[0][0]; cal = Matrix.mult(this.calA(preLayer.nodes), nowNode.delta); cal.mult(-this.eta); nowNode.w.add(cal.meanCol().T()); } // middle layer and input layer for (int i = this.net.layers_num-2; i >= 0; i--){ Node[] nextNodes = this.net.layers[i+1].nodes; Node[] nowNodes = this.net.layers[i].nodes; Node[] preNodes; Matrix deltas = new Matrix(new double[1][nextNodes.length]); Matrix preA; if (i != 0){ // middle layer preNodes = this.net.layers[i-1].nodes; preA = this.calA(preNodes); }else{ // input layer preA = Matrix.appendCol(x, 1.0); } for (int j = 0; j < nextNodes.length; j++){ deltas.matrix[0][j] = nextNodes[j].delta; } for (int j = 0; j < nowNodes.length; j++){ Node nowNode = nowNodes[j]; Matrix cal; nowNode.delta = Matrix.dot(deltas, this.calW(nextNodes, j)).matrix[0][0] * nowNode.aFunc.differential(nowNode.x.meanCol()).matrix[0][0]; cal = Matrix.mult(preA.meanCol(), -this.eta*nowNode.delta); nowNode.w.add(cal.T()); } } } } GradientDescentで試したネットワークを、設定そのままでバッチ数2で動かしてみました。 結果はこちら。 Epoch 1/5 loss: 0.0424 - valLoss: 0.0216 Epoch 2/5 loss: 0.0000 - valLoss: 0.0005 Epoch 3/5 loss: 0.0038 - valLoss: 0.0045 Epoch 4/5 loss: 0.0054 - valLoss: 0.0065 Epoch 5/5 loss: 0.0054 - valLoss: 0.0073 一瞬誤差0になってるのが気になりますね。GDと比べ非常に精度の高い結果になりました。 長いので載せませんでしたが、GDで30エポックやっても誤差はあまり(というか全く)変わらなかったので、単純に更新回数が違うからという理由ではなさそうです。 MomentumSGD.java SGDに慣性項を追加したもの。ここで$\alpha$は慣性項のパラメータ。前回の更新量にかける倍数。 $$ w_{new} = w_{old} - \eta \frac{\partial E}{\partial w_{old}}+\alpha\Delta w_{old} $$ こちらも式通りにしただけで、特筆すべきことはないので全文一気に載せます。ただし、forwardメソッドは全く変えていないので省略しました。 MomentumSGD.java package optimizer; import java.util.Random; import java.util.ArrayList; import network.*; import costFunction.*; import matrix.*; import layer.*; import nodes.*; /** * Class for Stochastic Gradient Descent. */ public class MomentumSGD extends Optimizer{ Random rand; /** Value of momentum */ double alpha = 0.9; /** Amount of change in weight */ ArrayList<ArrayList<Matrix>> dw; /** * Constructor for this class. */ public MomentumSGD(){ ; } /** * Constructor for this class. * @param net Network to which optimization is applied. * @param f Cost function in this net. */ public MomentumSGD(Network net, CostFunction f){ this.net = net; this.cFunc = f; rand = new Random(0); this.setDw(); } /** * Constructor for this class. * @param net Network to which optimization is applied. * @param f Cost function in this net. * @param eta Learning rate. * @param alpha Value of momentum. */ public MomentumSGD(Network net, CostFunction f, double eta, double alpha){ this.net = net; this.cFunc = f; this.eta = eta; this.alpha = alpha; rand = new Random(0); this.setDw(); } /** * Constructor for this class. * @param net Network to which optimization is applied. * @param f Cost function in this net. * @param seed Seed of random. */ public MomentumSGD(Network net, CostFunction f, int seed){ this.net = net; this.cFunc = f; rand = new Random(seed); this.setDw(); } /** * Constructor for this class. * @param net Network to which optimization is applied. * @param f Cost function in this net. * @param eta Learning rate. * @param alpha Value of momentum. * @param seed Seed of random. */ public MomentumSGD(Network net, CostFunction f, double eta, double alpha, int seed){ this.net = net; this.cFunc = f; this.eta = eta; this.alpha = alpha; rand = new Random(seed); this.setDw(); } /** * Set dw field. */ private void setDw(){ this.dw = new ArrayList<ArrayList<Matrix>>(); for (int i = 0; i < this.net.layers_num; i++){ this.dw.add(new ArrayList<Matrix>()); for (int j = 0; j < this.net.layers[i].nodes_num; j++){ this.dw.get(i).add(this.net.layers[i].nodes[j].w.clone()); this.dw.get(i).get(j).fillNum(0.); } } } /** * Doing back propagation. * @param x Input layer. * @param y Result of forward propagation. * @param t Answer. */ public void back(Matrix x, Matrix y, Matrix t){ // last layer Layer nowLayer = this.net.layers[this.net.layers_num-1]; Layer preLayer = this.net.layers[this.net.layers_num-2]; ArrayList<Matrix> dw = this.dw.get(this.net.layers_num-1); for (int i = 0; i < nowLayer.nodes.length; i++){ Node nowNode = nowLayer.nodes[i]; Matrix cal; cal = this.cFunc.differential(nowNode.a, t.getCol(i)); cal = Matrix.dot(cal.T(), nowNode.aFunc.differential(nowNode.x)); nowNode.delta = cal.matrix[0][0]; cal = Matrix.mult(this.calA(preLayer.nodes), nowNode.delta); cal.mult(-this.eta); dw.get(i).mult(this.alpha); dw.get(i).add(cal.meanCol().T()); nowNode.w.add(dw.get(i)); } // middle layer and input layer for (int i = this.net.layers_num-2; i >= 0; i--){ Node[] nextNodes = this.net.layers[i+1].nodes; Node[] nowNodes = this.net.layers[i].nodes; Node[] preNodes; Matrix deltas = new Matrix(new double[1][nextNodes.length]); Matrix preA; if (i != 0){ // middle layer preNodes = this.net.layers[i-1].nodes; preA = this.calA(preNodes); }else{ // input layer preA = Matrix.appendCol(x, 1.0); } for (int j = 0; j < nextNodes.length; j++){ deltas.matrix[0][j] = nextNodes[j].delta; } dw = this.dw.get(i); for (int j = 0; j < nowNodes.length; j++){ Node nowNode = nowNodes[j]; Matrix cal; nowNode.delta = Matrix.dot(deltas, this.calW(nextNodes, j)).matrix[0][0] * nowNode.aFunc.differential(nowNode.x.meanCol()).matrix[0][0]; cal = Matrix.mult(preA.meanCol(), -this.eta*nowNode.delta); dw.get(j).mult(this.alpha); dw.get(j).add(cal.T()); nowNode.w.add(dw.get(j)); } } } } また、GDとSGDでやった学習をMomentumSGDでもやってみました。バッチサイズは2。 Epoch 1/5 loss: 0.2173 - valLoss: 0.1467 Epoch 2/5 loss: 0.0106 - valLoss: 0.0004 Epoch 3/5 loss: 0.0000 - valLoss: 0.0092 Epoch 4/5 loss: 0.0181 - valLoss: 0.0426 Epoch 5/5 loss: 0.0105 - valLoss: 0.0021 訓練データの誤差はSGDより大きいですが、確認データでの誤差は小さいですね。 AdaGrad.java 学習係数を自動で調整する。少しずつ小さくすることで、振動することなく解へ向かうことを目的とします。 $$ h_{new}=h_{old}+\left(\frac{\partial E}{\partial w_{old}}\right)^2 $$$$ w_{new} = w_{old} - \frac{\eta}{\sqrt{h_{new}}} \frac{\partial E}{\partial w_{old}} $$ backメソッドのみ載せます。 コンストラクタなどは今までと似たようなものなのです。 AdaGrad.java /** * Doing back propagation. * @param x Input layer. * @param y Result of forward propagation. * @param t Answer. */ public void back(Matrix x, Matrix y, Matrix t){ double sum = 0.; double eta = this.eta / Math.sqrt(this.h); // last layer Layer nowLayer = this.net.layers[this.net.layers_num-1]; Layer preLayer = this.net.layers[this.net.layers_num-2]; for (int i = 0; i < nowLayer.nodes.length; i++){ Node nowNode = nowLayer.nodes[i]; Matrix cal; cal = this.cFunc.differential(nowNode.a, t.getCol(i)); cal = Matrix.dot(cal.T(), nowNode.aFunc.differential(nowNode.x)); nowNode.delta = cal.matrix[0][0]; cal = Matrix.mult(this.calA(preLayer.nodes), nowNode.delta); cal = cal.meanCol(); sum += Matrix.sum(Matrix.pow(cal)); cal.mult(-eta); nowNode.w.add(cal.T()); } // middle layer and input layer for (int i = this.net.layers_num-2; i >= 0; i--){ Node[] nextNodes = this.net.layers[i+1].nodes; Node[] nowNodes = this.net.layers[i].nodes; Node[] preNodes; Matrix deltas = new Matrix(new double[1][nextNodes.length]); Matrix preA; if (i != 0){ // middle layer preNodes = this.net.layers[i-1].nodes; preA = this.calA(preNodes); }else{ // input layer preA = Matrix.appendCol(x, 1.0); } for (int j = 0; j < nextNodes.length; j++){ deltas.matrix[0][j] = nextNodes[j].delta; } for (int j = 0; j < nowNodes.length; j++){ Node nowNode = nowNodes[j]; Matrix cal; nowNode.delta = Matrix.dot(deltas, this.calW(nextNodes, j)).matrix[0][0] * nowNode.aFunc.differential(nowNode.x.meanCol()).matrix[0][0]; cal = Matrix.mult(preA.meanCol(), nowNode.delta); sum += Matrix.sum(Matrix.pow(cal)); cal.mult(-eta); nowNode.w.add(cal.T()); } } this.h += sum; } } 例の掛け算学習実行結果はこちら。バッチ数2、イータ初期値0.001、h初期値10e-8です。 Epoch 1/5 loss: 2473214.3959 - valLoss: 1357515.9110 Epoch 2/5 loss: 2471777.0569 - valLoss: 1356725.6043 Epoch 3/5 loss: 2470778.9335 - valLoss: 1356176.7903 Epoch 4/5 loss: 2469965.5666 - valLoss: 1355729.5627 Epoch 5/5 loss: 2469261.4202 - valLoss: 1355342.3890 誤差の大きさが半端じゃないです。 h初期値を色々変えてみた結果、0.0005に変更することで非常に高い精度が出せました。 Epoch 1/5 loss: 0.0016 - valLoss: 0.0001 Epoch 2/5 loss: 0.0014 - valLoss: 0.0002 Epoch 3/5 loss: 0.0012 - valLoss: 0.0002 Epoch 4/5 loss: 0.0011 - valLoss: 0.0003 Epoch 5/5 loss: 0.0010 - valLoss: 0.0003 訓練データ、確認データ共に今までで最も低い誤差です。 RMSprop.java AdaGradを改良したもの。ある程度過去の情報はあまり参考にしないアルゴリズムです。 $$ h_{new}=\alpha h_{old}+(1-\alpha)\left(\frac{\partial E}{\partial w_{old}}\right)^2 $$$$ w_{new} = w_{old} - \frac{\eta}{\sqrt{h_{new}}} \frac{\partial E}{\partial w_{old}} $$ backメソッドのみ載せます。といっても、AdaGradのものと最後の一行しか変わりません。 RMSprop.java /** * Doing back propagation. * @param x Input layer. * @param y Result of forward propagation. * @param t Answer. */ public void back(Matrix x, Matrix y, Matrix t){ double sum = 0.; double eta = this.eta / Math.sqrt(this.h); // last layer Layer nowLayer = this.net.layers[this.net.layers_num-1]; Layer preLayer = this.net.layers[this.net.layers_num-2]; for (int i = 0; i < nowLayer.nodes.length; i++){ Node nowNode = nowLayer.nodes[i]; Matrix cal; cal = this.cFunc.differential(nowNode.a, t.getCol(i)); cal = Matrix.dot(cal.T(), nowNode.aFunc.differential(nowNode.x)); nowNode.delta = cal.matrix[0][0]; cal = Matrix.mult(this.calA(preLayer.nodes), nowNode.delta); cal = cal.meanCol(); sum += Matrix.sum(Matrix.pow(cal)); cal.mult(-eta); nowNode.w.add(cal.T()); } // middle layer and input layer for (int i = this.net.layers_num-2; i >= 0; i--){ Node[] nextNodes = this.net.layers[i+1].nodes; Node[] nowNodes = this.net.layers[i].nodes; Node[] preNodes; Matrix deltas = new Matrix(new double[1][nextNodes.length]); Matrix preA; if (i != 0){ // middle layer preNodes = this.net.layers[i-1].nodes; preA = this.calA(preNodes); }else{ // input layer preA = Matrix.appendCol(x, 1.0); } for (int j = 0; j < nextNodes.length; j++){ deltas.matrix[0][j] = nextNodes[j].delta; } for (int j = 0; j < nowNodes.length; j++){ Node nowNode = nowNodes[j]; Matrix cal; nowNode.delta = Matrix.dot(deltas, this.calW(nextNodes, j)).matrix[0][0] * nowNode.aFunc.differential(nowNode.x.meanCol()).matrix[0][0]; cal = Matrix.mult(preA.meanCol(), nowNode.delta); sum += Matrix.sum(Matrix.pow(cal)); cal.mult(-eta); nowNode.w.add(cal.T()); } } this.h = this.alpha * this.h + (1 - this.alpha) * sum; } 学習率0.01、アルファ0.99、h初期値10e-8で掛け算したら以下のようになりました。 Epoch 1/5 loss: 9264281289.1895 - valLoss: 5405078184.33163 Epoch 2/5 loss: 9261064856.0862 - valLoss: 5403223274.55897 Epoch 3/5 loss: 9258833049.5323 - valLoss: 5401935750.16145 Epoch 4/5 loss: 9256995415.2350 - valLoss: 5400875516.32297 Epoch 5/5 loss: 9255384580.6089 - valLoss: 5399946089.83136 すごい誤差です。 学習率0.01、アルファ0.99、h初期値0.00005でかなり学習できました。 Epoch 1/5 loss: 0.1589 - valLoss: 0.0898 Epoch 2/5 loss: 0.0586 - valLoss: 0.0326 Epoch 3/5 loss: 0.0072 - valLoss: 0.0039 Epoch 4/5 loss: 0.0041 - valLoss: 0.0032 Epoch 5/5 loss: 0.0016 - valLoss: 0.0031 Adam.java 現在、最も広く使われているものです。 $$ m_{new}=\beta_1m_{old}+(1-\beta_1)\frac{\partial E}{\partial w_{old}} $$$$ v_{new}=\beta_2v_{old}+(1-\beta_2)\left(\frac{\partial E}{\partial w_{old}}\right)^2 $$$$ \tilde{m}=\frac{m_{new}}{1-\beta_1} $$$$ \tilde{v}=\frac{v_{new}}{1-\beta_2} $$$$ w_{new}=w_{old}-\eta\frac{\tilde{m}}{\sqrt{\tilde{v}}} $$ Adam.java /** * Doing back propagation. * @param x Input layer. * @param y Result of forward propagation. * @param t Answer. */ public void back(Matrix x, Matrix y, Matrix t){ double sum = 0.; double v = 1 / Math.sqrt(this.v / (1-this.beta2)); // last layer Layer nowLayer = this.net.layers[this.net.layers_num-1]; Layer preLayer = this.net.layers[this.net.layers_num-2]; ArrayList<Matrix> m = this.m.get(this.net.layers_num-1); for (int i = 0; i < nowLayer.nodes.length; i++){ Node nowNode = nowLayer.nodes[i]; Matrix cal; cal = this.cFunc.differential(nowNode.a, t.getCol(i)); cal = Matrix.dot(cal.T(), nowNode.aFunc.differential(nowNode.x)); nowNode.delta = cal.matrix[0][0]; cal = Matrix.mult(this.calA(preLayer.nodes), nowNode.delta); cal = cal.meanCol(); sum += Matrix.sum(Matrix.pow(cal)); m.get(i).mult(this.beta1); m.get(i).add(Matrix.mult(cal.T(), (1-this.beta1))); nowNode.w.add(Matrix.mult(m.get(i), -this.eta*v/(1-this.beta1))); } // middle layer and input layer for (int i = this.net.layers_num-2; i >= 0; i--){ Node[] nextNodes = this.net.layers[i+1].nodes; Node[] nowNodes = this.net.layers[i].nodes; Node[] preNodes; Matrix deltas = new Matrix(new double[1][nextNodes.length]); Matrix preA; if (i != 0){ // middle layer preNodes = this.net.layers[i-1].nodes; preA = this.calA(preNodes); }else{ // input layer preA = Matrix.appendCol(x, 1.0); } for (int j = 0; j < nextNodes.length; j++){ deltas.matrix[0][j] = nextNodes[j].delta; } m = this.m.get(i); for (int j = 0; j < nowNodes.length; j++){ Node nowNode = nowNodes[j]; Matrix cal; nowNode.delta = Matrix.dot(deltas, this.calW(nextNodes, j)).matrix[0][0] * nowNode.aFunc.differential(nowNode.x.meanCol()).matrix[0][0]; cal = Matrix.mult(preA.meanCol(), nowNode.delta); sum += Matrix.sum(Matrix.pow(cal)); m.get(j).mult(this.beta1); m.get(j).add(Matrix.mult(cal.T(), (1-this.beta1))); nowNode.w.add(Matrix.mult(m.get(j), -this.eta*v/(1-this.beta1))); } } this.v = this.beta2 * this.v + (1 - this.beta2) * sum; } 掛け算機を実行するとこうなりました。 Epoch 1/5 loss: 0.8741 - valLoss: 0.5682 Epoch 2/5 loss: 0.8119 - valLoss: 0.5318 Epoch 3/5 loss: 0.7491 - valLoss: 0.4948 Epoch 4/5 loss: 0.6919 - valLoss: 0.4610 Epoch 5/5 loss: 0.6414 - valLoss: 0.4310 微妙。 学習率0.001、ベータ1が0.9、ベータ2が0.999、vが0.0000006で以下の結果になりました。 Epoch 1/5 loss: 0.0079 - valLoss: 0.0008 Epoch 2/5 loss: 0.0045 - valLoss: 0.0002 Epoch 3/5 loss: 0.0020 - valLoss: 0.0001 Epoch 4/5 loss: 0.0006 - valLoss: 0.0005 Epoch 5/5 loss: 0.0000 - valLoss: 0.0012 確認データの誤差はAdaGradに及びませんでしたが、訓練データの方は驚異の0でした。 次回は 最適化関数によって学習の推移が全く違って面白かったです。 次回はモデルの保存やロードができるようにしたいと思います。 参考文献 Optimizer : 深層学習における勾配法について - Qiita 【前編】Pytorchの様々な最適化手法(torch.optim.Optimizer)の更新過程や性能を比較検証してみた! – 株式会社ライトコード 【決定版】スーパーわかりやすい最適化アルゴリズム -損失関数からAdamとニュートン法- - Qiita
- 投稿日:2022-02-24T20:13:51+09:00
Discord Game SDKを使用してDiscordのRich Presenceを実装する
前置き 私は個人でゲームを開発している暇人です。 とある日、Discordで自分のゲームを遊んでいる人が見えたらうれしいなと思い、Discord RichPresenceを実装しようと思いました。 そこで、Discord-RPCというラッパーライブラリを使用しようと思いましたが、どうやらこのDiscord-RPCというプロジェクト、ラップされている側のDiscord-RPC(dll的な奴)がすでにメンテナンスされていないようです。 どうしようかなとDiscordのDeveloper Guideを眺めていたらDiscord Game SDKというものが存在することを知りました。 「絶対これはいける!!」 そう確信した私はDiscord-Game-SDK4Jというラッパーライブラリを探し当てたのでした... 本題 今回はDiscord-Game-SDK4Jというライブラリを使用させてもらいます。 サンプルを見る感じ、このライブラリはDiscord-Game-SDKの.dll .so .dylib(以下ネイティブライブラリ)に対応しているそうです。 すなわちWindows、Linux、MacOSということですね。 こちらのライブラリなんですが、ネイティブライブラリを手動で用意する必要があるようです。 自動でダウンロードして配置してくれるユティリティークラスを作ったので、今回はこれを使用します。 このソースはMITライセンスで公開されているのでどなたでも基本自由に使用することができますが、使用する際はクレジット表記の方、お願いします。 ネーミングは適当です。 java DiscordNativeFileUtil.java import java.io.File; import java.io.IOException; import java.net.URL; import java.nio.file.Files; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; public class DiscordNativeFileUtil { public static File getNativeFile() throws IOException { String ext; // ネイティブファイルの拡張子、OSによって変わる。 File dir = new File(System.getProperty("java.io.tmpdir"), "discord-game-sdk4j"); String os = System.getProperty("os.name").toLowerCase(); String arch = System.getProperty("os.arch").toLowerCase(); if (os.contains("windows")) ext = ".dll"; else if (os.contains("linux")) ext = ".so"; else if (os.contains("mac os")) ext = ".dylib"; else throw new RuntimeException("Not Supported OS Type: " + os); if ((new File(dir, "discord_game_sdk"+ext).exists())) { return new File(dir, "discord_game_sdk"+ext); } String nativePath = "lib/"+arch+"/discord_game_sdk"+ext; URL download = new URL("https://dl-game-sdk.discordapp.net/2.5.6/discord_game_sdk.zip"); ZipInputStream input = new ZipInputStream(download.openStream()); ZipEntry entry; while ((entry = input.getNextEntry()) != null) { if (entry.getName().equals(nativePath)) { if (!dir.mkdir()) { throw new IOException("Cannot create directory."); } File nativeLib = new File(dir, "discord_game_sdk"+ext); Files.copy(input, nativeLib.toPath()); input.close(); return nativeLib; } input.closeEntry(); } input.close(); return null; } } このコードはdiscord-game-sdk4j公式のサンプルを参考に作成しました。 一応ディスク上にキャッシュするようにしました(はずです)。 次にDiscord-Game-SDK4Jをビルドパスに追加します。 Mavenなら xml pom.xml <repository> <id>jitpack.io</id> <url>https://jitpack.io</url> </repository> xml pom.xml <dependency> <groupId>com.github.JnCrMx</groupId> <artifactId>discord-game-sdk4j</artifactId> <!-- 記事投稿時点での最新版は0.5.5です !--> <version>v0.5.5</version> </dependency> それ以外ならこのページを参考にしてください。 次にRich Presenceを実際に実装していきます。 まずDiscord Developer PortalでRich Presenceのためのアプリケーションを作成します。 New Applicationを押します。 名前を入れてCreateを押します。 左のタブでRich Presenceを押します。 Cover Imageを任意の画像に設定します。(これはよくわからないです) Add Imageを押して、mainとかその画像の役割を的確に表す素晴らしいネーミングを施した画像を追加しましょう。 完了すると上の画像みたいになります。 次にGeneral Informationタブに移動します。 Application IDをメモしておいてください(後で使います) 次はコード側です。 下のコードはラッパーのセットアップのためのコードです。 詳しくはコメントを見てもらえれば... java DiscordRP.java public class DiscordRP { private Core core = null; private boolean running = true; private static final DiscordRP INSTANCE = new DiscordRP(); private DiscordRP() {} private void Isetup() { File nativeLib = DiscordNativeFileUtil.getNativeFile(); if (nativeLib == null) { System.err.println("Discord Game SDKのダウンロードでエラーが発生しました。"); } Core.init(nativeLib); //ここでCoreにラップ対象のネイティブライブラリのファイルを注入する、でいいのかな? CreateParams params = new CreateParams(); params.setClientID(ここにApplicationIDを入れます); //ここはlong形で記述します 000000000000000000L みたいな感じ。 params.setFlags(CreateParams.getDefaultFlags()); core = new Core(params); new Thread("DiscordGameSDK4J Callback") { //Discord Game SDKのコールバック関数を呼び出し続けるスレッド @Override public void run() { while (running) { core.runCallbacks(); } } }.start(); } public static void setup() { INSTANCE.Isetup(); } public static Core getCore() { return INSTANCE.core; } public static void shutdown() { INSTANCE.running = false; } } 無理やりシングルトンにするためにめんどくさい記述方法をしてますが、普通のコードです。汚いのは許してください 使用するときは下のように呼び出します。 java example.java DiscordRP.setup(); //このコードはゲームの初期化処理で一回呼び出してください。 Core core = DiscordRP.getCore(); //これでセットアップされたCoreクラスがゲットできます。 Coreクラス!! ゲットだぜ!!! 次にActivity(Rich Presence)を作成するコードです。 java example.java Activity activity = new Activity(); activity.setDetails("Rich Presenceの一行目"); //一行目 activity.setState("Rich Presenceの二行目"); //二行目 activity.timestamps().setStart(Instant.now()); //Activityの開始時間を指定する 一番最初にInstantをインスタンス化しておいて、それを使いまわすと更新のたびに時間がリセットされることはなくなる。 activity.assets().setLargeImage("main"); //さっきDiscord Developer Portalでつけたイカした名前を指定する。 DiscordRP.getCore.activityManager().updateActivity(activity); これでRich Presenceを実装することができます。 追記 このままだと永遠にコールバック用のスレッドが裏で動き続けるので必ず下のコードを終了時に呼び出してください。 DiscordRP.shutdown();
- 投稿日:2022-02-24T11:34:26+09:00
blastengineを使ってJavaでメールを送信する
blastengineはシンプルに使える開発者向けメールサービスを提供しています。何かと面倒なメール配信をシンプルに、かつ確実に送信先に届くようになります。 今回はblastengineを使ってJavaでメール送信を行うまでの流れを解説します。 ユーザ登録する blastengineにユーザ登録します。管理画面に入るためのユーザID、パスワードが手に入るので、ログインします(ユーザIDは後で使います)。 送信元ドメインのSPFを設定する 送信元として利用するドメイン(自分で持っているもの)の設定をします。これは任意のドメイン管理サービスで設定できますが、TXTレコードに以下のSPFを追加します。 txt @ v=spf1 include:spf.besender.jp ~all API経由で配信する まずはAPIを使ってHTMLメールを配信する流れを紹介します。 APIキーを取得する ログイン後、管理画面の右上にある設定メニューに移動します。 そして設定の中で、APIキーを取得します。 クラスの準備 今回は jp.blastengine というパッケージで実装していきます。まずメール送信用のJSONオブジェクトの内容を格納するクラス BEMail を用意します。 public class BEMail { public String subject; public String encode; public String text_part; public String html_part; public BEMailAddress from; public String to; } さらにメールアドレスを入れる BEMailAddress を用意します。 public class BEMailAddress { public String name; public String email; } パッケージのインポート 必要なパッケージをインポートします。 // トークン生成用 import org.apache.commons.codec.digest.DigestUtils; import java.util.Base64; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; // HTTPリクエスト用 import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.impl.client.HttpClients; import org.apache.http.client.methods.HttpPost; import org.apache.http.util.EntityUtils; import org.apache.http.entity.StringEntity; import java.io.IOException; // JSON処理用 import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; 必要な変数を準備 トークンを生成するのに必要なユーザ名とAPIキーを準備します。 String username = "USERNAME"; String api_key = "APIKEY"; トークンを生成する APIリクエストするためのトークンを生成します。手順としては次のとおりです。 ユーザIDとAPIキーを連結する SHA256のハッシュを生成する ハッシュを全て小文字にする 3の文字列をBASE64エンコードする 実際のコードで言うと、次のようになります。 token がトークンです。 String digest = DigestUtils.sha256Hex(username + api_key); String token = new String(Base64.getEncoder().encode(digest.toLowerCase().getBytes())); メールオブジェクトの作成 BEMail のインスタンスを作成して、必要な情報をセットします。fromのemailやtoなど、メールアドレスは利用されるものに書き換えてください。 BEMail mail = new BEMail(); mail.subject = "テストメール"; mail.encode = "UTF-8"; mail.text_part = "テストメールの本文(テキスト)"; mail.html_part = "<h1>テストメールの本文(HTML)</h1>"; mail.to = "user@example.jp"; // Fromの作成 BEMailAddress fromAddress = new BEMailAddress(); fromAddress.name = "送信者サンプル"; fromAddress.email = "info@example.com"; // Fromをセット mail.from = fromAddress; そして mail をJSONにします。 ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(mail); StringEntity entity = new StringEntity(json, "UTF-8"); APIリクエストする では実際にメールを送信します。APIのエンドポイントは https://app.engn.jp/api/v1/deliveries/transaction になります。生成したトークンは Authorization ヘッダーに適用します。 // HttpPostを作成 HttpPost httpPost = new HttpPost("https://app.engn.jp/api/v1/deliveries/transaction"); // ヘッダーをセット httpPost.setHeader("Content-type", "application/json; charset=UTF-8"); httpPost.setHeader("Authorization", "Bearer " + token); // JSONデータをセット httpPost.setEntity(entity); // クライアントの作成 CloseableHttpClient client = HttpClients.createDefault(); // HTTPアクセス CloseableHttpResponse response = client.execute(httpPost); // 終了 client.close(); 結果 レスポンスを出力します。配信ID(delivery_id)は照会、変更および削除操作に必要です。 System.out.println(EntityUtils.toString(response.getEntity())); // { delivery_id: 9 } 全体のコード 今回のサンプルコードは次のようになります。実装時の参考にしてください。 // トークン生成用 import org.apache.commons.codec.digest.DigestUtils; import java.util.Base64; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; // HTTPリクエスト用 import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.impl.client.HttpClients; import org.apache.http.client.methods.HttpPost; import org.apache.http.util.EntityUtils; import org.apache.http.entity.StringEntity; import java.io.IOException; // JSON処理用 import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; String username = "USERNAME"; String api_key = "APIKEY"; BEMail mail = new BEMail(); mail.subject = "テストメール"; mail.encode = "UTF-8"; mail.text_part = "テストメールの本文(テキスト)"; mail.html_part = "<h1>テストメールの本文(HTML)</h1>"; BEMailAddress fromAddress = new BEMailAddress(); mail.to = "user@example.jp"; // Fromの作成 BEMailAddress fromAddress = new BEMailAddress(); fromAddress.name = "Admin"; fromAddress.email = "admin@example.com"; // Fromをセット mail.from = fromAddress; try { // トークンの生成 ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(mail); StringEntity entity = new StringEntity(json, "UTF-8"); String digest = DigestUtils.sha256Hex(username + api_key); String token = new String(Base64.getEncoder().encode(digest.toLowerCase().getBytes())); // HttpPostを作成 HttpPost httpPost = new HttpPost("https://app.engn.jp/api/v1/deliveries/transaction"); // ヘッダーをセット httpPost.setHeader("Content-type", "application/json; charset=UTF-8"); httpPost.setHeader("Authorization", "Bearer " + token); // JSONデータをセット httpPost.setEntity(entity); // クライアントの作成 CloseableHttpClient client = HttpClients.createDefault(); // HTTPアクセス CloseableHttpResponse response = client.execute(httpPost); // 終了 client.close(); } catch (JsonProcessingException e) { System.out.println(e); } catch (IOException e) { System.out.println(e); } SMTPリレーでの配信 次にSMTPリレーを使った方式です。 IPアドレスの登録 SMTPリレーを利用する場合、まず接続元サーバのIPアドレスを管理画面で登録する必要があります。 IPアドレスの設定ダイアログで、接続元サーバのIPアドレスを入力してください。 ライブラリのインポート Javaで一番手軽に使えるメール配信用パッケージ javax.mail を使います。まず必要なライブラリをインポートします。 import java.io.UnsupportedEncodingException; import java.util.Properties; import javax.mail.Message; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.Transport; import javax.mail.Authenticator; import javax.mail.PasswordAuthentication; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; プロパティのセット 次にSMTPサーバ、ポートを設定します。blastengineのSMTPサーバアドレスは smtp.engn.jp 固定になります。ポート番号は25/587/2525より選択してください。 String smtpHost = "smtp.engn.jp"; String smtpPort = "587"; Properties objPrp=new Properties(); objPrp.put("mail.smtp.host", smtpHost); objPrp.put("mail.host", smtpHost); objPrp.put("mail.smtp.port", smtpPort); セッションの確立 SMTPサーバとのセッションを確立します。 // メールセッションを確立 Session session = Session.getInstance(objPrp); MimeMessage msg = new MimeMessage(session); メールを送信する msg オブジェクトに必要なメール送信データを適用して、送信処理を実行します。 try { msg.setRecipients(Message.RecipientType.TO, "user@example.jp"); InternetAddress objFrm= new InternetAddress("info@example.com", "送信者サンプル"); msg.setFrom(objFrm); msg.setSubject("テストメール from SMTP", "UTF-8"); msg.setText("こんにちは", "UTF-8"); Transport.send(msg); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (MessagingException e) { e.printStackTrace(); } まとめ クラウドサービスではSMTPポートが塞がれている場合があるので、そうした時にはAPI経由を利用してください。SMTPリレーを使えば、より信頼性高く、安定した配信が実現できるでしょう。 APIとSMTPリレー、それぞれの要件に合わせて最適な方を選択してください。 エンジニア向けメール配信システム「ブラストエンジン」
- 投稿日:2022-02-24T07:35:46+09:00
【Java】Optionalクラスの使い方
Optionalクラスとは? Optionalクラスは、メソッドの処理結果を扱うためのクラスです。 このクラスを利用することで例外処理に関するコードを減らすことが出来ます。 Optionalクラスはファクトリメソッドです。なので、インスタンスを生成すためにはメソッドを使います。 Optionalクラスのインスタンス生成メソッド インスタンスを生成する方法として2つあります。 引数の値がnull入る可能性がない場合 ofメソッドを利用します。 Sample.java import java.util.Optional; public class Sample { public static void main(String[] args) { Optional <String> sample = Optional.of("こんにちは"); System.out.println(sample); } } Optional[こんにちは] 引数の値がnull入る可能性がある場合 ofNullableメソッドを利用します。 Sample.java import java.util.Optional; public class Sample { public static void main(String[] args) { Optional <String> sample = Optional.ofNullable(null); System.out.println(sample); } Optional.empty インスタンスが値を持っているか調べるメソッド isPresent 値を持っていればture,持っていなければfalseを返します。 値を持っているかを確認するメソッドです。 Sample.java import java.util.Optional; public class Sample { public static void main(String[] args) { Optional <String> sample = Optional.of("こんにちは"); if(sample.isPresent()) { System.out.println(sample.get()); } } } Optional[こんにちは] isEmpty 空かどうかを確認するメソッドです。 Sample.java import java.util.Optional; public class Sample { public static void main(String[] args) { Optional <String> sample = Optional.empty(); if(sample.isEmpty()) { System.out.println("空"); return; } System.out.println(sample.get()); } } 空 orElseメソッド 値を持っていれば、その値を返します。 値を持っていなければ、引数で指定したものを返します(固定値)。 Sample.java import java.util.Optional; public class Sample { public static void main(String[] args) { Optional <String> sample = Optional.empty(); System.out.println(sample.orElse("empty")); } } empty orElseGetメソッド 値を持っていれば、その値を返す。 値を持っていなければ、引数で指定したものを返します(ラムダ式で実行して、その結果を戻す)。 Sample.java import java.util.Optional; public class Sample { public static void main(String[] args) { Optional <String> sample = Optional.empty(); System.out.println(sample.orElseGet(() -> "empty")); } } empty orElseThrowメソッド 値が空の場合、意図的に例外を出すことができます。 Sample.java import java.util.Optional; public class Sample { public static void main(String[] args) throws Exception{ Optional <String> sample = Optional.empty(); System.out.println(sample.orElseThrow(() -> new Exception())); } } Exception in thread "main" java.lang.Exception at innerclass.Sample.lambda$0(Sample.java:8) at java.base/java.util.Optional.orElseThrow(Optional.java:408) at innerclass.Sample.main(Sample.java:8) インスタンスから値を取り出さずに処理をしたい場合 インスタンスに値がある場合 ifPresentメソッド Consumer型のラムダ式を受け取り、Optionalが値を持っていなければ、引数に渡して、ラムダ式を実行します。 Sample.java import java.util.Optional; public class Sample { public static void main(String[] args) throws Exception{ Optional<String> sample = Optional.of("test"); sample.ifPresent((str) -> System.out.println(str)); } } test インスタンスに値がない場合 ifPresentOrElseメソッド 値がある場合は第1引数で指定したラムダ式、値がない場合は第2引数で指定したラムダ式を実行します。 Sample.java import java.util.Optional; public class Sample { public static void main(String[] args) throws Exception{ Optional<String> sample = Optional.empty(); sample.ifPresentOrElse( (str) -> System.out.println(str), () -> System.out.println("empty")); }; } empty 参照を戻すメソッド getメソッド Optionalのインスタンスから値を戻します。 Sample.java import java.util.Optional; public class Sample { public static void main(String[] args) { Optional <String> sample = Optional.of("こんにちは"); System.out.println(sample.get()); } } こんにちは mapメソッド 処理結果を持ったOptionalのインスタンスを新たに生成し、その参照を戻します。 Sample.java import java.util.Optional; public class Sample { public static void main(String[] args) throws Exception{ Optional<String>sample = Optional.of("test"); Optional<String>result = sample.map(str -> str.toUpperCase()); System.out.println(sample.get()); System.out.println(result.get()); }; } test TEST flatMapメソッド 戻り値が新しいOptionalのインスタンスに入れずにそのまま戻します。 Sample.java import java.util.Optional; public class Sample { public static void main(String[] args) throws Exception{ Optional<String>a = Optional.of("test"); Optional<String>b = a.flatMap(str-> test(str)); System.out.println(b.get()); } private static Optional<String> test(String str){ return Optional.of(str.toUpperCase()); } } TEST getFormArrayメソッド 配列とインデックス番号を受け取り、配列からインデックス番号に一致する要素を戻します。 emptyメソッド arrayの中身がnullであれば空のOptialのインスタンスを生成して、参照を戻します。 ofNullableメソッド nullでなければindexに一致する配列の要素を持ったOptioanlのインスタンスを作成し参照を戻します。 Sample.java import java.util.Optional; public class Sample { public static void main(String[] args) { Optional<String> result =getFromArray(new String[]{"A","B",null},3); //結果がない場合 if(result.isEmpty()) { System.out.println("空"); return; } //インスタンスから値を受け取る System.out.println(result.get()); } private static <T> Optional<T> getFromArray(T[] array, int index){ if(array == null) { //arrayの中身がnullであれば空のOptialのインスタンスを生成して、参照を戻す return Optional.empty(); } try { //nullでなければindexに一致する配列の要素を持ったOptioanlのインスタンスを作成し参照を戻す return Optional.ofNullable(array[index]); }catch(ArrayIndexOutOfBoundsException e){ return Optional.empty(); } } }