Проблема с кучей Java в моем плеере MCTS Gomoku - PullRequest
0 голосов
/ 30 ноября 2018

Когда я запускаю свою программу, я получаю эту ошибку:

Exception in thread "AWT-EventQueue-0" java.lang.OutOfMemoryError: Java heap space
        at MCTSNode.setPossibleMoves(MCTSNode.java:66)
        at MCTSNode.Expand(MCTSNode.java:167)
        at MctsPlayer.getBestMove(MctsPlayer.java:39)
        at NewBoardGUI.btnClick(NewBoardGUI.java:617)
        at NewBoardGUI.lambda$createButton$0(NewBoardGUI.java:584)
        at NewBoardGUI$$Lambda$115/558922244.actionPerformed(Unknown Source)
        at java.desktop/javax.swing.AbstractButton.fireActionPerformed(Unknown Source)
        at java.desktop/javax.swing.AbstractButton$Handler.actionPerformed(Unknown Source)
        at java.desktop/javax.swing.DefaultButtonModel.fireActionPerformed(Unknown Source)
        at java.desktop/javax.swing.DefaultButtonModel.setPressed(Unknown Source)
        at java.desktop/javax.swing.plaf.basic.BasicButtonListener.mouseReleased(Unknown Source)
        at java.desktop/java.awt.Component.processMouseEvent(Unknown Source)
        at java.desktop/javax.swing.JComponent.processMouseEvent(Unknown Source)
        at java.desktop/java.awt.Component.processEvent(Unknown Source)
        at java.desktop/java.awt.Container.processEvent(Unknown Source)
        at java.desktop/java.awt.Component.dispatchEventImpl(Unknown Source)
        at java.desktop/java.awt.Container.dispatchEventImpl(Unknown Source)
        at java.desktop/java.awt.Component.dispatchEvent(Unknown Source)
        at java.desktop/java.awt.LightweightDispatcher.retargetMouseEvent(Unknown Source)
        at java.desktop/java.awt.LightweightDispatcher.processMouseEvent(Unknown Source)
        at java.desktop/java.awt.LightweightDispatcher.dispatchEvent(Unknown Source)
        at java.desktop/java.awt.Container.dispatchEventImpl(Unknown Source)
        at java.desktop/java.awt.Window.dispatchEventImpl(Unknown Source)
        at java.desktop/java.awt.Component.dispatchEvent(Unknown Source)
        at java.desktop/java.awt.EventQueue.dispatchEventImpl(Unknown Source)
        at java.desktop/java.awt.EventQueue.access$500(Unknown Source)
        at java.desktop/java.awt.EventQueue$3.run(Unknown Source)
        at java.desktop/java.awt.EventQueue$3.run(Unknown Source)
        at java.base/java.security.AccessController.doPrivileged(Native Method)
        at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
        at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(Unknown Source)
        at java.desktop/java.awt.EventQueue$4.run(Unknown Source)

Я использовал тот же код MCTS для платы размером 3x3, которая не дает сбоя и быстро возвращает конкурентный ход.Но когда я пытаюсь использовать его для доски размером 15x15, игра вылетает после 1235 итераций с указанной выше ошибкой.

Я думаю, что справился с симптомом проблемы, не допуская расширения какого-либо узлапосле 1235 итераций.Это в конечном итоге возвращает конкурентное движение, хотя на это уходит много времени.

Для меня основная причина - размер дерева, которое я пытаюсь создать, поскольку тот же код работал с 3x3доска, но не с доской 15х15;Размер дерева, содержащего все объекты узлов, слишком велик.Таким образом, это скорее проблема с этим подходом, чем с моим кодированием.

Я действительно подумал, что мог бы попробовать: после x итераций, если узел был посещен y раз, но имеет выигрышный счет ниже z, то удалитеэтот узел.Я думаю, что если после x итераций и посещения y раз, но все еще с низким выигрышным счетом, то этот узел, вероятно, занимает ненужное место в дереве, и поэтому может позволить себе быть удаленным.

Мой вопросis:

Есть ли лучший способ для моей программы возвращать движение, а не сбой, не просто уменьшая количество расширений и не выполняя вышеуказанную проверку?(Даже если лучший ход занимает много времени для расчета).

Вот мой неотредактированный код:

EDITED ** Функция расширения MCTS:

public MCTSNode Expand(BoardGame game){
    MCTSNode child = new MCTSNode(game);
    for(int k = 0;k<this.gameState[0].length;k++){
      for(int l = 0;l<this.gameState[1].length;l++){
        child.gameState[k][l] = this.gameState[k][l];
      }
    }
    Random r = new Random();
    int possibleMoveSelected = r.nextInt(getPossibleMovesList());
    int row = getPossibleMoveX(possibleMoveSelected);
    int col = getPossibleMoveY(possibleMoveSelected);
    if(this.currentPlayer==2){
      child.gameState[row][col] = 2;
      child.moveMadeRow = row;
      child.moveMadeCol = col;
      child.currentPlayer = 1;
      child.setPossibleMoves();
      child.possibleMoves.size();
    }
    else{
      child.gameState[row][col] = 1;
      child.moveMadeRow = row;
      child.moveMadeCol = col;
      child.currentPlayer = 2;
      child.setPossibleMoves();
      child.possibleMoves.size();
    }
    childrenNode.add(child);
    child.parentNode = this;
    this.removePossibleMove(possibleMoveSelected);
    this.possibleMoves.trimToSize();
    return this;
}

Функция MCTSPlayer:

public class MctsPlayer {

  private static int maxIterations;

  public MctsPlayer(int i){
    maxIterations = i;
  }


  public static String getBestMove(BoardGame game){
    MCTSNode root = new MCTSNode(game);
    root.getBoardState(game);
    root.setPossibleMoves();
    for(int iteration = 0; iteration < maxIterations; iteration++){
      MCTSNode initialNode = selectInitialNode(root);
      if(initialNode.getPossibleMovesList()>0){
        initialNode.Expand(game);
      }
      MCTSNode nodeSelected = initialNode;
      if(nodeSelected.childrenLeft() == true){
        nodeSelected = initialNode.getRNDChild();
      }
      nodeSelected.Simulate();
    }

    MCTSNode best = root.getMostVisitNode();
    System.out.println("This is the selected node's best move for the row: "+best.getMoveMadeRow());
    System.out.println("This is the selected node's best move for the col: "+best.getMoveMadeCol());
    best.printNodeInfo();
  }

ЗНАЧИТЕЛЬНО ВКЛЮЧЕНО НИЖЕ **

Выберите начальную функцию узла (будет продолжаться до тех пор, пока возможный размер списка перемещения не будет == до 0):

public static MCTSNode selectInitialNode(MCTSNode node){
    MCTSNode initialNode = node;
    while (initialNode.getPossibleMovesSize()==0&&initialNode.checkForEmptySpace()==true){
      initialNode = initialNode.Select();

"+ initialNode.childrenList ()); //System.out.println("Nodes возможные оставшиеся перемещения:" + initialNode.getPossibleMovesSize ());} return initialNode;}

Выбрать функцию:

public MCTSNode Select(){
  double maxUCT = Integer.MIN_VALUE;
  MCTSNode Node = this;
  if(this.possibleMoves.size()>0){
    return Node;
      }
  else{
    for(int i = 0;i<childrenNode.size();i++){
      double UCTValue = getUCTValue(getChildren(i));
      if(UCTValue > maxUCT){
        Node = getChildren(i);
        maxUCT = UCTValue;
      }
    }
    return Node;
  }

private double getUCTValue(MCTSNode childNode) {
        double UCTValue;
        if (childNode.getVisitCount() >= 1) {
          UCTValue = (Math.sqrt(2)*
              (Math.sqrt(Math.log(childNode.getParent().getVisitCount()* 1.0) / childNode.getVisitCount())) + (1.0 *childNode.getWinCount() / childNode.getVisitCount()* 1.0));
        } else {
            UCTValue = Double.MAX_VALUE;
        }
        return UCTValue;
  }

Функция childrenLeft:

public boolean childrenLeft(){
  return childrenNode.size()>0;
}

1 Ответ

0 голосов
/ 30 ноября 2018

Я не уверен на 100%, не увидев код методов типа childrenLeft() и некоторых других, но у меня складывается впечатление, что вы в основном добавляете b новых узлов в дерево, где b - это ваше ветвлениефактор.Другими словами, на каждой итерации вы добавляете новый, полный список дочерних элементов в один узел.Это, вероятно, действительно может привести к тому, что вам быстро не хватит памяти.

На сегодняшний день наиболее распространенной стратегией является расширение вашего дерева путем добавления только одного нового узла за итерацию.Затем каждому узлу необходимо:

  • Список текущих дочерних элементов (соответствует уже развернутым действиям)
  • Список действий, которые еще не были расширены

Ваша фаза выбора, как правило, заканчивается, как только она достигает узла, у которого есть непустой список действий, которые необходимо расширить.Затем MCTS случайным образом выберет одно действие из этого списка, добавит новый узел, соответствующий этому действию (т.е. ваш первый список увеличивается на одну запись, а второй список уменьшится на одну запись), и продолжит развертывание оттуда.

При такой реализации маловероятно, что не хватит памяти, если вы не позволите вашему алгоритму выполнять поиск в течение очень долгого времени.Если у вас все еще не хватает памяти, вы можете посмотреть на такие вещи, как:

  • Оптимизация объема памяти, необходимого для узла (он хранит полные игровые состояния, оптимизировано ли использование памяти игровыми состояниями?)
  • Увеличение размера кучи JVM с помощью аргументов командной строки (см. Увеличение размера кучи в Java )
...