使用 pgfplots 生成 Hinton 图

使用 pgfplots 生成 Hinton 图

在我之前的一次问题,宏伟的杰克帮助我生成与在 MATLAB 中的二维矩阵上使用 imagesc 相同的热图。

结果看起来很棒,但现在我想修改它的行为以创建 Hinton 图。Hinton 图是一种可视化神经网络中权重的大小和方向的酷方法。它不使用颜色图来决定每个“像素”的颜色(例如,黑色为 -1,白色为 1,介于两者之间的任何颜色都是灰色),而是分别使用颜色 x 和 y 表示负值和正值,并通过方形标记的大小来可视化大小。下面是来自Python 解释(该代码可能会启发某些人)。

在此处输入图片描述

理想情况下,可以将所有这些都隐藏在一种风格中。

作为奖励,如果能够读取以矩阵的自然表示形式存储数据的文件就太好了:

 2.76658    -0.07990     0.10551    -2.94131     0.00840     0.23832    -2.56759     0.04593
-0.21476     2.37350    -2.30670     0.11634    -2.36124     2.62034    -0.32261     2.36860
 0.01118    -2.42926     2.42470     0.08698     2.45065    -2.39544    -0.16931    -2.32933
-3.04568    -0.15376     0.03051     2.74830    -0.07237     0.02359     2.96758     0.05812
-0.12791    -2.50370     2.63524     0.15000     2.26310    -2.39198    -0.05032    -2.41050
-0.07663     2.40350    -2.45346    -0.00479    -2.62160     2.29896    -0.11746     2.49031
-2.90385    -0.11742    -0.15037     2.88956     0.01517    -0.06700     2.96463    -0.10442
-0.17761     2.01661    -2.23660    -0.07113    -2.39688     2.19306     0.07902     2.37361

或者,如果这不可行,则采用如下传统的 xy 数据格式:

0 0 2.766580
1 0 -0.079900
2 0 0.105510
3 0 -2.941310
4 0 0.008400
5 0 0.238320
6 0 -2.567590
7 0 0.045930
0 1 -0.214760
1 1 2.373500
2 1 -2.306700
3 1 0.116340
4 1 -2.361240
5 1 2.620340
6 1 -0.322610
7 1 2.368600
0 2 0.011180
1 2 -2.429260
2 2 2.424700
3 2 0.086980
4 2 2.450650
5 2 -2.395440
6 2 -0.169310
7 2 -2.329330
0 3 -3.045680
1 3 -0.153760
2 3 0.030510
3 3 2.748300
4 3 -0.072370
5 3 0.023590
6 3 2.967580
7 3 0.058120
0 4 -0.127910
1 4 -2.503700
2 4 2.635240
3 4 0.150000
4 4 2.263100
5 4 -2.391980
6 4 -0.050320
7 4 -2.410500
0 5 -0.076630
1 5 2.403500
2 5 -2.453460
3 5 -0.004790
4 5 -2.621600
5 5 2.298960
6 5 -0.117460
7 5 2.490310
0 6 -2.903850
1 6 -0.117420
2 6 -0.150370
3 6 2.889560
4 6 0.015170
5 6 -0.067000
6 6 2.964630
7 6 -0.104420
0 7 -0.177610
1 7 2.016610
2 7 -2.236600
3 7 -0.071130
4 7 -2.396880
5 7 2.193060
6 7 0.079020
7 7 2.373610

您可以使用如下 Python 脚本从第一种数据格式生成第二种数据格式:

import numpy as np
rawdata = np.loadtxt('data.dat')
coords = np.meshgrid(np.arange(np.shape(rawdata)[0]),
                     np.arange(np.shape(rawdata)[1]))
data = np.vstack([coords[0].flatten(), coords[1].flatten(), rawdata.flatten()]).T
np.savetxt('weights-example.dat', data, fmt="%i %i %f")

TikZ这可能对大师们的要求有点过分,pgfplots因为我并没有真正努力去解决这个问题,但是,唉,我甚至不能完全理解杰克的代码是如何工作的。如果有人愿意帮忙,我想杰克的回答我应该从我之前的问题开始。

答案1

这是在 PGFPlots 中执行此操作的一种方法。我使用x,y,value数据而不是矩阵,因为使用 PGFPlots 处理数据更容易。

我已定义fenton可添加到axis选项中的样式。使用您的测试文件和以下代码...

\begin{axis}[fenton]
\addplot table [meta index=2] {weights-example.dat};
\end{axis}

... 你会得到

fenton/positive style={...}可以使用和来设置正值和负值的样式fenton/negative style={...}


完整代码如下:

\documentclass[border=5mm]{standalone}
\usepackage{pgfplots}
\pgfplotsset{compat=1.9}

\makeatletter
\pgfplotsset{
    fenton/positive/.style={fill=white, draw=none},
    fenton/positive style/.style={/pgfplots/fenton/positive/.append style={#1}},
    fenton/negative/.style={fill=black, draw=none},
    fenton/negative style/.style={/pgfplots/fenton/negative/.append style={#1}},
    fenton/.style={
        axis background/.style={fill=gray},
        only marks, no markers,
        scatter,
        axis equal image,
        axis on top,
        major tick length=0pt,
        enlarge x limits={abs=0.5},
        enlarge y limits={abs=0.5},
        point meta=explicit,
        scatter/@pre marker code/.code={
            \pgfkeys{/pgf/fpu,/pgf/fpu/output format=fixed}
            \pgfmathsetmacro\normalisedsize{
                sqrt(
                    abs(
                        \pgfplotspointmeta / max(
                            abs(\pgfplots@metamin),
                            abs(\pgfplots@metamax)
                        )
                    )
                )
            }
            \pgfmathfloatifflags{\pgfplotspointmeta}{+}{
                \pgfplotsset{fenton/current value/.style=/pgfplots/fenton/positive}
            }{
                \pgfplotsset{fenton/current value/.style=/pgfplots/fenton/negative}
            }
            \path [/pgfplots/fenton/current value] ([scale=\normalisedsize]axis direction cs:-0.5,-0.5) rectangle ([scale=\normalisedsize]axis direction cs:0.5,0.5);   
        },
        scatter/@post marker code/.code={}
    }
}
\makeatother

\begin{document}  

\begin{tikzpicture}
\begin{axis}[fenton]
\addplot table [meta index=2] {weights-example.dat};
\end{axis}
\end{tikzpicture}
\end{document}

答案2

我认为这样做可以。本来可以用纯方法完成tex,但今天我懒了,所以需要lualatex。文件读取方法是不是稳健。另外,我只使用了黑色或白色(中间没有阴影)。

\documentclass[tikz,border=.1cm]{standalone}
\usepackage{filecontents}

\begin{filecontents*}{data.dat}
2.76658    -0.07990     0.10551    -2.94131     0.00840     0.23832    -2.56759     0.04593
-0.21476     2.37350    -2.30670     0.11634    -2.36124     2.62034    -0.32261     2.36860
 0.01118    -2.42926     2.42470     0.08698     2.45065    -2.39544    -0.16931    -2.32933
-3.04568    -0.15376     0.03051     2.74830    -0.07237     0.02359     2.96758     0.05812
-0.12791    -2.50370     2.63524     0.15000     2.26310    -2.39198    -0.05032    -2.41050
-0.07663     2.40350    -2.45346    -0.00479    -2.62160     2.29896    -0.11746     2.49031
-2.90385    -0.11742    -0.15037     2.88956     0.01517    -0.06700     2.96463    -0.10442
-0.17761     2.01661    -2.23660    -0.07113    -2.39688     2.19306     0.07902     2.37361
\end{filecontents*}

{\catcode`\%=11\gdef\pc{%}}
\begin{document}
\directlua{%
Matrix = {}
Matrix.__index = Matrix

function Matrix.new()
  local obj = {nrow=0, ncol=0, max=-math.huge, min=math.huge, data={}}
  setmetatable(obj, Matrix)
  return obj
end
function Matrix.fromFile(f)
  local obj, file, line, row, v, c
  obj = Matrix.new()
  file = io.open(f)
  if file then
     for line in file:lines() do
       row = {}
       c = 0
       for v in string.gmatch(line, "\pc S+") do
          v = tonumber(v)
          if v > obj.max then obj.max = v end
          if v < obj.min then obj.min = v end
          table.insert(row, v)
          c = c + 1
       end
       table.insert(obj.data, row)
       obj.nrow = obj.nrow + 1
       if c > obj.ncol then
          obj.ncol = obj.ncol + 1
       end
     end
  end
  return obj
end

function Matrix:get(i, j)
  if self.data[i][j] then
    return self.data[i][j]
  else
    return 0
  end
end  

function Matrix:set(i, j, v)
  if self.data[i] == nil then
    table.insert(self.data, i, {})
  end
  table.insert(self.data[i], j, v)
  if i > self.nrow then self.nrow = i end
  if j > self.ncol then self.ncol = j end
end

function Matrix:normalize()
  local i, j, v
  for i = 1,self.ncol do
    for j = 1,self.nrow do
      self.data[i][j] = 2*(self.data[i][j] - self.min) / (self.max - self.min) - 1
    end
  end
  self.max = 1
  self.min = -1
end
}

\def\getlua#1{\directlua{tex.print("" .. #1)}}

\directlua{
  M = Matrix.fromFile("data.dat")
  M:normalize()
}
\newcommand\hinton[2][]{%
\begin{tikzpicture}[#1]
\fill [black!15] (0,0) rectangle ++(\getlua{#2.ncol}+1, \getlua{#2.nrow}+1);
\foreach \i in {1,...,\getlua{#2.ncol}}{
  \foreach \j [evaluate={\v=\getlua{#2:get(\i, \j)}; \c=(\v<0 ? 100 : 0); \s=\v; }] in {1,...,\getlua{#2.nrow}}{
    \fill [black!\c!white] (\i, \j) ++(-\s/2,-\s/2) rectangle ++(\s,\s);
  }}
\end{tikzpicture}}

\hinton{M}

\directlua{%
N = Matrix.new()
local i, j
for i = 1,25 do
  for j = 1,25 do
    N:set(i, j, math.random()*2-1)
  end
end
}

\hinton[x=5pt, y=5pt]{N}
\end{document}

在此处输入图片描述

在此处输入图片描述

答案3

正如评论中提到的,您可以将图形导出matplotlibpgf代码中,然后用 latex 编译它。我添加了一小段代码来生成一些虚假数据,并用 替换P.show()P.savefig('hinton_code.pgf')将图表写入文件:

import numpy as N
import pylab as P

def _blob(x,y,area,colour):
    hs = N.sqrt(area) / 2
    xcorners = N.array([x - hs, x + hs, x + hs, x - hs])
    ycorners = N.array([y - hs, y - hs, y + hs, y + hs])
    P.fill(xcorners, ycorners, colour, edgecolor=colour)

def hinton(W, maxWeight=None):
    reenable = False
    if P.isinteractive():
        P.ioff()
    P.clf()
    height, width = W.shape
    if not maxWeight:
        maxWeight = 2**N.ceil(N.log(N.max(N.abs(W)))/N.log(2))
    P.fill(N.array([0,width,width,0]),N.array([0,0,height,height]), 'gray')
    P.axis('off')
    P.axis('equal')
    for x in xrange(width):
        for y in xrange(height):
            _x = x+1
            _y = y+1
            w = W[y,x]
            if w > 0:
                _blob(_x - 0.5, height - _y + 0.5, min(1,w/maxWeight),'white')
            elif w < 0:
                _blob(_x - 0.5, height - _y + 0.5, min(1,-w/maxWeight), 'k')
    if reenable:
        P.ion()
    P.savefig('hinton_code.pgf')

if __name__ == "__main__":
    W = N.random.rand(15, 15) - 0.5*N.ones((15,15))
    hinton(W)

创建文档就很简单了

\documentclass{article}
\usepackage{pgf}
\begin{document}
\input{hinton_code.pgf}
\end{document}

此外,我希望在 Python 中读取数据文件比在 TeX 中容易得多。

答案4

这是一个仅使用 TikZ 的非常简单的解决方案:

\documentclass[tikz]{standalone}
\usetikzlibrary{fit,backgrounds}
\def\mymatrix{%
  {2.76658, -0.07990, 0.10551, -2.94131, 0.00840, 0.23832, -2.56759, 0.04593},
  {-0.21476, 2.37350, -2.30670, 0.11634, -2.36124, 2.62034, -0.32261, 2.36860},
  {0.01118, -2.42926, 2.42470, 0.08698, 2.45065, -2.39544, -0.16931, -2.32933},
  {-3.04568, -0.15376, 0.03051, 2.74830, -0.07237, 0.02359, 2.96758, 0.05812},
  {-0.12791, -2.50370, 2.63524, 0.15000, 2.26310, -2.39198, -0.05032, -2.41050},
  {-0.07663, 2.40350, -2.45346, -0.00479, -2.62160, 2.29896, -0.11746, 2.49031},
  {-2.90385, -0.11742, -0.15037, 2.88956, 0.01517, -0.06700, 2.96463, -0.10442},
  {-0.17761, 2.01661, -2.23660, -0.07113, -2.39688, 2.19306, 0.07902, 2.37361}%
}
\def\max{3}
\begin{document}
\begin{tikzpicture}
   \foreach \line [count=\y] in \mymatrix {
     \foreach \val [count=\x] in \line {
       \pgfmathsetmacro\col{(\val>=0)?"orange":"lime"}
       \pgfmathsetmacro\len{sqrt(abs(\val)/\max)/2}
       \fill[fill=\col] (\x,-\y) +(\len cm,\len cm) rectangle +(-\len cm,-\len cm);
     }
   }
   \begin{scope}[on background layer]
     \node[fill=gray,fit=(current bounding box),inner sep=1mm,draw]{};
   \end{scope}
\end{tikzpicture}
\end{document}

在此处输入图片描述

相关内容