sirven - Crear imagen de la estructura de red neuronal
redes neuronales ejemplos (2)
Muchos periódicos usan imágenes muy bonitas de redes neuronales. También me gusta crear una imagen para un informe que estoy escribiendo.
Un ejemplo: "SegNet: Un codificador convolucional profundo-Arquitectura del decodificador para la segmentación de imágenes" de V. Badrinarayanan et al., Página 4
https://arxiv.org/pdf/1511.00561v3.pdf
Mi pregunta: ¿Qué herramienta podría usarse para crear tales imágenes? Especialmente los rectángulos convvolución se ven muy bien.
Muchas gracias
Escribí una clase pequeña que ayuda a dibujar tales imágenes. Probablemente lo extenderé y lo limpiaré pronto.
Es posible definir algunas capas (colores / tipos), dibujarlas y agregar algunas conexiones entre ellas. El resultado es un archivo svg.
Código (al final (ver if __name__ == ''__main__''
) existe el código para generar la imagen de arriba):
import svgwrite
import math
def layer_def(type, height, id=None):
return {
''type'': type,
''height'': height,
''id'': id
}
class CNNDraw:
def __init__(self, dwg):
self.__dwg = dwg
def draw_arrow(self, p_points, width=2, color=''black''):
marker = self.__dwg.marker(insert=(2.1, 2), size=(2, 4), orient=''auto'')
marker.add(self.__dwg.path(d=''M0,0 V4 L2,2 Z'', fill=color))
self.__dwg.defs.add(marker)
line = self.__dwg.add(svgwrite.shapes.Polyline(
p_points, stroke_width=width,
stroke=''black'', fill=''none''))
line.set_markers((self.__dwg.marker(), self.__dwg.marker(), marker))
def draw_3d_rectangle(self, p_start, width, height, stretch, fg_color=''white'', bg_color=''grey'', border_color=''black''):
x_start, y_start = p_start
x_end, y_end = x_start + width, y_start + height
polygon1 = [(x_start, y_start), (x_start + width, y_start), (x_start + width, y_start + height), (x_start, y_start + height)]
polygon2 = [(x_start, y_start), (x_start + stretch, y_start - stretch), (x_end + stretch, y_start - stretch), (x_end, y_start)]
polygon3 = [(x_end, y_start), (x_end + stretch, y_start - stretch), (x_end + stretch, y_end - stretch), (x_end, y_end)]
self.__dwg.add(svgwrite.shapes.Polygon(polygon1, fill=fg_color, stroke=border_color))
self.__dwg.add(svgwrite.shapes.Polygon(polygon2, fill=bg_color, stroke=border_color))
self.__dwg.add(svgwrite.shapes.Polygon(polygon3, fill=bg_color, stroke=border_color))
def draw_multiple_layers(self, p_start, p_stretch=0.4, layer_width=10, space_width=7, layers=[], return_hash=True): # layers = (height, color)
if len(layers) == 0:
return
max_height = max(map(lambda l: l[1], filter(lambda l: isinstance(l, tuple), layers)))
x_offset = 0
side_width_before = 0
drawn_polygons = []
for n in xrange(len(layers)):
layer = layers[n]
if not isinstance(layer, tuple):
x_offset += layer
continue
id, height, fg_color, bg_color = layer
side_width = math.floor(height * p_stretch)
total_width = side_width + layer_width
if n > 0:
space_offset = space_width
if total_width < side_width_before:
space_offset = math.floor((side_width_before - total_width) * 0.5)
space_offset = max(space_width, space_offset)
pass
x_offset += space_offset
curr_p_start = (p_start[0] + x_offset, p_start[1] + math.floor((max_height - height) * 0.5))
self.draw_3d_rectangle(curr_p_start, layer_width, height, side_width, fg_color, bg_color)
drawn_polygons.append((id, curr_p_start, layer_width, height, side_width))
side_width_before = side_width
x_offset += layer_width
if return_hash:
polygons = {}
for polygon in filter(lambda l: l[0] is not None, drawn_polygons):
polygons[polygon[0]] = polygon[1:]
drawn_polygons = polygons
return drawn_polygons
def draw_multiple_defined_layers(self, p_start, layer_definitions={}, layers=[]):
plain_layers = []
for layer in layers:
if not isinstance(layer, dict):
plain_layers.append(layer)
else:
layer_definition = layer_definitions[layer[''type'']]
if ''ignore'' in layer_definition and layer_definition[''ignore'']:
continue
plain_layer = (layer[''id''], layer[''height''], layer_definition[''fg_color''], layer_definition[''bg_color''])
if ''add_space_before'' in layer_definition:
plain_layers.append(layer_definition[''add_space_before''])
plain_layers.append(plain_layer)
if ''add_space_after'' in layer_definition:
plain_layers.append(layer_definition[''add_space_after''])
return self.draw_multiple_layers(p_start, layers=plain_layers)
def draw_arrow_between_points(self, source_point, target_point, text=None, y_delta=10, bottom=False):
if not bottom:
y = min(source_point[1], target_point[1]) - y_delta
else:
y = max(source_point[1], target_point[1]) + y_delta
source_delta = source_point[1] - y
target_delta = target_point[1] - y
if not bottom:
mp0 = (source_point[0] + source_delta, source_point[1] - source_delta)
mp1 = (target_point[0] - target_delta, target_point[1] - target_delta)
else:
mp0 = (source_point[0] - source_delta, source_point[1] - source_delta)
mp1 = (target_point[0] + target_delta, target_point[1] - target_delta)
points = [
source_point,
mp0, mp1,
target_point,
]
self.draw_arrow(points)
# If required: Draw the text
if text is not None:
text_lines = text.split(''/n'')
line_height = 15
mp_middle = (math.floor((mp0[0] + mp1[0]) * 0.5), math.floor((mp0[1] + mp1[1]) * 0.5))
if not bottom:
y_offset = -10 - line_height * (len(text_lines) - 1)
else:
y_offset = 13
for text_line in text_lines:
mp_text = (mp_middle[0], mp_middle[1] + y_offset)
self.__dwg.add(self.__dwg.text(text_line, insert=mp_text, text_anchor="middle", style="font-family:Sans-Serif"))
y_offset += line_height
def draw_arrow_between_layers(self, source_layer, target_layer, text=None, y_delta=10):
source_point = (source_layer[0][0] + source_layer[1] + source_layer[3], source_layer[0][1] - source_layer[3])
target_point = (target_layer[0][0] + target_layer[3], target_layer[0][1] - target_layer[3])
self.draw_arrow_between_points(source_point, target_point, text, y_delta)
def draw_additive_arrow_between_layers(self, source_layers, target_layer, text=None, y_delta=10, y_add=0, bottom=True):
if bottom:
target_point = (target_layer[0][0], target_layer[0][1] + target_layer[2])
# Get the source points
sp0 = min(map(lambda l: (l[0][0] + math.floor(l[1] * 0.5), l[0][1] + l[2]), source_layers), key=lambda l: l[0])
sp1 = max(map(lambda l: (l[0][0] + math.floor(l[1] * 0.5), l[0][1] + l[2]), source_layers), key=lambda l: l[0])
sp_y = max(map(lambda l: l[0][1] + l[2], source_layers)) + 5
sp0d = (sp0[0], sp_y + y_add)
sp1d = (sp1[0], sp_y + y_add)
else:
target_point = (target_layer[0][0] + target_layer[3], target_layer[0][1] - target_layer[3])
# Get the source points
sp0 = min(map(lambda l: (l[0][0] + l[3] + math.floor(l[1] * 0.5), l[0][1] - l[3]), source_layers), key=lambda l: l[0])
sp1 = max(map(lambda l: (l[0][0] + l[3] + math.floor(l[1] * 0.5), l[0][1] - l[3]), source_layers), key=lambda l: l[0])
sp_y = min(map(lambda l: l[0][1] - l[3], source_layers)) - 5
sp0d = (sp0[0], sp_y - y_add)
sp1d = (sp1[0], sp_y - y_add)
self.__dwg.add(svgwrite.shapes.Polyline([sp0, sp0d], stroke_width=2, stroke=''black'', fill=''none''))
self.__dwg.add(svgwrite.shapes.Polyline([sp1, sp1d], stroke_width=2, stroke=''black'', fill=''none''))
if bottom:
mp0 = (sp0d[0] + y_delta, sp0d[1] + y_delta)
mp1 = (sp1d[0] - y_delta, sp1d[1] + y_delta)
else:
mp0 = (sp0d[0] + y_delta, sp0d[1] - y_delta)
mp1 = (sp1d[0] - y_delta, sp1d[1] - y_delta)
mp_middle = (math.floor((mp0[0] + mp1[0]) * 0.5), math.floor((mp0[1] + mp1[1]) * 0.5))
# Draw the first line
self.__dwg.add(svgwrite.shapes.Polyline(
[sp0d, mp0, mp1, sp1d], stroke_width=2,
stroke=''black'', fill=''none''))
# And now the line itself
self.draw_arrow_between_points(mp_middle, target_point, text, y_delta, bottom=bottom)
if __name__ == ''__main__'':
dwg = svgwrite.Drawing(''test.svg'')
cnn_draw = CNNDraw(dwg)
# Draw multiple layers
layer_polygons = cnn_draw.draw_multiple_defined_layers((100, 200), { # Start coordinate
# Define several layer types
''dropout'': {''fg_color'': ''#ffffcc'', ''bg_color'': ''#ffff99'', ''ignore'': True},
''conv'': {''fg_color'': ''#e6faff'', ''bg_color'': ''#99ebff''},
''t_conv'': {''fg_color'': ''#ffe6cc'', ''bg_color'': ''#ffcc99''},
''pool'': {''fg_color'': ''#ccffcc'', ''bg_color'': ''#99ff99'', ''add_space_after'': 20},
''upscale'': {''fg_color'': ''#e6ccff'', ''bg_color'': ''#cc99ff''}
}, [
# The layers to draw; as can be seen a layer can have an id
layer_def(''dropout'', 300),
layer_def(''conv'', 300),
layer_def(''conv'', 300),
layer_def(''pool'', 300, id=''POOL1''),
layer_def(''dropout'', 250),
layer_def(''conv'', 250),
layer_def(''conv'', 250),
layer_def(''pool'', 250, id=''POOL2''),
layer_def(''dropout'', 200),
layer_def(''conv'', 200),
layer_def(''conv'', 200),
layer_def(''pool'', 200, id=''POOL3''),
layer_def(''dropout'', 150),
layer_def(''conv'', 150),
layer_def(''conv'', 150),
layer_def(''pool'', 150, id=''POOL4''),
layer_def(''dropout'', 100),
layer_def(''conv'', 100),
layer_def(''conv'', 100),
layer_def(''pool'', 100, id=''POOL5''),
layer_def(''dropout'', 50),
layer_def(''conv'', 50),
layer_def(''conv'', 50),
layer_def(''pool'', 50, id=''POOL6''),
layer_def(''dropout'', 30),
layer_def(''conv'', 30, id=''FINAL''),
# Plain numbers are just space
100,
layer_def(''t_conv'', 50, id=''T_CONV1''),
60,
layer_def(''t_conv'', 100, id=''T_CONV2''),
60,
layer_def(''t_conv'', 150, id=''T_CONV3''),
60,
layer_def(''t_conv'', 250, id=''T_CONV4''),
layer_def(''conv'', 250),
layer_def(''conv'', 250),
layer_def(''conv'', 250),
layer_def(''dropout'', 250),
layer_def(''conv'', 250),
layer_def(''conv'', 250, id=''FINAL_CONV''),
50,
layer_def(''upscale'', 300, id=''UPSCALED'')
])
cnn_draw.draw_arrow_between_layers(layer_polygons[''FINAL''], layer_polygons[''T_CONV1''], text="transposed conv.")
cnn_draw.draw_additive_arrow_between_layers([layer_polygons[''POOL5''], layer_polygons[''T_CONV1'']], layer_polygons[''T_CONV2''], text="element-wise sum and/ntransposed conv.", y_add=0)
cnn_draw.draw_additive_arrow_between_layers([layer_polygons[''POOL4''], layer_polygons[''T_CONV2'']], layer_polygons[''T_CONV3''], text="element-wise sum and/ntransposed conv.", y_add=0, bottom=False)
cnn_draw.draw_additive_arrow_between_layers([layer_polygons[''POOL3''], layer_polygons[''T_CONV3'']], layer_polygons[''T_CONV4''], text="element-wise sum and/ntransposed conv.", y_add=0)
cnn_draw.draw_arrow_between_layers(layer_polygons[''FINAL_CONV''], layer_polygons[''UPSCALED''], text="upscale")
dwg.save()
Si desea obtener el código del programa, entonces D3.js es la mejor solución que le dará una imagen clara de limpieza como desee. También puede poner la lógica matemática allí.