{
"cells": [
{
"cell_type": "markdown",
"id": "215a6c3a",
"metadata": {},
"source": [
"# NN inference analysis\n",
"\n",
"Анализ CSV с результатами инференса: доля класса `drone`, частоты срабатываний, уверенность модели и интервалы между `drone`-классификациями."
]
},
{
"cell_type": "code",
"execution_count": 30,
"id": "4e8cff32",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CSV path: /home/sibscience-4/from_ssh/DroneDetector/logs/nn_results_live_6gb.csv\n",
"Rows: 27258\n",
"Time range: 2026-05-04 17:35:21.019627763+07:00 -> 2026-05-05 12:17:19.369858371+07:00\n",
"Missing freq rows: 0\n"
]
},
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" docker_timestamp | \n",
" event_time_iso | \n",
" event_time_epoch | \n",
" freq | \n",
" model_id | \n",
" model_type | \n",
" prediction | \n",
" probability | \n",
" ts | \n",
" local_time | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" 2026-05-04T10:35:21.019627763Z | \n",
" 2026-05-04T17:35:21+07:00 | \n",
" 1.777891e+09 | \n",
" 2400 | \n",
" 2 | \n",
" ensemble_2400_v44 | \n",
" drone | \n",
" 0.99 | \n",
" 2026-05-04 10:35:21.019627763+00:00 | \n",
" 2026-05-04 17:35:21.019627763+07:00 | \n",
"
\n",
" \n",
" | 1 | \n",
" 2026-05-04T10:35:21.019631281Z | \n",
" 2026-05-04T17:35:21+07:00 | \n",
" 1.777891e+09 | \n",
" 1200 | \n",
" 1 | \n",
" ensemble_1200_v44 | \n",
" noise | \n",
" 1.00 | \n",
" 2026-05-04 10:35:21.019631281+00:00 | \n",
" 2026-05-04 17:35:21.019631281+07:00 | \n",
"
\n",
" \n",
" | 2 | \n",
" 2026-05-04T10:35:27.048188525Z | \n",
" 2026-05-04T17:35:27+07:00 | \n",
" 1.777891e+09 | \n",
" 2400 | \n",
" 2 | \n",
" ensemble_2400_v44 | \n",
" drone | \n",
" 0.99 | \n",
" 2026-05-04 10:35:27.048188525+00:00 | \n",
" 2026-05-04 17:35:27.048188525+07:00 | \n",
"
\n",
" \n",
" | 3 | \n",
" 2026-05-04T10:35:29.238925690Z | \n",
" 2026-05-04T17:35:29+07:00 | \n",
" 1.777891e+09 | \n",
" 1200 | \n",
" 1 | \n",
" ensemble_1200_v44 | \n",
" noise | \n",
" 1.00 | \n",
" 2026-05-04 10:35:29.238925690+00:00 | \n",
" 2026-05-04 17:35:29.238925690+07:00 | \n",
"
\n",
" \n",
" | 4 | \n",
" 2026-05-04T10:35:32.842234116Z | \n",
" 2026-05-04T17:35:32+07:00 | \n",
" 1.777891e+09 | \n",
" 2400 | \n",
" 2 | \n",
" ensemble_2400_v44 | \n",
" drone | \n",
" 0.92 | \n",
" 2026-05-04 10:35:32.842234116+00:00 | \n",
" 2026-05-04 17:35:32.842234116+07:00 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" docker_timestamp event_time_iso \\\n",
"0 2026-05-04T10:35:21.019627763Z 2026-05-04T17:35:21+07:00 \n",
"1 2026-05-04T10:35:21.019631281Z 2026-05-04T17:35:21+07:00 \n",
"2 2026-05-04T10:35:27.048188525Z 2026-05-04T17:35:27+07:00 \n",
"3 2026-05-04T10:35:29.238925690Z 2026-05-04T17:35:29+07:00 \n",
"4 2026-05-04T10:35:32.842234116Z 2026-05-04T17:35:32+07:00 \n",
"\n",
" event_time_epoch freq model_id model_type prediction \\\n",
"0 1.777891e+09 2400 2 ensemble_2400_v44 drone \n",
"1 1.777891e+09 1200 1 ensemble_1200_v44 noise \n",
"2 1.777891e+09 2400 2 ensemble_2400_v44 drone \n",
"3 1.777891e+09 1200 1 ensemble_1200_v44 noise \n",
"4 1.777891e+09 2400 2 ensemble_2400_v44 drone \n",
"\n",
" probability ts \\\n",
"0 0.99 2026-05-04 10:35:21.019627763+00:00 \n",
"1 1.00 2026-05-04 10:35:21.019631281+00:00 \n",
"2 0.99 2026-05-04 10:35:27.048188525+00:00 \n",
"3 1.00 2026-05-04 10:35:29.238925690+00:00 \n",
"4 0.92 2026-05-04 10:35:32.842234116+00:00 \n",
"\n",
" local_time \n",
"0 2026-05-04 17:35:21.019627763+07:00 \n",
"1 2026-05-04 17:35:21.019631281+07:00 \n",
"2 2026-05-04 17:35:27.048188525+07:00 \n",
"3 2026-05-04 17:35:29.238925690+07:00 \n",
"4 2026-05-04 17:35:32.842234116+07:00 "
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"from pathlib import Path\n",
"import pandas as pd\n",
"\n",
"csv_path = Path('/home/sibscience-4/from_ssh/DroneDetector/logs/nn_results_live_6gb.csv')\n",
"df = pd.read_csv(csv_path)\n",
"\n",
"df['ts'] = pd.to_datetime(df['docker_timestamp'], utc=True)\n",
"df['local_time'] = df['ts'].dt.tz_convert('Asia/Novosibirsk')\n",
"df['freq'] = pd.to_numeric(df['freq'], errors='coerce').astype('Int64')\n",
"df['probability'] = pd.to_numeric(df['probability'], errors='coerce')\n",
"df = df.sort_values('ts').reset_index(drop=True)\n",
"\n",
"print(f'CSV path: {csv_path}')\n",
"print(f'Rows: {len(df)}')\n",
"print(f'Time range: {df[\"local_time\"].min()} -> {df[\"local_time\"].max()}')\n",
"print(f'Missing freq rows: {df[\"freq\"].isna().sum()}')\n",
"display(df.head())"
]
},
{
"cell_type": "markdown",
"id": "0fcc6dd6",
"metadata": {},
"source": [
"## Общая сводка по частотам и классам"
]
},
{
"cell_type": "code",
"execution_count": 31,
"id": "7bf0fc3f",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" freq | \n",
" prediction | \n",
" count | \n",
" avg_probability | \n",
" min_probability | \n",
" max_probability | \n",
" freq_total | \n",
" class_rate | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" 1200 | \n",
" drone | \n",
" 3 | \n",
" 0.840000 | \n",
" 0.78 | \n",
" 0.91 | \n",
" 13632 | \n",
" 0.000220 | \n",
"
\n",
" \n",
" | 1 | \n",
" 1200 | \n",
" noise | \n",
" 13629 | \n",
" 0.997436 | \n",
" 0.91 | \n",
" 1.00 | \n",
" 13632 | \n",
" 0.999780 | \n",
"
\n",
" \n",
" | 2 | \n",
" 2400 | \n",
" drone | \n",
" 11921 | \n",
" 0.868013 | \n",
" 0.50 | \n",
" 1.00 | \n",
" 13626 | \n",
" 0.874872 | \n",
"
\n",
" \n",
" | 3 | \n",
" 2400 | \n",
" noise | \n",
" 1705 | \n",
" 0.649185 | \n",
" 0.50 | \n",
" 1.00 | \n",
" 13626 | \n",
" 0.125128 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" freq prediction count avg_probability min_probability max_probability \\\n",
"0 1200 drone 3 0.840000 0.78 0.91 \n",
"1 1200 noise 13629 0.997436 0.91 1.00 \n",
"2 2400 drone 11921 0.868013 0.50 1.00 \n",
"3 2400 noise 1705 0.649185 0.50 1.00 \n",
"\n",
" freq_total class_rate \n",
"0 13632 0.000220 \n",
"1 13632 0.999780 \n",
"2 13626 0.874872 \n",
"3 13626 0.125128 "
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"class_summary = (\n",
" df.groupby(['freq', 'prediction'], dropna=False)\n",
" .agg(\n",
" count=('prediction', 'size'),\n",
" avg_probability=('probability', 'mean'),\n",
" min_probability=('probability', 'min'),\n",
" max_probability=('probability', 'max'),\n",
" )\n",
" .reset_index()\n",
")\n",
"\n",
"freq_total = df.groupby('freq', dropna=False).size().rename('freq_total').reset_index()\n",
"class_summary = class_summary.merge(freq_total, on='freq', how='left')\n",
"class_summary['class_rate'] = class_summary['count'] / class_summary['freq_total']\n",
"class_summary = class_summary.sort_values(['freq', 'prediction']).reset_index(drop=True)\n",
"\n",
"display(class_summary)"
]
},
{
"cell_type": "markdown",
"id": "039fcfa6",
"metadata": {},
"source": [
"## Статистика по классу drone"
]
},
{
"cell_type": "code",
"execution_count": 32,
"id": "c1ed2bc4",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" freq | \n",
" total_inferences | \n",
" drone_count | \n",
" avg_drone_probability | \n",
" median_drone_probability | \n",
" min_drone_probability | \n",
" max_drone_probability | \n",
" first_drone_time | \n",
" last_drone_time | \n",
" drone_rate | \n",
" dataset_duration_min | \n",
" drone_per_min | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" 1200 | \n",
" 13632 | \n",
" 3 | \n",
" 0.840000 | \n",
" 0.83 | \n",
" 0.78 | \n",
" 0.91 | \n",
" 2026-05-05 03:18:55.374394280+07:00 | \n",
" 2026-05-05 08:39:23.676669045+07:00 | \n",
" 0.000220 | \n",
" 1121.972504 | \n",
" 0.002674 | \n",
"
\n",
" \n",
" | 1 | \n",
" 2400 | \n",
" 13626 | \n",
" 11921 | \n",
" 0.868013 | \n",
" 0.93 | \n",
" 0.50 | \n",
" 1.00 | \n",
" 2026-05-04 17:35:21.019627763+07:00 | \n",
" 2026-05-05 12:17:19.369858371+07:00 | \n",
" 0.874872 | \n",
" 1121.972504 | \n",
" 10.625038 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" freq total_inferences drone_count avg_drone_probability \\\n",
"0 1200 13632 3 0.840000 \n",
"1 2400 13626 11921 0.868013 \n",
"\n",
" median_drone_probability min_drone_probability max_drone_probability \\\n",
"0 0.83 0.78 0.91 \n",
"1 0.93 0.50 1.00 \n",
"\n",
" first_drone_time last_drone_time \\\n",
"0 2026-05-05 03:18:55.374394280+07:00 2026-05-05 08:39:23.676669045+07:00 \n",
"1 2026-05-04 17:35:21.019627763+07:00 2026-05-05 12:17:19.369858371+07:00 \n",
"\n",
" drone_rate dataset_duration_min drone_per_min \n",
"0 0.000220 1121.972504 0.002674 \n",
"1 0.874872 1121.972504 10.625038 "
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"drone = df[df['prediction'].eq('drone')].copy()\n",
"\n",
"total_by_freq = df.groupby('freq', dropna=False).size().rename('total_inferences')\n",
"drone_by_freq = drone.groupby('freq', dropna=False).agg(\n",
" drone_count=('prediction', 'size'),\n",
" avg_drone_probability=('probability', 'mean'),\n",
" median_drone_probability=('probability', 'median'),\n",
" min_drone_probability=('probability', 'min'),\n",
" max_drone_probability=('probability', 'max'),\n",
" first_drone_time=('local_time', 'min'),\n",
" last_drone_time=('local_time', 'max'),\n",
")\n",
"\n",
"drone_stats = total_by_freq.to_frame().join(drone_by_freq, how='left').fillna({'drone_count': 0})\n",
"drone_stats['drone_count'] = drone_stats['drone_count'].astype(int)\n",
"drone_stats['drone_rate'] = drone_stats['drone_count'] / drone_stats['total_inferences']\n",
"\n",
"if len(df) > 1:\n",
" duration_min = (df['ts'].max() - df['ts'].min()).total_seconds() / 60\n",
"else:\n",
" duration_min = 0\n",
"\n",
"drone_stats['dataset_duration_min'] = duration_min\n",
"drone_stats['drone_per_min'] = drone_stats['drone_count'] / duration_min if duration_min > 0 else 0\n",
"drone_stats = drone_stats.reset_index().sort_values('freq').reset_index(drop=True)\n",
"\n",
"display(drone_stats)"
]
},
{
"cell_type": "markdown",
"id": "56ae5e2b",
"metadata": {},
"source": [
"## Интервалы между drone-классификациями"
]
},
{
"cell_type": "code",
"execution_count": 33,
"id": "0c43eb07",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" freq | \n",
" interval_count | \n",
" avg_interval_sec | \n",
" median_interval_sec | \n",
" min_interval_sec | \n",
" max_interval_sec | \n",
" p90_interval_sec | \n",
" p95_interval_sec | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" 1200 | \n",
" 2 | \n",
" 9614.151137 | \n",
" 9614.151137 | \n",
" 619.196112 | \n",
" 18609.106163 | \n",
" 16810.115158 | \n",
" 17709.610661 | \n",
"
\n",
" \n",
" | 1 | \n",
" 2400 | \n",
" 11920 | \n",
" 5.647513 | \n",
" 4.974157 | \n",
" 0.000000 | \n",
" 1210.107950 | \n",
" 9.203728 | \n",
" 10.079097 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" freq interval_count avg_interval_sec median_interval_sec \\\n",
"0 1200 2 9614.151137 9614.151137 \n",
"1 2400 11920 5.647513 4.974157 \n",
"\n",
" min_interval_sec max_interval_sec p90_interval_sec p95_interval_sec \n",
"0 619.196112 18609.106163 16810.115158 17709.610661 \n",
"1 0.000000 1210.107950 9.203728 10.079097 "
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"if drone.empty:\n",
" print('No drone predictions found')\n",
" drone_interval_stats = pd.DataFrame()\n",
"else:\n",
" drone = drone.sort_values(['freq', 'ts']).copy()\n",
" drone['dt_drone_freq_sec'] = drone.groupby('freq')['ts'].diff().dt.total_seconds()\n",
" drone_interval_stats = (\n",
" drone.groupby('freq', dropna=False)['dt_drone_freq_sec']\n",
" .agg(\n",
" interval_count='count',\n",
" avg_interval_sec='mean',\n",
" median_interval_sec='median',\n",
" min_interval_sec='min',\n",
" max_interval_sec='max',\n",
" p90_interval_sec=lambda s: s.quantile(0.90),\n",
" p95_interval_sec=lambda s: s.quantile(0.95),\n",
" )\n",
" .reset_index()\n",
" )\n",
" display(drone_interval_stats)"
]
},
{
"cell_type": "markdown",
"id": "2ecf96f0",
"metadata": {},
"source": [
"## Drone-события"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ff14a339",
"metadata": {},
"outputs": [],
"source": [
"drone_events = drone[[\n",
" 'local_time',\n",
" 'docker_timestamp',\n",
" 'freq',\n",
" 'model_id',\n",
" 'model_type',\n",
" 'prediction',\n",
" 'probability',\n",
"]].sort_values('local_time').reset_index(drop=True)\n",
"\n",
"display(drone_events.head(50))\n",
"display(drone_events.tail(50))"
]
},
{
"cell_type": "markdown",
"id": "376b84d0",
"metadata": {},
"source": [
"## Частота drone-классификаций по минутам"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5f82f5c1",
"metadata": {},
"outputs": [],
"source": [
"if drone.empty:\n",
" print('No drone predictions found')\n",
"else:\n",
" drone_per_minute = (\n",
" drone.set_index('local_time')\n",
" .groupby('freq')\n",
" .resample('1min')\n",
" .size()\n",
" .rename('drone_count')\n",
" .reset_index()\n",
" )\n",
" display(drone_per_minute.tail(100))\n",
"\n",
" pivot = drone_per_minute.pivot_table(\n",
" index='local_time',\n",
" columns='freq',\n",
" values='drone_count',\n",
" fill_value=0,\n",
" )\n",
" display(pivot.tail(50))"
]
},
{
"cell_type": "markdown",
"id": "ad570d05",
"metadata": {},
"source": [
"## Быстрые графики"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "895550a1",
"metadata": {},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"\n",
"if not drone.empty:\n",
" ax = drone_stats.set_index('freq')['drone_rate'].plot(kind='bar', figsize=(8, 4), title='Drone rate by frequency')\n",
" ax.set_ylabel('drone_count / total_inferences')\n",
" plt.show()\n",
"\n",
" ax = drone.boxplot(column='probability', by='freq', figsize=(8, 4))\n",
" ax.set_title('Drone probability by frequency')\n",
" ax.set_xlabel('freq')\n",
" ax.set_ylabel('probability')\n",
" plt.suptitle('')\n",
" plt.show()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"pygments_lexer": "ipython3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}