20220223のJavaに関する記事は16件です。

「マイクロサービスパターン」の復習 6章

概要 Java読書会でせっかく勉強したのにつぎつぎと忘れていくので、印象に残ったところを記録していく 6章 イベントソーシングを使ったビジネスロジックの開発 モデリングした結果を反映したテーブルを作るのではなく、エンティティに対する生成・更新イベントをDBに記録していくという話 https://microservices.io/patterns/data/event-sourcing.html 従来のアプローチ やり方 なんらかモデリング 対応するクラスを作る 対応するテーブル作る ORマッパーでテーブルとクラスをつなぐ 欠点 履歴が残らない 後付で履歴つくるのは大変 生成・更新イベントも後付 イベントソーシング やり方 モデリングはする 対応するクラスは作る でも永続化するのは、生成・更新イベントのみ(永続化用のテーブルをイベントストアと呼ぶ) デシリアライズは、空オブジェクトにイベント履歴を繰り返し適用する 場合によっては、スナップショットテーブルを用意して、高速化する イベントの発行は、イベントテーブルのトランザクションログを見るなり、ポーリングするなりして発行する 利点 履歴ファーストなので、当然すべての履歴が残る イベント発行もネイティブに対応 欠点 イベントや対象となるオブジェクトのスキーマに追随するのが大変 従来手法とあまりに違いすぎるので習得難易度が高すぎる 履歴が残る分、削除が苦手 履歴しかないので、検索が困難⇒これは7章のCQRSで説明されている検索専用サービスを建てることで解決する 実現手段 基本的に作者の作成したEventuateフレームワークシリーズを使う Eventuate Local イベントストアの実装用ライブラリ イベントの永続化はMySQL、イベントのパブリッシュはKafkaベース スナップショット機能もあり Eventuage Client Eventuate Localを基盤として、サービスを実装するためのライブラリ Eventuate Tram Saga Sagaを実現するためのライブラリ Sagaは4章で説明されている。マイクロサービスだとサービスをまたがるところはトランザクションが効かないので、変わりにSagaと呼ばれるものを作る 感想 難しくてついていけません。 概要レベルなら分かるのですが。 Eventuateシリーズをよほど使いこなせないと実現できなさそう ピットフォールになる部分をよくしっているか トラブル時にどうデバッグするか? 6章、7章を合わせて読んでみると、マイクロサービスはイベントを発行するのが当たり前なのか? 世の中のマイクロサービス開発というのは本当にこの本に書いてあるようなことをやっているのか?それとも2、3種類程度のWebアプリがコンテナ化されて動いているだけでモノリシックアプリと大差ないのか?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Keycloakでmaster realmを無効化しちゃった場合の回復方法

この記事の概要 Keycloakの設定を誤って、管理者ログインできなくなるケースと、その回復方法の話。 起きたこと Keycloakで管理者ログインしようとしたら、以下のような画面が表示されログインできなくなってしまった。 原因に至った操作 よく分かってないまま、初期状態で作成されていた「Master」realmの「Enabled」をオフにしてしまった。 (Keycloak自身の管理に必要なのかよ!!知らないよ!!) 回復方法 デフォルトのH2データベースを使っているので、以下の手順でrealmを再度有効に設定する。 Keycloakサービスを停止 H2データベース管理アプリを起動 管理アプリ(ブラウザ)からKeycloakのデータベースを指定して開く realmの設定を更新 参考 [keycloak-user] I disabled "master" realm...now I'm stuck H2 DBの使い方
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DOMによるXML読み込みテストアプリ

package com.example.myapplication; import android.annotation.SuppressLint; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import java.io.IOException; import java.io.InputStream; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Locale; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; class XmlReader { private static final String TAG = XmlReader.class.getSimpleName(); // 日時フォーマット private final String FORMAT = "yyyy-MM-dd HHss.SSS"; /** * 各要素のタグ名称 / private final String TARGET = "target"; private final String TARGET_LIST = "targetList"; private final String TARGET_NUM = "targetNum"; public final String TARGET_NAME = "targetName"; private final String TARGET_ID = "targetId"; private final String EXPIRATION_DATA = "expirationDate"; /* * targetListの要素数 / private final int TARGET_LIST_NUM = 1; /* * targetIdの要素数 / private final int TARGET_ID_NUM = 1; /* * expirationDateの要素数 / private final int TARGET_EXPIRATION_DATE_NUM = 1; /* * targetNumの要素数の最大値 / public final int TARGET_NUM_MAX = 2; /* * targetNumの要素数の最小値 */ public final int TARGET_NUM_MIN = 1; /** * ファイル検索に使用するnameのリスト */ private List<Node> mNameElementList; /** * name要素取得 * * @return name要素のリスト */ public List<Node> getNameElementList() { Log.i(TAG, "getNameElementList()"); return mNameElementList; } /** * DOMによるxml読み込み処理 * * @param inputStream 読み込み対象XML * @return true:正常なXML, false:不正なXML * @throws SAXException * @throws IOException * @throws ParserConfigurationException */ public boolean domRead(InputStream inputStream) throws SAXException, IOException, ParserConfigurationException { Log.i(TAG, "domRead()"); // 初期化 mNameElementList = null; DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = factory.newDocumentBuilder(); Document document = documentBuilder.parse(inputStream); boolean result = false; // ルート要素取得 Element root = document.getDocumentElement(); if (null != root) { if (TARGET.equals(root.getTagName())) { Log.i(TAG, "root.getTagName() : " + root.getTagName()); //ルートからexpirationDateを取得 NodeList expirationDate = root.getElementsByTagName(EXPIRATION_DATA); if (checkExpirationDate(expirationDate)) { //ルートからtargetIdを取得 NodeList targetId = root.getElementsByTagName(TARGET_ID); if (checkTargetId(targetId)) { //ルートからtargetListを取得 NodeList targetList = root.getElementsByTagName(TARGET_LIST); if (checkTargetList(targetList)) { result = true; } else { Log.i(TAG, "Error : targetList is invalid"); } } else { Log.i(TAG, "Error : targetId is invalid"); } } else { Log.i(TAG, "Error : expirationDate is invalid"); } } else { Log.i(TAG, "Error : target tag name is invalid"); } } else { Log.i(TAG, "Error : Root is null"); } return result; } /** * targetListのチェック * * @param targetList チェック対象のNodeList * @return true:正常なtargetList, false:不正なtargetList */ private boolean checkTargetList(@Nullable NodeList targetList) { Log.i(TAG, "checkTargetList()"); boolean result = false; if (null != targetList) { if (TARGET_LIST_NUM == targetList.getLength()) { Element targetListElement = (Element) targetList.item(0); if (null != targetListElement) { NodeList targetListChildren = targetListElement.getChildNodes(); if (null != targetListChildren) { result = checkTargetListChildren(targetListChildren); } else { Log.i(TAG, "Error : targetList children is null"); } } else { Log.i(TAG, "Error : targetList is null"); } } else { Log.i(TAG, "Error : targetList is invalid configuration"); } } else { Log.i(TAG, "Error : targetList is null"); } Log.i(TAG, "checkTargetList() result : " + result); return result; } /** * targetListの子要素のチェック * * @param targetListChildren チェック対象のNodeList * @return true:正常なtargetListの子要素, false:不正なtargetListの子要素 */ private boolean checkTargetListChildren(@NonNull NodeList targetListChildren) { Log.i(TAG, "checkTargetListChildren()"); List<Node> targetNumList = new ArrayList<>(); List<Node> targetNameList = new ArrayList<>(); boolean result = false; for (int i = 0; i < targetListChildren.getLength(); i++) { Node node = targetListChildren.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { String nodeName = node.getNodeName(); Log.i(TAG, "nodeName : " + nodeName); if (TARGET_NUM.equals(nodeName)) { targetNumList.add(node); } else if (TARGET_NAME.equals(nodeName)) { targetNameList.add(node); } else { Log.i(TAG, "Error : element tag name is invalid"); break; } } } result = targetNumWithTargetNameVerification(targetNumList, targetNameList); Log.i(TAG, "checkTargetListChildren() result : " + result); return result; } /** * targetNumとtargetNameの整合性確認 * * @param targetNumList 確認対象のtargetNumList * @param targetNameList 確認対象のtargetNameList * @return true:整合OK, false:整合NG */ private boolean targetNumWithTargetNameVerification(@NonNull List<Node> targetNumList, @NonNull List<Node> targetNameList) { Log.i(TAG, "targetNumWithTargetNameVerification()"); boolean result = false; // targetNumの要素数確認 if (TARGET_ID_NUM == targetNumList.size()) { int targetNum = 0; try { // targetNumの要素取得 targetNum = Integer.parseInt(targetNumList.get(0).getTextContent()); } catch (NumberFormatException | DOMException e) { e.printStackTrace(); } // targetNumの整合性を確認 if (TARGET_NUM_MIN == targetNum || TARGET_NUM_MAX == targetNum) { // targetNumの値とtargetNameの数が一致しているか確認 if (targetNum == targetNameList.size()) { Log.i(TAG, "Success : targetElement is valid"); result = true; mNameElementList = targetNameList; } else { Log.i(TAG, "Error : The number of targetNum does not match the targetName"); } } else { Log.i(TAG, "Error : targetNum is invalid"); } } else { Log.i(TAG, "Error : targetNum is invalid"); } Log.i(TAG, "targetNumWithTargetNameVerification() result : " + result); return result; } /** * expirationDateのチェック * * @param expirationDate チェック対象のNodeList * @return true:正常なexpirationDate, false:不正なexpirationDate */ private boolean checkExpirationDate(@Nullable NodeList expirationDate) { Log.i(TAG, "checkExpirationDate()"); String expirationDateString = null; boolean result = false; if (null != expirationDate) { if (TARGET_EXPIRATION_DATE_NUM == expirationDate.getLength()) { Node node = expirationDate.item(0); if (node.getNodeType() == Node.ELEMENT_NODE) { Element element = (Element) node; if (EXPIRATION_DATA.equals(element.getNodeName())) { try { expirationDateString = element.getTextContent(); } catch (DOMException e) { e.printStackTrace(); } if (null != expirationDateString) { result = expirationDateVerification(expirationDateString); } else { Log.i(TAG, "Error : expirationDateString is null"); } } else { Log.i(TAG, "Error : element tag name is invalid"); } } else { Log.i(TAG, "Error : expirationDate element is invalid"); } } else { Log.i(TAG, "Error : expirationDate is invalid configuration"); } } else { Log.i(TAG, "Error : expirationDate is null"); } Log.i(TAG, "checkExpirationDate() result : " + result); return result; } /** * expirationDateの整合性確認 * * @param date チェック対象のexpirationDate * @return true:正常な要素, false:不正な要素 */ private boolean expirationDateVerification(@NonNull String date) { Log.i(TAG, "expirationDateVerification() date : " + date); // 現在時刻取得 LocalDateTime nowDate = LocalDateTime.now(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat(FORMAT, Locale.getDefault()); LocalDateTime expirationDate = null; try { // expirationDateのフォーマット変換 String dateString = simpleDateFormat.format(date); expirationDate = LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern(FORMAT)); } catch (DateTimeParseException | IllegalArgumentException e) { e.printStackTrace(); } // 現在時刻とexpirationDateの比較 Log.i(TAG, "nowDate : " + nowDate + ", expirationDate : " + expirationDate); boolean result = false; if (null != nowDate && null != expirationDate) { if (nowDate.isBefore(expirationDate)) { Log.i(TAG, "Success : Within the expiration date"); result = true; } else if (nowDate.isEqual(expirationDate)) { Log.i(TAG, "Success : Expiration date is just"); result = true; } else { Log.i(TAG, "Error : Expired"); } } else { Log.i(TAG, "Error : Verification failure"); } Log.i(TAG, "expirationDateVerification() result : " + result); return result; } /** * targetIdのチェック * * @param targetId チェック対象のNodeList * @return true:正常なtargetId, false:不正なtargetId */ private boolean checkTargetId(@Nullable NodeList targetId) { Log.i(TAG, "checkTargetId()"); String targetIdString = null; boolean result = false; if (null != targetId) { if (TARGET_ID_NUM == targetId.getLength()) { Node node = targetId.item(0); if (node.getNodeType() == Node.ELEMENT_NODE) { Element element = (Element) node; if (TARGET_ID.equals(element.getNodeName())) { try { targetIdString = element.getTextContent(); } catch (DOMException e) { e.printStackTrace(); } if (null != targetIdString) { Log.i(TAG, "Success : checkTargetId() targetIdString : " + targetIdString); result = true; } else { Log.i(TAG, "Error : targetIdString is null"); } } else { Log.i(TAG, "Error : element tag name is invalid"); } } else { Log.i(TAG, "Error : targetId element is invalid"); } } else { Log.i(TAG, "Error : targetId is invalid configuration"); } } else { Log.i(TAG, "Error : targetId is null"); } Log.i(TAG, "checkTargetId() result : " + result); return result; } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WebSphereでデータベースとのコネクションリークを検出する

概要 データベースとのコネクションリーク(接続の解放漏れ)が疑われる場合、問題の処理を特定するにはどうすればよいでしょうか。WebSphere上にデプロイしているアプリケーションの場合、管理コンソールからログレベルを上げることで、問題の処理を特定できる可能性があります。 手順 WebSphereの管理コンソールにて、 トラブルシューティング > ログおよびトレース > (サーバー名称) > ログ詳細レベルの変更 を選択する 初期状態では *=info が指定されているはず。末尾に :ConnLeakLogic=finest を追加する *=info: ConnLeakLogic=finest と入力したことを確認する 設定の反映のために、サーバーを再起動する 結果 $WAS_HOME/Profiles/node/logs/%Server_Name%/trace.log を確認します。 たとえば、コネクションリークによるコネクションプールの枯渇が発生している場合、下記のようなStackTraceが出力されます。既定では、10秒以上使用されているデータベース接続を出力するようです。 000000xx FreePool E J2CA0045E: リソース jdbc/xxx に対してメソッド createOrWaitForConnection を呼び出しているとき、接続が利用不能でした。 000000xx ConnLeakLogic 3 Dumping initial request stack traces 000000xx ConnLeakLogic 3 MCWrapper id xxxxxxxx Managed connection WSRdbManagedConnectionImpl@57055705 State:STATE_ACTIVE_INUSE Thread Id: 000000xx Thread Name: Thread-xx Handle count 1 in-use for 720000ms java.lang.Throwable at com.ibm.ejs.j2c.ConnectionManager.allocateConnection(ConnectionManager.java:899) at com.ibm.ws.rsadapter.jdbc.WSJdbcDataSource.getConnection(WSJdbcDataSource.java:668) at com.ibm.ws.rsadapter.jdbc.WSJdbcDataSource.getConnection(WSJdbcDataSource.java:635) at (以下略) 参考URL How to Locate Connection Leak in Websphere ログ・レベル設定 - IBM Documentation
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javaでreplace()メソッドを使う時にエラー: replaceに適切なメソッドが見つかりませんの対処法

Javaでコードを書いている時に、replace()メソッドを使う時に、タイトルのエラーで詰まってしまったので、備忘録としてまとめます。 以下のようにしてコードを書いていたのですが、 main.java class Main { public static void main(String args[]){ String str = "あいうえお"; str.charAt(0); String targetWord = String.valueOf(str.charAt(0)); System.out.println(str.replace(str.charAt(0), "か")); } } これで実行しようとしたらタイトルのエラーになってしまいました。 やろうとしたこと やろうとしたこととしては、 ・charAt()メソッドで取得した文字を、replaceメソッドで置換する というものです。 それをする時に、replaceの引数に直接charAt()メソッドで指定した文字列を System.out.println(str.replace(str.charAt(0), "か")); このようにして変数strに対して、頭文字をかに置換したかったので、第一引数に対象の文字列の頭文字を取得する処理を直接入れようとしました。 ただ、それをしてしまうと main.java:6: エラー: replaceに適切なメソッドが見つかりません(char,String) となってしまいます。 原因としては、charAt()メソッドで指定して取得するとそのデータ型はString型ではなくchar型になります。 しかし、replace()メソッドの引数に入れられるものはString型だけです。なのでchar型を入れるとエラーになってしまうわけです。 ではどうしたらいいのかというと、char型をStringに変換してしまえば解決します。 変換するには、StringオブジェクトのvalueOf()メソッドで指定します。 String targetWord = String.valueOf(str.charAt(0)); このように、valueOfメソッドでChar型からStringに変更します。 こうすることで、replace()メソッドに入れてもエラーにならないようになりました。 後は単純にreplaceメソッドの中に入れてあげれば大丈夫です。 String targetWord = String.valueOf(str.charAt(0)); System.out.println(str.replace(targetWord, "か")); 修正後のコード Main.java class Main { public static void main(String args[]){ String str = "あいうえお"; String targetWord = String.valueOf(str.charAt(0)); System.out.println(str.replace(targetWord, "か")); } } 以上になります。 なにかの参考になったら嬉しいです。 追記 Main.java public static void main(String[] args) { String str = "あいうえお"; System.out.println(str.replace(str.charAt(0), 'か')); } でSystem.out.println(str.replace(str.charAt(0), 'か'));の部分で、第荷引数をシングルコーテーションで囲うとchar型として指定することができます。 そうすれば、第一引数と第二引数で同じchar型に指定できます。 初めて知りました。 コメントでアドバイスをもらいました。 ありがとうございます!!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ObjectMapperについて

概要 jackson-databindに含まれるObjectMapperについて疑問に思うところがあったため、調査した記録を残す。 1. [readValue] recordにreadValueすることはできるか? できる。 recordクラスのフィールドは自ずとfinalになるため、もしかしたらデシリアライズできないのではないか?と思っていたが杞憂であった。 2. [readValue] jsonに想定しないpropertyが入っている場合にはどうなるか? UnrecognizedPropertyExceptionをthrowする。 もし想定外のpropertyが来たとしても無視したいならば、下記のようにObjectMapperのFAIL_ON_UNKNOWN_PROPERTIESをdisableしてやれば良い。 ObjectMapper disableFailOnUnknownPropertiesObjectMapper = new ObjectMapper() .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); 検証コード
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コンピュータとオセロ対戦44 ~勾配降下法と誤差逆伝播法~

前回 今回の目標 誤差逆伝播法を実装します。 この記事は、こちらの文献を参考にさせていただきました。 まずは勾配降下法。 ここから本編 誤差逆伝播法のまとめ 勉強不足だったので備忘録も兼ねて書いておきます。 下付き文字ですが、「pre」が前の層、「next」が次の層、「now」もしくは何も書いていなければ現在着目している層、「last」が最終層(出力層)を指します。下付き文字がコンマで区切られて二つある場合、二つ目はその層の何番目のノードのものであるかを指します。 また、「old」が更新前のパラメータ、「new」が更新後のパラメータです。 一般的に他の文献では層の数が上付き文字であらわされることが多いですが、累乗と区別がつかずわかりにくいのでここでは用いません。 勾配降下法 誤差を$\mathbf E$、重みを$\mathbf w$、学習率を$\eta$とすると、勾配降下法は $$ \mathbf w_{new} = \mathbf w_{old} - \eta \frac{\partial \mathbf E}{\partial \mathbf w_{old}} $$ と表せられます。$\mathbf w_{old}$はわかっていますので、$\mathbf w_{new}$を求めるうえで未知数となるのは右辺第二項の分数部分です。 ここで、連鎖律を用いることで、 $$ \frac{\partial \mathbf E}{\partial \mathbf w_{old}} = \frac{\partial \mathbf E}{\partial \mathbf x}\frac{\partial \mathbf x}{\partial \mathbf w_{old}} $$ と式変形できます。ここで$\mathbf x$は、ノードでの線形変換後の値です。なぜそんな値を選んだのかというと、おそらく後の計算がうまくいくからだと思います。 そして、繰り返しになりますが$\mathbf x$は前の層の出力にそのノードの重みをかけたものですので、層の出力を$\mathbf a$とおくと $$ \frac{\partial \mathbf x_{now}}{\partial \mathbf w_{old}} = \mathbf a_{pre} $$ となります。$\mathbf a_{pre}$を$\mathbf w_{old}$で線形変換したものが$\mathbf x_{now}$ですから、上の式になるのは当然ですね。このあたり、どれがどの値なのか混乱したので気を付けてください。 これを連鎖律の式の右辺第二項に代入すると $$ \frac{\partial \mathbf E}{\partial \mathbf w_{old}} = \frac{\partial \mathbf E}{\partial \mathbf x_{now}}\mathbf a_{pre} $$ と変形できます。式を簡単にするために、 $$ \mathbf \delta = \frac{\partial \mathbf E}{\partial \mathbf x_{now}} $$ と置きます。すると連鎖律の式は以下のように、簡単に表記できます。 $$ \frac{\partial \mathbf E}{\partial \mathbf w_{old}} = \mathbf \delta \mathbf a_{pre} $$ これで、未知数は$\mathbf \delta$のみとなりました。 デルタを求める ここで、$\mathbf \delta$は最終層とそれ以外の層で求め方が変わります。 最終層 まず最終層での求め方を見ていきます。 $$ \mathbf \delta_{last} = \frac{\partial \mathbf E}{\partial \mathbf x_{last}} $$ 上に書いたのは$\mathbf \delta$の定義です。連鎖律から、 $$ \mathbf \delta_{last} = \frac{\partial \mathbf E}{\partial \mathbf a_{last}}\frac{\partial \mathbf a_{last}}{\partial \mathbf x_{last}} $$ がいえます。ここで$\mathbf a$は、ここでは最終層の出力です。ここで、右辺一つ目の分数を見てますと、 $$ \frac{\partial \mathbf E}{\partial \mathbf a_{last}} = \frac{\partial E(\mathbf a_{last})}{\partial \mathbf a_{last}} = E'(\mathbf a_{last}) $$ となります。ここで、$E()$は誤差関数です。平均二乗誤差とか、平均絶対誤差とかその辺です。誤差関数の引数は最終層の出力ですから、上式が成り立つのは当然です。 たとえば平均二乗誤差なら、正解を$\mathbf t$、データ数を$N$とおくと のようになります(なぜか$\LaTeX$のコンパイルが通らなかったので画像を貼りました)。 次に、右辺二つ目の分数を変形します。出力層の活性化関数を$f()$とおくと、 $$ \frac{\partial \mathbf a_{last}}{\partial \mathbf x_{last}} = \frac{\partial f(\mathbf x_{last})}{\partial \mathbf x_{last}} = f'(\mathbf x_{last}) $$ となります。この層の出力$\mathbf a_{last}$は、この層の線形変換後の値$\mathbf x_{last}$を活性化関数に投げたものですので、上の式が成り立ちます。繰り返しになりますが、$\mathbf x_{last}$は各ノードでの線形変換後、活性化関数にかける前の行列です。活性化関数はReLuとかsigmoidとかです。例えばReLuなら、 $$ \frac{\partial \mathbf a}{\partial \mathbf x} = ReLu'(\mathbf x) $$ とでも書くのでしょうか。 入力が0未満の時、 $$ \frac{\partial \mathbf a}{\partial \mathbf x} = 0 $$ 入力が0以上の時、 $$ \frac{\partial \mathbf a}{\partial \mathbf x} = 1 $$ のようになります。 式をまとめると、 $$ \mathbf \delta_{last} = E'(\mathbf a_{last})f'(\mathbf x_{last}) $$ となります。全て既知関数ですので、これで$\mathbf \delta_{last}$を求められます。$\mathbf \delta_{last}$を求められるということは、$\frac{\partial \mathbf E}{\partial \mathbf w_{old}}$が求められるということであり、$\frac{\partial \mathbf E}{\partial \mathbf w_{old}}$が求められるということは、冒頭で示した勾配降下法の式の中から未知数がなくなるということです。これで、最終層の重みが更新できることになります。 また、上式を見ればわかる通り、勾配降下法をプログラムで実現するなら誤差関数の微分関数と活性化関数の微分関数がそれぞれ必要になるということになります。さらに、各ノードの線形変換後の値や出力も保持する必要があります。 また、回帰であれば出力層はこの式一つだけですが、分類の場合はノードごとにこれがあることになります。$\mathbf a_{last}$は一つの層に一つしかないですが、$\mathbf x_{last}$はノードの数だけあります。 最終層以外 次に、最終層以外の$\mathbf \delta$について見ていきます。 $$ \mathbf \delta = \frac{\partial \mathbf E}{\partial \mathbf x_{now}} $$ 上に書いたのは$\mathbf \delta$の定義です。 次の層のノード数を$m$とし、連鎖律を用いると、 $$ \mathbf \delta = \frac{\partial \mathbf E}{\partial \mathbf x_{now}} = \sum\limits_{n=1}^m\frac{\partial \mathbf E}{\partial \mathbf x_{next, n}}\frac{\partial \mathbf x_{next, n}}{\partial \mathbf a_{now}}\frac{\partial \mathbf a_{now}}{\partial \mathbf x_{now}} $$ がいえます。ここで、$\mathbf a$及び$\mathbf x$は今までどおり、層の出力及びノード内での線形変換後の値です。 次の層のノードの値を合計する理由を説明します。現在全結合層を考えているため、このノードの重みは、巡り巡って次の層のすべてのノードに影響を与えるからです。では、なぜ足し算なのでしょうか。これには数学的な理由が絡んでいます。それを説明するために、まず以下の式を見てください。 $$ \frac{\partial \mathbf E}{\partial \mathbf x_{now}} = \frac{\partial \mathbf E}{\partial \mathbf x_{next}}\frac{\partial \mathbf x_{next}}{\partial \mathbf x_{now}} $$ これは、先ほど$\mathbf \delta$を変形した式と違い、連鎖律を一回だけ使ったようなものだと納得してください。ただし、厳密にいえば正しい式ではありません。なぜなら$\mathbf x_{next}$はノードごとに異なる値を持つため、ここでの$\mathbf E$は多変数関数となるからです。よって合成関数の微分が必要となり、シグマ記号が必要になるというわけです。 話を戻します。先ほど示した式、 $$ \mathbf \delta = \frac{\partial \mathbf E}{\partial \mathbf x_{now}} = \sum\limits_{n=1}^m\frac{\partial \mathbf E}{\partial \mathbf x_{next, n}}\frac{\partial \mathbf x_{next, n}}{\partial \mathbf a_{now}}\frac{\partial \mathbf a_{now}}{\partial \mathbf x_{now}} $$ はなんだか長いし複雑ですので、最終層の時と同様に簡単な値にまとめていきます。 ここで右辺一つ目の分数は、定義より、 $$ \frac{\partial \mathbf E}{\partial \mathbf x_{next, n}} = \mathbf \delta_{next, n} $$ が言えます。最終層の一つ手前の層なら最終層の値が入ります。最終層での$\mathbf \delta$の求め方はすでに導出していますので、連鎖的にすべての層の$\frac{\partial \mathbf E}{\partial \mathbf x_{next, n}}$がこれで求められます。 次に二つ目の分数を見ます。次の層の重みを$\mathbf w_{next, n}$とおくと、 $$ \frac{\partial \mathbf x_{next, n}}{\partial \mathbf a_{now}} = \mathbf w_{next, n} $$ がいえます。$\mathbf x_{next, n}$は$\mathbf a_{now}$を$\mathbf w_{next, n}$で線形変換した値ですので、上式が成り立ちます。当然$\mathbf w_{next, n}$は既知ですから、$\frac{\partial \mathbf x_{next, n}}{\partial \mathbf a_{now}}$もこれで求められます。 最後に三つ目の分数を見ます。 $$ \frac{\partial \mathbf a_{now}}{\partial \mathbf x_{now}} = f'(\mathbf x) $$ 最終層でもこの変形は登場しました。$\mathbf a_{now}$は$\mathbf x_{now}$を活性化関数に入れたものですので、当然上式は成り立ちます。そして活性化関数は当然既知ですから(自分で設定しますからね)、$\mathbf \delta$を求めるうえですべての未知数が消えたことになります。つまり、最終層以外では、 $$ \mathbf \delta = \sum\limits_{n=1}^{m}\mathbf \delta_{next, n} \mathbf w_{next, n}f'(\mathbf x) $$ となります。これをプログラムで実現するなら、各ノードは自分の$\mathbf \delta$を保持せねばならないと分かります。 まとめ これで最終層、そして最終層以外の$\mathbf \delta$が求められるようになりましたから、 $$ \frac{\partial \mathbf E}{\partial \mathbf w_{old}} = \mathbf \delta \mathbf a_{pre} $$ が求められるようになり、これを勾配降下法の式 $$ \mathbf w_{new} = \mathbf w_{old} - \eta \frac{\partial \mathbf E}{\partial \mathbf w_{old}} $$ に代入することで重みの更新ができるようになります。 実際に代入すると、最終層では $$ \mathbf w_{new} = \mathbf w_{old} - \eta E'(\mathbf a_{last})f'(\mathbf x_{last})\mathbf a_{pre} $$ 最終層以外では $$ \mathbf w_{new} = \mathbf w_{old} - \eta \sum\limits_{n=1}^{m}\mathbf \delta_{next, n} \mathbf w_{next, n}f'(\mathbf x_{now})\mathbf a_{pre} $$ となります。 現在のディレクトリ構成 「optimizer」を、今までずっと「optimzer」と間違えて書いていたので修正しました。 MyNet ├── costFunction // 損失関数 ├── layer // 層 ├── matrix // 行列 ├── network // ネットワーク ├── nodes // ノード │   └── activationFunction // 活性化関数 └── optimizer // 最適化(今回作成) 修正点 Networkクラスをいじりました。 まず、以下のフィールドを追加。 /** Number of input variables for this network. */ int input_num; それに伴い、コンストラクタも若干変更。「this.input_num = ~」の行を追加しました。 また、ノードのインスタンス化時に乱数のseed値をノードごとに変更するためコンストラクタにnumを追加しました。ノードのコンストラクタについては後述。 /** * Constructor for this class. * @param input_num number of input. * @param layers Each layers. */ public Network(int input_num, Layer ... layers){ for (int i = 0; i < layers.length; i++){ if (layers[i].nodes[0] != null){ System.out.println("Wrong constractor."); System.exit(-1); } } this.input_num = input_num; this.layers = new Layer[layers.length]; for (int i = 0; i < this.layers.length; i++){ this.layers[i] = layers[i].clone(); } int num = 0; for (int i = 0; i < this.layers.length; i++){ AF type = this.layers[i].AF; for (int j = 0; j < this.layers[i].nodes.length; j++){ this.layers[i].nodes[j] = new Node(input_num, type, num); num++; } input_num = this.layers[i].nodes_num; } this.layers_num = this.layers.length; } 後で使うため、空のコンストラクタを追加しました。事故を防ぐためprivateです。 /** * Constructor for this class. */ private Network(){ ; } cloneメソッドを追加。 層の中のノードも含め、全てのフィールドをインスタンス化せずに空のネットワークを作成し、その後に層、ノードの順にインスタンス化しています。 理由は、入力層以外の各層の入力数が前の層のノード数に依存するために、一度にすべてインスタンス化できないからです。正規のコンストラクタでは各層の入力数を指定せずに済むようにしています。詳しくは42 ~ネットワーク~を参照。 @Override public Network clone(){ Network rtn = new Network(); rtn.input_num = this.input_num; rtn.layers = new Layer[this.layers.length]; for (int i = 0; i < this.layers.length; i++){ rtn.layers[i] = this.layers[i].clone(); } int num = 0; for (int i = 0; i < this.layers.length; i++){ AF type = this.layers[i].AF; for (int j = 0; j < this.layers[i].nodes.length; j++){ this.layers[i].nodes[j] = new Node(this.input_num, type, num); num++; } this.input_num = this.layers[i].nodes_num; } rtn.layers_num = this.layers_num; return rtn; } ただ、作ったはいいんですがネットワーククラスのこれらの変更は結局使いませんでした。 今更ですが、活性化関数としてLinerを追加しました。 package nodes.activationFunction; /** * Liner function. */ public class Liner extends ActivationFunction { /** * Constructor for this class. * Nothing to do. */ public Liner(){ ; } @Override public String toString(){ return "Liner"; } } MatrixクラスのfillメソッドをfillNumに改名しました。 また、fillRandomメソッド、fillNextRnadomメソッドを新規で作成しました。 /** * Fill this matrix with a number. * @param num Number to fill. */ public void fillNum(double num){ for (int i = 0; i < this.row; i++){ for (int j = 0; j < this.col; j++){ this.matrix[i][j] = num; } } } /** * Fill this matrix with random numbers has range 0~1. */ public void fillNextRandom(){ Random rand = new Random(0); for (int i = 0; i < this.row; i++){ for (int j = 0; j < this.col; j++){ this.matrix[i][j] = rand.nextDouble(); } } } /** * Fill this matrix with random number has range min~max. * @param min Number of min for range. * @param max Number of max for range. */ public void fillRandom(double min, double max){ Random rand = new Random(0); for (int i = 0; i < this.row; i++){ for (int j = 0; j < this.col; j++){ this.matrix[i][j] = rand.nextDouble()*(max-min) + min; } } } /** * Fill this matrix with random numbers has range 0~1. * @param num Number of seed. */ public void fillNextRandom(int num){ Random rand = new Random(num); for (int i = 0; i < this.row; i++){ for (int j = 0; j < this.col; j++){ this.matrix[i][j] = rand.nextDouble(); } } } /** * Fill this matrix with random number has range min~max. * @param min Number of min for range. * @param max Number of max for range. * @param num Number of seed. */ public void fillRandom(double min, double max, int num){ Random rand = new Random(num); for (int i = 0; i < this.row; i++){ for (int j = 0; j < this.col; j++){ this.matrix[i][j] = rand.nextDouble()*(max-min) + min; } } } さらに、Matrixクラスに以下のメソッドを追加しました。 /** * Power of a matrix element. * @param matrix A Matrix instance. * @return Multiplying a matrix by itself. */ public static Matrix pow(Matrix matrix){ Matrix rtn = matrix.clone(); for (int i = 0; i < rtn.row; i++){ for (int j = 0; j < rtn.col; j++){ rtn.matrix[i][j] = Math.pow(rtn.matrix[i][j], 2); } } return rtn; } /** * Power of this matrix element. */ public void pow(){ for (int i = 0; i < this.row; i++){ for (int j = 0; j < this.col; j++){ this.matrix[i][j] = Math.pow(this.matrix[i][j], 2); } } } /** * Power of a matrix element. * @param matrix A Matrix instance. * @param num Number to a power. * @return Powered a matrix by itself. */ public static Matrix pow(Matrix matrix, int num){ Matrix rtn = matrix.clone(); for (int i = 0; i < rtn.row; i++){ for (int j = 0; j < rtn.col; j++){ rtn.matrix[i][j] = Math.pow(rtn.matrix[i][j], num); } } return rtn; } /** * Power of this matrix element. * @param num Number to a power. */ public void pow(int num){ for (int i = 0; i < this.row; i++){ for (int j = 0; j < this.col; j++){ this.matrix[i][j] = Math.pow(this.matrix[i][j], num); } } } /** * Get a single column matrix from a matrix. * @param in Matrix instance. * @param num Number of column that extract. * @return Extracted matrix. */ public static Matrix getCol(Matrix in, int num){ Matrix rtn = new Matrix(new double[in.row][1]); for (int i = 0; i < in.row; i++){ rtn.matrix[i][0] = in.matrix[i][num]; } return rtn; } /** * Get a single column matrix from this matrix. * @param num Number of column that extract. * @return Extracted matrix. */ public Matrix getCol(int num){ Matrix rtn = new Matrix(new double[this.row][1]); for (int i = 0; i < this.row; i++){ rtn.matrix[i][0] = this.matrix[i][num]; } return rtn; } /** * Get a single row matrix from a matrix. * @param in Matrix instance. * @param num Number of row that extract. * @return Extracted matrix. */ public static Matrix getRow(Matrix in, int num){ Matrix rtn = new Matrix(new double[1][in.col]); for (int i = 0; i < in.col; i++){ rtn.matrix[0][i] = in.matrix[num][i]; } return rtn; } /** * Get a single row matrix from this matrix. * @param num Number of row that extract. * @return Extracted matrix. */ public Matrix getRow(int num){ Matrix rtn = new Matrix(new double[1][this.col]); for (int i = 0; i < this.col; i++){ rtn.matrix[0][i] = this.matrix[num][i]; } return rtn; } multメソッドにミスがあったので修正しました。 /** * Multiply this matrix by a number. * @param num Number. */ public void mult(double num){ for (int i = 0; i < this.row; i++){ for (int j = 0; j < this.col; j++){ this.matrix[i][j] = this.matrix[i][j] * num; } } } Nodeクラスのコンストラクタで、重みの初期値を乱数にできるようにしました。 また、デルタなどを追加しました。 また、損失関数と区別するため、ノードクラス内の活性化関数の名前を「function」から「aFunc」に変更しました。 /** Valiable for back propagation. */ public Matrix delta; /** Matrix linear transformed. */ public Matrix x; /** Matrix of output from this node. */ public Matrix a; /** * Constructor for this class. * Number of inputs includes bias. * @param input_num Number of inputs. * @param type Type of activation function. * @exception System.exit The specified activation function * does not exist or misspecified. */ public Node(int input_num, AF type){ in += input_num; w = new Matrix(new double[in][1]); w.fillNum(0.5); // w.fillRandom(-1., 1.); switch (type) { case RELU: aFunc = new ReLu(); break; case SIGMOID: aFunc = new Sigmoid(); break; case TANH: aFunc = new Tanh(); break; case LINER: aFunc = new Liner(); break; default: System.out.println("ERROR: The specified activation function is wrong"); System.exit(-1); } } /** * Constructor for this class. * Number of inputs includes bias. * @param input_num Number of inputs. * @param type Type of activation function. * @param num Number of seed. * @exception System.exit The specified activation function * does not exist or misspecified. */ public Node(int input_num, AF type, int num){ in += input_num; w = new Matrix(new double[in][1]); // w.fillNum(0.5); w.fillRandom(-1., 1., num); switch (type) { case RELU: aFunc = new ReLu(); break; case SIGMOID: aFunc = new Sigmoid(); break; case TANH: aFunc = new Tanh(); break; case LINER: aFunc = new Liner(); break; default: System.out.println("ERROR: The specified activation function is wrong"); System.exit(-1); } } 活性化関数に微分を追加しました。線形変換後の値で微分しますので、$x$で微分します。 まず親クラスですが、親クラスのメソッドはLinerクラスでそのまま使おうと思うのでLinearの微分です。 $$ f(x) = x $$$$ f'(x) = 1 $$ ActivationFunction.java /** * Calcurate this activation function's differential. * @param matrix Matrix of input. * @return The result of differentiating this activation function. */ public Matrix differential(Matrix matrix){ Matrix rtn = matrix.clone(); rtn.fillNum(1.0); return rtn; } ReLu関数。こちらも、Qiitaの$\LaTeX$がなぜかコンパイル通らなかったので写真です。 ReLu.java /** * Calcurate this activation function's differential. * @param matrix Matrix of input. * @return The result of differentiating this activation function. */ public Matrix differential(Matrix matrix){ Matrix rtn = matrix.clone(); for (int i = 0; i < rtn.row; i++){ for (int j = 0; j < rtn.col; j++){ if (rtn.matrix[i][j] < 0){ rtn.matrix[i][j] = 0; }else{ rtn.matrix[i][j] = 1.0; } } } return rtn; } シグモイド関数。 $$ f(x) = \frac{1}{1+e^{-x}} $$$$ f'(x) = \left(1-\frac{1}{1+e^{-x}}\right)\frac{1}{1+e^{-x}} $$ Sigmoid.java /** * Calcurate this activation function's differential. * @param matrix Matrix of input. * @return The result of differentiating this activation function. */ public Matrix differential(Matrix matrix){ Matrix rtn = matrix.clone(); for (int i = 0; i < rtn.row; i++){ for (int j = 0; j < rtn.col; j++){ rtn.matrix[i][j] = (1 - 1/(1+Math.exp(-matrix.matrix[i][j]))) / (1 + Math.exp(-matrix.matrix[i][j])); } } return rtn; } ハイパボリックタンジェント。 $$ f(x) = \frac{e^{x}-e^{-x}}{e^{x}+e^{-x}} $$$$ f'(x) = 1-\tanh ^2(x) $$ Tanh.java /** * Calcurate this activation function's differential. * @param matrix Matrix of input. * @return The result of differentiating this activation function. */ public Matrix differential(Matrix matrix){ Matrix rtn = this.calcurate(matrix); rtn.pow(); rtn.mult(-1); return Matrix.add(rtn, 1.0); } 損失関数にも微分を追加します。そのノードの出力で微分しますので、説明で使用した記号で言うと$a$で微分します。プログラム内だと$y$と記述しています。 親クラスは適当。どうせ使わないので。 CostFunction.java /** * Calcurate this cost function's differential. * @param y Matrix of network's output. * @param t Matrix of actual data. * @return The result of differentiating the difference between y and t. */ public Matrix differential(Matrix y, Matrix t){ return Matrix.sub(y, t); } 平均絶対誤差。 $$ E(y) = \frac{1}{N}\sum\limits_{n=1}^{N}|y_t-t_n| $$ 画像の式は、厳密には正しくないですが分かりやすさ重視ということで・・・。 MeanAbsoluteError.java /** * Calcurate this cost function's differential. * @param y Matrix of network's output. * @param t Matrix of actual data. * @return The result of differentiating the MAE between y and t. */ public Matrix differential(Matrix y, Matrix t){ Matrix rtn = new Matrix(t); for (int i = 0; i < t.row; i++){ for (int j = 0; j < t.col; j++){ if (y.matrix[i][j] - t.matrix[i][j] > 0){ t.matrix[i][j] = 1.0; }else{ t.matrix[i][j] = -1.0; } } } return rtn; } 平均二乗誤差。 $$ E(y) = \frac{1}{N}\sum\limits_{n=1}^{N}(y_n-t_n)^2 $$$$ E'(y) = \frac{2}{N}\sum\limits_{n=1}^{N}(y_n-t_n) $$ MeanSquaredError.java /** * Calcurate this cost function's differential. * @param y Matrix of network's output. * @param t Matrix of actual data. * @return The result of differentiating the MSE between y and t. */ public Matrix differential(Matrix y, Matrix t){ Matrix rtn = Matrix.sub(y, t); rtn.meanCol(); rtn.mult(2); return rtn; } 平均平方二乗誤差。 $$ E(y) = \frac{1}{N}\sqrt{\sum\limits_{n=1}^{N}(y_n-t_n)^2} $$ 微分方法が分かりませんので、解決次第追記します。 実装方針 誤差逆伝播法をどう実装するか悩みましたが、新しくそれ用のクラスを作成し、その中にネットワーククラスや損失関数クラスをまとめパラメータ更新をすることにしました。 たぶんですが、chainerもそんな感じなんじゃないでしょうか。 Optimizer.java まず最適化の親クラス。正直、あまり必要ない気はしますが・・・。 package optimizer; import network.*; import costFunction.*; /** * Parent class of optimizer. */ public class Optimizer { /** Network to which optimization is applied. */ Network net; /** Learning rate. */ double eta = 0.01; /** Cost function of this network. */ CostFunction cFunction; /** * Constructor for this class. */ protected Optimizer(){ ; } } GradientDescent.java 勾配降下法。いわゆるSGD、確率的勾配降下法の原型です。 ここで、GradientDescentクラスはネットワークをフィールドとして所持しています。当然ネットワークの中には層があり、層の中にはノードがあり、層の中には重み行列が存在します。つまりこのクラスからネットワーク全体を俯瞰することができます。 なので、ここからすべてのパラメータを調整することにします。 また、誤差逆伝播法に直接かかわる修正点について以下に記載します。 コンストラクタなどについて、以下に載せておきます。 もともとネットワーククラスをcloneする予定でしたが、よく考えたらそんなことする必要ないので参照代入してます。 GradientDescent.java package optimizer; import network.*; import costFunction.*; import matrix.*; import layer.*; import nodes.*; /** * Class for GD. */ public class GradientDescent extends Optimizer{ /** * Constructor for this class. */ public GradientDescent(){ ; } /** * Constructor for this class. * @param net Network to which optimization is applied. */ public GradientDescent(Network net, CostFunction f){ this.net = net; this.cFunc = f; } /** * Constructor for this class. * @param net Network to which optimization is applied. * @param eta Learning rate. */ public GradientDescent(Network net, CostFunction f, double eta){ this.net = net; this.cFunc = f; this.eta = eta; } /** * Doing forward propagation. * @param in input matrix. * @return Matrix instance of output. */ public Matrix forward(Matrix in){ return this.net.forward(in); } また、各ノードはそれぞれ自分に関する情報しか保持していないので、上の説明で用いた$\mathbf a$、つまり 層の出力 は誰も持っていません。自分の出力なら保持しています。 なのでそれを作成するメソッドを準備しました。 /** * 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; } 修正点 Nodeクラス。 順方向計算メソッド内で、線形変換後と出力値をそれぞれ保持してもらいます。 また、警告文を削除しました。どうせ内積計算でチェックするので冗長設計でした。 Node.java /** * Doing forward propagation. * @param input Array of inputs. * @return output array's element. */ public Matrix forward(Matrix input){ // 削除部分 // if (input.col != w.row){ // System.out.println("node forward error"); // System.exit(-1); // } this.x = Matrix.dot(input, w); this.a = aFunc.calcurate(x); return a.clone(); } 最終層 最終層での重み更新の式をもう一度貼ります。 $$ \mathbf w_{new} = \mathbf w_{old} - \eta E'(\mathbf a_{last})f'(\mathbf x_{last})\mathbf a_{pre} $$ ここで$\eta$は学習率で、これは最適化クラスがdoubleで保持しています。 $E'$は損失関数の微分で、損失関数クラスがメソッドとして保持しています。 $\mathbf a_{last}$は出力層からの出力で、ノードクラスがMatrixインスタンスとして保持しています。 $f'$は活性化関数の微分で、活性化関数クラスがメソッドとして保持しています。 $\mathbf x_{last}$は出力層での線形変換後の値(活性化関数に入れる前の値)です。これは出力と同様ノードクラスがMatrixインスタンスとして保持しています。 $\mathbf a_{pre}$は前の層の出力で、こちらもノードクラスがMatrixインスタンスとして保持しています。 この式を、最終層で、ノードの数だけ繰り返す必要があります。 また、最終層でのデルタは $$ \mathbf \delta_{last} = E'(\mathbf a_{last})f'(\mathbf x_{last}) $$ です。 上式をプログラムで再現します。 なお、当然ですがすでに最低一回は順方向計算を行っている前提で誤差逆伝播メソッドを作成します。 誤差逆伝播法はbackという名前のメソッドで実装しました。 まず、その最終層部分を記載します。 /** * 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; // E'(a) cal = this.cFunc.differential(nowNode.a, t.getCol(i)); // E'(a)f'(x) ※これが最終層のデルタ cal = Matrix.dot(cal.T(), nowNode.aFunc.differential(nowNode.x)); nowNode.delta = cal.matrix[0][0]; // E'(a)f'(x)a cal = Matrix.mult(this.calA(preLayer.nodes), nowNode.delta); // -ηE'(a)f'(x)a cal.mult(-this.eta); // w-ηE'(a)f'(x)a nowNode.w.add(cal.meanCol().T()); } コメントの量が多くて申し訳ありませんが、コメントなしの方が分かりにくそうだったので追加しました。 データ数をNとおきますと、 $$ E'(a_{last}) $$ の結果はN行1列になります。ここで、aは層の出力ですが、ノードごとにデルタの計算をするためプログラム内ではそのノードの出力を使っています。また、プログラム内でtも入力しているのは、微分の関係上必要になるからです。 次に、 $$ E'(a_{last})f'(x_{last}) $$ を計算します。$f'(x_{last})$もN行1列になる(xは各ノード・各データにつき、一つしかありません)ので、普通に内積して結果はスカラとなります。 aは前の層の出力です。前の層のノード数が例えば4だった場合、N行5列となります(バイアスも含むため)。これをデータごとに平均します。なぜ平均するかについて説明します。 実はこの平均については、「おそらくこうだろう」という予想の元行っています。というのも、参考にさせていただいた文献ではデータ1セットごとに誤差逆伝播法を使っているものしかなく、GDのように複数のデータから勾配を更新する具体的な方法は分かりませんでした。 しかし、重みを更新するためには計算式の右辺第二項部分が二次元行列ではつじつまが合いませんので、おそらく平均だろう、と考えています。もし間違えがあれば教えてください。 本題に戻りまして、データごとに平均したことでaは1行5列となりました。この層の重みは5行1列ですので、学習率かけて転置させて引けば重みが更新されます。 中間・入力層 中間層及び入力層での重み更新式とデルタ計算式は以下の通りです。 $$ \mathbf w_{new} = \mathbf w_{old} - \eta \sum\limits_{n=1}^{m}\mathbf \delta_{next, n} \mathbf w_{next, n}f'(\mathbf x_{now})\mathbf a_{pre} $$$$ \mathbf \delta = \sum\limits_{n=1}^{m}\mathbf \delta_{next, n} \mathbf w_{next, n}f'(\mathbf x_{now}) $$ バックプロぱげーしょんの中間層及び入力層部分は以下の通り。 // 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; // Σδwf'(x)を一気に計算 nowNode.delta = Matrix.dot(deltas, this.calW(nextNodes, j)).matrix[0][0] * nowNode.aFunc.differential(nowNode.x.meanCol()).matrix[0][0]; // -ηΣδwf'(x)a cal = Matrix.mult(preA.meanCol(), -this.eta*nowNode.delta); // w-Σδwf'(x) nowNode.w.add(cal.T()); } } こちらも怒涛のコメントで申し訳ないです。 まず中間層の場合について説明します。 はじめに、前の層の出力をまとめています。前の層のノード数を仮に10とおくと、これはN行11列となります(バイアスも含めるため)。 次に、次の層のデルタをまとめます。次の層のノード数を仮に5としたとき、deltasは1行5列になります。 ここまでは一つの層の中で共通しますが、ここから先は別なのでノードごとに計算します。 calWメソッドを用いて、着目しているノードからの出力に対して、次の層の各ノードでつけられる重みを取得します。例えば今の層がノードを3個持っているとすると、次の層のノード5個はそれぞれ4行1列の重み行列を所持しています。そして、今の層一つ目のノードからの出力にかけられる重みは5個ある4行1列の行列の、それぞれ1行1列目の数字です。それを集めて5行1列にまとめます。 deltasとcalWでまとめた行列の内積をすることで、スカラが得られます。 これに活性化関数の微分をかけたものが中間層での$\delta$になります。xを平均しているのは、最終層でのaと同じ理由です。 そしてこれに前の層の出力N行11列(こちらも平均して1行11列にする)、そして学習率をかけることで重みが更新できます。前の層のノード数が10ですから、今の層の重み行列は当然11行1列です。転置させて引きました。 入力層については、前の層の出力がネットワークの入力になるだけで同じ流れです。 使ってみた 足し算やってみました。 使用した誤差関数は平均絶対誤差。 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 MeanAbsoluteError() ); // データ 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]; } MeanAbsoluteError f = new MeanAbsoluteError(); // 30回学習させ、精度表示 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)); } // 別データで精度確認 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)); } } 各学習結果はこちら。 [[1.3821 ]] [[1.0006 ]] [[0.5186 ]] [[0.0878 ]] [[0.5171 ]] [[0.0865 ]] [[0.5160 ]] [[0.0856 ]] [[0.5153 ]] [[0.0851 ]] [[0.5150 ]] [[0.0850 ]] [[0.5151 ]] [[0.0853 ]] [[0.5156 ]] [[0.0860 ]] [[0.5165 ]] [[0.0871 ]] [[0.5178 ]] [[0.0886 ]] [[0.5195 ]] [[0.0906 ]] [[0.5217 ]] [[0.0929 ]] [[0.5242 ]] [[0.0957 ]] [[0.5272 ]] [[0.0989 ]] [[0.5306 ]] [[0.1025 ]] [[0.5345 ]] 行ったり来たりしてます。確認データによる最終誤差はこちら。 score: [[0.5183 ]] 微妙。 次に、ネットワークを以下のように変更して試してみました。 Network net = new Network( 2, new Input(4, AF.RELU), new Dense(10, AF.RELU), new Output(1, AF.LINER) ); 結果はこちら。 [[2.7587 ]] [[2.2969 ]] [[1.9678 ]] [[1.6999 ]] [[1.4321 ]] [[1.2185 ]] [[1.0253 ]] [[0.8622 ]] [[0.7627 ]] [[0.7040 ]] [[0.6755 ]] [[0.6468 ]] [[0.6239 ]] [[0.6157 ]] [[0.6074 ]] [[0.5990 ]] [[0.5904 ]] [[0.5817 ]] [[0.5732 ]] [[0.5732 ]] [[0.5732 ]] [[0.5732 ]] [[0.5732 ]] [[0.5732 ]] [[0.5732 ]] [[0.5732 ]] [[0.5732 ]] [[0.5732 ]] [[0.5732 ]] [[0.5732 ]] [[0.5732 ]] score: [[0.4964 ]] なかなか順調に学習できてるんじゃないでしょうか。 確認データによる誤差がなぜか低いことが気になりますが。 ついでに、訓練データと確認データそれぞれについて、このネットワークの予測と正解値を比較してみました。 まず訓練データ。左が予測値、右が正解です。なお表示している行列はMatrixクラスのhstackメソッドで作成しました。 [[0.7414 0.0000 ] [0.8582 0.3000 ] [1.0148 0.6000 ] [1.1304 0.9000 ] [1.2017 1.2000 ] [1.2111 1.5000 ] [1.2445 1.8000 ] [1.3321 2.1000 ] [1.4198 2.4000 ] [1.5075 2.7000 ]] 次に確認データ。左が予測値、右が正解です。 [[0.7414 0.0000 ] [0.8053 0.2700 ] [0.9220 0.5400 ] [1.0308 0.8100 ] [1.1131 1.0800 ] [1.1955 1.3500 ] [1.2100 1.6200 ] [1.2614 1.8900 ] [1.3309 2.1600 ] [1.4005 2.4300 ]] 平均絶対誤差が0.5もあるので、やはりあまり正確ではありませんが、入力データが大きくなるほど出力値も大きくなる正の相関を保ってはいました。 次回は かなり大変でした。次回何をするかはまだ決めていません。 (追記) 勾配降下法以外の最適化関数を実装しました。 次回 参考文献 番号ふってますが、文の引用などはしていません。深層学習の理解のために参考にさせていただきました。 Excel関数で学ぶニューラルネットワーク(逆伝播編)|Excelでわかる深層学習の仕組み | LiCLOG 初心者の初心者による初心者のためのニューラルネットワーク#3〜理論:逆伝播編〜 - Qiita 【決定版】スーパーわかりやすい最適化アルゴリズム -損失関数からAdamとニュートン法- - Qiita 機械学習の要「誤差逆伝播学習法」を解説・実装してみる! – 株式会社ライトコード 石川聡彦, 人工知能プログラミングのための数学がわかる本, 株式会社KADOKAWA, 2018年 瀧雅人, これならわかる深層学習入門, 株式会社講談社, 2017年 岡谷貴之, 深層学習, 株式会社講談社, 2015年
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

専門学校生がカジュアル面談で会社の話を聞いてどんな感じだったか、その後どうなったかのお話

自己紹介 こんにちは、CoKoNPILeをtomio2480さんと運営しているのーりです。 学生プログラマーアルバイトを始めました。 実は、私の学科では少しずつ就職活動のときが近づいています。 今回は、私がWanteldyを使って第一志望の会社にカジュアル面談を申し込んで実際に話を聞いて2月21日から最初は学生アルバイトとして受け入れてもらったエピソードやそれまでに今までやってきたことなどをお話しします。 この記事にはどんな内容がある? この記事では下記の内容などを書きます。 ・カジュアル面談とは ・なぜ選考前にカジュアル面談から行ったのか ・カジュアル面談は選考なのかどうか ・カジュアル面談や選考をを受けるまでやってきたこと カジュアル面談とは何 ざっくり言うとそもそもカジュアル面談というのは、面接とはまた違うものです。 デートとかで例えるなら、自分と相手(会社)といい感じにマッチングするかどうかを見たり、情報交換な感じでお話をしたりします。 カジュアル面談とは、選考前に求職者と社員がカジュアルに話をして、お互いの知りたい情報を交換する機会のことです。 企業が求職者に対して一方的に質問する面接とは異なり、お互いに質問することで双方向の興味づけを行います。 出典: https://www.wantedly.com/hiringeek/recruit/casual_interview/ メリットは、よくあるイメージ(?)のガチガチ面接というよりか、カジュアルにリラックスして会社の話を聞くことができるというところです。 なぜ選考前にカジュアル面談から行ったのか カジュアル面談のきっかけ 就職活動を意識し始めたのが、2022年元旦のころからです。 そういえば、以前tomio2480さんから「この会社いいかも!」と紹介をされたのを思い出し 本番を受ける前に、どんな感じの会社なのか聞いてみよう!と思いました。 なんとなんと、Wantedlyにも新卒枠で募集を行っていたためプロフィールや実績を書いてから思い切って「話を聞きに行く」ボタンを押しました。 そうすると1~2時間ほどで代表者から返信があり、 CoKoNPILeにも参加をしたことがあるとのことでした! 是非カジュアル面談しませんかと招待を受け、話をしました。 本当はオンラインで行う予定でしたが、PCがダメになったため「たまたま」対面で受けることができました。 その後については、実際に履歴書や適性検査等を受けて、面接を受けてコーディング課題(アルゴリズム系ではなかったです)を解きレビューを受けて現在、最初は学生アルバイトから始めることができました。(前向きに、しばらくしたら内定も考えているとのことで大変感謝です。) カジュアル面談は選考なのかどうか これは断言できません。すいません(会社によります。) しかしながら、会社側も準備とそのコストをかけているので何かしらの評価をしているはずです。 ですので、事前に応募前までにポートフォリオを用意したり質問を考えておくとよいと感じました。 カジュアル面談や選考をを受けるまでやってきたこと 私の場合、下記のようなことを行ってきました。(思いつく物) ・プログラミングはなんでもいいから学校以外でもプライベートでも学ぶ。 ・GitHubなどのアカウントを使って実際に練習をする。 ・connpass.comを使ってIT勉強会をに参加をしてお付き合い・人脈を広げること。 2つめまでは技術的な問題です。ちょこっとでもいいので個人でも練習しておいてよかったなと思いました。 私はもしかするとJava言語が一番コーディング歴長いかもしれません。 ときどき、Java言語はオワコンだ~みたいな意見があり「どうしよう..」と不安になるときもありました。 しかしカジュアル面談で話をしたら、「Java言語 C++のをやったことあるのは心強いです!」と返事が返ってきたため「やっておいてよかったな」といまは思っています。 プログラミングこれからだけど、どの言語を選ぶか迷うという人は私の意見だと、 型を書かずに変数宣言ができる言語は控えた方がいいかなーと思います。べつに悪いわけではありません。 ただ、最初は基本となる変数の型の種類をがっつり学んだりオブジェクト指向を学んでからの方が応用が利いたりすることもあるため、Java・C#・C++あたりがいいのかなと思います。(ポインタを学びたいときはC++のほかにC言語も挑戦するといいです。) サンプルや成果物をただ作るだけで終わりにせず、どんな物でもいいからGitを使ってプッシュしてみましょう。そうするとプログラミングを学びながら開発で使うツールについても学ぶことができるため一石二鳥です。 最後の点も非常に重要で、独学で一人でもくもくやっているのも楽しいですがとても寂しいです。(自分もそうでした。) だから、実際にIT勉強会に参加をして人脈を広げましょう。わからないこともプロに聞けるチャンスでもあります。 勉強会ってつよつよのお兄さんの集まりがいて怖いと言う場合、はじめは少人数の規模や近所でやっているような勉強会(たとえば、中高生ならCoderDojoなど)に参加をしましょう。 さいごに まとめるとこのような形です。 ・カジュアル面談はリラックスして会社の話を聞ける。 ・カジュアル面談でもなにかしら評価される(はず)。 ・それまでにいっぱい人脈を作ったり技術的な面でしっかり勉強をする もしカジュアル面談で少しでも気になる人がいたときにこの記事がお役に立てれば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaのCollections.sort()使っての並べ替え(昇順、降順)

目次 この記事を作成したきっかけ 実際に作ってみる この記事を作成したきっかけ Javaのソースコードの修正時、ソート処理に悩んだので今回の記事を作成した 実際に作ってみる 昇順 並べ替えるListを用意 //数値のリストを用意 List<Integer> list = new ArrayList<Integer>(); Random r = new Random(); for(int i = 0; i < 10; i++) { //0~99までの数値をランダムに設定 list.add(r.nextInt(100)); } System.out.println("ソート前:"); //リストを表示する for(int i = 0; i < 10; i++) { System.out.print("[" + list.get(i) + "] "); } 昇順にソートする //昇順にソートする Collections.sort(list); System.out.println("\nソート後(昇順):"); //リストを表示する for(int i = 0; i < 10; i++) { System.out.print("[" + list.get(i) + "] "); } 実行結果 ソート前: [82] [79] [38] [63] [30] [65] [89] [97] [59] [68] ソート後(昇順): [30] [38] [59] [63] [65] [68] [79] [82] [89] [97] 降順 次に、降順に並び替える 通常、Collections.sort()は昇順でソートが行われる 降順でソートされるように、以下を実施する。 Comparatorをimplements(継承)したクラスNumberDescendingSortを作成 compareメソッドをオーバーライドする o1 > o2 の場合は正の数が返される(return 1;) → 負の数が返されるようにする(return -1;) o1 < o2の場合は負の数が返される(return -1;) → 正の数が返されるようにする(return 1;) 降順でソートされるよう定義する package sort; import java.util.Comparator; public class NumberDescendingSort implements Comparator<Integer>{ //通常は、o1 > o2 の場合は正の数が返され(return 1;)、 o1 < o2の場合は負の数が返される(return -1;) //昇順がデフォルトなので、小さい順に並べられる。 //今回は降順としたいので、返される値を逆にしている。 //o1 > o2 の場合は負の数が返され(return -1;)、 o1 < o2の場合は正の数が返される(return 1;) @Override public int compare(Integer o1, Integer o2) { // if(o1 > o2) { return -1; }else if(o1 < o2) { return 1; }else { return 0; } } } 降順にソートする //降順にソートする(NumberDescendingSortクラスを使用する) //第一引数のリストを、第二引数で定義している通りに並び変える Collections.sort(list,new NumberDescendingSort()); System.out.println("\nソート後(降順):"); for(int i = 0; i < 10; i++) { System.out.print("[" + list.get(i) + "] "); } 実行結果 ソート前: [82] [79] [38] [63] [30] [65] [89] [97] [59] [68] ソート後(降順): [97] [89] [82] [79] [68] [65] [63] [59] [38] [30] 最後に ソートの処理を修正時に、昇順降順の条件に迷ってしまっていたが、 今回実際に作ってみて理解を深めることができました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javaの文字列操作についてまとめてみた

Javaの文字列操作について学習したのでまとめてみようと思います。 Javaシルバーの勉強をしていて、文字列操作の知識を問う問題で詰まってしまったので備忘録としてまとめてみようと思います。 indexOf()メソッド indexOfメソッドは、引数に指定された文字がどこから始まるのかというのを検索するためのメソッドです。 そして、実行すると、1や2のように数値を返します。 文法1つ目(indexOf) 1.public int indexOf(調べたいワード) 2.public int indexOf(調べたいワード [,int index]) indexOfメソッドの書き方は二通りあります。 一番最初の場合は、対象の文字列から調べたいワードが先頭から何番目にあるのかを検索し、その値を返します。 例えば、 main.java class Main { public static void main(String args[]){ String str = "あいうえお"; System.out.println(str.indexOf("あ")); } } 以上のコードでstr.indexOf("あ");とすることで、変数strの中で"あ"が先頭から数えて何番目にあるのかをSystem.out.println();で出力させます。 返ってきた値は 0 です。なぜかというと、Javaは文字列の場所を指定する時に、0から始めるからです。 一番最初だからといって1から始めるわけではありません。ここは配列の値を呼び出す時と同じです。 でもこれだけでは不便です。 なぜかというと、これは先頭から何番目の文字を検索するだけなので、これだと複数の変数を扱い、それぞれから文字を抜き出す時に 「それぞれの変数の最初から3番目までは共通化されているので、そこは検索対象に入れないで検索をかけたい。」 といった時に非常に不便ですよね。 なので、それを解消するためのものがindexOf()には用意されているので次からそれを紹介します。 文法2つ目(indexOf) 今度は、先程行ったことを解消するためのindexOf()の文法を紹介します。 先程は、一番先頭から見て何番目かを見てきました。今度は、先頭から何番目から見てどこにその指定されたワードがあるのかを見る方法を紹介します。 main.java class Main { public static void main(String args[]){ String str = "あいうえお"; System.out.println(str.indexOf("あ", 2)); } } 上記のサンプルコードでstr.indexOf("あ", 2)の部分で、第2引数の部分に数字が入っていると思います。 これをすることで、0から始まって2番めの所から(この場合はうの場所)から先にあという文字が入っているのかを検索をかけます。 これをすると戻り値は -1 になります。 これはなぜかというと、指定された範囲の中に第一引数で指定された文字が存在しないからです。 指定された文字が存在しない場合は、 -1 となります。これが-2とかになることはありません。 以上になります。 次に、指定された範囲の文字を取得するメソッドを紹介します。 これは指定した範囲の文字列を抽出するメソッドです。 先程のように、数字を返してくるのではなく、抽出した文字を戻り値として返します。 なので、 「指定した範囲の部分の内容に応じて処理を変えたい。」という時に便利です。 文法としては2つの書き方があります。 1.substring(引数の数値の値); 2.substring(引数の数値の値, 引数の数値の値); 文法1つ目(substring()) substring()の()の中に引数を入れると、引数で指定された文字から先頭をすべて取得することができます。実際にやると main.java class Main { public static void main(String args[]){ String str = "あいうえお"; System.out.println(str.substring(0)); } } substring()の引数に0が指定されているので、 先頭のあから検索がかかります。なので出力結果は あいうえお となります。 でもこれだけだと範囲を選択できなくて不便なので、範囲を選択できるやり方を紹介します。 文法2つ目(substring()) 次に、substringの引数を2つ指定することで、範囲を指定できるやり方について紹介します。 やり方としては、 Main.java class Main { public static void main(String args[]){ String str = "あいうえお"; System.out.println(str.substring(0, 4)); } } 上記のsubstring(0,4)のように引数を2つ指定するだけです。 出力結果は あいうえ となります。 ここで疑問に思うかもしれませんが、じつは引数を指定するやり方があるのですが、少し複雑なところがあるので混乱しないように気をつけてください。 説明すると、 「始まりの位置以上、終わりの位置未満まで指定する」 というものです。 なので、第2引数をの所で4と指定されていますが、これは4番目の文字を指定するのではなくて「4未満」なので、上記のコードの場合は0番目から3番目までの文字を検索して抽出します。 皆さん扱うときは気をつけてください。 今度は、指定された文字を反転させるメソッドを紹介します。 startsWith()メソッド substring()メソッドは引数の値で対象の文字列が始まるのかを判定するメソッドです。 戻り値としてtrue/falseが返ってきます。 やり方について解説します。 文法(startsWith()) startsWith()メソッドの文法について解説します。 main.java class Main { public static void main(String args[]){ String str = "あいうえお"; System.out.println(str.startsWith("a")); //false System.out.println(str.startsWith("あ")); //true } } 対象の文字列である変数strに対して、.をつけてつなげて、startsWith();の引数の中に検査したい文字を入れます。 このコードを実行すると、出力してみると false true となります。最初にaという文字から始まるかを判定します、この場合aから始まっていないので出力結果はfalseになります。 次に、"あ"から始まるかを判定します。変数strはあから始まるので、戻り値としてはtrueになります。 ただ、これは最初に何が始まるのかを判定するものなので、拡張子が何なのかで判定したい場合は不便です。 これしか使えないと、文字列をreverseして反転させて、そこからstartsWith()で判定しないといけないので、くっそ不便です。 .txtかどうかを判定したいのに、これをいちいち反転させてやるとなると、 変数.startsWith(txt.); なんていうあとから見たらまじでクソみたいなコードになるので、こうならない対処法を次に解説しています。 endsWith()メソッド これは先程言った問題を解決するためのメソッドです。 これは先程と反対に 「引数で指定された文字で終わるかどうか」 を判定するメソッドです。 これを使えば、先程行った拡張子を判定する時に 変数.startsWith(txt.); なんていうクソコードをやめることができます。 あとreverseするときはStringBuilderオブジェクトを生成しないといけません。 こんなクソコードを書くためにオブジェクトを生成するのはだるいので、この機会に覚えます。 文法(endsWith()) main.java class Main { public static void main(String args[]){ String fileName = "hoge.txt"; System.out.println(fileName.endsWith(".html")); //false System.out.println(fileName.endsWith(".txt")); //true } } 上記のように、変数fileNameにフィアル名を格納します。 そして、filenname.endsWith()とすることで引数の値でファイル名が終わるかどうかを判定します。 最初の判定で、.htmlになるかどうかを判定していますが、変数fileNameの拡張子は.txtなのでこれはfalseになります。 次の判定で、変数fileNameが.txtで終わるかどうかを判定していますが、これはその拡張子で終わるのでtrueになります。 charAt()メソッド これは、引数の値(数値)で指定されたところが何なのかを戻り値として返します。 文法(cahrAt()) main.java class Main { public static void main(String args[]){ String str = "あいうえお"; System.out.println(str.charAt(0)); } } 上記の様に、str.cahrAt(0);として、引数の値に0をしていするので0番目、つまり一番最初の文字を指定します。 よって出力結果は あ となります。 でも抜き出すだけだとなんに使うんだとと思うと思うので、抜き出した文字を変更できるメソッドについて解説したいと思います。 replace()メソッド これを使えば先程解説したcharAt()で抽出した文字を変更させたりする事ができます。 文法(replace()) main.java class Main { public static void main(String args[]){ String str = "あいうえお"; str.charAt(0); System.out.println("replaceする対象の文字" + str.charAt(0)); String targetWord = String.valueOf(str.charAt(0)); System.out.println(str.replace(targetWord, "か")); } } 上記でreplace(targetWord, "か")とすることで、変数targetWordで抽出された文字であるあをかに変更することができました。 この時に注意なのですが、replaceの引数はStringしか使うことができません。 なので、replaceの引数に System.out.println(str.replace(str.charAt(0), "か")); として、直接指定させようとしてもできません。 これをしてしまうと、 main.java:6: エラー: replaceに適切なメソッドが見つかりません(char,String) となってしまいます。第一引数で指定したところがchar型担っているんですね。 なので使うときはStringに変換して使うようにしましょう。 reverse()メソッド これはStringBuilderオブジェクトに格納されているものです。 なので、これを使うときはStringBuilderオブジェクトを宣言する必要があります。やり方としては StringBuilder 変数名 = new StringBuilder(); と宣言をすると、StringBuilderオブジェクトが生成されて、StringBuilderオブジェクトのメソッドを使うことができるようになります。 そして、今回の主題である文字列を反転させる手順について解説します。 文法(reverse()) 以下、サンプルコードです。 main.java class Main { public static void main(String args[]){ StringBuilder sb = new StringBuilder(); String str = "あいうえお"; sb.append(str); sb.reverse(); System.out.println(sb); } } 上記のサンプルコードを見ると、sb.append(str);でStringBuilderオブジェクトに対して今回反転させたいあいうえおの文字列を与えています。そして、StringBuilder型の変数sbに対してreverse();メソッドをつけることで、System.out.println();を実行した時に、あいうえおの文字が おえういあ となって、並び順が逆になっています。 これはStringBuilderオブジェクトを宣言するというところに注意してください。これ宣言しないとエラー: シンボルを見つけられませんと出てしまいます。 初めて使った時にこれ知らなくて一時間くらい悩んでいた人がいうので間違いありません。 今回は以上になります。 他にもいろいろあるので皆さんやってみてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javaの基本ルール(備忘録)

クラス名とファイル名 クラス名とファイル名は同じに(頭文字は大文字にしなくてはならない) 例 Main.java Sub.java 例 class Main {クラスブロック} class Sub{クラスブロック} コメントの記述方式 コメントは他言語と同様の記述方式を採用しているようです。 /* コメント本文(複数行記述可能) */ // コメント本文 (行末まで記述可能) 変数と定数 変数と定数の宣言方法です。 定数名は基本的にすべて大文字で記述します。 定数は使用する計算式などで今後変化しないものを入れることが多いです。 //宣言のみの場合 型 変数名; 例 int age; final 型 定数名; 例 final double TAX; //初期値を入れる場合 型 変数名 = 初期値; 例 int age = 22; final 型 定数名 = 初期値; 例 final double PIE = 3.14;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

競合対策方法 【初心者向け】

競合とは 一言で言うと、複数のスレッドで一つのインスタンスを共有すると競合が起きます。こうなることで、インスタンスのフィールドの値を読みだしてから、変更するまでの間に 、別のスレッドが変更してしまいます。いつ実行されるかはプログラミング側で制御することが出来ません。飲食店に例えると、あるお客さんがカレーを注文したが、料理人がカレーを作っている間にカレーからオムライスに変更されてしまったというイメージです。 対策 結論から言うと、対策はスレッドから処理している間は別のスレッドで処理が実行されないようにすることです。これを排他制御と呼びます。排他制御の方法を紹介していきます。 方法 synchronizedキーワードを使用します。パターンは2つあります。 ①メソッド宣言に使う場合 ②メソッド内の1部の処理だけを対象に使う場合 メソッド宣言に使う場合 synchronizedを修飾するだけです。 しかし、メソッド内の処理が複雑であればあるほど、待ち時間が出来てしまいます。 public synchronized void method(){ doSomething; } メソッド内の一部の処理だけを対象に使う場合 syncronizedブロックを使用するだけです。どのインスタンスに対して、排他制御するのかをブロック引数として指定します。一部の制御をすることで、待ち時間を最小限にすることが出来ます。 void method(){ syncronized(instance){ doSomething() } 問題点 複数のスレッド間で排他制御された複数のインスタンスを共有していて、それぞれのインスタンス同士が連携することで、デットロックが発生してしましいます。解決方法として、ロックする順番をスレッド間で揃える方法があります。また、syncnizedは別のスレッドを待機させるので、パフォーマンスに影響を及ぼしてしまいます。その対策として、単純な処理には実行されないか、一連の処理が完全に終わるかになるような結果が保証されているjava.util.concurrent.atomicパッケージを利用します。 java.util.concurrent.atomicパッケージのAtomicIntegerクラスの使用例 atomicパッケージのAtomicIntegerクラスを使用して、排他制御していきます。 atomicパッケージにどのようなクラスがあるのかは、下記のサイトを参考にしてください。 Value.java public class Value { private int num = 0; public void add(int num) { this.num +=num; } public int get() { return num; } } AtomicIntegerクラスのaddAndGedメソッドを使うことで、値の呼び出しから、変更までの間に他のスレッドは処理できない。 AtomicValue.java import java.util.concurrent.atomic.AtomicInteger; public class AtomicValue extends Value{ private AtomicInteger num = new AtomicInteger(0); public void add(int num) { //読み出しから値の変更まで他のスレッドからの処理は受けない this.num.addAndGet(num); } public int get() { return this.num.intValue(); } } Sample.java public class Sample { public static void main(String[] args) { Value val = new AtomicValue(); //スレッドプールを2つ作成 ExecutorService exec = Executors.newFixedThreadPool(2); exec.submit(new Task(val)); exec.submit(new Task(val)); try { Thread.sleep(200); }catch(InterruptedException e) { throw new RuntimeException(e); } System.out.println(val.get()); exec.shutdown(); } } 200 まとめ 競合を防ぐにはsyncronizedキーワードを使用する デットロックを防ぐにはロックする順番をスレッド間で揃える
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaでPowerPointスライドマスターを作成して適用する方法

スライドマスターによって、フォント、プレースホルダーのサイズや位置、背景のデザインや配色など、デザインテンプレート情報をスライドに事前に保存できます。セットマスターは、すべてのスライドに適用することも、複数の異なるマスターをに適用して設計することもできます。別のスライド。以下では、Javaコードの例を使用して、単一のマスターとさまざまなマスターを作成する方法について説明します。 使用したツール:Free Spire.Office for Java(無料版) jarの取得とインポート:公式Webサイトからjarパッケージをダウンロードし、libフォルダー内のjarファイルを解凍してJavaプログラムにインポートします。 次のようにエフェクトをインポートします。 Javaコード一覧 1. 単一のマスターを作成し、すべてのスライドに適用する import com.spire.presentation.*; import com.spire.presentation.drawing.BackgroundType; import com.spire.presentation.drawing.FillFormatType; import com.spire.presentation.drawing.IImageData; import com.spire.presentation.drawing.PictureFillType; import javax.imageio.ImageIO; import java.awt.*; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.io.FileInputStream; public class CreateMasterSlide { public static void main(String[] args) throws Exception { //PowerPointドキュメントを作成し、スライドサイズを設定する Presentation ppt = new Presentation(); ppt.getSlideSize().setType(SlideSizeType.SCREEN_16_X_9); //最初のマスターを取得する IMasterSlide masterSlide = ppt.getMasters().get(0); //マスターの背景を設定する BufferedImage image = ImageIO.read(new FileInputStream("tp.png")); IImageData imageData = ppt.getImages().append(image); masterSlide.getSlideBackground().setType(BackgroundType.CUSTOM); masterSlide.getSlideBackground().getFill().setFillType(FillFormatType.PICTURE); masterSlide.getSlideBackground().getFill().getPictureFill().setFillType(PictureFillType.STRETCH); masterSlide.getSlideBackground().getFill().getPictureFill().getPicture().setEmbedImage(imageData); //マスターに画像を追加する image = ImageIO.read(new FileInputStream("logo.png")); imageData = ppt.getImages().append(image); IEmbedImage imageShape = masterSlide.getShapes().appendEmbedImage(ShapeType.RECTANGLE,imageData,new Rectangle2D.Float((float) ppt.getSlideSize().getSize().getWidth()-240,40,60,60)); imageShape.getLine().setFillType(FillFormatType.NONE); //マスターにテキストを追加する IAutoShape textShape = masterSlide.getShapes().appendShape(ShapeType.RECTANGLE, new Rectangle2D.Float((float) ppt.getSlideSize().getSize().getWidth()-230,85,200,30)); textShape.getTextFrame().setText("サンプル文字"); textShape.getTextFrame().getTextRange().setFontHeight(20f); textShape.getTextFrame().getTextRange().getFill().setFillType(FillFormatType.SOLID); textShape.getTextFrame().getTextRange().getFill().getSolidColor().setColor(Color.black); textShape.getTextFrame().getTextRange().getParagraph().setAlignment(TextAlignmentType.CENTER); textShape.getFill().setFillType(FillFormatType.NONE); textShape.getLine().setFillType(FillFormatType.NONE); //スライドを追加する(PowerPointドキュメントを作成する場合、デフォルトでスライドが生成されています。ここにスライドを追加して、マスターを追加した場合の効果を比較してください) ppt.getSlides().append(); //ドキュメントを保存する ppt.saveToFile("CreateSlideMaster.pptx", FileFormat.PPTX_2013); ppt.dispose(); } } マスターを作成した効果: 2. 異なるスライドに適用する複数のマスターを作成する import com.spire.presentation.*; import com.spire.presentation.drawing.BackgroundType; import com.spire.presentation.drawing.FillFormatType; import com.spire.presentation.drawing.IImageData; import com.spire.presentation.drawing.PictureFillType; import javax.imageio.ImageIO; import java.awt.*; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.io.FileInputStream; public class CreateMasterSlide2 { public static void main(String[] args) throws Exception{ //PowerPointドキュメントを作成し、スライドサイズを設定する Presentation ppt = new Presentation(); ppt.getSlideSize().setType(SlideSizeType.SCREEN_16_X_9); //4枚のスライドを挿入する(デフォルトのスライドを含めて、ドキュメントには5ページある) for (int i = 0; i < 4; i++) { ppt.getSlides().append(); } //デフォルトのマスターを取得する IMasterSlide first_master = ppt.getMasters().get(0); //2番目のマスターを作成して入手する ppt.getMasters().appendSlide(first_master); IMasterSlide second_master = ppt.getMasters().get(1); //2つのマスターごとに異なる背景画像を設定する BufferedImage image = ImageIO.read(new FileInputStream("C:\\Users\\Administrator\\Pictures\\pic1.png")); IImageData imageData = ppt.getImages().append(image); first_master.getSlideBackground().setType(BackgroundType.CUSTOM); first_master.getSlideBackground().getFill().setFillType(FillFormatType.PICTURE); first_master.getSlideBackground().getFill().getPictureFill().setFillType(PictureFillType.STRETCH); first_master.getSlideBackground().getFill().getPictureFill().getPicture().setEmbedImage(imageData); IAutoShape textShape = first_master.getShapes().appendShape(ShapeType.RECTANGLE, new Rectangle2D.Float((float) ppt.getSlideSize().getSize().getWidth()/3,180,400,30)); textShape.getTextFrame().setText("ホームマスター"); textShape.getTextFrame().getTextRange().setFontHeight(40f); textShape.getTextFrame().getTextRange().getFill().setFillType(FillFormatType.SOLID); textShape.getTextFrame().getTextRange().getFill().getSolidColor().setColor(Color.red); textShape.getTextFrame().getTextRange().getParagraph().setAlignment(TextAlignmentType.CENTER); textShape.getFill().setFillType(FillFormatType.NONE); textShape.getLine().setFillType(FillFormatType.NONE); image = ImageIO.read(new FileInputStream("C:\\Users\\Administrator\\Pictures\\pic2.png")); imageData = ppt.getImages().append(image); second_master.getSlideBackground().setType(BackgroundType.CUSTOM); second_master.getSlideBackground().getFill().setFillType(FillFormatType.PICTURE); second_master.getSlideBackground().getFill().getPictureFill().setFillType(PictureFillType.STRETCH); second_master.getSlideBackground().getFill().getPictureFill().getPicture().setEmbedImage(imageData); //最初のマスターとレイアウトを最初のページに適用する ppt.getSlides().get(0).setLayout(first_master.getLayouts().get(6)); //2番目のマスターとレイアウトを残りのスライドに適用する for (int i = 1; i < ppt.getSlides().getCount(); i++) { ppt.getSlides().get(i).setLayout(second_master.getLayouts().get(6)); } //ドキュメントを保存する ppt.saveToFile("MultiSlideMaters.pptx", FileFormat.PPTX_2013); ppt.dispose(); } } 複数のマスターを作成した効果: 今回のPowerPointスライドマスターを作成して適用する方法は以上でした、最後まで読んでいただきありがとうございます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

参考になったデバック方法

自分の知らないデバック方法をためておくためのページ!! ファイル出力機能でどこにファイルがでるかわからない →ファイルを作成するメソッドを使って、ルートにファイルを作成するようにしてみる。デバックをしながらそのパスを拾い上げる try,catchを何階層も作成し、どこで落ちるか、当てはまるのかを特定する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

マルチスレッド概念から使い方まで 「初心者から中級者向け」

はじめに 皆さんは、マルチスレッドについてご存知でしょうか。 正しくマルチスレッドを使うことによって、システムのパフォーマンスを向上させることが出来ます。今回は初心者に向けてマルチスレッドにについて概念から使い方まで解説していきます。 マルチスレッドはなぜ必要なのか? プログラムの流れは、プログラムの処理が終わるまで次の処理に進めないという特徴を持っています。この特徴の問題点は処理が終わるまで待つ時間ができてしまうことです。 このような問題を解決するのがマルチスレッド並列処理です。マルチスレッド並列処理を解説する前に、並列処理と並行処理について解説していきます。 並行処理と並列処理 並列処理を一言で言えば、マルチコアで同時に複数の処理を実行することです。詳しく説明すると、コンピュータに搭載しているCPUには複数のコアを持っています。コアの数だけ、同時に複数の処理を進めることができるので、待ち時間を解消することが出来ます。飲食店を例にすると、お店の人が1人だった場合は料理、配膳、食器洗いと時間ごとに仕事をします。このことを並行処理だとイメージしてください。それが複数いると、料理をする人、配膳する人、皿洗いする人に分けることができるので同時進行で仕事を進めることができます。このことを並列処理だとイメージしてください。 マルチスレッドとは 一言で言うとアプリケーションのプロセス(タスク)を複数のスレッドに分けて並行処理する方式のことをマルチスレッドといいます。スレッドとは、簡単に言えば処理の最小単位です。先程の例だと料理や盛り付けなと1つの仕事だと思ってください。 マルチスレッドでの並行処理の実装方法 新しいスレッドを作るには2通りあります。 ・1つ目はRunnableインターフェースを実現したクラスを用意し、そのインスタンスをThreadクラスのコンストラクターに渡します。2つ目はThreadクラスを継承したサブクラスを定義します。 この2通りについて詳しく解説していきます。 ①Runnableインターフェース Runnableインターインターフェースは新しいスレッドで実行したいrunメソッドのみ持っています。Threadのstartメソッドでstartメソッドを呼び出したスレッドで動作します。一方runメソッドはstartメソッドによって作られた新しいスレッドで動作します。 Sample.java public class Sample { public static void main (String[] args) { //Runnableを実現した匿名クラスをThreadコンストラクタに渡してスレッドを作成 Thread t = new Thread (new Runnable() { public void run() { System.out.println("sub"); } }); t.start(); System.out.println("main"); } } main sub ②Threadクラスのサブクラスを実装 新しいスレッドで実行したいことをThreadクラスのrunメソッドで実装します。 SampleThread.java public class SampleThread extends Thread{ public void run() { System.out.println("sub"); } } 新しいスタックを生成してスレッドを開始するにはThreadクラスのインスタンスを生成し、startメソッドを実行します。 Sample.java public class Sample { public static void main (String[] args) { Thread t = new SampleThread(); t.start(); System.out.println("main"); } } main sub 問題点 先程の2つの方法だと実行したい処理の数だけ、新しいスレッドを生成する必要性があります。例えば、実行したい処理が50あれば、50個のスレッドを作成する必要性があるということです。この時に空きの状態になったスレットがあるにも関わらず、新しくスレットを作らなけらばなりません。これではコンピュータに負荷をかけてしまいます。スレッドの無駄遣いを解消するのがスレットプールです。 スレッドプールとは スレッドプールを一言で言えば、スレッドを無駄遣いしない仕組みのことです。 具体的には、空のスレッドとタスク(処理)を実行するスレッドを分けることで、スレッドを効率的に作成できます。飲食店を例にすると注文が来てから、料理をすれば無駄に料理をすることはなくなりますよね。 詳しい説明はこちらのサイトがおすすめ スレッドプールの実装方法 実装方法としては2通りあります。 ・1つ目はExecutorsのサブインターフェースであるExecutorServiceを利用 ・2つ目はExecutorsのサブインターフェースであるScheduledExecutorServiceを利用 この2通りについてサンプルコードと合わせて、解説していきます。 ①ExecutorService 1つだけ新しいスレッドを使う場合 ExecuteorsクラスのnewSingleThreadExecutorメソッドを使用して、ExecutorServiceを作成します。submitメソッドはスレッドにタスクを与えて、実行します。 下記のコードは、スレッドIDを表示させています。 Sample.java mport java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Sample { public static void main (String[] args) { //タスクを持つ新しいスレッドを作成 ExecutorService exec = Executors.newSingleThreadExecutor(); //ExecutorServiceにタスクを与えて、実行する exec.submit(()->{ System.out.println(Thread.currentThread().getId()); }); } } 12 決まった数のスレッドを生成し、スレッドプールに貯めておきたい場合 ExecutorsクラスのnewFixedThreadPoolメソッドを使用します。 生成したいスレッド数を引数で受け取りタスク持ちのスレッドをを保持するスレッドプールを作成します。 Sample.java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Sample { public static void main (String[] args) { //3つタスクを持つ新しいスレッドを作成 ExecutorService exec = Executors.newFixedThreadPool(3); //タスクを与えて、実行する for(int i =0 ; i<3;i++) { exec.submit(()->{ System.out.println(Thread.currentThread().getId()); }); } } } 12 13 14 必要に応じてスレッドを増減させる場合 newCatchedThreadPoolメソッドを使用します。 下記のコードはsubmitメソッドでスレッドを5つ作成し、sleepメソッドで60秒間mainメソッドを停止させています。1度生成されたスレッドは60秒間使用されないと破棄されます。また、submitクラスで新しいスレッドを作成しています。 Sample.java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Sample { public static void main (String[] args) throws InterruptedException { //タスクを持つ新しいスレッドを作成 ExecutorService exec = Executors.newCachedThreadPool(); Runnable test = () ->{ System.out.println(Thread.currentThread().getId()); }; //タスクを与えて、実行する for(int i =0 ; i<3;i++) { exec.submit(test); } //65秒間mainメソッドを停止 Thread.sleep(1 * 65000); System.out.println("------65秒後-------"); //タスクを与えて、実行する for(int i =0 ; i<5;i++) { exec.submit(test); } } } 12 14 13 ------65秒後------- 16 17 18 18 19 ②SucheduleExecutorServiceインターフェースの利用 処理を実行するタイミングを制御できます。今回は3つのパターンを解説していきます。 処理を1回だけ遅延時間を設定したい場合 ExecutorsクラスのnewSingleThreadScheduledExecutorメソッドを使います。遅延実行するにはSucheduleExecutorServiceのsucheduleメソッドを使用します。3つの引数を受け取ります。第1引数にはRunnable型の実行したい処理を入れます。第2引数にはlong型の遅延させる時間を入れます。第3引数にはTimeUnitを使用した、時間の単位を入れます。 Sample.java public class Sample { public static void main (String[] args) throws InterruptedException { //新しいスレッドを1つ作成 ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor(); //4秒後に処理を実行 exec.schedule(()->{ System.out.println("だー!"); exec.shutdown(); },4,TimeUnit.SECONDS); int count = 0; //4秒待機している間に実行 while(true){ Thread.sleep(1000); if(exec.isShutdown()) { break; } System.out.println(++count); } } } 1 2 3 だー! 定期的に繰り返し実行する場合 scheduleAtFixedRateメソッドを使用します。第1引数にRunnable型の実行したい処理を入れます。第2引数に、初期遅延。第3引数にインターバルを指定します。第4引数に時間単位を指定します。 Sample.java import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class Sample { public static void main (String[] args) throws InterruptedException { //新しいスレッドを1つ作成 ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor(); //1秒ごとに処理を実行 exec.scheduleAtFixedRate(()->{ System.out.println("interrupt"); },1,1,TimeUnit.SECONDS); int count = 0; //countが50になるまでループ while(true){ Thread.sleep(100); System.out.print(">"); count++; if(count==50) { exec.shutdown(); break; } } } } >>>>>>>>>interrupt >>>>>>>>>>interrupt >>>>>>>>>interrupt >>>>>>>>>>interrupt >>>>>>>>>interrupt >>> 処理の時間関係なく、インターバルを一定にしたい場合 scheduleWithFixedDelayメソッドを使用します。 引数は第1引数にRunnable型の処理、第2引数に初期遅延、第3引数にインターバル、第4引数にインターバルの単位を受け取ります。 Sample.java import java.util.Random; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class Sample { public static void main (String[] args) throws InterruptedException { //新しいスレッドを1つ作成 ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor(); //一定のタイミングで実行 exec.scheduleWithFixedDelay(()->{ //ランダムなタイミングで実行 int r = new Random().nextInt(10); System.out.print(r); try { Thread.sleep(r*100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("interrupt"); },1,1,TimeUnit.SECONDS); int count = 0; //countが50になるまでループ while(true){ Thread.sleep(100); System.out.print(">"); count++; if(count==50) { exec.shutdown(); break; } } } } 下記の数字部分を見れば分るように、処理が一定だと言うことがわかります。 >>>>>>>>>1>interrupt >>>>>>>>>>6>>>>>>interrupt >>>>>>>>>5>>>>>interrupt >>>>>>>>>> mainメソッドからスレッドの結果を受け取る方法 ThreadクラスやRunnableインtーフェースでは、mainメソッドからでは、結果を知ることが出来ません。その場合はFutureインターフェースを使います。スレッドの処理結果を受け取るには、getメソッドを使用します。処理が終了すればnullを戻します。 Sample.java import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Sample { public static void main (String[] args) throws InterruptedException, ExecutionException { //スレッドを1つ作成 ExecutorService exec = Executors .newSingleThreadExecutor(); Future future = exec.submit(()->{ try { System.out.println("start"); Thread.sleep(2000); System.out.println("end"); }catch(InterruptedException e) { throw new RuntimeException(e); } }); //最後にnullを戻ったら、finishを出力 if(future.get() == null) { System.out.println("finish"); } } } start end finish null以外を戻したい場合 submitメソッドを使用します。第1引数は処理を指定し、最後に戻したいものを第2引数で指定します。 Sample.java public class Sample { public static void main (String[] args) throws InterruptedException, ExecutionException { //スレッドを1つ作成 ExecutorService exec = Executors .newSingleThreadExecutor(); Future future = exec.submit(()->{ try { System.out.println("start"); Thread.sleep(2000); System.out.println("end"); }catch(InterruptedException e) { throw new RuntimeException(e); } },"finish"); Object result = future.get(); System.out.println(result); } } start end finish 処理や、例外をスローした場合 getメソッドは固定の値しか戻せません。なので、検査例外をスローしたり、処理を戻したり出来ません。ここで使用するのが、Callableです。唯一callメソッド持っていいて、Callableのインスタンスを取得する際に指定する際に型パラメータとなります。 Sample.java import java.util.List; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Sample { public static void main (String[] args) throws InterruptedException, ExecutionException { //スレッドを1つ作成 ExecutorService exec = Executors.newSingleThreadExecutor(); //偶数かどうか調べる Callable <Boolean> task = new Callable<Boolean>() { public Boolean call() throws Exception{ return new Random().nextInt()%2 ==0; } }; List<Future<Boolean>> futures = new ArrayList<>(); for(int i = 0; i<5 ; i++) { futures.add(exec.submit(task)); } //trueの合計を求める int total = 0; for(Future<Boolean>future : futures) { Boolean result = future.get(); System.out.println(result); if(result) { total++; } } System.out.println(total); } } false true true true false 3 複数のスレッドが並行して実行されているときに、処理の順番を制御する(同期化処理)方法 CyclicBariierクラスのインスタンスを生成するとき、同期を取るスレッドの数とバリアーアクションをコンストラクターで受け取ります。第1引数に同期化したいスレッドの数、第2引数にバリアアクションが入ります。バリアアクションとは、複数のスレッドが待ち合わせるポイントに全てのスレッドが到達した時の処理。 Task.java import java.util.Random; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class Task implements Runnable{ private CyclicBarrier barrier; public Task(CyclicBarrier barrier) { super(); this.barrier = barrier; } @Override public void run() { long id = Thread.currentThread().getId(); System.out.println("START" + id); int r = new Random().nextInt(10); try { Thread.sleep(r*100); }catch(InterruptedException e) { throw new RuntimeException(e); } System.out.println("END"+id); try { //処理を中断 this.barrier.await(); }catch(InterruptedException | BrokenBarrierException e) { throw new RuntimeException(e); } } } Sample.java import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Sample { public static void main(String[] args) { ExecutorService exec = Executors.newFixedThreadPool(3); CyclicBarrier barrier = new CyclicBarrier(3,()->{ System.out.println("it's all done."); }); for(int i = 0 ; i<3; i++) { exec.submit(new Task(barrier)); } } } START14 START12 START13 END13 END12 END14 it's all done. 終わりに 複数のスレッドを使用すると、一つのインスタンスに複数のスレッドが共有して起こる「競合」という問題が起きてしまいます。競合については、別の記事にまとめましたので、こちらを参考にしてください。 参考文献 徹底攻略Java SE 11 Gold問題集
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

勉強会のためにGitLabのパイプライン機能で競プロっぽい環境を作ってみた

以前作ったCI環境に一手間加えて、自社の社員が楽しみながらJavaを学べるような環境を整えてみた。 できるだけ手軽にJavaのプログラムを体験できるように準備しました。 構成 今回のシステムの構成はこのような感じ。 生徒がgitlabにソースコードをプッシュする gitlab-ci.yamlにジョブを登録しておき、適当な環境でコードをコンパイル、実行する 実行結果をslackに投稿 パイプライン設定 前回ci環境を整えた後色々調べた所、gitlabのrunnerの設定を変更すればプリインストールされているpowershellを使えることがわかった。 それも踏まえて、下記のようなパイプラインを構築してみた。 下記のパイプラインを参考にする場合、絶対に不特定多数がアクセスできるようにはしないでください gitlab-ci.yamlのサンプル variables: CHAT_SCRIPT: 'C:\GitLab-Runner\local_scripts\chat.py' CHECK_SCRIPT: 'C:\GitLab-Runner\local_scripts\score.py' VENV_PATH: 'C:\GitLab-Runner\local_scripts\venv\Scripts\activate.ps1' JAVAC_HOME: 'C:\gitlab-runner\jdk-17.0.2\bin\' default: before_script: - CHCP 65001 stages: # List of stages for jobs, and their order of execution - build - run cache: untracked: true build-job: # This job runs in the build stage, which runs first. stage: build tags: - windows rules: - if: '$CI_PIPELINE_SOURCE == "push"' script: - $PSDefaultParameterValues['Out-File:Encoding'] = 'ascii' - gci *.java -Recurse | Get-ItemPropertyValue -Name FullName > files.log - '& "$($env:JAVAC_HOME)javac.exe" "@files.log" -encoding UTF-8' after_script: - '& "$env:VENV_PATH"' - $player_name = git log --pretty=format:"%an" -1 - python "$env:CHAT_SCRIPT" "$player_name" "$env:CI_JOB_STATUS" calc-score-job: # This job runs in the test stage. stage: run # It only starts when the job in the build stage completes successfully. tags: - windows script: - '& "$($env:JAVAC_HOME)java.exe" -cp ./src/main/java com.practice.numberguesser.Main > result.log' - '& "$env:VENV_PATH"' - $player_name = git log --pretty=format:"%an" -1 - python "$env:CHECK_SCRIPT" "$player_name" artifacts: paths: - result.log expire_in: 1 day パイプライン解説 パイプラインと言うよりpowershellの解説って言ったほうが近いかも。 variablesにJavaのパスを登録してある理由 環境の再現性を取りやすいという建前、ユーザを指定してのgitlabサービスを何故か建てれなかったってのが本音 CHCP 65001 powershellの使用する文字コードをUTF-8に変更する。gitlab上での表示を文字化けさせないようにするために必要。 $PSDefaultParameterValues['Out-File:Encoding'] = 'ascii' PowershellのパイプラインはUTF-16という圧倒的な取り回しの悪さなのでUTF-8で読み込めるように設定 gci *.java -Recurse | Get-ItemPropertyValue -Name FullName > files.log コンパイル対象のコードを一覧取得してファイルに格納する。 '& "$($env:JAVAC_HOME)javac.exe" "@files.log" -encoding UTF-8' 上で書いた対象ファイルをコンパイルする。gitのエンコーディングは基本的にUTF-8となる。 $player_name = git log --pretty=format:"%an" -1 最後にコミットした人物名を出力するコマンド(git log以降)をプレイヤー名として記録 '& "$($env:JAVAC_HOME)java.exe" -cp ./src/main/java com.practice.numberguesser.Main > result.log' コンパイルされたバイナリを実行する場合、クラスパスのルートを指定しなければきちんと動かないので注意。 pythonのコードはslackにメッセージを送るためのコードでした。公式のAPIにメッセージの送り方までコードが載っているので読んで参考にしました。 ちなみに今回使ったコードはここで見れるようにしています。 大雑把に説明するとハイアンドロー形式で数字を当てるときの効率の良さを競う感じにしています。 その結果を標準出力経由でファイルに出力して、点数をslackに乗せています。 投稿サンプル slackの様子はこんな感じ。 100までの数字のハイアンドローの数あてゲームで10万回問い合わせした時、何回正解できたかをスコアにしています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む