<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ryota Nishiyama</title>
    <description>The latest articles on DEV Community by Ryota Nishiyama (@ryotanishiyama).</description>
    <link>https://dev.to/ryotanishiyama</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4002638%2Fa3610914-4fa2-41ca-bc94-8dde3c384d77.jpg</url>
      <title>DEV Community: Ryota Nishiyama</title>
      <link>https://dev.to/ryotanishiyama</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ryotanishiyama"/>
    <language>en</language>
    <item>
      <title>How AI Text Detectors Actually Work (And Why They Flag You)</title>
      <dc:creator>Ryota Nishiyama</dc:creator>
      <pubDate>Sat, 25 Jul 2026 08:09:33 +0000</pubDate>
      <link>https://dev.to/ryotanishiyama/how-ai-text-detectors-actually-work-and-why-they-flag-you-18b4</link>
      <guid>https://dev.to/ryotanishiyama/how-ai-text-detectors-actually-work-and-why-they-flag-you-18b4</guid>
      <description>&lt;p&gt;Every few months someone forwards me a screenshot: a detector says their essay is "98% AI-generated," and they wrote every word themselves. Usually they're a non-native English speaker. I've spent enough time inside these systems to explain why that happens, and why the number on the dial is not a probability in any sense you'd want to bet on.&lt;/p&gt;

&lt;p&gt;Blunt version up front: statistical AI-text detection is a real technique with a real signal, and it is also fundamentally incapable of the thing people use it for — adjudicating individual documents.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the classifiers are actually measuring
&lt;/h2&gt;

&lt;p&gt;Almost every zero-shot detector reduces to one idea: &lt;strong&gt;run the candidate text through a language model and ask how surprised the model is.&lt;/strong&gt; The core quantity is per-token log-likelihood:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mean_logprob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Average log P(token | prefix). Higher = text is predictable.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tok&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;dist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next_token_distribution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dist&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tok&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;1e-12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perplexity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nf"&gt;mean_logprob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Low perplexity means the scoring model found the text easy to predict. Since decoding strategies used in practice — greedy, low-temperature sampling, nucleus sampling with modest top-p — prefer high-probability continuations, generated text tends to sit in a lower-perplexity region than unconstrained human writing. That's the entire foundation.&lt;/p&gt;

&lt;p&gt;"Burstiness" is the second-moment version of the same observation. Human writing varies: a dense clause, then a short one, a surprising word choice, then three predictable ones. So the &lt;em&gt;variance&lt;/em&gt; of per-sentence perplexity tends to run higher for humans. Detectors feed both mean and variance into a threshold or small classifier.&lt;/p&gt;

&lt;p&gt;The more interesting modern approach is curvature-based — DetectGPT and its faster descendants. The insight: machine text tends to sit near a &lt;em&gt;local maximum&lt;/em&gt; of the model's log-probability surface. Perturb it slightly (mask-and-refill spans), rescore, measure the drop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;curvature_score&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;perturb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mean_logprob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tokenize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;drops&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;mean_logprob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tokenize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;perturb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
             &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;drops&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;drops&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# large positive =&amp;gt; near a local max
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Human text, already off-peak, doesn't drop much when you jiggle it. A cleverer signal than raw perplexity — and it still inherits the flaw below.&lt;/p&gt;

&lt;h2&gt;
  
  
  The flaw: you are measuring fluency-typicality, not authorship
&lt;/h2&gt;

&lt;p&gt;Every one of these scores is a proxy. What they measure is &lt;em&gt;how closely this text tracks the scoring model's expectations.&lt;/em&gt; Authorship is not in the equation anywhere. It's inferred, on the assumption that "typical for the model" correlates with "produced by a model." That assumption breaks in predictable ways:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Non-native writers.&lt;/strong&gt; Second-language writing often uses a smaller, more conventional lexicon and more regular syntax — precisely because the writer deploys reliably-learned constructions rather than idiomatic risks. That is &lt;em&gt;low perplexity by construction.&lt;/em&gt; This isn't a hypothesis; a well-known 2023 Stanford study by Liang et al. found GPT detectors flagged TOEFL essays by non-native English writers as AI-generated at dramatically higher rates than native-writer essays, which were classified near-correctly. The detectors were, in effect, measuring English fluency and reporting it as machine authorship.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Formulaic genres.&lt;/strong&gt; Lab reports, legal boilerplate, clinical notes, and technical documentation are low-entropy by design — the point is conventional phrasing. A human-written incident postmortem can score more "AI-like" than a chatty blog post that was actually generated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Domain mismatch.&lt;/strong&gt; The score depends on the scorer's training distribution. Score a language or register it under-represents and perplexity rises for reasons unrelated to who wrote it — which is why accuracy claims measured on English news never transfer to Japanese academic prose.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Editing.&lt;/strong&gt; Light human revision measurably moves perplexity and curvature scores — the signal degrades under exactly the workflow most common in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the "99% accurate" number is misleading
&lt;/h2&gt;

&lt;p&gt;Even granting a good classifier, base rates destroy individual-document use. Suppose a detector has 95% sensitivity and 95% specificity — better than most honest evaluations — and 5% of submissions are machine-written. Out of 10,000 documents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;truly AI (500):      475 flagged, 25 missed
truly human (9500):  475 flagged (false positives!), 9025 clear

Of 950 flagged documents, 475 are innocent. Precision = 50%.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A coin flip, from a classifier you'd call excellent. Push specificity to 99% and precision only reaches about 84% — still one wrongly-accused person in six. And the false positives aren't randomly distributed: they concentrate on non-native writers and formulaic genres, so the harm lands unevenly on the people least equipped to contest it.&lt;/p&gt;

&lt;p&gt;This is standard screening-test arithmetic, and it's why honest detector documentation says "not for disciplinary decisions." The problem is not calibration. It's that a probabilistic signal cannot become a verdict just because a UI renders it as a big percentage.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use these tools without lying to yourself
&lt;/h2&gt;

&lt;p&gt;The signals are useful — just not as verdicts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Aggregate, don't adjudicate.&lt;/strong&gt; Detector scores over a corpus of 50,000 documents tell you something real about distribution shift. On one document they tell you almost nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Report a distribution, not a badge.&lt;/strong&gt; Show the score against a reference distribution for that genre and language. "Perplexity 21, the 12th percentile for technical documentation" is honest. "98% AI" is not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calibrate per language and register.&lt;/strong&gt; A threshold tuned on English blog posts is meaningless on Korean academic writing. Morphologically rich languages tokenize differently and land in a different perplexity range before you've measured anything about the author.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never let a score be sole evidence.&lt;/strong&gt; Version history, drafts, and a five-minute conversation about the content are all higher-signal than any classifier.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When I was building a per-language calibration harness, the multilingual tooling at &lt;a href="https://suikouai.com" rel="noopener noreferrer"&gt;Suikou AI&lt;/a&gt; was a useful comparison point precisely because it treats detection and rewriting as two views on one distribution problem — and because the Japanese and Korean side makes the tokenization issue impossible to ignore: change your morphological segmentation and every perplexity number moves, with no change to the text at all.&lt;/p&gt;

&lt;p&gt;That's the whole lesson. If a metric shifts when you change your tokenizer, it was never measuring authorship.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>python</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>Why Japanese and Korean Break Your Text Processing Pipeline</title>
      <dc:creator>Ryota Nishiyama</dc:creator>
      <pubDate>Fri, 24 Jul 2026 19:03:00 +0000</pubDate>
      <link>https://dev.to/ryotanishiyama/why-japanese-and-korean-break-your-text-processing-pipeline-3j5l</link>
      <guid>https://dev.to/ryotanishiyama/why-japanese-and-korean-break-your-text-processing-pipeline-3j5l</guid>
      <description>&lt;p&gt;Most text processing assumes tokens are separated by spaces. Japanese and Korean quietly violate that assumption in different ways, and pipelines built on English defaults produce subtly wrong output rather than obvious errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Japanese: no spaces at all
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;私は本を読んでいます
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No delimiters. Segmentation requires a morphological analyser with a dictionary — MeCab, Sudachi, Janome. And they legitimately disagree about compound nouns: 東京都 can be one token or 東京 + 都 depending on the dictionary, and both are defensible.&lt;/p&gt;

&lt;p&gt;Consequences that catch people out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Character count is not word count.&lt;/strong&gt; Any length heuristic tuned on English is meaningless here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Naive &lt;code&gt;split()&lt;/code&gt; yields one token.&lt;/strong&gt; Anything downstream — TF-IDF, keyword extraction, readability — silently produces garbage rather than failing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three scripts interleave.&lt;/strong&gt; Hiragana, katakana and kanji mix within a single word. Script-based splitting seems clever and is wrong.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Korean: spaces exist but agglutinate
&lt;/h2&gt;

&lt;p&gt;Korean has spacing, which lulls you into thinking &lt;code&gt;split()&lt;/code&gt; works. It does not, because particles attach to stems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;학교에서    school + at
학교를      school + (object marker)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;학교에서&lt;/code&gt; and &lt;code&gt;학교를&lt;/code&gt; are the same noun with different particles. Without morphological analysis they are two unrelated vocabulary items, which wrecks frequency counts and matching.&lt;/p&gt;

&lt;p&gt;Hangul also composes: &lt;code&gt;한&lt;/code&gt; is a single syllable block built from three jamo. Depending on Unicode normalisation (NFC vs NFD) the same visible text has different code point counts — a classic source of off-by-N bugs in length limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for detection and rewriting
&lt;/h2&gt;

&lt;p&gt;Anything scoring "how machine-like is this text" on English features — burstiness, sentence-length variance, function word ratios — needs those features recomputed on &lt;strong&gt;morphemes&lt;/strong&gt;, not characters or space-delimited chunks. Ported directly, the scores are noise.&lt;/p&gt;

&lt;p&gt;Rewriting is harder still. Preserving meaning while altering phrasing requires respecting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keigo levels (Japanese politeness registers) — casually rewriting 丁寧語 into plain form changes social meaning entirely&lt;/li&gt;
&lt;li&gt;Korean speech levels — same problem, different system&lt;/li&gt;
&lt;li&gt;Particle correctness after any reordering&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Practical stack
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Normalise Unicode first (NFC), before anything else touches the text&lt;/li&gt;
&lt;li&gt;Morphological analysis with an explicit dictionary choice — pin the version, results change between them&lt;/li&gt;
&lt;li&gt;Compute features over morphemes&lt;/li&gt;
&lt;li&gt;Validate on native text, never on machine-translated English&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last one matters most: translated text has different statistical properties from natively written text, so validating on it produces a system tuned for the wrong distribution.&lt;/p&gt;

&lt;p&gt;This is the problem I work on at &lt;a href="https://suikouai.com" rel="noopener noreferrer"&gt;Suikou AI&lt;/a&gt; — multilingual humanizing and detection with the morphological layer done properly for Japanese and Korean.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveat
&lt;/h2&gt;

&lt;p&gt;Analyser choice is a real trade-off. Sudachi has better modern coverage, MeCab has more ecosystem tooling. Neither is strictly correct — pick one and pin it, because switching mid-project invalidates every threshold you tuned.&lt;/p&gt;

</description>
      <category>nlp</category>
      <category>python</category>
      <category>i18n</category>
      <category>machinelearning</category>
    </item>
  </channel>
</rss>
