- A+
最近在项目中,为了算法结果的可视化,需要用到混淆矩阵(Confusion Matrix),而网上资源大多是基于Python绘制的混淆矩阵,并且是输出图片格式,并不能响应用户点击,今天以一个简单的小例子,简述如何通过WPF绘制混淆矩阵,并可响应用户点击事件,仅供学习分享使用,如有不足之处,还请指正。
什么是混淆矩阵?
在机器学习中, 混淆矩阵是一个误差矩阵, 常用来可视化地评估监督学习算法的性能. 混淆矩阵大小为 (n_classes, n_classes) 的方阵, 其中 n_classes 表示类的数量. 这个矩阵的每一行表示真实类中的实例, 而每一列表示预测类中的实例 (Tensorflow 和 scikit-learn 采用的实现方式). 也可以是, 每一行表示预测类中的实例, 而每一列表示真实类中的实例 (Confusion matrix From Wikipedia 中的定义). 通过混淆矩阵, 可以很容易看出系统是否会弄混两个类, 这也是混淆矩阵名字的由来.
混淆矩阵是一种特殊类型的列联表(contingency table)或交叉制表(cross tabulation or crosstab). 其有两维 (真实值 "actual" 和 预测值 "predicted" ), 这两维都具有相同的类("classes")的集合. 在列联表中, 每个维度和类的组合是一个变量. 列联表以表的形式, 可视化地表示多个变量的频率分布.
对于应用程序开发人员而言,混淆矩阵就是一个二维数组,分别表示预测值和真实值,里面的值表示对应值的占比。
开发步骤
创建WPF应用程序项目
在了解了混淆矩阵的用途和原理后,就可以着手去开发,首先创建一个WPF应用程序项目,然后创建模型MatrixM,主要包括标题,x轴,y轴的标签和刻度说明,数据,颜色设置。如下所示:
public class MatrixM:ObservableObject { private string title; public string Title { get { return title; } set { SetProperty(ref title, value); } } private string xLabel; public string XLabel { get { return xLabel; } set { SetProperty(ref xLabel, value); } } private string yLabel; public string YLabel { get { return yLabel; } set { SetProperty(ref yLabel,value); } } private string[] yaxis; public string[] Yaxis { get { return yaxis; } set { SetProperty(ref yaxis, value); } } private string[] xaxis; public string[] Xaxis { get { return xaxis; } set { SetProperty(ref xaxis, value); } } public double[,] Data { get; set; } private Color minBrush; public Color MinBrush { get { return minBrush; } set { SetProperty(ref minBrush, value); } } private Color maxBrush; public Color MaxBrush { get { return maxBrush; } set { SetProperty(ref maxBrush, value); } } }
注意:在本示例中,矩阵的数据采用二维数组进行存储。
构造数据
构造示例数据,在实际开发中,数据来源于算法的真实分析,本例主要用于演示前端开发,所以构造一些测试数据,如下所示:
private MatrixM dataM; public MatrixM DataM { get { return dataM; } set { SetProperty(ref dataM,value); } } private UniformGrid matrix; public MainWindowViewModel() { this.DataM = new MatrixM(); this.DataM.Title = "Confusion Matrix on Fer2024"; this.DataM.XLabel = "Predict Label"; this.DataM.YLabel = "Truth Label"; this.DataM.Xaxis = new string[] { "Angry","Disgust","Fear","Happy","Sad","Surprise","Neutral"}; this.DataM.Yaxis = new string[] { "Angry", "Disgust", "Fear", "Happy", "Sad", "Surprise", "Neutral" }; this.DataM.MinBrush = Colors.White; this.DataM.MaxBrush= Colors.DarkSlateBlue; this.DataM.Data = new double[,] { {0.66,0.01,0.09,0.04,0.11,0.01,0.09 }, {0.23,0.64,0.0,0.04,0.09,0.0,0.0}, {0.08,0.0,0.58,0.02,0.15,0.08,0.1}, {0.01,0.0,0.01,0.89,0.01,0.02,0.06 }, {0.09,0.0,0.11,0.03,0.6,0.01,0.15}, {0.02,0.0,0.05,0.04,0.02,0.85,0.02 }, {0.05,0.0,0.04,0.07,0.11,0.01,0.72 } }; }
页面布局
在WPF中,为了弹性呈现数据及自动缩放,主要用Grid,UniformGrid进行页面布局,交互主要用Button来实现,都是基础知识。如下所示:
<Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> <TextBlock Text="{Binding DataM.Title}" FontSize="20" FontWeight="Bold" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="5"></TextBlock> <TextBlock Text="{Binding DataM.YLabel}" Grid.Row="1" Grid.Column="0" FontWeight="Bold" FontSize="14" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="5"> <TextBlock.LayoutTransform> <RotateTransform Angle="270"></RotateTransform> </TextBlock.LayoutTransform> </TextBlock> <Grid Grid.Row="1" Grid.Column="1"> <Grid.RowDefinitions> <RowDefinition Height="*"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> <ColumnDefinition Width="Auto"></ColumnDefinition> </Grid.ColumnDefinitions> <ItemsControl Grid.Row="0" Grid.Column="0" Grid.RowSpan="1" ItemsSource="{Binding DataM.Yaxis}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <UniformGrid Columns="1"></UniformGrid> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Right"> <TextBlock Text="{Binding}"></TextBlock> <Border Width="10" Height="1" BorderBrush="Black" BorderThickness="1" HorizontalAlignment="Right" Margin="10 0 0 0"></Border> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> <Border BorderBrush="Black" BorderThickness="1" Grid.Row="0" Grid.Column="1" > <UniformGrid x:Name="matrix" > <UniformGrid.Resources> <Style TargetType="Button"> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Opacity" Value="0.5"></Setter> <Setter Property="FontWeight" Value="Bold"></Setter> <Setter Property="FontSize" Value="16"></Setter> <Setter Property="Cursor" Value="Hand"></Setter> </Trigger> </Style.Triggers> </Style> </UniformGrid.Resources> </UniformGrid> </Border> <ItemsControl Grid.Row="1" Grid.Column="1" ItemsSource="{Binding DataM.Xaxis}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <UniformGrid Rows="1"></UniformGrid> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Vertical" VerticalAlignment="Top" HorizontalAlignment="Center"> <Border Width="10" Height="1" BorderBrush="Black" BorderThickness="1" HorizontalAlignment="Center" Margin="0 0 0 0"> <Border.RenderTransformOrigin> <Point X="0.5" Y="0.5"></Point> </Border.RenderTransformOrigin> <Border.LayoutTransform> <TransformGroup> <RotateTransform Angle="90"></RotateTransform> </TransformGroup> </Border.LayoutTransform> </Border> <TextBlock Text="{Binding}"> <TextBlock.RenderTransformOrigin> <Point X="0.5" Y="0.5"></Point> </TextBlock.RenderTransformOrigin> <TextBlock.LayoutTransform> <TransformGroup> <RotateTransform Angle="-45"></RotateTransform> <TranslateTransform Y="20"></TranslateTransform> </TransformGroup> </TextBlock.LayoutTransform> </TextBlock> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> <Rectangle Grid.Row="0" Grid.Column="2" Width="20" Margin="10 0"> <Rectangle.Fill> <LinearGradientBrush StartPoint="0 0" EndPoint="1 1"> <GradientStop Offset="0" Color="{Binding DataM.MaxBrush}"></GradientStop> <GradientStop Offset="1" Color="{Binding DataM.MinBrush}"></GradientStop> </LinearGradientBrush> </Rectangle.Fill> </Rectangle> </Grid> <TextBlock Text="{Binding DataM.XLabel}" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" FontWeight="Bold" FontSize="14" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="5"></TextBlock> </Grid>
注意:标题,刻度,轴说明 ,都是固定布局,可以进行数据绑定,而矩阵内容需要动态创建,所以分开处理。
构造矩阵
在本示例中,混淆矩阵以UniformGrid为容器,动态根据数据创建,并填充到容器中,如下所示:
private IRelayCommand<object> loadedCommand; public IRelayCommand<object> LoadedCommand =>loadedCommand??=new RelayCommand<object>(Loaded); private void Loaded(object obj) { if (obj != null) { var win = obj as MainWindow; if (win != null) { this.matrix = win.matrix; InitMatrix(); } } } private void InitMatrix() { if(this.matrix == null) { return; } this.matrix.Children.Clear(); this.matrix.Rows = this.DataM.Data.GetLength(0); this.matrix.Columns = this.DataM.Data.GetLength(1); var color = this.DataM.MaxBrush; for(int row = 0; row < this.DataM.Data.GetLength(0); row++) { for(int col = 0; col < this.DataM.Data.GetLength(1); col++) { Border border = new Border(); border.Background = new SolidColorBrush(Color.FromArgb((byte)(this.DataM.Data[row, col] * color.A), color.R, color.G, color.B)); var button = new Button(); button.Content = this.DataM.Data[row, col].ToString("0.00"); button.FontSize = 12; button.HorizontalContentAlignment= System.Windows.HorizontalAlignment.Center; button.VerticalContentAlignment= System.Windows.VerticalAlignment.Center; button.Background = Brushes.Transparent; button.BorderThickness = new System.Windows.Thickness(0); border.Child= button; this.matrix.Children.Add(border); } } }
注意,在Grid,UniformGrid此类容器中,控件不需要设置宽和高,会自动根据容器大小进行自适应。且不能设置对齐属性,否则控件大小则不会自适应调整大小。
示例效果
运行VS,实例效果如下所示:
以上就是【基于WPF开发动态可交互混淆矩阵】的全部内容。希望可以一起学习,共同进步。